Commit 0a02e2ae authored by Alexander Korotkov's avatar Alexander Korotkov

GIN support for @@ and @? jsonpath operators

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
parent 72419117
...@@ -102,6 +102,8 @@ ...@@ -102,6 +102,8 @@
<literal>?&amp;</literal> <literal>?&amp;</literal>
<literal>?|</literal> <literal>?|</literal>
<literal>@&gt;</literal> <literal>@&gt;</literal>
<literal>@?</literal>
<literal>@@</literal>
</entry> </entry>
</row> </row>
<row> <row>
...@@ -109,6 +111,8 @@ ...@@ -109,6 +111,8 @@
<entry><type>jsonb</type></entry> <entry><type>jsonb</type></entry>
<entry> <entry>
<literal>@&gt;</literal> <literal>@&gt;</literal>
<literal>@?</literal>
<literal>@@</literal>
</entry> </entry>
</row> </row>
<row> <row>
......
...@@ -480,6 +480,22 @@ CREATE INDEX idxgintags ON api USING GIN ((jdoc -&gt; 'tags')); ...@@ -480,6 +480,22 @@ CREATE INDEX idxgintags ON api USING GIN ((jdoc -&gt; 'tags'));
(More information on expression indexes can be found in <xref (More information on expression indexes can be found in <xref
linkend="indexes-expressional"/>.) linkend="indexes-expressional"/>.)
</para> </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> <para>
Another approach to querying is to exploit containment, for example: Another approach to querying is to exploit containment, for example:
<programlisting> <programlisting>
...@@ -498,7 +514,8 @@ SELECT jdoc-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"tags": ["qu ...@@ -498,7 +514,8 @@ SELECT jdoc-&gt;'guid', jdoc-&gt;'name' FROM api WHERE jdoc @&gt; '{"tags": ["qu
<para> <para>
Although the <literal>jsonb_path_ops</literal> operator class supports 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 performance advantages over the default operator
class <literal>jsonb_ops</literal>. A <literal>jsonb_path_ops</literal> class <literal>jsonb_ops</literal>. A <literal>jsonb_path_ops</literal>
index is usually much smaller than a <literal>jsonb_ops</literal> index is usually much smaller than a <literal>jsonb_ops</literal>
......
...@@ -5,21 +5,69 @@ ...@@ -5,21 +5,69 @@
* *
* Copyright (c) 2014-2019, PostgreSQL Global Development Group * 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 * IDENTIFICATION
* src/backend/utils/adt/jsonb_gin.c * src/backend/utils/adt/jsonb_gin.c
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
#include "postgres.h" #include "postgres.h"
#include "access/gin.h" #include "access/gin.h"
#include "access/stratnum.h" #include "access/stratnum.h"
#include "catalog/pg_collation.h" #include "catalog/pg_collation.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "miscadmin.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/hashutils.h" #include "utils/hashutils.h"
#include "utils/jsonb.h" #include "utils/jsonb.h"
#include "utils/jsonpath.h"
#include "utils/varlena.h" #include "utils/varlena.h"
typedef struct PathHashStack typedef struct PathHashStack
...@@ -28,9 +76,123 @@ typedef struct PathHashStack ...@@ -28,9 +76,123 @@ typedef struct PathHashStack
struct PathHashStack *parent; struct PathHashStack *parent;
} PathHashStack; } 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_text_key(char flag, const char *str, int len);
static Datum make_scalar_key(const JsonbValue *scalarVal, bool is_key); 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 * jsonb_ops GIN opclass support functions
...@@ -68,12 +230,11 @@ gin_extract_jsonb(PG_FUNCTION_ARGS) ...@@ -68,12 +230,11 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
{ {
Jsonb *jb = (Jsonb *) PG_GETARG_JSONB_P(0); Jsonb *jb = (Jsonb *) PG_GETARG_JSONB_P(0);
int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
int total = 2 * JB_ROOT_COUNT(jb); int total = JB_ROOT_COUNT(jb);
JsonbIterator *it; JsonbIterator *it;
JsonbValue v; JsonbValue v;
JsonbIteratorToken r; JsonbIteratorToken r;
int i = 0; GinEntries entries;
Datum *entries;
/* If the root level is empty, we certainly have no keys */ /* If the root level is empty, we certainly have no keys */
if (total == 0) if (total == 0)
...@@ -83,30 +244,23 @@ gin_extract_jsonb(PG_FUNCTION_ARGS) ...@@ -83,30 +244,23 @@ gin_extract_jsonb(PG_FUNCTION_ARGS)
} }
/* Otherwise, use 2 * root count as initial estimate of result size */ /* 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); it = JsonbIteratorInit(&jb->root);
while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE) 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) switch (r)
{ {
case WJB_KEY: case WJB_KEY:
entries[i++] = make_scalar_key(&v, true); add_gin_entry(&entries, make_scalar_key(&v, true));
break; break;
case WJB_ELEM: case WJB_ELEM:
/* Pretend string array elements are keys, see jsonb.h */ /* 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; break;
case WJB_VALUE: case WJB_VALUE:
entries[i++] = make_scalar_key(&v, false); add_gin_entry(&entries, make_scalar_key(&v, false));
break; break;
default: default:
/* we can ignore structural items */ /* we can ignore structural items */
...@@ -114,9 +268,580 @@ gin_extract_jsonb(PG_FUNCTION_ARGS) ...@@ -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 Datum
...@@ -181,6 +906,17 @@ gin_extract_jsonb_query(PG_FUNCTION_ARGS) ...@@ -181,6 +906,17 @@ gin_extract_jsonb_query(PG_FUNCTION_ARGS)
if (j == 0 && strategy == JsonbExistsAllStrategyNumber) if (j == 0 && strategy == JsonbExistsAllStrategyNumber)
*searchMode = GIN_SEARCH_MODE_ALL; *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 else
{ {
elog(ERROR, "unrecognized strategy number: %d", strategy); elog(ERROR, "unrecognized strategy number: %d", strategy);
...@@ -199,7 +935,7 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS) ...@@ -199,7 +935,7 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS)
/* Jsonb *query = PG_GETARG_JSONB_P(2); */ /* Jsonb *query = PG_GETARG_JSONB_P(2); */
int32 nkeys = PG_GETARG_INT32(3); 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 *recheck = (bool *) PG_GETARG_POINTER(5);
bool res = true; bool res = true;
int32 i; int32 i;
...@@ -256,6 +992,18 @@ gin_consistent_jsonb(PG_FUNCTION_ARGS) ...@@ -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 else
elog(ERROR, "unrecognized strategy number: %d", strategy); elog(ERROR, "unrecognized strategy number: %d", strategy);
...@@ -270,8 +1018,7 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS) ...@@ -270,8 +1018,7 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
/* Jsonb *query = PG_GETARG_JSONB_P(2); */ /* Jsonb *query = PG_GETARG_JSONB_P(2); */
int32 nkeys = PG_GETARG_INT32(3); 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; GinTernaryValue res = GIN_MAYBE;
int32 i; int32 i;
...@@ -308,6 +1055,20 @@ gin_triconsistent_jsonb(PG_FUNCTION_ARGS) ...@@ -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 else
elog(ERROR, "unrecognized strategy number: %d", strategy); elog(ERROR, "unrecognized strategy number: %d", strategy);
...@@ -331,14 +1092,13 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) ...@@ -331,14 +1092,13 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
{ {
Jsonb *jb = PG_GETARG_JSONB_P(0); Jsonb *jb = PG_GETARG_JSONB_P(0);
int32 *nentries = (int32 *) PG_GETARG_POINTER(1); int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
int total = 2 * JB_ROOT_COUNT(jb); int total = JB_ROOT_COUNT(jb);
JsonbIterator *it; JsonbIterator *it;
JsonbValue v; JsonbValue v;
JsonbIteratorToken r; JsonbIteratorToken r;
PathHashStack tail; PathHashStack tail;
PathHashStack *stack; PathHashStack *stack;
int i = 0; GinEntries entries;
Datum *entries;
/* If the root level is empty, we certainly have no keys */ /* If the root level is empty, we certainly have no keys */
if (total == 0) if (total == 0)
...@@ -348,7 +1108,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) ...@@ -348,7 +1108,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
} }
/* Otherwise, use 2 * root count as initial estimate of result size */ /* 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 */ /* We keep a stack of partial hashes corresponding to parent key levels */
tail.parent = NULL; tail.parent = NULL;
...@@ -361,13 +1121,6 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) ...@@ -361,13 +1121,6 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
{ {
PathHashStack *parent; 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) switch (r)
{ {
case WJB_BEGIN_ARRAY: case WJB_BEGIN_ARRAY:
...@@ -398,7 +1151,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) ...@@ -398,7 +1151,7 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS)
/* mix the element or value's hash into the prepared hash */ /* mix the element or value's hash into the prepared hash */
JsonbHashScalarValue(&v, &stack->hash); JsonbHashScalarValue(&v, &stack->hash);
/* and emit an index entry */ /* 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 */ /* reset hash for next key, value, or sub-object */
stack->hash = stack->parent->hash; stack->hash = stack->parent->hash;
break; break;
...@@ -419,9 +1172,9 @@ gin_extract_jsonb_path(PG_FUNCTION_ARGS) ...@@ -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 Datum
...@@ -432,9 +1185,8 @@ gin_extract_jsonb_query_path(PG_FUNCTION_ARGS) ...@@ -432,9 +1185,8 @@ gin_extract_jsonb_query_path(PG_FUNCTION_ARGS)
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
Datum *entries; Datum *entries;
if (strategy != JsonbContainsStrategyNumber) if (strategy == JsonbContainsStrategyNumber)
elog(ERROR, "unrecognized strategy number: %d", strategy); {
/* Query is a jsonb, so just apply gin_extract_jsonb_path ... */ /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */
entries = (Datum *) entries = (Datum *)
DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path, DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
...@@ -444,6 +1196,23 @@ gin_extract_jsonb_query_path(PG_FUNCTION_ARGS) ...@@ -444,6 +1196,23 @@ gin_extract_jsonb_query_path(PG_FUNCTION_ARGS)
/* ... although "contains {}" requires a full index scan */ /* ... although "contains {}" requires a full index scan */
if (*nentries == 0) if (*nentries == 0)
*searchMode = GIN_SEARCH_MODE_ALL; *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, 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); PG_RETURN_POINTER(entries);
} }
...@@ -456,22 +1225,21 @@ gin_consistent_jsonb_path(PG_FUNCTION_ARGS) ...@@ -456,22 +1225,21 @@ gin_consistent_jsonb_path(PG_FUNCTION_ARGS)
/* Jsonb *query = PG_GETARG_JSONB_P(2); */ /* Jsonb *query = PG_GETARG_JSONB_P(2); */
int32 nkeys = PG_GETARG_INT32(3); 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 *recheck = (bool *) PG_GETARG_POINTER(5);
bool res = true; bool res = true;
int32 i; int32 i;
if (strategy != JsonbContainsStrategyNumber) if (strategy == JsonbContainsStrategyNumber)
elog(ERROR, "unrecognized strategy number: %d", strategy); {
/* /*
* jsonb_path_ops is necessarily lossy, not only because of hash * jsonb_path_ops is necessarily lossy, not only because of hash
* collisions but also because it doesn't preserve complete information * collisions but also because it doesn't preserve complete
* about the structure of the JSON object. Besides, there are some * information about the structure of the JSON object. Besides, there
* special rules around the containment of raw scalars in arrays that are * are some special rules around the containment of raw scalars in
* not handled here. So we must always recheck a match. However, if not * arrays that are not handled here. So we must always recheck a
* all of the keys are present, the tuple certainly doesn't match. * match. However, if not all of the keys are present, the tuple
* certainly doesn't match.
*/ */
*recheck = true; *recheck = true;
for (i = 0; i < nkeys; i++) for (i = 0; i < nkeys; i++)
...@@ -482,6 +1250,21 @@ gin_consistent_jsonb_path(PG_FUNCTION_ARGS) ...@@ -482,6 +1250,21 @@ gin_consistent_jsonb_path(PG_FUNCTION_ARGS)
break; 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); PG_RETURN_BOOL(res);
} }
...@@ -494,18 +1277,16 @@ gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS) ...@@ -494,18 +1277,16 @@ gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS)
/* Jsonb *query = PG_GETARG_JSONB_P(2); */ /* Jsonb *query = PG_GETARG_JSONB_P(2); */
int32 nkeys = PG_GETARG_INT32(3); 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; GinTernaryValue res = GIN_MAYBE;
int32 i; int32 i;
if (strategy != JsonbContainsStrategyNumber) if (strategy == JsonbContainsStrategyNumber)
elog(ERROR, "unrecognized strategy number: %d", strategy); {
/* /*
* Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE;
* corresponds to always forcing recheck in the regular consistent * this corresponds to always forcing recheck in the regular
* function, for the reasons listed there. * consistent function, for the reasons listed there.
*/ */
for (i = 0; i < nkeys; i++) for (i = 0; i < nkeys; i++)
{ {
...@@ -515,6 +1296,23 @@ gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS) ...@@ -515,6 +1296,23 @@ gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS)
break; 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); PG_RETURN_GIN_TERNARY_VALUE(res);
} }
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201903301 #define CATALOG_VERSION_NO 201904011
#endif #endif
...@@ -1468,11 +1468,23 @@ ...@@ -1468,11 +1468,23 @@
{ amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb', { amopfamily => 'gin/jsonb_ops', amoplefttype => 'jsonb',
amoprighttype => '_text', amopstrategy => '11', amopopr => '?&(jsonb,_text)', amoprighttype => '_text', amopstrategy => '11', amopopr => '?&(jsonb,_text)',
amopmethod => 'gin' }, 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 # GIN jsonb_path_ops
{ amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb', { amopfamily => 'gin/jsonb_path_ops', amoplefttype => 'jsonb',
amoprighttype => 'jsonb', amopstrategy => '7', amopopr => '@>(jsonb,jsonb)', amoprighttype => 'jsonb', amopstrategy => '7', amopopr => '@>(jsonb,jsonb)',
amopmethod => 'gin' }, 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 # SP-GiST range_ops
{ amopfamily => 'spgist/range_ops', amoplefttype => 'anyrange', { amopfamily => 'spgist/range_ops', amoplefttype => 'anyrange',
......
...@@ -34,6 +34,9 @@ typedef enum ...@@ -34,6 +34,9 @@ typedef enum
#define JsonbExistsStrategyNumber 9 #define JsonbExistsStrategyNumber 9
#define JsonbExistsAnyStrategyNumber 10 #define JsonbExistsAnyStrategyNumber 10
#define JsonbExistsAllStrategyNumber 11 #define JsonbExistsAllStrategyNumber 11
#define JsonbJsonpathExistsStrategyNumber 15
#define JsonbJsonpathPredicateStrategyNumber 16
/* /*
* In the standard jsonb_ops GIN opclass for jsonb, we choose to index both * In the standard jsonb_ops GIN opclass for jsonb, we choose to index both
......
...@@ -35,6 +35,8 @@ typedef struct ...@@ -35,6 +35,8 @@ typedef struct
#define PG_GETARG_JSONPATH_P_COPY(x) DatumGetJsonPathPCopy(PG_GETARG_DATUM(x)) #define PG_GETARG_JSONPATH_P_COPY(x) DatumGetJsonPathPCopy(PG_GETARG_DATUM(x))
#define PG_RETURN_JSONPATH_P(p) PG_RETURN_POINTER(p) #define PG_RETURN_JSONPATH_P(p) PG_RETURN_POINTER(p)
#define jspIsScalar(type) ((type) >= jpiNull && (type) <= jpiBool)
/* /*
* All node's type of jsonpath expression * All node's type of jsonpath expression
*/ */
......
...@@ -2731,6 +2731,114 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; ...@@ -2731,6 +2731,114 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42 42
(1 row) (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); CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off; SET enable_seqscan = off;
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
...@@ -2806,6 +2914,196 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled']; ...@@ -2806,6 +2914,196 @@ SELECT count(*) FROM testjsonb WHERE j ?& ARRAY['public','disabled'];
42 42
(1 row) (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) -- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array')); CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
...@@ -2956,6 +3254,161 @@ SELECT count(*) FROM testjsonb WHERE j @> '{}'; ...@@ -2956,6 +3254,161 @@ SELECT count(*) FROM testjsonb WHERE j @> '{}';
1012 1012
(1 row) (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; RESET enable_seqscan;
DROP INDEX jidx; DROP INDEX jidx;
-- nested tests -- nested tests
......
...@@ -1920,6 +1920,8 @@ ORDER BY 1, 2, 3; ...@@ -1920,6 +1920,8 @@ ORDER BY 1, 2, 3;
2742 | 9 | ? 2742 | 9 | ?
2742 | 10 | ?| 2742 | 10 | ?|
2742 | 11 | ?& 2742 | 11 | ?&
2742 | 15 | @?
2742 | 16 | @@
3580 | 1 | < 3580 | 1 | <
3580 | 1 | << 3580 | 1 | <<
3580 | 2 | &< 3580 | 2 | &<
...@@ -1985,7 +1987,7 @@ ORDER BY 1, 2, 3; ...@@ -1985,7 +1987,7 @@ ORDER BY 1, 2, 3;
4000 | 26 | >> 4000 | 26 | >>
4000 | 27 | >>= 4000 | 27 | >>=
4000 | 28 | ^@ 4000 | 28 | ^@
(123 rows) (125 rows)
-- Check that all opclass search operators have selectivity estimators. -- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing -- This is not absolutely required, but it seems a reasonable thing
......
...@@ -740,6 +740,24 @@ SELECT count(*) FROM testjsonb WHERE j ? 'public'; ...@@ -740,6 +740,24 @@ SELECT count(*) FROM testjsonb WHERE j ? 'public';
SELECT count(*) FROM testjsonb WHERE j ? 'bar'; 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 ?& 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); CREATE INDEX jidx ON testjsonb USING gin (j);
SET enable_seqscan = off; SET enable_seqscan = off;
...@@ -758,6 +776,39 @@ SELECT count(*) FROM testjsonb WHERE j ? 'bar'; ...@@ -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'];
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) -- array exists - array elements should behave as keys (for GIN index scans too)
CREATE INDEX jidx_array ON testjsonb USING gin((j->'array')); CREATE INDEX jidx_array ON testjsonb USING gin((j->'array'));
SELECT count(*) from testjsonb WHERE j->'array' ? 'bar'; SELECT count(*) from testjsonb WHERE j->'array' ? 'bar';
...@@ -807,6 +858,34 @@ SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}'; ...@@ -807,6 +858,34 @@ SELECT count(*) FROM testjsonb WHERE j @> '{"age":25.0}';
-- exercise GIN_SEARCH_MODE_ALL -- exercise GIN_SEARCH_MODE_ALL
SELECT count(*) FROM testjsonb WHERE j @> '{}'; 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; RESET enable_seqscan;
DROP INDEX jidx; DROP INDEX jidx;
......
...@@ -867,6 +867,7 @@ GinBtreeEntryInsertData ...@@ -867,6 +867,7 @@ GinBtreeEntryInsertData
GinBtreeStack GinBtreeStack
GinBuildState GinBuildState
GinChkVal GinChkVal
GinEntries
GinEntryAccumulator GinEntryAccumulator
GinIndexStat GinIndexStat
GinMetaPageData GinMetaPageData
...@@ -1106,6 +1107,13 @@ JsonPath ...@@ -1106,6 +1107,13 @@ JsonPath
JsonPathBool JsonPathBool
JsonPathExecContext JsonPathExecContext
JsonPathExecResult JsonPathExecResult
JsonPathGinAddPathItemFunc
JsonPathGinContext
JsonPathGinExtractNodesFunc
JsonPathGinNode
JsonPathGinNodeType
JsonPathGinPath
JsonPathGinPathItem
JsonPathItem JsonPathItem
JsonPathItemType JsonPathItemType
JsonPathParseItem JsonPathParseItem
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment