GIN support for @@ and @? jsonpath operators
authorAlexander Korotkov <akorotkov@postgresql.org>
Mon, 1 Apr 2019 15:08:15 +0000 (18:08 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Mon, 1 Apr 2019 15:08:52 +0000 (18:08 +0300)
This commit makes existing GIN operator classes jsonb_ops and json_path_ops
support "jsonb @@ jsonpath" and "jsonb @? jsonpath" operators.  Basic idea is
to extract statements of following form out of jsonpath.

 key1.key2. ... .keyN = const

The rest of jsonpath is rechecked from heap.

Catversion is bumped.

Discussion: https://postgr.es/m/fcc6fc6a-b497-f39a-923d-aa34d0c588e8%402ndQuadrant.com
Author: Nikita Glukhov, Alexander Korotkov
Reviewed-by: Jonathan Katz, Pavel Stehule
doc/src/sgml/gin.sgml
doc/src/sgml/json.sgml
src/backend/utils/adt/jsonb_gin.c
src/include/catalog/catversion.h
src/include/catalog/pg_amop.dat
src/include/utils/jsonb.h
src/include/utils/jsonpath.h
src/test/regress/expected/jsonb.out
src/test/regress/expected/opr_sanity.out
src/test/regress/sql/jsonb.sql
src/tools/pgindent/typedefs.list

index 488c3d8b45de0a33c2c9bc7b9a1f0c19d6b0b96d..91197b838355203416cc32c4684271c908bb0ef0 100644 (file)
        <literal>?&amp;</literal>
        <literal>?|</literal>
        <literal>@&gt;</literal>
+       <literal>@?</literal>
+       <literal>@@</literal>
       </entry>
      </row>
      <row>
       <entry><type>jsonb</type></entry>
       <entry>
        <literal>@&gt;</literal>
+       <literal>@?</literal>
+       <literal>@@</literal>
       </entry>
      </row>
      <row>
index 2eccf244cd32316c02d6cb59ecf6afd78e8741d6..3e0e92a785005add33370688d38a8aca4c501c3b 100644 (file)
@@ -480,6 +480,22 @@ CREATE INDEX idxgintags ON api USING GIN ((jdoc -&gt; 'tags'));
     (More information on expression indexes can be found in <xref
     linkend="indexes-expressional"/>.)
   </para>
+  <para>
+    Also, GIN index supports <literal>@@</literal> and <literal>@?</literal>
+    operators, which perform <literal>jsonpath</literal> matching.
+<programlisting>
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';
+</programlisting>
+<programlisting>
+SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] ? (@ == "qui")';
+</programlisting>
+    GIN index extracts statements of following form out of
+    <literal>jsonpath</literal>: <literal>accessors_chain = const</literal>.
+    Accessors chain may consist of <literal>.key</literal>,
+    <literal>[*]</literal> and <literal>[index]</literal> accessors.
+    <literal>jsonb_ops</literal> additionally supports <literal>.*</literal>
+    and <literal>.**</literal> statements.
+  </para>
   <para>
     Another approach to querying is to exploit containment, for example:
 <programlisting>
@@ -498,7 +514,8 @@ SELECT jdoc-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"tags": ["qu
 
   <para>
     Although the <literal>jsonb_path_ops</literal> operator class supports
-    only queries with the <literal>@&gt;</literal> operator, it has notable
+    only queries with the <literal>@&gt;</literal>, <literal>@@</literal>
+    and <literal>@?</literal> operators, it has notable
     performance advantages over the default operator
     class <literal>jsonb_ops</literal>.  A <literal>jsonb_path_ops</literal>
     index is usually much smaller than a <literal>jsonb_ops</literal>
index bae5287f705766c6337e39a88f90f93c486ece18..a20e44b1fea30e5134bfed96ae8b5d5b41578931 100644 (file)
@@ -5,21 +5,69 @@
  *
  * Copyright (c) 2014-2019, PostgreSQL Global Development Group
  *
+ * We provide two opclasses for jsonb indexing: jsonb_ops and jsonb_path_ops.
+ * For their description see json.sgml and comments in jsonb.h.
+ *
+ * The operators support, among the others, "jsonb @? jsonpath" and
+ * "jsonb @@ jsonpath".  Expressions containing these operators are easily
+ * expressed through each other.
+ *
+ *     jb @? 'path' <=> jb @@ 'EXISTS(path)'
+ *     jb @@ 'expr' <=> jb @? '$ ? (expr)'
+ *
+ * Thus, we're going to consider only @@ operator, while regarding @? operator
+ * the same is true for jb @@ 'EXISTS(path)'.
+ *
+ * Result of jsonpath query extraction is a tree, which leaf nodes are index
+ * entries and non-leaf nodes are AND/OR logical expressions.  Basically we
+ * extract following statements out of jsonpath:
+ *
+ *     1) "accessors_chain = const",
+ *     2) "EXISTS(accessors_chain)".
+ *
+ * Accessors chain may consist of .key, [*] and [index] accessors.  jsonb_ops
+ * additionally supports .* and .**.
+ *
+ * For now, both jsonb_ops and jsonb_path_ops supports only statements of
+ * the 1st find.  jsonb_ops might also support statements of the 2nd kind,
+ * but given we have no statistics keys extracted from accessors chain
+ * are likely non-selective.  Therefore, we choose to not confuse optimizer
+ * and skip statements of the 2nd kind altogether.  In future versions that
+ * might be changed.
+ *
+ * In jsonb_ops statement of the 1st kind is split into expression of AND'ed
+ * keys and const.  Sometimes const might be interpreted as both value or key
+ * in jsonb_ops.  Then statement of 1st kind is decomposed into the expression
+ * below.
+ *
+ *     key1 AND key2 AND ... AND keyN AND (const_as_value OR const_as_key)
+ *
+ * jsonb_path_ops transforms each statement of the 1st kind into single hash
+ * entry below.
+ *
+ *     HASH(key1, key2, ... , keyN, const)
+ *
+ * Despite statements of the 2nd kind are not supported by both jsonb_ops and
+ * jsonb_path_ops, EXISTS(path) expressions might be still supported,
+ * when statements of 1st kind could be extracted out of their filters.
  *
  * IDENTIFICATION
  *       src/backend/utils/adt/jsonb_gin.c
  *
  *-------------------------------------------------------------------------
  */
+
 #include "postgres.h"
 
 #include "access/gin.h"
 #include "access/stratnum.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
+#include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/hashutils.h"
 #include "utils/jsonb.h"
+#include "utils/jsonpath.h"
 #include "utils/varlena.h"
 
 typedef struct PathHashStack
@@ -28,9 +76,123 @@ typedef struct PathHashStack
        struct PathHashStack *parent;
 } PathHashStack;
 
+/* Buffer for GIN entries */
+typedef struct GinEntries
+{
+       Datum      *buf;
+       int                     count;
+       int                     allocated;
+} GinEntries;
+
+typedef enum JsonPathGinNodeType
+{
+       JSP_GIN_OR,
+       JSP_GIN_AND,
+       JSP_GIN_ENTRY
+} JsonPathGinNodeType;
+
+typedef struct JsonPathGinNode JsonPathGinNode;
+
+/* Node in jsonpath expression tree */
+struct JsonPathGinNode
+{
+       JsonPathGinNodeType 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;
+       JsonPathGinNode *args[FLEXIBLE_ARRAY_MEMBER];   /* valid for OR and AND
+                                                                                                        * nodes */
+};
+
+/*
+ * jsonb_ops entry extracted from jsonpath item.  Corresponding path item
+ * may be: '.key', '.*', '.**', '[index]' or '[*]'.
+ * Entry type is stored in 'type' field.
+ */
+typedef struct JsonPathGinPathItem
+{
+       struct JsonPathGinPathItem *parent;
+       Datum           keyName;                /* key name (for '.key' path item) or NULL */
+       JsonPathItemType type;          /* type of jsonpath item */
+} JsonPathGinPathItem;
+
+/* GIN representation of the extracted json path */
+typedef union JsonPathGinPath
+{
+       JsonPathGinPathItem *items; /* list of path items (jsonb_ops) */
+       uint32          hash;                   /* hash of the path (jsonb_path_ops) */
+} JsonPathGinPath;
+
+typedef struct JsonPathGinContext JsonPathGinContext;
+
+/* Callback, which stores information about path item into JsonPathGinPath */
+typedef bool (*JsonPathGinAddPathItemFunc) (JsonPathGinPath *path,
+                                                                                       JsonPathItem *jsp);
+
+/*
+ * Callback, which extracts set of nodes from statement of 1st kind
+ * (scalar != NULL) or statement of 2nd kind (scalar == NULL).
+ */
+typedef List *(*JsonPathGinExtractNodesFunc) (JsonPathGinContext *cxt,
+                                                                                         JsonPathGinPath path,
+                                                                                         JsonbValue *scalar,
+                                                                                         List *nodes);
+
+/* Context for jsonpath entries extraction */
+struct JsonPathGinContext
+{
+       JsonPathGinAddPathItemFunc add_path_item;
+       JsonPathGinExtractNodesFunc extract_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 JsonPathGinNode *extract_jsp_bool_expr(JsonPathGinContext *cxt,
+                                         JsonPathGinPath path, JsonPathItem *jsp, bool not);
+
+
+/* Initialize GinEntries struct */
+static void
+init_gin_entries(GinEntries *entries, int preallocated)
+{
+       entries->allocated = preallocated;
+       entries->buf = preallocated ? palloc(sizeof(Datum) * preallocated) : NULL;
+       entries->count = 0;
+}
+
+/* Add new entry to GinEntries */
+static int
+add_gin_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 +230,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 +244,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_gin_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_gin_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_gin_entry(&entries, make_scalar_key(&v, v.type == jbvString));
                                break;
                        case WJB_VALUE:
-                               entries[i++] = make_scalar_key(&v, false);
+                               add_gin_entry(&entries, make_scalar_key(&v, false));
                                break;
                        default:
                                /* we can ignore structural items */
@@ -114,9 +268,580 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
                }
        }
 
-       *nentries = i;
+       *nentries = entries.count;
 
-       PG_RETURN_POINTER(entries);
+       PG_RETURN_POINTER(entries.buf);
+}
+
+/* Append JsonPathGinPathItem to JsonPathGinPath (jsonb_ops) */
+static bool
+jsonb_ops__add_path_item(JsonPathGinPath *path, JsonPathItem *jsp)
+{
+       JsonPathGinPathItem *pentry;
+       Datum           keyName;
+
+       switch (jsp->type)
+       {
+               case jpiRoot:
+                       path->items = NULL; /* reset path */
+                       return true;
+
+               case jpiKey:
+                       {
+                               int                     len;
+                               char       *key = jspGetString(jsp, &len);
+
+                               keyName = make_text_key(JGINFLAG_KEY, key, len);
+                               break;
+                       }
+
+               case jpiAny:
+               case jpiAnyKey:
+               case jpiAnyArray:
+               case jpiIndexArray:
+                       keyName = PointerGetDatum(NULL);
+                       break;
+
+               default:
+                       /* other path items like item methods are not supported */
+                       return false;
+       }
+
+       pentry = palloc(sizeof(*pentry));
+
+       pentry->type = jsp->type;
+       pentry->keyName = keyName;
+       pentry->parent = path->items;
+
+       path->items = pentry;
+
+       return true;
+}
+
+/* Combine existing path hash with next key hash (jsonb_path_ops) */
+static bool
+jsonb_path_ops__add_path_item(JsonPathGinPath *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 JsonPathGinNode *
+make_jsp_entry_node(Datum entry)
+{
+       JsonPathGinNode *node = palloc(offsetof(JsonPathGinNode, args));
+
+       node->type = JSP_GIN_ENTRY;
+       node->val.entryDatum = entry;
+
+       return node;
+}
+
+static JsonPathGinNode *
+make_jsp_entry_node_scalar(JsonbValue *scalar, bool iskey)
+{
+       return make_jsp_entry_node(make_scalar_key(scalar, iskey));
+}
+
+static JsonPathGinNode *
+make_jsp_expr_node(JsonPathGinNodeType type, int nargs)
+{
+       JsonPathGinNode *node = palloc(offsetof(JsonPathGinNode, args) +
+                                                                  sizeof(node->args[0]) * nargs);
+
+       node->type = type;
+       node->val.nargs = nargs;
+
+       return node;
+}
+
+static JsonPathGinNode *
+make_jsp_expr_node_args(JsonPathGinNodeType type, List *args)
+{
+       JsonPathGinNode *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 JsonPathGinNode *
+make_jsp_expr_node_binary(JsonPathGinNodeType type,
+                                                 JsonPathGinNode *arg1, JsonPathGinNode *arg2)
+{
+       JsonPathGinNode *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_nodes(JsonPathGinContext *cxt, JsonPathGinPath path,
+                                                JsonbValue *scalar, List *nodes)
+{
+       JsonPathGinPathItem *pentry;
+
+       if (scalar)
+       {
+               JsonPathGinNode *node;
+
+               /*
+                * Append path entry nodes only if scalar is provided.  See header
+                * comment for details.
+                */
+               for (pentry = path.items; pentry; pentry = pentry->parent)
+               {
+                       if (pentry->type == jpiKey) /* only keys are indexed */
+                               nodes = lappend(nodes, make_jsp_entry_node(pentry->keyName));
+               }
+
+               /* Append scalar node for equality queries. */
+               if (scalar->type == jbvString)
+               {
+                       JsonPathGinPathItem *last = path.items;
+                       GinTernaryValue key_entry;
+
+                       /*
+                        * Assuming that jsonb_ops interprets string array elements as
+                        * keys, we may extract key or non-key entry or even both.  In the
+                        * latter case we create OR-node.  It is possible in lax mode
+                        * where arrays are automatically unwrapped, or in strict mode for
+                        * jpiAny items.
+                        */
+
+                       if (cxt->lax)
+                               key_entry = GIN_MAYBE;
+                       else if (!last)         /* root ($) */
+                               key_entry = GIN_FALSE;
+                       else if (last->type == jpiAnyArray || last->type == jpiIndexArray)
+                               key_entry = GIN_TRUE;
+                       else if (last->type == jpiAny)
+                               key_entry = GIN_MAYBE;
+                       else
+                               key_entry = GIN_FALSE;
+
+                       if (key_entry == GIN_MAYBE)
+                       {
+                               JsonPathGinNode *n1 = make_jsp_entry_node_scalar(scalar, true);
+                               JsonPathGinNode *n2 = make_jsp_entry_node_scalar(scalar, false);
+
+                               node = make_jsp_expr_node_binary(JSP_GIN_OR, n1, n2);
+                       }
+                       else
+                       {
+                               node = make_jsp_entry_node_scalar(scalar,
+                                                                                                 key_entry == 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_nodes(JsonPathGinContext *cxt, JsonPathGinPath 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(JsonPathGinContext *cxt, JsonPathGinPath path,
+                                                       JsonPathItem *jsp, JsonbValue *scalar)
+{
+       JsonPathItem next;
+       List       *nodes = NIL;
+
+       for (;;)
+       {
+               switch (jsp->type)
+               {
+                       case jpiCurrent:
+                               break;
+
+                       case jpiFilter:
+                               {
+                                       JsonPathItem arg;
+                                       JsonPathGinNode *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_item(&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_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 JsonPathGinNode *
+extract_jsp_path_expr(JsonPathGinContext *cxt, JsonPathGinPath 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(JSP_GIN_AND, nodes);
+}
+
+/* Recursively extract nodes from the boolean jsonpath expression. */
+static JsonPathGinNode *
+extract_jsp_bool_expr(JsonPathGinContext *cxt, JsonPathGinPath path,
+                                         JsonPathItem *jsp, bool not)
+{
+       check_stack_depth();
+
+       switch (jsp->type)
+       {
+               case jpiAnd:                    /* expr && expr */
+               case jpiOr:                             /* expr || expr */
+                       {
+                               JsonPathItem arg;
+                               JsonPathGinNode *larg;
+                               JsonPathGinNode *rarg;
+                               JsonPathGinNodeType 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) ? JSP_GIN_AND : JSP_GIN_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_gin_entries(JsonPathGinNode *node, GinEntries *entries)
+{
+       check_stack_depth();
+
+       switch (node->type)
+       {
+               case JSP_GIN_ENTRY:
+                       /* replace datum with its index in the array */
+                       node->val.entryIndex = add_gin_entry(entries, node->val.entryDatum);
+                       break;
+
+               case JSP_GIN_OR:
+               case JSP_GIN_AND:
+                       {
+                               int                     i;
+
+                               for (i = 0; i < node->val.nargs; i++)
+                                       emit_jsp_gin_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)
+{
+       JsonPathGinContext cxt;
+       JsonPathItem root;
+       JsonPathGinNode *node;
+       JsonPathGinPath path = {0};
+       GinEntries      entries = {0};
+
+       cxt.lax = (jp->header & JSONPATH_LAX) != 0;
+
+       if (pathOps)
+       {
+               cxt.add_path_item = jsonb_path_ops__add_path_item;
+               cxt.extract_nodes = jsonb_path_ops__extract_nodes;
+       }
+       else
+       {
+               cxt.add_path_item = jsonb_ops__add_path_item;
+               cxt.extract_nodes = jsonb_ops__extract_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_gin_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_gin_node(JsonPathGinNode *node, void *check, bool ternary)
+{
+       GinTernaryValue res;
+       GinTernaryValue v;
+       int                     i;
+
+       switch (node->type)
+       {
+               case JSP_GIN_AND:
+                       res = GIN_TRUE;
+                       for (i = 0; i < node->val.nargs; i++)
+                       {
+                               v = execute_jsp_gin_node(node->args[i], check, ternary);
+                               if (v == GIN_FALSE)
+                                       return GIN_FALSE;
+                               else if (v == GIN_MAYBE)
+                                       res = GIN_MAYBE;
+                       }
+                       return res;
+
+               case JSP_GIN_OR:
+                       res = GIN_FALSE;
+                       for (i = 0; i < node->val.nargs; i++)
+                       {
+                               v = execute_jsp_gin_node(node->args[i], check, ternary);
+                               if (v == GIN_TRUE)
+                                       return GIN_TRUE;
+                               else if (v == GIN_MAYBE)
+                                       res = GIN_MAYBE;
+                       }
+                       return res;
+
+               case JSP_GIN_ENTRY:
+                       {
+                               int                     index = node->val.entryIndex;
+
+                               if (ternary)
+                                       return ((GinTernaryValue *) check)[index];
+                               else
+                                       return ((bool *) check)[index] ? GIN_TRUE : GIN_FALSE;
+                       }
+
+               default:
+                       elog(ERROR, "invalid jsonpath gin node type: %d", node->type);
+                       return GIN_FALSE;       /* keep compiler quiet */
+       }
 }
 
 Datum
@@ -181,6 +906,17 @@ 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 +935,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 +992,18 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
                        }
                }
        }
+       else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+                        strategy == JsonbJsonpathExistsStrategyNumber)
+       {
+               *recheck = true;
+
+               if (nkeys > 0)
+               {
+                       Assert(extra_data && extra_data[0]);
+                       res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check,
+                                                                          false) != GIN_FALSE;
+               }
+       }
        else
                elog(ERROR, "unrecognized strategy number: %d", strategy);
 
@@ -270,8 +1018,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 +1055,20 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
                        }
                }
        }
+       else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
+                        strategy == JsonbJsonpathExistsStrategyNumber)
+       {
+               if (nkeys > 0)
+               {
+                       Assert(extra_data && extra_data[0]);
+                       res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check,
+                                                                          true);
+
+                       /* Should always recheck the result */
+                       if (res == GIN_TRUE)
+                               res = GIN_MAYBE;
+               }
+       }
        else
                elog(ERROR, "unrecognized strategy number: %d", strategy);
 
@@ -331,14 +1092,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 +1108,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_gin_entries(&entries, 2 * total);
 
        /* We keep a stack of partial hashes corresponding to parent key levels */
        tail.parent = NULL;
@@ -361,13 +1121,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 +1151,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_gin_entry(&entries, UInt32GetDatum(stack->hash));
                                /* reset hash for next key, value, or sub-object */
                                stack->hash = stack->parent->hash;
                                break;
@@ -419,9 +1172,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 +1185,34 @@ 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)));
 
-       /* 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);
 
-       /* ... although "contains {}" requires a full index scan */
-       if (*nentries == 0)
-               *searchMode = GIN_SEARCH_MODE_ALL;
+               entries = extract_jsp_query(jp, strategy, true, nentries, extra_data);
+
+               if (!entries)
+                       *searchMode = GIN_SEARCH_MODE_ALL;
+       }
+       else
+       {
+               elog(ERROR, "unrecognized strategy number: %d", strategy);
+               entries = NULL;
+       }
 
        PG_RETURN_POINTER(entries);
 }
@@ -456,32 +1225,46 @@ 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)
+               {
+                       Assert(extra_data && extra_data[0]);
+                       res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check,
+                                                                          false) != GIN_FALSE;
+               }
+       }
+       else
+               elog(ERROR, "unrecognized strategy number: %d", strategy);
 
        PG_RETURN_BOOL(res);
 }
@@ -494,27 +1277,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)
+               {
+                       Assert(extra_data && extra_data[0]);
+                       res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check,
+                                                                          true);
+
+                       /* Should always recheck the result */
+                       if (res == GIN_TRUE)
+                               res = GIN_MAYBE;
+               }
+       }
+       else
+               elog(ERROR, "unrecognized strategy number: %d", strategy);
 
        PG_RETURN_GIN_TERNARY_VALUE(res);
 }
index 6155c3234d97910ca1e5f461ad91e657a02b81a4..dfb94bfadfb0194809f3f59719b89aea5e8c1fcd 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201903301
+#define CATALOG_VERSION_NO     201904011
 
 #endif
index 0ab95d8a24d7628cdf48dac835a989125eb1c173..cf63eb7d54604e26773a0653c81ce64e6d6c4fd6 100644 (file)
 { 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',
index ec0355f13c2f66f36e3a4c02e6308ad95a79529a..432331b3b9e41d682097bb84bf6ed43bceaf093b 100644 (file)
@@ -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
index 14f837e00d511cecc8966a55914f22d66eb2b837..ae8a995c7f88c491e046988ec360703a6a01fe27 100644 (file)
@@ -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
  */
index c251eb70be96ba549362c4542172eab60db91bd8..10183030068745bf5734847fb22d33b20e32eb17 100644 (file)
@@ -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
index 49fc313af06874ff5234226d5da07befb769d140..85af36ee5bca2f8010642647f3bf49ac03a04249 100644 (file)
@@ -1920,6 +1920,8 @@ ORDER BY 1, 2, 3;
        2742 |            9 | ?
        2742 |           10 | ?|
        2742 |           11 | ?&
+       2742 |           15 | @?
+       2742 |           16 | @@
        3580 |            1 | <
        3580 |            1 | <<
        3580 |            2 | &<
@@ -1985,7 +1987,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
index 1bf32076e30de5f2533eea41b81dad355fefc648..c1a7880792d9a78237dca908fa52a901b86d281c 100644 (file)
@@ -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;
 
index 4816b5b271d2eab284ff2767af5c3245640b540b..f31929664ac96517d0b88f6448e1c5e7c1b29890 100644 (file)
@@ -867,6 +867,7 @@ GinBtreeEntryInsertData
 GinBtreeStack
 GinBuildState
 GinChkVal
+GinEntries
 GinEntryAccumulator
 GinIndexStat
 GinMetaPageData
@@ -1106,6 +1107,13 @@ JsonPath
 JsonPathBool
 JsonPathExecContext
 JsonPathExecResult
+JsonPathGinAddPathItemFunc
+JsonPathGinContext
+JsonPathGinExtractNodesFunc
+JsonPathGinNode
+JsonPathGinNodeType
+JsonPathGinPath
+JsonPathGinPathItem
 JsonPathItem
 JsonPathItemType
 JsonPathParseItem