From 203e42eb03b2eabf604aeae6aa782a9d3a295e9a Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Fri, 1 Mar 2019 03:15:18 +0300
Subject: [PATCH 05/13] Jsonpath GIN support

---
 doc/src/sgml/gin.sgml                    |   4 +
 src/backend/utils/adt/jsonb_gin.c        | 903 ++++++++++++++++++++++++++++---
 src/include/catalog/pg_amop.dat          |  12 +
 src/include/utils/jsonb.h                |   3 +
 src/include/utils/jsonpath.h             |   2 +
 src/test/regress/expected/jsonb.out      | 453 ++++++++++++++++
 src/test/regress/expected/opr_sanity.out |   4 +-
 src/test/regress/sql/jsonb.sql           |  79 +++
 8 files changed, 1384 insertions(+), 76 deletions(-)

diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index cc7cd1e..ccab220 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -102,6 +102,8 @@
        <literal>?&amp;</literal>
        <literal>?|</literal>
        <literal>@&gt;</literal>
+       <literal>@?</literal>
+       <literal>@@</literal>
       </entry>
      </row>
      <row>
@@ -109,6 +111,8 @@
       <entry><type>jsonb</type></entry>
       <entry>
        <literal>@&gt;</literal>
+       <literal>@?</literal>
+       <literal>@@</literal>
       </entry>
      </row>
      <row>
diff --git a/src/backend/utils/adt/jsonb_gin.c b/src/backend/utils/adt/jsonb_gin.c
index a7f73b6..c3231f5 100644
--- a/src/backend/utils/adt/jsonb_gin.c
+++ b/src/backend/utils/adt/jsonb_gin.c
@@ -13,6 +13,7 @@
  */
 #include "postgres.h"
 
+#include "miscadmin.h"
 #include "access/gin.h"
 #include "access/hash.h"
 #include "access/stratnum.h"
@@ -20,6 +21,7 @@
 #include "catalog/pg_type.h"
 #include "utils/builtins.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 #include "utils/varlena.h"
 
 typedef struct PathHashStack
@@ -28,9 +30,120 @@ typedef struct PathHashStack
 	struct PathHashStack *parent;
 } PathHashStack;
 
+/* Buffer for GIN entries */
+typedef struct GinEntries
+{
+	Datum	   *buf;
+	int			count;
+	int			allocated;
+} GinEntries;
+
+typedef enum GinJsonPathNodeType
+{
+	GIN_JSP_OR,
+	GIN_JSP_AND,
+	GIN_JSP_ENTRY
+} GinJsonPathNodeType;
+
+typedef struct GinJsonPathNode GinJsonPathNode;
+
+/* Node in jsonpath expression tree */
+struct GinJsonPathNode
+{
+	GinJsonPathNodeType type;
+	union
+	{
+		int			nargs;			/* valid for OR and AND nodes */
+		int			entryIndex;		/* index in GinEntries array, valid for
+									 * ENTRY nodes after entries output */
+		Datum		entryDatum;		/* path hash or key name/scalar, valid
+									 * for ENTRY nodes before entries output */
+	} val;
+	GinJsonPathNode *args[FLEXIBLE_ARRAY_MEMBER]; /* valid for OR and AND nodes */
+};
+
+/*
+ * Single entry in the extracted json path (used for jsonb_ops only).
+ * Path entry can be one of:
+ *   .key        (key name is stored in 'entry' field)
+ *   .*
+ *   .**
+ *   [index]
+ *   [*]
+ * Entry type is stored in 'type' field.
+ */
+typedef struct GinJsonPathEntry
+{
+	struct GinJsonPathEntry *parent;
+	Datum		entry;				/* key name or NULL */
+	JsonPathItemType type;
+} GinJsonPathEntry;
+
+/* GIN representation of the extracted json path */
+typedef union GinJsonPath
+{
+	GinJsonPathEntry *entries;		/* list of path entries (jsonb_ops) */
+	uint32		hash;				/* hash of the path (jsonb_path_ops) */
+} GinJsonPath;
+
+typedef struct GinJsonPathContext GinJsonPathContext;
+
+/* Add entry to the extracted json path */
+typedef bool (*GinAddPathEntryFunc)(GinJsonPath *path, JsonPathItem *jsp);
+typedef List *(*GinExtractPathNodesFunc)(GinJsonPathContext *cxt,
+										 GinJsonPath path, JsonbValue *scalar,
+										 List *nodes);
+
+/* Context for jsonpath entries extraction */
+struct GinJsonPathContext
+{
+	GinAddPathEntryFunc add_path_entry;
+	GinExtractPathNodesFunc extract_path_nodes;
+	bool		lax;
+};
+
 static Datum make_text_key(char flag, const char *str, int len);
 static Datum make_scalar_key(const JsonbValue *scalarVal, bool is_key);
 
+static GinJsonPathNode *extract_jsp_bool_expr(GinJsonPathContext *cxt,
+					  GinJsonPath path, JsonPathItem *jsp, bool not);
+
+
+/* Init GIN entry buffer. */
+static void
+init_entries(GinEntries *entries, int preallocated)
+{
+	entries->allocated = preallocated;
+	entries->buf = preallocated ? palloc(sizeof(Datum) * preallocated) : NULL;
+	entries->count = 0;
+}
+
+/* Add GIN entry to the buffer. */
+static int
+add_entry(GinEntries *entries, Datum entry)
+{
+	int			id = entries->count;
+
+	if (entries->count >= entries->allocated)
+	{
+		if (entries->allocated)
+		{
+			entries->allocated *= 2;
+			entries->buf = repalloc(entries->buf,
+									sizeof(Datum) * entries->allocated);
+		}
+		else
+		{
+			entries->allocated = 8;
+			entries->buf = palloc(sizeof(Datum) * entries->allocated);
+		}
+	}
+
+	entries->buf[entries->count++] = entry;
+
+	return id;
+}
+
 /*
  *
  * jsonb_ops GIN opclass support functions
@@ -68,12 +181,11 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
 {
 	Jsonb	   *jb = (Jsonb *) PG_GETARG_JSONB_P(0);
 	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
-	int			total = 2 * JB_ROOT_COUNT(jb);
+	int			total = JB_ROOT_COUNT(jb);
 	JsonbIterator *it;
 	JsonbValue	v;
 	JsonbIteratorToken r;
-	int			i = 0;
-	Datum	   *entries;
+	GinEntries	entries;
 
 	/* If the root level is empty, we certainly have no keys */
 	if (total == 0)
@@ -83,30 +195,23 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
 	}
 
 	/* Otherwise, use 2 * root count as initial estimate of result size */
-	entries = (Datum *) palloc(sizeof(Datum) * total);
+	init_entries(&entries, 2 * total);
 
 	it = JsonbIteratorInit(&jb->root);
 
 	while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
 	{
-		/* Since we recurse into the object, we might need more space */
-		if (i >= total)
-		{
-			total *= 2;
-			entries = (Datum *) repalloc(entries, sizeof(Datum) * total);
-		}
-
 		switch (r)
 		{
 			case WJB_KEY:
-				entries[i++] = make_scalar_key(&v, true);
+				add_entry(&entries, make_scalar_key(&v, true));
 				break;
 			case WJB_ELEM:
 				/* Pretend string array elements are keys, see jsonb.h */
-				entries[i++] = make_scalar_key(&v, (v.type == jbvString));
+				add_entry(&entries, make_scalar_key(&v, v.type == jbvString));
 				break;
 			case WJB_VALUE:
-				entries[i++] = make_scalar_key(&v, false);
+				add_entry(&entries, make_scalar_key(&v, false));
 				break;
 			default:
 				/* we can ignore structural items */
@@ -114,9 +219,575 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
 		}
 	}
 
-	*nentries = i;
+	*nentries = entries.count;
 
-	PG_RETURN_POINTER(entries);
+	PG_RETURN_POINTER(entries.buf);
+}
+
+/* Append key name to the path (jsonb_ops). */
+static bool
+jsonb_ops__add_path_entry(GinJsonPath *path, JsonPathItem *jsp)
+{
+	GinJsonPathEntry *pentry;
+	Datum		entry;
+
+	switch (jsp->type)
+	{
+		case jpiRoot:
+			path->entries = NULL;	/* reset path */
+			return true;
+
+		case jpiKey:
+			{
+				int			len;
+				char	   *key = jspGetString(jsp, &len);
+
+				entry = make_text_key(JGINFLAG_KEY, key, len);
+				break;
+			}
+
+		case jpiAny:
+		case jpiAnyKey:
+		case jpiAnyArray:
+		case jpiIndexArray:
+			entry = PointerGetDatum(NULL);
+			break;
+
+		default:
+			/* other path items like item methods are not supported */
+			return false;
+	}
+
+	pentry = palloc(sizeof(*pentry));
+
+	pentry->type = jsp->type;
+	pentry->entry = entry;
+	pentry->parent = path->entries;
+
+	path->entries = pentry;
+
+	return true;
+}
+
+/* Combine existing path hash with next key hash (jsonb_path_ops). */
+static bool
+jsonb_path_ops__add_path_entry(GinJsonPath *path, JsonPathItem *jsp)
+{
+	switch (jsp->type)
+	{
+		case jpiRoot:
+			path->hash = 0;	/* reset path hash */
+			return true;
+
+		case jpiKey:
+			{
+				JsonbValue 	jbv;
+
+				jbv.type = jbvString;
+				jbv.val.string.val = jspGetString(jsp, &jbv.val.string.len);
+
+				JsonbHashScalarValue(&jbv, &path->hash);
+				return true;
+			}
+
+		case jpiIndexArray:
+		case jpiAnyArray:
+			return true;	/* path hash is unchanged */
+
+		default:
+			/* other items (wildcard paths, item methods) are not supported */
+			return false;
+	}
+}
+
+static inline GinJsonPathNode *
+make_jsp_entry_node(Datum entry)
+{
+	GinJsonPathNode *node = palloc(offsetof(GinJsonPathNode, args));
+
+	node->type = GIN_JSP_ENTRY;
+	node->val.entryDatum = entry;
+
+	return node;
+}
+
+static inline GinJsonPathNode *
+make_jsp_entry_node_scalar(JsonbValue *scalar, bool iskey)
+{
+	return make_jsp_entry_node(make_scalar_key(scalar, iskey));
+}
+
+static inline GinJsonPathNode *
+make_jsp_expr_node(GinJsonPathNodeType type, int nargs)
+{
+	GinJsonPathNode *node = palloc(offsetof(GinJsonPathNode, args) +
+								   sizeof(node->args[0]) * nargs);
+
+	node->type = type;
+	node->val.nargs = nargs;
+
+	return node;
+}
+
+static inline GinJsonPathNode *
+make_jsp_expr_node_args(GinJsonPathNodeType type, List *args)
+{
+	GinJsonPathNode *node = make_jsp_expr_node(type, list_length(args));
+	ListCell   *lc;
+	int			i = 0;
+
+	foreach(lc, args)
+		node->args[i++] = lfirst(lc);
+
+	return node;
+}
+
+static inline GinJsonPathNode *
+make_jsp_expr_node_binary(GinJsonPathNodeType type,
+						  GinJsonPathNode *arg1, GinJsonPathNode *arg2)
+{
+	GinJsonPathNode *node = make_jsp_expr_node(type, 2);
+
+	node->args[0] = arg1;
+	node->args[1] = arg2;
+
+	return node;
+}
+
+/* Append a list of nodes from the jsonpath (jsonb_ops). */
+static List *
+jsonb_ops__extract_path_nodes(GinJsonPathContext *cxt, GinJsonPath path,
+							  JsonbValue *scalar, List *nodes)
+{
+	GinJsonPathEntry *pentry;
+
+	/* append path entry nodes */
+	for (pentry = path.entries; pentry; pentry = pentry->parent)
+	{
+		if (pentry->type == jpiKey)		/* only keys are indexed */
+			nodes = lappend(nodes, make_jsp_entry_node(pentry->entry));
+	}
+
+	if (scalar)
+	{
+		/* Append scalar node for equality queries. */
+		GinJsonPathNode *node;
+
+		if (scalar->type == jbvString)
+		{
+			GinJsonPathEntry *last = path.entries;
+			GinTernaryValue array_access;
+
+			/*
+			 * Create OR-node when the string scalar can be matched as a key
+			 * and a non-key. It is possible in lax mode where arrays are
+			 * automatically unwrapped, or in strict mode for jpiAny items.
+			 */
+
+			if (cxt->lax)
+				array_access = GIN_MAYBE;
+			else if (!last)	/* root ($) */
+				array_access = GIN_FALSE;
+			else if (last->type == jpiAnyArray || last->type == jpiIndexArray)
+				array_access = GIN_TRUE;
+			else if (last->type == jpiAny)
+				array_access = GIN_MAYBE;
+			else
+				array_access = GIN_FALSE;
+
+			if (array_access == GIN_MAYBE)
+			{
+				GinJsonPathNode *n1 = make_jsp_entry_node_scalar(scalar, true);
+				GinJsonPathNode *n2 = make_jsp_entry_node_scalar(scalar, false);
+
+				node = make_jsp_expr_node_binary(GIN_JSP_OR, n1, n2);
+			}
+			else
+			{
+				node = make_jsp_entry_node_scalar(scalar,
+												  array_access == GIN_TRUE);
+			}
+		}
+		else
+		{
+			node = make_jsp_entry_node_scalar(scalar, false);
+		}
+
+		nodes = lappend(nodes, node);
+	}
+
+	return nodes;
+}
+
+/* Append a list of nodes from the jsonpath (jsonb_path_ops). */
+static List *
+jsonb_path_ops__extract_path_nodes(GinJsonPathContext *cxt, GinJsonPath path,
+								   JsonbValue *scalar, List *nodes)
+{
+	if (scalar)
+	{
+		/* append path hash node for equality queries */
+		uint32		hash = path.hash;
+
+		JsonbHashScalarValue(scalar, &hash);
+
+		return lappend(nodes,
+					   make_jsp_entry_node(UInt32GetDatum(hash)));
+	}
+	else
+	{
+		/* jsonb_path_ops doesn't support EXISTS queries => nothing to append */
+		return nodes;
+	}
+}
+
+/*
+ * Extract a list of expression nodes that need to be AND-ed by the caller.
+ * Extracted expression is 'path == scalar' if 'scalar' is non-NULL, and
+ * 'EXISTS(path)' otherwise.
+ */
+static List *
+extract_jsp_path_expr_nodes(GinJsonPathContext *cxt, GinJsonPath path,
+							JsonPathItem *jsp, JsonbValue *scalar)
+{
+	JsonPathItem next;
+	List	   *nodes = NIL;
+
+	for (;;)
+	{
+		switch (jsp->type)
+		{
+			case jpiCurrent:
+				break;
+
+			case jpiFilter:
+				{
+					JsonPathItem arg;
+					GinJsonPathNode *filter;
+
+					jspGetArg(jsp, &arg);
+
+					filter = extract_jsp_bool_expr(cxt, path, &arg, false);
+
+					if (filter)
+						nodes = lappend(nodes, filter);
+
+					break;
+				}
+
+			default:
+				if (!cxt->add_path_entry(&path, jsp))
+					/*
+					 * Path is not supported by the index opclass, return only
+					 * the extracted filter nodes.
+					 */
+					return nodes;
+				break;
+		}
+
+		if (!jspGetNext(jsp, &next))
+			break;
+
+		jsp = &next;
+	}
+
+	/*
+	 * Append nodes from the path expression itself to the already extracted
+	 * list of filter nodes.
+	 */
+	return cxt->extract_path_nodes(cxt, path, scalar, nodes);
+}
+
+/*
+ * Extract an expression node from one of following jsonpath path expressions:
+ *   EXISTS(jsp)    (when 'scalar' is NULL)
+ *   jsp == scalar  (when 'scalar' is not NULL).
+ *
+ * The current path (@) is passed in 'path'.
+ */
+static GinJsonPathNode *
+extract_jsp_path_expr(GinJsonPathContext *cxt, GinJsonPath path,
+					  JsonPathItem *jsp, JsonbValue *scalar)
+{
+	/* extract a list of nodes to be AND-ed */
+	List	   *nodes = extract_jsp_path_expr_nodes(cxt, path, jsp, scalar);
+
+	if (list_length(nodes) <= 0)
+		/* no nodes were extracted => full scan is needed for this path */
+		return NULL;
+
+	if (list_length(nodes) == 1)
+		return linitial(nodes);		/* avoid extra AND-node */
+
+	/* construct AND-node for path with filters */
+	return make_jsp_expr_node_args(GIN_JSP_AND, nodes);
+}
+
+/* Recursively extract nodes from the boolean jsonpath expression. */
+static GinJsonPathNode *
+extract_jsp_bool_expr(GinJsonPathContext *cxt, GinJsonPath path,
+					  JsonPathItem *jsp, bool not)
+{
+	check_stack_depth();
+
+	switch (jsp->type)
+	{
+		case jpiAnd:		/* expr && expr */
+		case jpiOr:			/* expr || expr */
+			{
+				JsonPathItem arg;
+				GinJsonPathNode *larg;
+				GinJsonPathNode *rarg;
+				GinJsonPathNodeType type;
+
+				jspGetLeftArg(jsp, &arg);
+				larg = extract_jsp_bool_expr(cxt, path, &arg, not);
+
+				jspGetRightArg(jsp, &arg);
+				rarg = extract_jsp_bool_expr(cxt, path, &arg, not);
+
+				if (!larg || !rarg)
+				{
+					if (jsp->type == jpiOr)
+						return NULL;
+
+					return larg ? larg : rarg;
+				}
+
+				type = not ^ (jsp->type == jpiAnd) ? GIN_JSP_AND : GIN_JSP_OR;
+
+				return make_jsp_expr_node_binary(type, larg, rarg);
+			}
+
+		case jpiNot:		/* !expr  */
+			{
+				JsonPathItem arg;
+
+				jspGetArg(jsp, &arg);
+
+				/* extract child expression inverting 'not' flag */
+				return extract_jsp_bool_expr(cxt, path, &arg, !not);
+			}
+
+		case jpiExists:		/* EXISTS(path) */
+			{
+				JsonPathItem arg;
+
+				if (not)
+					return NULL;	/* NOT EXISTS is not supported */
+
+				jspGetArg(jsp, &arg);
+
+				return extract_jsp_path_expr(cxt, path, &arg, NULL);
+			}
+
+		case jpiNotEqual:
+			/*
+			 * 'not' == true case is not supported here because
+			 * '!(path != scalar)' is not equivalent to 'path == scalar' in the
+			 * general case because of sequence comparison semantics:
+			 *   'path == scalar'  === 'EXISTS (path, @ == scalar)',
+			 * '!(path != scalar)' === 'FOR_ALL(path, @ == scalar)'.
+			 * So, we should translate '!(path != scalar)' into GIN query
+			 * 'path == scalar || EMPTY(path)', but 'EMPTY(path)' queries
+			 * are not supported by the both jsonb opclasses.  However in strict
+			 * mode we could omit 'EMPTY(path)' part if the path can return
+			 * exactly one item (it does not contain wildcard accessors or
+			 * item methods like .keyvalue() etc.).
+			 */
+			return NULL;
+
+		case jpiEqual:		/* path == scalar */
+			{
+				JsonPathItem left_item;
+				JsonPathItem right_item;
+				JsonPathItem *path_item;
+				JsonPathItem *scalar_item;
+				JsonbValue	scalar;
+
+
+
+				if (not)
+					return NULL;
+
+				jspGetLeftArg(jsp, &left_item);
+				jspGetRightArg(jsp, &right_item);
+
+				if (jspIsScalar(left_item.type))
+				{
+					scalar_item = &left_item;
+					path_item = &right_item;
+				}
+				else if (jspIsScalar(right_item.type))
+				{
+					scalar_item = &right_item;
+					path_item = &left_item;
+				}
+				else
+					return NULL; /* at least one operand should be a scalar */
+
+				switch (scalar_item->type)
+				{
+					case jpiNull:
+						scalar.type = jbvNull;
+						break;
+					case jpiBool:
+						scalar.type = jbvBool;
+						scalar.val.boolean = !!*scalar_item->content.value.data;
+						break;
+					case jpiNumeric:
+						scalar.type = jbvNumeric;
+						scalar.val.numeric =
+							(Numeric) scalar_item->content.value.data;
+						break;
+					case jpiString:
+						scalar.type = jbvString;
+						scalar.val.string.val = scalar_item->content.value.data;
+						scalar.val.string.len =
+							scalar_item->content.value.datalen;
+						break;
+					default:
+						elog(ERROR, "invalid scalar jsonpath item type: %d",
+							 scalar_item->type);
+						return NULL;
+				}
+
+				return extract_jsp_path_expr(cxt, path, path_item, &scalar);
+			}
+
+		default:
+			return NULL;	/* not a boolean expression */
+	}
+}
+
+/* Recursively emit all GIN entries found in the node tree */
+static void
+emit_jsp_entries(GinJsonPathNode *node, GinEntries *entries)
+{
+	check_stack_depth();
+
+	switch (node->type)
+	{
+		case GIN_JSP_ENTRY:
+			/* replace datum with its index in the array */
+			node->val.entryIndex = add_entry(entries, node->val.entryDatum);
+			break;
+
+		case GIN_JSP_OR:
+		case GIN_JSP_AND:
+			{
+				int			i;
+
+				for (i = 0; i < node->val.nargs; i++)
+					emit_jsp_entries(node->args[i], entries);
+
+				break;
+			}
+	}
+}
+
+/*
+ * Recursively extract GIN entries from jsonpath query.
+ * Root expression node is put into (*extra_data)[0].
+ */
+static Datum *
+extract_jsp_query(JsonPath *jp, StrategyNumber strat, bool pathOps,
+				  int32 *nentries, Pointer **extra_data)
+{
+	GinJsonPathContext cxt;
+	JsonPathItem root;
+	GinJsonPathNode *node;
+	GinJsonPath path = { 0 };
+	GinEntries	entries = { 0 };
+
+	cxt.lax = (jp->header & JSONPATH_LAX) != 0;
+
+	if (pathOps)
+	{
+		cxt.add_path_entry = jsonb_path_ops__add_path_entry;
+		cxt.extract_path_nodes = jsonb_path_ops__extract_path_nodes;
+	}
+	else
+	{
+		cxt.add_path_entry = jsonb_ops__add_path_entry;
+		cxt.extract_path_nodes = jsonb_ops__extract_path_nodes;
+	}
+
+	jspInit(&root, jp);
+
+	node = strat == JsonbJsonpathExistsStrategyNumber
+		? extract_jsp_path_expr(&cxt, path, &root, NULL)
+		: extract_jsp_bool_expr(&cxt, path, &root, false);
+
+	if (!node)
+	{
+		*nentries = 0;
+		return NULL;
+	}
+
+	emit_jsp_entries(node, &entries);
+
+	*nentries = entries.count;
+	if (!*nentries)
+		return NULL;
+
+	*extra_data = palloc0(sizeof(**extra_data) * entries.count);
+	**extra_data = (Pointer) node;
+
+	return entries.buf;
+}
+
+/*
+ * Recursively execute jsonpath expression.
+ * 'check' is a bool[] or a GinTernaryValue[] depending on 'ternary' flag.
+ */
+static GinTernaryValue
+execute_jsp_expr(GinJsonPathNode *node, void *check, bool ternary)
+{
+	GinTernaryValue	res;
+	GinTernaryValue	v;
+	int			i;
+
+	switch (node->type)
+	{
+		case GIN_JSP_AND:
+			res = GIN_TRUE;
+			for (i = 0; i < node->val.nargs; i++)
+			{
+				v = execute_jsp_expr(node->args[i], check, ternary);
+				if (v == GIN_FALSE)
+					return GIN_FALSE;
+				else if (v == GIN_MAYBE)
+					res = GIN_MAYBE;
+			}
+			return res;
+
+		case GIN_JSP_OR:
+			res = GIN_FALSE;
+			for (i = 0; i < node->val.nargs; i++)
+			{
+				v = execute_jsp_expr(node->args[i], check, ternary);
+				if (v == GIN_TRUE)
+					return GIN_TRUE;
+				else if (v == GIN_MAYBE)
+					res = GIN_MAYBE;
+			}
+			return res;
+
+		case GIN_JSP_ENTRY:
+			{
+				int			index = node->val.entryIndex;
+				bool		maybe = ternary
+					? ((GinTernaryValue *) check)[index] != GIN_FALSE
+					: ((bool *) check)[index];
+
+				return maybe ? GIN_MAYBE : GIN_FALSE;
+			}
+
+		default:
+			elog(ERROR, "invalid jsonpath gin node type: %d", node->type);
+			return GIN_FALSE;
+	}
 }
 
 Datum
@@ -181,6 +852,18 @@ gin_extract_jsonb_query(PG_FUNCTION_ARGS)
 		if (j == 0 && strategy == JsonbExistsAllStrategyNumber)
 			*searchMode = GIN_SEARCH_MODE_ALL;
 	}
+	else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+			 strategy == JsonbJsonpathExistsStrategyNumber)
+	{
+		JsonPath   *jp = PG_GETARG_JSONPATH_P(0);
+		Pointer	  **extra_data = (Pointer **) PG_GETARG_POINTER(4);
+
+		entries = extract_jsp_query(jp, strategy, false, nentries,
+											 extra_data);
+
+		if (!entries)
+			*searchMode = GIN_SEARCH_MODE_ALL;
+	}
 	else
 	{
 		elog(ERROR, "unrecognized strategy number: %d", strategy);
@@ -199,7 +882,7 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
 	/* Jsonb	   *query = PG_GETARG_JSONB_P(2); */
 	int32		nkeys = PG_GETARG_INT32(3);
 
-	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+	Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4);
 	bool	   *recheck = (bool *) PG_GETARG_POINTER(5);
 	bool		res = true;
 	int32		i;
@@ -256,6 +939,22 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
 			}
 		}
 	}
+	else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+			 strategy == JsonbJsonpathExistsStrategyNumber)
+	{
+		*recheck = true;
+
+		if (nkeys <= 0)
+		{
+			res = true;
+		}
+		else
+		{
+			Assert(extra_data && extra_data[0]);
+			res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+								   false) != GIN_FALSE;
+		}
+	}
 	else
 		elog(ERROR, "unrecognized strategy number: %d", strategy);
 
@@ -270,8 +969,7 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
 
 	/* Jsonb	   *query = PG_GETARG_JSONB_P(2); */
 	int32		nkeys = PG_GETARG_INT32(3);
-
-	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+	Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4);
 	GinTernaryValue res = GIN_MAYBE;
 	int32		i;
 
@@ -308,6 +1006,20 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
 			}
 		}
 	}
+	else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+			 strategy == JsonbJsonpathExistsStrategyNumber)
+	{
+		if (nkeys <= 0)
+		{
+			res = GIN_MAYBE;
+		}
+		else
+		{
+			Assert(extra_data && extra_data[0]);
+			res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+								   true);
+		}
+	}
 	else
 		elog(ERROR, "unrecognized strategy number: %d", strategy);
 
@@ -331,14 +1043,13 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
 {
 	Jsonb	   *jb = PG_GETARG_JSONB_P(0);
 	int32	   *nentries = (int32 *) PG_GETARG_POINTER(1);
-	int			total = 2 * JB_ROOT_COUNT(jb);
+	int			total = JB_ROOT_COUNT(jb);
 	JsonbIterator *it;
 	JsonbValue	v;
 	JsonbIteratorToken r;
 	PathHashStack tail;
 	PathHashStack *stack;
-	int			i = 0;
-	Datum	   *entries;
+	GinEntries	entries;
 
 	/* If the root level is empty, we certainly have no keys */
 	if (total == 0)
@@ -348,7 +1059,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
 	}
 
 	/* Otherwise, use 2 * root count as initial estimate of result size */
-	entries = (Datum *) palloc(sizeof(Datum) * total);
+	init_entries(&entries, 2 * total);
 
 	/* We keep a stack of partial hashes corresponding to parent key levels */
 	tail.parent = NULL;
@@ -361,13 +1072,6 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
 	{
 		PathHashStack *parent;
 
-		/* Since we recurse into the object, we might need more space */
-		if (i >= total)
-		{
-			total *= 2;
-			entries = (Datum *) repalloc(entries, sizeof(Datum) * total);
-		}
-
 		switch (r)
 		{
 			case WJB_BEGIN_ARRAY:
@@ -398,7 +1102,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
 				/* mix the element or value's hash into the prepared hash */
 				JsonbHashScalarValue(&v, &stack->hash);
 				/* and emit an index entry */
-				entries[i++] = UInt32GetDatum(stack->hash);
+				add_entry(&entries, UInt32GetDatum(stack->hash));
 				/* reset hash for next key, value, or sub-object */
 				stack->hash = stack->parent->hash;
 				break;
@@ -419,9 +1123,9 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
 		}
 	}
 
-	*nentries = i;
+	*nentries = entries.count;
 
-	PG_RETURN_POINTER(entries);
+	PG_RETURN_POINTER(entries.buf);
 }
 
 Datum
@@ -432,18 +1136,35 @@ gin_extract_jsonb_query_path(PG_FUNCTION_ARGS)
 	int32	   *searchMode = (int32 *) PG_GETARG_POINTER(6);
 	Datum	   *entries;
 
-	if (strategy != JsonbContainsStrategyNumber)
-		elog(ERROR, "unrecognized strategy number: %d", strategy);
+	if (strategy == JsonbContainsStrategyNumber)
+	{
+		/* Query is a jsonb, so just apply gin_extract_jsonb_path ... */
+		entries = (Datum *)
+			DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
+												PG_GETARG_DATUM(0),
+												PointerGetDatum(nentries)));
+
+		/* ... although "contains {}" requires a full index scan */
+		if (*nentries == 0)
+			*searchMode = GIN_SEARCH_MODE_ALL;
+	}
+	else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+			 strategy == JsonbJsonpathExistsStrategyNumber)
+	{
+		JsonPath   *jp = PG_GETARG_JSONPATH_P(0);
+		Pointer	  **extra_data = (Pointer **) PG_GETARG_POINTER(4);
 
-	/* Query is a jsonb, so just apply gin_extract_jsonb_path ... */
-	entries = (Datum *)
-		DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
-											PG_GETARG_DATUM(0),
-											PointerGetDatum(nentries)));
+		entries = extract_jsp_query(jp, strategy, true, nentries,
+										extra_data);
 
-	/* ... although "contains {}" requires a full index scan */
-	if (*nentries == 0)
-		*searchMode = GIN_SEARCH_MODE_ALL;
+		if (!entries)
+			*searchMode = GIN_SEARCH_MODE_ALL;
+	}
+	else
+	{
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
+		entries = NULL;
+	}
 
 	PG_RETURN_POINTER(entries);
 }
@@ -456,32 +1177,49 @@ gin_consistent_jsonb_path(PG_FUNCTION_ARGS)
 
 	/* Jsonb	   *query = PG_GETARG_JSONB_P(2); */
 	int32		nkeys = PG_GETARG_INT32(3);
-
-	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+	Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4);
 	bool	   *recheck = (bool *) PG_GETARG_POINTER(5);
 	bool		res = true;
 	int32		i;
 
-	if (strategy != JsonbContainsStrategyNumber)
-		elog(ERROR, "unrecognized strategy number: %d", strategy);
-
-	/*
-	 * jsonb_path_ops is necessarily lossy, not only because of hash
-	 * collisions but also because it doesn't preserve complete information
-	 * about the structure of the JSON object.  Besides, there are some
-	 * special rules around the containment of raw scalars in arrays that are
-	 * not handled here.  So we must always recheck a match.  However, if not
-	 * all of the keys are present, the tuple certainly doesn't match.
-	 */
-	*recheck = true;
-	for (i = 0; i < nkeys; i++)
+	if (strategy == JsonbContainsStrategyNumber)
 	{
-		if (!check[i])
+		/*
+		 * jsonb_path_ops is necessarily lossy, not only because of hash
+		 * collisions but also because it doesn't preserve complete information
+		 * about the structure of the JSON object.  Besides, there are some
+		 * special rules around the containment of raw scalars in arrays that are
+		 * not handled here.  So we must always recheck a match.  However, if not
+		 * all of the keys are present, the tuple certainly doesn't match.
+		 */
+		*recheck = true;
+		for (i = 0; i < nkeys; i++)
 		{
-			res = false;
-			break;
+			if (!check[i])
+			{
+				res = false;
+				break;
+			}
+		}
+	}
+	else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+			 strategy == JsonbJsonpathExistsStrategyNumber)
+	{
+		*recheck = true;
+
+		if (nkeys <= 0)
+		{
+			res = true;
+		}
+		else
+		{
+			Assert(extra_data && extra_data[0]);
+			res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+								   false) != GIN_FALSE;
 		}
 	}
+	else
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
 
 	PG_RETURN_BOOL(res);
 }
@@ -494,27 +1232,42 @@ gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS)
 
 	/* Jsonb	   *query = PG_GETARG_JSONB_P(2); */
 	int32		nkeys = PG_GETARG_INT32(3);
-
-	/* Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
+	Pointer	   *extra_data = (Pointer *) PG_GETARG_POINTER(4);
 	GinTernaryValue res = GIN_MAYBE;
 	int32		i;
 
-	if (strategy != JsonbContainsStrategyNumber)
-		elog(ERROR, "unrecognized strategy number: %d", strategy);
-
-	/*
-	 * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this
-	 * corresponds to always forcing recheck in the regular consistent
-	 * function, for the reasons listed there.
-	 */
-	for (i = 0; i < nkeys; i++)
+	if (strategy == JsonbContainsStrategyNumber)
 	{
-		if (check[i] == GIN_FALSE)
+		/*
+		 * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this
+		 * corresponds to always forcing recheck in the regular consistent
+		 * function, for the reasons listed there.
+		 */
+		for (i = 0; i < nkeys; i++)
 		{
-			res = GIN_FALSE;
-			break;
+			if (check[i] == GIN_FALSE)
+			{
+				res = GIN_FALSE;
+				break;
+			}
+		}
+	}
+	else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+			 strategy == JsonbJsonpathExistsStrategyNumber)
+	{
+		if (nkeys <= 0)
+		{
+			res = GIN_MAYBE;
+		}
+		else
+		{
+			Assert(extra_data && extra_data[0]);
+			res = execute_jsp_expr((GinJsonPathNode *) extra_data[0], check,
+								   true);
 		}
 	}
+	else
+		elog(ERROR, "unrecognized strategy number: %d", strategy);
 
 	PG_RETURN_GIN_TERNARY_VALUE(res);
 }
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0ab95d8..cf63eb7 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1468,11 +1468,23 @@
 { amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
   amoprighttype => '_text', amopstrategy => '11', amopopr => '?&(jsonb,_text)',
   amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
+  amoprighttype => 'jsonpath', amopstrategy => '15',
+  amopopr => '@?(jsonb,jsonpath)', amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
+  amoprighttype => 'jsonpath', amopstrategy => '16',
+  amopopr => '@@(jsonb,jsonpath)', amopmethod => 'gin' },
 
 # GIN jsonb_path_ops
 { amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
   amoprighttype => 'jsonb', amopstrategy => '7', amopopr => '@>(jsonb,jsonb)',
   amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
+  amoprighttype => 'jsonpath', amopstrategy => '15',
+  amopopr => '@?(jsonb,jsonpath)', amopmethod => 'gin' },
+{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
+  amoprighttype => 'jsonpath', amopstrategy => '16',
+  amopopr => '@@(jsonb,jsonpath)', amopmethod => 'gin' },
 
 # SP-GiST range_ops
 { amopfamily => 'spgist/range_ops', amoplefttype => 'anyrange',
diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h
index a5d1df4..4dc00f4 100644
--- a/src/include/utils/jsonb.h
+++ b/src/include/utils/jsonb.h
@@ -34,6 +34,9 @@ typedef enum
 #define JsonbExistsStrategyNumber		9
 #define JsonbExistsAnyStrategyNumber	10
 #define JsonbExistsAllStrategyNumber	11
+#define JsonbJsonpathExistsStrategyNumber		15
+#define JsonbJsonpathPredicateStrategyNumber	16
+
 
 /*
  * In the standard jsonb_ops GIN opclass for jsonb, we choose to index both
diff --git a/src/include/utils/jsonpath.h b/src/include/utils/jsonpath.h
index 2170a70..6266f86 100644
--- a/src/include/utils/jsonpath.h
+++ b/src/include/utils/jsonpath.h
@@ -35,6 +35,8 @@ typedef struct
 #define PG_GETARG_JSONPATH_P_COPY(x)	DatumGetJsonPathPCopy(PG_GETARG_DATUM(x))
 #define PG_RETURN_JSONPATH_P(p)			PG_RETURN_POINTER(p)
 
+#define jspIsScalar(type) ((type) >= jpiNull && (type) <= jpiBool)
+
 /*
  * All node's type of jsonpath expression
  */
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 8dcdaf5..5438af7 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2731,6 +2731,114 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
     42
 (1 row)
 
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)';
+ count 
+-------
+  1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public)';
+ count 
+-------
+   194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.bar)';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) || exists($.disabled)';
+ count 
+-------
+   337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) && exists($.disabled)';
+ count 
+-------
+    42
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count 
+-------
+  1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count 
+-------
+   194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count 
+-------
+     0
+(1 row)
+
 CREATE INDEX jidx ON testjsonb USING gin (j);
 SET enable_seqscan = off;
 SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
@@ -2806,6 +2914,196 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
     42
 (1 row)
 
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null';
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on testjsonb
+         Recheck Cond: (j @@ '($."wait" == null)'::jsonpath)
+         ->  Bitmap Index Scan on jidx
+               Index Cond: (j @@ '($."wait" == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.wait == null))';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.wait ? (@ == null))';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "foo"';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "bar"';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.array[*] == "bar"))';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array ? (@[*] == "bar"))';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array[*] ? (@ == "bar"))';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)';
+ count 
+-------
+  1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public)';
+ count 
+-------
+   194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.bar)';
+ count 
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) || exists($.disabled)';
+ count 
+-------
+   337
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) && exists($.disabled)';
+ count 
+-------
+    42
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on testjsonb
+         Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+         ->  Bitmap Index Scan on jidx
+               Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count 
+-------
+  1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count 
+-------
+   194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count 
+-------
+     0
+(1 row)
+
 -- array exists - array elements should behave as keys (for GIN index scans too)
 CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
 SELECT count(*) from testjsonb  WHERE j->'array' ? 'bar';
@@ -2956,6 +3254,161 @@ SELECT count(*) FROM testjsonb WHERE j @> '{}';
   1012
 (1 row)
 
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.wait == null))';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.wait ? (@ == null))';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "foo"';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "bar"';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.array[*] == "bar"))';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array ? (@[*] == "bar"))';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array[*] ? (@ == "bar"))';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)';
+ count 
+-------
+  1012
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on testjsonb
+         Recheck Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+         ->  Bitmap Index Scan on jidx
+               Index Cond: (j @? '$."wait"?(@ == null)'::jsonpath)
+(5 rows)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+ count 
+-------
+    15
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+ count 
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+ count 
+-------
+  1012
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+ count 
+-------
+   194
+(1 row)
+
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+ count 
+-------
+     0
+(1 row)
+
 RESET enable_seqscan;
 DROP INDEX jidx;
 -- nested tests
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index ce25ee0..f6f450e 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1906,6 +1906,8 @@ ORDER BY 1, 2, 3;
        2742 |            9 | ?
        2742 |           10 | ?|
        2742 |           11 | ?&
+       2742 |           15 | @?
+       2742 |           16 | @@
        3580 |            1 | <
        3580 |            1 | <<
        3580 |            2 | &<
@@ -1971,7 +1973,7 @@ ORDER BY 1, 2, 3;
        4000 |           26 | >>
        4000 |           27 | >>=
        4000 |           28 | ^@
-(123 rows)
+(125 rows)
 
 -- Check that all opclass search operators have selectivity estimators.
 -- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 6cbdfe4..ed273dc 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -740,6 +740,24 @@ SELECT count(*) FROM testjsonb WHERE j ? 'public';
 SELECT count(*) FROM testjsonb WHERE j ? 'bar';
 SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
 SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) && exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
 
 CREATE INDEX jidx ON testjsonb USING gin (j);
 SET enable_seqscan = off;
@@ -758,6 +776,39 @@ SELECT count(*) FROM testjsonb WHERE j ? 'bar';
 SELECT count(*) FROM testjsonb WHERE j ?| ARRAY['public','disabled'];
 SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
 
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public)';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.bar)';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) || exists($.disabled)';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.public) && exists($.disabled)';
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
 -- array exists - array elements should behave as keys (for GIN index scans too)
 CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
 SELECT count(*) from testjsonb  WHERE j->'array' ? 'bar';
@@ -807,6 +858,34 @@ SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
 -- exercise GIN_SEARCH_MODE_ALL
 SELECT count(*) FROM testjsonb WHERE j @> '{}';
 
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == null';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.wait == null))';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.wait ? (@ == null))';
+SELECT count(*) FROM testjsonb WHERE j @@ '"CC" == $.wait';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.wait == "CC" && true == $.public';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.age == 25.0';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "foo"';
+SELECT count(*) FROM testjsonb WHERE j @@ '$.array[*] == "bar"';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($ ? (@.array[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array ? (@[*] == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($.array[*] ? (@ == "bar"))';
+SELECT count(*) FROM testjsonb WHERE j @@ 'exists($)';
+
+EXPLAIN (COSTS OFF)
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? (@ == null)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.wait ? ("CC" == @)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.wait == "CC" && true == @.public)';
+SELECT count(*) FROM testjsonb WHERE j @? '$.age ? (@ == 25)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.age == 25.0)';
+SELECT count(*) FROM testjsonb WHERE j @? '$ ? (@.array[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array ? (@[*] == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$.array[*] ? (@ == "bar")';
+SELECT count(*) FROM testjsonb WHERE j @? '$';
+SELECT count(*) FROM testjsonb WHERE j @? '$.public';
+SELECT count(*) FROM testjsonb WHERE j @? '$.bar';
+
 RESET enable_seqscan;
 DROP INDEX jidx;
 
-- 
2.7.4

