Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * jsonb_gin.c
4 : : * GIN support functions for jsonb
5 : : *
6 : : * Copyright (c) 2014-2026, PostgreSQL Global Development Group
7 : : *
8 : : * We provide two opclasses for jsonb indexing: jsonb_ops and jsonb_path_ops.
9 : : * For their description see json.sgml and comments in jsonb.h.
10 : : *
11 : : * The operators support, among the others, "jsonb @? jsonpath" and
12 : : * "jsonb @@ jsonpath". Expressions containing these operators are easily
13 : : * expressed through each other.
14 : : *
15 : : * jb @? 'path' <=> jb @@ 'EXISTS(path)'
16 : : * jb @@ 'expr' <=> jb @? '$ ? (expr)'
17 : : *
18 : : * Thus, we're going to consider only @@ operator, while regarding @? operator
19 : : * the same is true for jb @@ 'EXISTS(path)'.
20 : : *
21 : : * Result of jsonpath query extraction is a tree, which leaf nodes are index
22 : : * entries and non-leaf nodes are AND/OR logical expressions. Basically we
23 : : * extract following statements out of jsonpath:
24 : : *
25 : : * 1) "accessors_chain = const",
26 : : * 2) "EXISTS(accessors_chain)".
27 : : *
28 : : * Accessors chain may consist of .key, [*] and [index] accessors. jsonb_ops
29 : : * additionally supports .* and .**.
30 : : *
31 : : * For now, both jsonb_ops and jsonb_path_ops supports only statements of
32 : : * the 1st find. jsonb_ops might also support statements of the 2nd kind,
33 : : * but given we have no statistics keys extracted from accessors chain
34 : : * are likely non-selective. Therefore, we choose to not confuse optimizer
35 : : * and skip statements of the 2nd kind altogether. In future versions that
36 : : * might be changed.
37 : : *
38 : : * In jsonb_ops statement of the 1st kind is split into expression of AND'ed
39 : : * keys and const. Sometimes const might be interpreted as both value or key
40 : : * in jsonb_ops. Then statement of 1st kind is decomposed into the expression
41 : : * below.
42 : : *
43 : : * key1 AND key2 AND ... AND keyN AND (const_as_value OR const_as_key)
44 : : *
45 : : * jsonb_path_ops transforms each statement of the 1st kind into single hash
46 : : * entry below.
47 : : *
48 : : * HASH(key1, key2, ... , keyN, const)
49 : : *
50 : : * Despite statements of the 2nd kind are not supported by both jsonb_ops and
51 : : * jsonb_path_ops, EXISTS(path) expressions might be still supported,
52 : : * when statements of 1st kind could be extracted out of their filters.
53 : : *
54 : : * IDENTIFICATION
55 : : * src/backend/utils/adt/jsonb_gin.c
56 : : *
57 : : *-------------------------------------------------------------------------
58 : : */
59 : :
60 : : #include "postgres.h"
61 : :
62 : : #include "access/gin.h"
63 : : #include "access/stratnum.h"
64 : : #include "catalog/pg_collation.h"
65 : : #include "catalog/pg_type.h"
66 : : #include "common/hashfn.h"
67 : : #include "miscadmin.h"
68 : : #include "utils/fmgrprotos.h"
69 : : #include "utils/jsonb.h"
70 : : #include "utils/jsonpath.h"
71 : : #include "utils/varlena.h"
72 : :
73 : : typedef struct PathHashStack
74 : : {
75 : : uint32 hash;
76 : : struct PathHashStack *parent;
77 : : } PathHashStack;
78 : :
79 : : /* Buffer for GIN entries */
80 : : typedef struct GinEntries
81 : : {
82 : : Datum *buf;
83 : : int count;
84 : : int allocated;
85 : : } GinEntries;
86 : :
87 : : typedef enum JsonPathGinNodeType
88 : : {
89 : : JSP_GIN_OR,
90 : : JSP_GIN_AND,
91 : : JSP_GIN_ENTRY,
92 : : } JsonPathGinNodeType;
93 : :
94 : : typedef struct JsonPathGinNode JsonPathGinNode;
95 : :
96 : : /* Node in jsonpath expression tree */
97 : : struct JsonPathGinNode
98 : : {
99 : : JsonPathGinNodeType type;
100 : : union
101 : : {
102 : : int nargs; /* valid for OR and AND nodes */
103 : : int entryIndex; /* index in GinEntries array, valid for ENTRY
104 : : * nodes after entries output */
105 : : Datum entryDatum; /* path hash or key name/scalar, valid for
106 : : * ENTRY nodes before entries output */
107 : : } val;
108 : : JsonPathGinNode *args[FLEXIBLE_ARRAY_MEMBER]; /* valid for OR and AND
109 : : * nodes */
110 : : };
111 : :
112 : : /*
113 : : * jsonb_ops entry extracted from jsonpath item. Corresponding path item
114 : : * may be: '.key', '.*', '.**', '[index]' or '[*]'.
115 : : * Entry type is stored in 'type' field.
116 : : */
117 : : typedef struct JsonPathGinPathItem
118 : : {
119 : : struct JsonPathGinPathItem *parent;
120 : : Datum keyName; /* key name (for '.key' path item) or NULL */
121 : : JsonPathItemType type; /* type of jsonpath item */
122 : : } JsonPathGinPathItem;
123 : :
124 : : /* GIN representation of the extracted json path */
125 : : typedef union JsonPathGinPath
126 : : {
127 : : JsonPathGinPathItem *items; /* list of path items (jsonb_ops) */
128 : : uint32 hash; /* hash of the path (jsonb_path_ops) */
129 : : } JsonPathGinPath;
130 : :
131 : : typedef struct JsonPathGinContext JsonPathGinContext;
132 : :
133 : : /* Callback, which stores information about path item into JsonPathGinPath */
134 : : typedef bool (*JsonPathGinAddPathItemFunc) (JsonPathGinPath *path,
135 : : JsonPathItem *jsp);
136 : :
137 : : /*
138 : : * Callback, which extracts set of nodes from statement of 1st kind
139 : : * (scalar != NULL) or statement of 2nd kind (scalar == NULL).
140 : : */
141 : : typedef List *(*JsonPathGinExtractNodesFunc) (JsonPathGinContext *cxt,
142 : : JsonPathGinPath path,
143 : : JsonbValue *scalar,
144 : : List *nodes);
145 : :
146 : : /* Context for jsonpath entries extraction */
147 : : struct JsonPathGinContext
148 : : {
149 : : JsonPathGinAddPathItemFunc add_path_item;
150 : : JsonPathGinExtractNodesFunc extract_nodes;
151 : : bool lax;
152 : : };
153 : :
154 : : static Datum make_text_key(char flag, const char *str, int len);
155 : : static Datum make_scalar_key(const JsonbValue *scalarVal, bool is_key);
156 : :
157 : : static JsonPathGinNode *extract_jsp_bool_expr(JsonPathGinContext *cxt,
158 : : JsonPathGinPath path, JsonPathItem *jsp, bool not);
159 : :
160 : :
161 : : /* Initialize GinEntries struct */
162 : : static void
2775 akorotkov@postgresql 163 :CBC 13358 : init_gin_entries(GinEntries *entries, int preallocated)
164 : : {
165 : 13358 : entries->allocated = preallocated;
330 michael@paquier.xyz 166 [ + - ]: 13358 : entries->buf = preallocated ? palloc_array(Datum, preallocated) : NULL;
2775 akorotkov@postgresql 167 : 13358 : entries->count = 0;
168 : 13358 : }
169 : :
170 : : /* Add new entry to GinEntries */
171 : : static int
172 : 90662 : add_gin_entry(GinEntries *entries, Datum entry)
173 : : {
174 : 90662 : int id = entries->count;
175 : :
176 [ + + ]: 90662 : if (entries->count >= entries->allocated)
177 : : {
178 [ + + ]: 5873 : if (entries->allocated)
179 : : {
180 : 5462 : entries->allocated *= 2;
330 michael@paquier.xyz 181 : 5462 : entries->buf = repalloc_array(entries->buf,
182 : : Datum,
183 : : entries->allocated);
184 : : }
185 : : else
186 : : {
2775 akorotkov@postgresql 187 : 411 : entries->allocated = 8;
330 michael@paquier.xyz 188 : 411 : entries->buf = palloc_array(Datum, entries->allocated);
189 : : }
190 : : }
191 : :
2775 akorotkov@postgresql 192 : 90662 : entries->buf[entries->count++] = entry;
193 : :
194 : 90662 : return id;
195 : : }
196 : :
197 : : /*
198 : : *
199 : : * jsonb_ops GIN opclass support functions
200 : : *
201 : : */
202 : :
203 : : Datum
4610 andrew@dunslane.net 204 : 639176 : gin_compare_jsonb(PG_FUNCTION_ARGS)
205 : : {
206 : 639176 : text *arg1 = PG_GETARG_TEXT_PP(0);
207 : 639176 : text *arg2 = PG_GETARG_TEXT_PP(1);
208 : : int32 result;
209 : : char *a1p,
210 : : *a2p;
211 : : int len1,
212 : : len2;
213 : :
214 : 639176 : a1p = VARDATA_ANY(arg1);
215 : 639176 : a2p = VARDATA_ANY(arg2);
216 : :
217 : 639176 : len1 = VARSIZE_ANY_EXHDR(arg1);
218 : 639176 : len2 = VARSIZE_ANY_EXHDR(arg2);
219 : :
220 : : /* Compare text as bttextcmp does, but always using C collation */
221 : 639176 : result = varstr_cmp(a1p, len1, a2p, len2, C_COLLATION_OID);
222 : :
223 [ - + ]: 639176 : PG_FREE_IF_COPY(arg1, 0);
224 [ - + ]: 639176 : PG_FREE_IF_COPY(arg2, 1);
225 : :
226 : 639176 : PG_RETURN_INT32(result);
227 : : }
228 : :
229 : : Datum
230 : 10161 : gin_extract_jsonb(PG_FUNCTION_ARGS)
231 : : {
3335 tgl@sss.pgh.pa.us 232 : 10161 : Jsonb *jb = (Jsonb *) PG_GETARG_JSONB_P(0);
4610 andrew@dunslane.net 233 : 10161 : int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
2775 akorotkov@postgresql 234 : 10161 : int total = JB_ROOT_COUNT(jb);
235 : : JsonbIterator *it;
236 : : JsonbValue v;
237 : : JsonbIteratorToken r;
238 : : GinEntries entries;
239 : :
240 : : /* If the root level is empty, we certainly have no keys */
4610 andrew@dunslane.net 241 [ + + ]: 10161 : if (total == 0)
242 : : {
243 : 481 : *nentries = 0;
244 : 481 : PG_RETURN_POINTER(NULL);
245 : : }
246 : :
247 : : /* Otherwise, use 2 * root count as initial estimate of result size */
2775 akorotkov@postgresql 248 : 9680 : init_gin_entries(&entries, 2 * total);
249 : :
4565 heikki.linnakangas@i 250 : 9680 : it = JsonbIteratorInit(&jb->root);
251 : :
4610 andrew@dunslane.net 252 [ + + ]: 125087 : while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
253 : : {
254 [ + + + + ]: 115407 : switch (r)
255 : : {
256 : 29010 : case WJB_KEY:
2775 akorotkov@postgresql 257 : 29010 : add_gin_entry(&entries, make_scalar_key(&v, true));
4610 andrew@dunslane.net 258 : 29010 : break;
259 : 15288 : case WJB_ELEM:
260 : : /* Pretend string array elements are keys, see jsonb.h */
2775 akorotkov@postgresql 261 : 15288 : add_gin_entry(&entries, make_scalar_key(&v, v.type == jbvString));
4610 andrew@dunslane.net 262 : 15288 : break;
263 : 26239 : case WJB_VALUE:
2775 akorotkov@postgresql 264 : 26239 : add_gin_entry(&entries, make_scalar_key(&v, false));
4610 andrew@dunslane.net 265 : 26239 : break;
266 : 44870 : default:
267 : : /* we can ignore structural items */
4563 tgl@sss.pgh.pa.us 268 : 44870 : break;
269 : : }
270 : : }
271 : :
2775 akorotkov@postgresql 272 : 9680 : *nentries = entries.count;
273 : :
274 : 9680 : PG_RETURN_POINTER(entries.buf);
275 : : }
276 : :
277 : : /* Append JsonPathGinPathItem to JsonPathGinPath (jsonb_ops) */
278 : : static bool
279 : 695 : jsonb_ops__add_path_item(JsonPathGinPath *path, JsonPathItem *jsp)
280 : : {
281 : : JsonPathGinPathItem *pentry;
282 : : Datum keyName;
283 : :
284 [ + + + - ]: 695 : switch (jsp->type)
285 : : {
286 : 334 : case jpiRoot:
287 : 334 : path->items = NULL; /* reset path */
288 : 334 : return true;
289 : :
290 : 289 : case jpiKey:
291 : : {
292 : : int len;
293 : 289 : char *key = jspGetString(jsp, &len);
294 : :
295 : 289 : keyName = make_text_key(JGINFLAG_KEY, key, len);
296 : 289 : break;
297 : : }
298 : :
299 : 72 : case jpiAny:
300 : : case jpiAnyKey:
301 : : case jpiAnyArray:
302 : : case jpiIndexArray:
303 : 72 : keyName = PointerGetDatum(NULL);
304 : 72 : break;
305 : :
2775 akorotkov@postgresql 306 :UBC 0 : default:
307 : : /* other path items like item methods are not supported */
308 : 0 : return false;
309 : : }
310 : :
330 michael@paquier.xyz 311 :CBC 361 : pentry = palloc_object(JsonPathGinPathItem);
312 : :
2775 akorotkov@postgresql 313 : 361 : pentry->type = jsp->type;
314 : 361 : pentry->keyName = keyName;
315 : 361 : pentry->parent = path->items;
316 : :
317 : 361 : path->items = pentry;
318 : :
319 : 361 : return true;
320 : : }
321 : :
322 : : /* Combine existing path hash with next key hash (jsonb_path_ops) */
323 : : static bool
324 : 541 : jsonb_path_ops__add_path_item(JsonPathGinPath *path, JsonPathItem *jsp)
325 : : {
326 [ + + + - ]: 541 : switch (jsp->type)
327 : : {
328 : 239 : case jpiRoot:
329 : 239 : path->hash = 0; /* reset path hash */
330 : 239 : return true;
331 : :
332 : 230 : case jpiKey:
333 : : {
334 : : JsonbValue jbv;
335 : :
336 : 230 : jbv.type = jbvString;
337 : 230 : jbv.val.string.val = jspGetString(jsp, &jbv.val.string.len);
338 : :
339 : 230 : JsonbHashScalarValue(&jbv, &path->hash);
340 : 230 : return true;
341 : : }
342 : :
343 : 72 : case jpiIndexArray:
344 : : case jpiAnyArray:
345 : 72 : return true; /* path hash is unchanged */
346 : :
2775 akorotkov@postgresql 347 :UBC 0 : default:
348 : : /* other items (wildcard paths, item methods) are not supported */
349 : 0 : return false;
350 : : }
351 : : }
352 : :
353 : : static JsonPathGinNode *
2775 akorotkov@postgresql 354 :CBC 808 : make_jsp_entry_node(Datum entry)
355 : : {
356 : 808 : JsonPathGinNode *node = palloc(offsetof(JsonPathGinNode, args));
357 : :
358 : 808 : node->type = JSP_GIN_ENTRY;
359 : 808 : node->val.entryDatum = entry;
360 : :
361 : 808 : return node;
362 : : }
363 : :
364 : : static JsonPathGinNode *
365 : 379 : make_jsp_entry_node_scalar(JsonbValue *scalar, bool iskey)
366 : : {
367 : 379 : return make_jsp_entry_node(make_scalar_key(scalar, iskey));
368 : : }
369 : :
370 : : static JsonPathGinNode *
371 : 379 : make_jsp_expr_node(JsonPathGinNodeType type, int nargs)
372 : : {
373 : 379 : JsonPathGinNode *node = palloc(offsetof(JsonPathGinNode, args) +
374 : : sizeof(node->args[0]) * nargs);
375 : :
376 : 379 : node->type = type;
377 : 379 : node->val.nargs = nargs;
378 : :
379 : 379 : return node;
380 : : }
381 : :
382 : : static JsonPathGinNode *
383 : 217 : make_jsp_expr_node_args(JsonPathGinNodeType type, List *args)
384 : : {
385 : 217 : JsonPathGinNode *node = make_jsp_expr_node(type, list_length(args));
386 : : ListCell *lc;
387 : 217 : int i = 0;
388 : :
389 [ + - + + : 651 : foreach(lc, args)
+ + ]
390 : 434 : node->args[i++] = lfirst(lc);
391 : :
392 : 217 : return node;
393 : : }
394 : :
395 : : static JsonPathGinNode *
396 : 162 : make_jsp_expr_node_binary(JsonPathGinNodeType type,
397 : : JsonPathGinNode *arg1, JsonPathGinNode *arg2)
398 : : {
399 : 162 : JsonPathGinNode *node = make_jsp_expr_node(type, 2);
400 : :
401 : 162 : node->args[0] = arg1;
402 : 162 : node->args[1] = arg2;
403 : :
404 : 162 : return node;
405 : : }
406 : :
407 : : /* Append a list of nodes from the jsonpath (jsonb_ops). */
408 : : static List *
409 : 483 : jsonb_ops__extract_nodes(JsonPathGinContext *cxt, JsonPathGinPath path,
410 : : JsonbValue *scalar, List *nodes)
411 : : {
412 : : JsonPathGinPathItem *pentry;
413 : :
414 [ + + ]: 483 : if (scalar)
415 : : {
416 : : JsonPathGinNode *node;
417 : :
418 : : /*
419 : : * Append path entry nodes only if scalar is provided. See header
420 : : * comment for details.
421 : : */
422 [ + + ]: 542 : for (pentry = path.items; pentry; pentry = pentry->parent)
423 : : {
424 [ + + ]: 289 : if (pentry->type == jpiKey) /* only keys are indexed */
425 : 217 : nodes = lappend(nodes, make_jsp_entry_node(pentry->keyName));
426 : : }
427 : :
428 : : /* Append scalar node for equality queries. */
429 [ + + ]: 253 : if (scalar->type == jbvString)
430 : : {
431 : 144 : JsonPathGinPathItem *last = path.items;
432 : : GinTernaryValue key_entry;
433 : :
434 : : /*
435 : : * Assuming that jsonb_ops interprets string array elements as
436 : : * keys, we may extract key or non-key entry or even both. In the
437 : : * latter case we create OR-node. It is possible in lax mode
438 : : * where arrays are automatically unwrapped, or in strict mode for
439 : : * jpiAny items. Also, root scalars are stored as single-element
440 : : * arrays, so those will be key entries.
441 : : */
442 : :
443 [ + + ]: 144 : if (cxt->lax)
444 : 126 : key_entry = GIN_MAYBE;
445 [ + - ]: 18 : else if (!last) /* root ($) */
12 tgl@sss.pgh.pa.us 446 : 18 : key_entry = GIN_TRUE;
2775 akorotkov@postgresql 447 [ # # # # ]:UBC 0 : else if (last->type == jpiAnyArray || last->type == jpiIndexArray)
448 : 0 : key_entry = GIN_TRUE;
449 [ # # ]: 0 : else if (last->type == jpiAny)
450 : 0 : key_entry = GIN_MAYBE;
451 : : else
452 : 0 : key_entry = GIN_FALSE;
453 : :
2775 akorotkov@postgresql 454 [ + + ]:CBC 144 : if (key_entry == GIN_MAYBE)
455 : : {
456 : 126 : JsonPathGinNode *n1 = make_jsp_entry_node_scalar(scalar, true);
457 : 126 : JsonPathGinNode *n2 = make_jsp_entry_node_scalar(scalar, false);
458 : :
459 : 126 : node = make_jsp_expr_node_binary(JSP_GIN_OR, n1, n2);
460 : : }
461 : : else
462 : : {
463 : 18 : node = make_jsp_entry_node_scalar(scalar,
464 : : key_entry == GIN_TRUE);
465 : : }
466 : : }
467 : : else
468 : : {
469 : 109 : node = make_jsp_entry_node_scalar(scalar, false);
470 : : }
471 : :
472 : 253 : nodes = lappend(nodes, node);
473 : : }
474 : :
475 : 483 : return nodes;
476 : : }
477 : :
478 : : /* Append a list of nodes from the jsonpath (jsonb_path_ops). */
479 : : static List *
480 : 370 : jsonb_path_ops__extract_nodes(JsonPathGinContext *cxt, JsonPathGinPath path,
481 : : JsonbValue *scalar, List *nodes)
482 : : {
483 [ + + ]: 370 : if (scalar)
484 : : {
485 : : /* append path hash node for equality queries */
486 : 212 : uint32 hash = path.hash;
487 : :
488 : 212 : JsonbHashScalarValue(scalar, &hash);
489 : :
490 : 212 : return lappend(nodes,
491 : 212 : make_jsp_entry_node(UInt32GetDatum(hash)));
492 : : }
493 : : else
494 : : {
495 : : /* jsonb_path_ops doesn't support EXISTS queries => nothing to append */
496 : 158 : return nodes;
497 : : }
498 : : }
499 : :
500 : : /*
501 : : * Extract a list of expression nodes that need to be AND-ed by the caller.
502 : : * Extracted expression is 'path == scalar' if 'scalar' is non-NULL, and
503 : : * 'EXISTS(path)' otherwise.
504 : : */
505 : : static List *
506 : 853 : extract_jsp_path_expr_nodes(JsonPathGinContext *cxt, JsonPathGinPath path,
507 : : JsonPathItem *jsp, JsonbValue *scalar)
508 : : {
509 : : JsonPathItem next;
510 : 853 : List *nodes = NIL;
511 : :
512 : : for (;;)
513 : : {
514 [ + + + ]: 1778 : switch (jsp->type)
515 : : {
516 : 280 : case jpiCurrent:
517 : 280 : break;
518 : :
519 : 262 : case jpiFilter:
520 : : {
521 : : JsonPathItem arg;
522 : : JsonPathGinNode *filter;
523 : :
524 : 262 : jspGetArg(jsp, &arg);
525 : :
526 : 262 : filter = extract_jsp_bool_expr(cxt, path, &arg, false);
527 : :
528 [ + - ]: 262 : if (filter)
529 : 262 : nodes = lappend(nodes, filter);
530 : :
531 : 262 : break;
532 : : }
533 : :
534 : 1236 : default:
535 [ - + ]: 1236 : if (!cxt->add_path_item(&path, jsp))
536 : :
537 : : /*
538 : : * Path is not supported by the index opclass, return only
539 : : * the extracted filter nodes.
540 : : */
2775 akorotkov@postgresql 541 :UBC 0 : return nodes;
2775 akorotkov@postgresql 542 :CBC 1236 : break;
543 : : }
544 : :
545 [ + + ]: 1778 : if (!jspGetNext(jsp, &next))
546 : 853 : break;
547 : :
548 : 925 : jsp = &next;
549 : : }
550 : :
551 : : /*
552 : : * Append nodes from the path expression itself to the already extracted
553 : : * list of filter nodes.
554 : : */
555 : 853 : return cxt->extract_nodes(cxt, path, scalar, nodes);
556 : : }
557 : :
558 : : /*
559 : : * Extract an expression node from one of following jsonpath path expressions:
560 : : * EXISTS(jsp) (when 'scalar' is NULL)
561 : : * jsp == scalar (when 'scalar' is not NULL).
562 : : * Returns NULL if no usable filter expression can be extracted.
563 : : *
564 : : * The current path (@) is passed in 'path'.
565 : : */
566 : : static JsonPathGinNode *
567 : 853 : extract_jsp_path_expr(JsonPathGinContext *cxt, JsonPathGinPath path,
568 : : JsonPathItem *jsp, JsonbValue *scalar)
569 : : {
570 : : /* extract a list of nodes to be AND-ed */
571 : 853 : List *nodes = extract_jsp_path_expr_nodes(cxt, path, jsp, scalar);
572 : :
1541 tgl@sss.pgh.pa.us 573 [ + + ]: 853 : if (nodes == NIL)
574 : : /* no nodes were extracted => full scan is needed for this path */
2775 akorotkov@postgresql 575 : 126 : return NULL;
576 : :
577 [ + + ]: 727 : if (list_length(nodes) == 1)
578 : 510 : return linitial(nodes); /* avoid extra AND-node */
579 : :
580 : : /* construct AND-node for path with filters */
581 : 217 : return make_jsp_expr_node_args(JSP_GIN_AND, nodes);
582 : : }
583 : :
584 : : /*
585 : : * Recursively extract a GIN qual from the boolean jsonpath expression.
586 : : * Returns NULL if no usable indexqual can be extracted.
587 : : */
588 : : static JsonPathGinNode *
589 : 771 : extract_jsp_bool_expr(JsonPathGinContext *cxt, JsonPathGinPath path,
590 : : JsonPathItem *jsp, bool not)
591 : : {
592 : 771 : check_stack_depth();
593 : :
594 [ + + + - : 771 : switch (jsp->type)
+ - ]
595 : : {
596 : 72 : case jpiAnd: /* expr && expr */
597 : : case jpiOr: /* expr || expr */
598 : : {
599 : : /* We use De Morgan's laws to handle a passed-down NOT */
23 tgl@sss.pgh.pa.us 600 : 72 : bool is_and = (not ^ (jsp->type == jpiAnd));
601 : : JsonPathItem arg;
602 : : JsonPathGinNode *larg;
603 : : JsonPathGinNode *rarg;
604 : : JsonPathGinNodeType type;
605 : :
2775 akorotkov@postgresql 606 : 72 : jspGetLeftArg(jsp, &arg);
607 : 72 : larg = extract_jsp_bool_expr(cxt, path, &arg, not);
608 : :
609 : 72 : jspGetRightArg(jsp, &arg);
610 : 72 : rarg = extract_jsp_bool_expr(cxt, path, &arg, not);
611 : :
612 [ + + + + ]: 72 : if (!larg || !rarg)
613 : : {
614 : : /*
615 : : * For OR, we have to fail if either arm is unsupported.
616 : : * For AND, we can just ignore the unsupported arm and
617 : : * apply the supported one, relying on indexscan qual
618 : : * recheck to reject rows that don't match the other arm.
619 : : */
23 tgl@sss.pgh.pa.us 620 [ + + ]: 36 : if (!is_and)
2775 akorotkov@postgresql 621 : 27 : return NULL;
622 : :
623 [ - + ]: 9 : return larg ? larg : rarg;
624 : : }
625 : :
23 tgl@sss.pgh.pa.us 626 : 36 : type = is_and ? JSP_GIN_AND : JSP_GIN_OR;
627 : :
2775 akorotkov@postgresql 628 : 36 : return make_jsp_expr_node_binary(type, larg, rarg);
629 : : }
630 : :
631 : 36 : case jpiNot: /* !expr */
632 : : {
633 : : JsonPathItem arg;
634 : :
635 : 36 : jspGetArg(jsp, &arg);
636 : :
637 : : /* extract child expression inverting 'not' flag */
638 : 36 : return extract_jsp_bool_expr(cxt, path, &arg, !not);
639 : : }
640 : :
641 : 162 : case jpiExists: /* EXISTS(path) */
642 : : {
643 : : JsonPathItem arg;
644 : :
645 [ - + ]: 162 : if (not)
2775 akorotkov@postgresql 646 :UBC 0 : return NULL; /* NOT EXISTS is not supported */
647 : :
2775 akorotkov@postgresql 648 :CBC 162 : jspGetArg(jsp, &arg);
649 : :
650 : 162 : return extract_jsp_path_expr(cxt, path, &arg, NULL);
651 : : }
652 : :
2775 akorotkov@postgresql 653 :UBC 0 : case jpiNotEqual:
654 : :
655 : : /*
656 : : * 'not' == true case is not supported here because '!(path !=
657 : : * scalar)' is not equivalent to 'path == scalar' in the general
658 : : * case because of sequence comparison semantics: 'path == scalar'
659 : : * === 'EXISTS (path, @ == scalar)', '!(path != scalar)' ===
660 : : * 'FOR_ALL(path, @ == scalar)'. So, we should translate '!(path
661 : : * != scalar)' into GIN query 'path == scalar || EMPTY(path)', but
662 : : * 'EMPTY(path)' queries are not supported by the both jsonb
663 : : * opclasses. However in strict mode we could omit 'EMPTY(path)'
664 : : * part if the path can return exactly one item (it does not
665 : : * contain wildcard accessors or item methods like .keyvalue()
666 : : * etc.).
667 : : */
668 : 0 : return NULL;
669 : :
2775 akorotkov@postgresql 670 :CBC 501 : case jpiEqual: /* path == scalar */
671 : : {
672 : : JsonPathItem left_item;
673 : : JsonPathItem right_item;
674 : : JsonPathItem *path_item;
675 : : JsonPathItem *scalar_item;
676 : : JsonbValue scalar;
677 : :
678 : : /* Can't support !(x == y) */
679 [ + + ]: 501 : if (not)
680 : 18 : return NULL;
681 : :
682 : 483 : jspGetLeftArg(jsp, &left_item);
683 : 483 : jspGetRightArg(jsp, &right_item);
684 : :
685 : : /* To build an indexqual, one side must be a simple scalar */
23 tgl@sss.pgh.pa.us 686 [ + + ]: 483 : if (jspIsScalar(left_item.type) &&
687 [ + - ]: 72 : !jspHasNext(&left_item))
688 : : {
2775 akorotkov@postgresql 689 : 72 : scalar_item = &left_item;
690 : 72 : path_item = &right_item;
691 : : }
23 tgl@sss.pgh.pa.us 692 [ + - ]: 411 : else if (jspIsScalar(right_item.type) &&
693 [ + + ]: 411 : !jspHasNext(&right_item))
694 : : {
2775 akorotkov@postgresql 695 : 393 : scalar_item = &right_item;
696 : 393 : path_item = &left_item;
697 : : }
698 : : else
23 tgl@sss.pgh.pa.us 699 : 18 : return NULL;
700 : :
2775 akorotkov@postgresql 701 [ + + + + : 465 : switch (scalar_item->type)
- ]
702 : : {
703 : 87 : case jpiNull:
704 : 87 : scalar.type = jbvNull;
705 : 87 : break;
706 : 54 : case jpiBool:
707 : 54 : scalar.type = jbvBool;
708 : 54 : scalar.val.boolean = !!*scalar_item->content.value.data;
709 : 54 : break;
710 : 72 : case jpiNumeric:
711 : 72 : scalar.type = jbvNumeric;
712 : 72 : scalar.val.numeric =
713 : 72 : (Numeric) scalar_item->content.value.data;
714 : 72 : break;
715 : 252 : case jpiString:
716 : 252 : scalar.type = jbvString;
717 : 252 : scalar.val.string.val = scalar_item->content.value.data;
718 : 252 : scalar.val.string.len =
719 : 252 : scalar_item->content.value.datalen;
720 : 252 : break;
2775 akorotkov@postgresql 721 :UBC 0 : default:
722 [ # # ]: 0 : elog(ERROR, "invalid scalar jsonpath item type: %d",
723 : : scalar_item->type);
724 : : return NULL;
725 : : }
726 : :
2775 akorotkov@postgresql 727 :CBC 465 : return extract_jsp_path_expr(cxt, path, path_item, &scalar);
728 : : }
729 : :
2775 akorotkov@postgresql 730 :UBC 0 : default:
731 : 0 : return NULL; /* not a boolean expression */
732 : : }
733 : : }
734 : :
735 : : /* Recursively emit all GIN entries found in the node tree */
736 : : static void
2775 akorotkov@postgresql 737 :CBC 1151 : emit_jsp_gin_entries(JsonPathGinNode *node, GinEntries *entries)
738 : : {
739 : 1151 : check_stack_depth();
740 : :
741 [ + + - ]: 1151 : switch (node->type)
742 : : {
743 : 781 : case JSP_GIN_ENTRY:
744 : : /* replace datum with its index in the array */
745 : 781 : node->val.entryIndex = add_gin_entry(entries, node->val.entryDatum);
746 : 781 : break;
747 : :
748 : 370 : case JSP_GIN_OR:
749 : : case JSP_GIN_AND:
750 : : {
751 : : int i;
752 : :
753 [ + + ]: 1110 : for (i = 0; i < node->val.nargs; i++)
754 : 740 : emit_jsp_gin_entries(node->args[i], entries);
755 : :
756 : 370 : break;
757 : : }
758 : : }
759 : 1151 : }
760 : :
761 : : /*
762 : : * Recursively extract GIN entries from jsonpath query.
763 : : * Root expression node is put into (*extra_data)[0].
764 : : */
765 : : static Datum *
766 : 555 : extract_jsp_query(JsonPath *jp, StrategyNumber strat, bool pathOps,
767 : : int32 *nentries, Pointer **extra_data)
768 : : {
769 : : JsonPathGinContext cxt;
770 : : JsonPathItem root;
771 : : JsonPathGinNode *node;
772 : 555 : JsonPathGinPath path = {0};
773 : 555 : GinEntries entries = {0};
774 : :
775 : 555 : cxt.lax = (jp->header & JSONPATH_LAX) != 0;
776 : :
777 [ + + ]: 555 : if (pathOps)
778 : : {
779 : 239 : cxt.add_path_item = jsonb_path_ops__add_path_item;
780 : 239 : cxt.extract_nodes = jsonb_path_ops__extract_nodes;
781 : : }
782 : : else
783 : : {
784 : 316 : cxt.add_path_item = jsonb_ops__add_path_item;
785 : 316 : cxt.extract_nodes = jsonb_ops__extract_nodes;
786 : : }
787 : :
788 : 555 : jspInit(&root, jp);
789 : :
790 : 555 : node = strat == JsonbJsonpathExistsStrategyNumber
791 : 226 : ? extract_jsp_path_expr(&cxt, path, &root, NULL)
792 [ + + ]: 555 : : extract_jsp_bool_expr(&cxt, path, &root, false);
793 : :
794 [ + + ]: 555 : if (!node)
795 : : {
796 : 144 : *nentries = 0;
797 : 144 : return NULL;
798 : : }
799 : :
800 : 411 : emit_jsp_gin_entries(node, &entries);
801 : :
802 : 411 : *nentries = entries.count;
803 [ - + ]: 411 : if (!*nentries)
2775 akorotkov@postgresql 804 :UBC 0 : return NULL;
805 : :
330 michael@paquier.xyz 806 :CBC 411 : *extra_data = palloc0_array(Pointer, entries.count);
2775 akorotkov@postgresql 807 : 411 : **extra_data = (Pointer) node;
808 : :
809 : 411 : return entries.buf;
810 : : }
811 : :
812 : : /*
813 : : * Recursively execute jsonpath expression.
814 : : * 'check' is a bool[] or a GinTernaryValue[] depending on 'ternary' flag.
815 : : */
816 : : static GinTernaryValue
817 : 8456 : execute_jsp_gin_node(JsonPathGinNode *node, void *check, bool ternary)
818 : : {
819 : : GinTernaryValue res;
820 : : GinTernaryValue v;
821 : : int i;
822 : :
823 [ + + + - ]: 8456 : switch (node->type)
824 : : {
825 : 3392 : case JSP_GIN_AND:
826 : 3392 : res = GIN_TRUE;
827 [ + + ]: 5376 : for (i = 0; i < node->val.nargs; i++)
828 : : {
829 : 4592 : v = execute_jsp_gin_node(node->args[i], check, ternary);
830 [ + + ]: 4592 : if (v == GIN_FALSE)
831 : 2608 : return GIN_FALSE;
832 [ + + ]: 1984 : else if (v == GIN_MAYBE)
833 : 160 : res = GIN_MAYBE;
834 : : }
835 : 784 : return res;
836 : :
837 : 720 : case JSP_GIN_OR:
838 : 720 : res = GIN_FALSE;
839 [ + + ]: 1472 : for (i = 0; i < node->val.nargs; i++)
840 : : {
841 : 1336 : v = execute_jsp_gin_node(node->args[i], check, ternary);
842 [ + + ]: 1336 : if (v == GIN_TRUE)
843 : 584 : return GIN_TRUE;
844 [ + + ]: 752 : else if (v == GIN_MAYBE)
845 : 56 : res = GIN_MAYBE;
846 : : }
847 : 136 : return res;
848 : :
849 : 4344 : case JSP_GIN_ENTRY:
850 : : {
851 : 4344 : int index = node->val.entryIndex;
852 : :
853 [ + + ]: 4344 : if (ternary)
854 : 4328 : return ((GinTernaryValue *) check)[index];
855 : : else
856 : 16 : return ((bool *) check)[index] ? GIN_TRUE : GIN_FALSE;
857 : : }
858 : :
2775 akorotkov@postgresql 859 :UBC 0 : default:
860 [ # # ]: 0 : elog(ERROR, "invalid jsonpath gin node type: %d", node->type);
861 : : return GIN_FALSE; /* keep compiler quiet */
862 : : }
863 : : }
864 : :
865 : : Datum
4610 andrew@dunslane.net 866 :CBC 451 : gin_extract_jsonb_query(PG_FUNCTION_ARGS)
867 : : {
868 : 451 : int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
869 : 451 : StrategyNumber strategy = PG_GETARG_UINT16(2);
870 : 451 : int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
871 : : Datum *entries;
872 : :
873 [ + + ]: 451 : if (strategy == JsonbContainsStrategyNumber)
874 : : {
875 : : /* Query is a jsonb, so just apply gin_extract_jsonb... */
876 : : entries = (Datum *)
877 : 81 : DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb,
878 : : PG_GETARG_DATUM(0),
879 : : PointerGetDatum(nentries)));
880 : : /* ...although "contains {}" requires a full index scan */
4563 tgl@sss.pgh.pa.us 881 [ + + ]: 81 : if (*nentries == 0)
4610 andrew@dunslane.net 882 : 9 : *searchMode = GIN_SEARCH_MODE_ALL;
883 : : }
884 [ + + ]: 370 : else if (strategy == JsonbExistsStrategyNumber)
885 : : {
886 : : /* Query is a text string, which we treat as a key */
887 : 36 : text *query = PG_GETARG_TEXT_PP(0);
888 : :
889 : 36 : *nentries = 1;
330 michael@paquier.xyz 890 : 36 : entries = palloc_object(Datum);
4563 tgl@sss.pgh.pa.us 891 : 36 : entries[0] = make_text_key(JGINFLAG_KEY,
892 : 36 : VARDATA_ANY(query),
893 : 36 : VARSIZE_ANY_EXHDR(query));
894 : : }
4610 andrew@dunslane.net 895 [ + + + + ]: 334 : else if (strategy == JsonbExistsAnyStrategyNumber ||
896 : : strategy == JsonbExistsAllStrategyNumber)
897 : 18 : {
898 : : /* Query is a text array; each element is treated as a key */
899 : 18 : ArrayType *query = PG_GETARG_ARRAYTYPE_P(0);
900 : : Datum *key_datums;
901 : : bool *key_nulls;
902 : : int key_count;
903 : : int i,
904 : : j;
905 : :
1588 peter@eisentraut.org 906 : 18 : deconstruct_array_builtin(query, TEXTOID, &key_datums, &key_nulls, &key_count);
907 : :
330 michael@paquier.xyz 908 : 18 : entries = palloc_array(Datum, key_count);
909 : :
4563 tgl@sss.pgh.pa.us 910 [ + + ]: 54 : for (i = 0, j = 0; i < key_count; i++)
911 : : {
912 : : /* Nulls in the array are ignored */
4610 andrew@dunslane.net 913 [ - + ]: 36 : if (key_nulls[i])
4610 andrew@dunslane.net 914 :UBC 0 : continue;
915 : : /* We rely on the array elements not being toasted */
4563 tgl@sss.pgh.pa.us 916 :CBC 36 : entries[j++] = make_text_key(JGINFLAG_KEY,
457 peter@eisentraut.org 917 : 36 : VARDATA_ANY(DatumGetPointer(key_datums[i])),
918 : 36 : VARSIZE_ANY_EXHDR(DatumGetPointer(key_datums[i])));
919 : : }
920 : :
4610 andrew@dunslane.net 921 : 18 : *nentries = j;
922 : : /* ExistsAll with no keys should match everything */
923 [ - + - - ]: 18 : if (j == 0 && strategy == JsonbExistsAllStrategyNumber)
4610 andrew@dunslane.net 924 :UBC 0 : *searchMode = GIN_SEARCH_MODE_ALL;
925 : : }
2775 akorotkov@postgresql 926 [ + + + - ]:CBC 316 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
927 : : strategy == JsonbJsonpathExistsStrategyNumber)
928 : 316 : {
929 : 316 : JsonPath *jp = PG_GETARG_JSONPATH_P(0);
930 : 316 : Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4);
931 : :
932 : 316 : entries = extract_jsp_query(jp, strategy, false, nentries, extra_data);
933 : :
934 [ + + ]: 316 : if (!entries)
935 : 90 : *searchMode = GIN_SEARCH_MODE_ALL;
936 : : }
937 : : else
938 : : {
4610 andrew@dunslane.net 939 [ # # ]:UBC 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
940 : : entries = NULL; /* keep compiler quiet */
941 : : }
942 : :
4610 andrew@dunslane.net 943 :CBC 451 : PG_RETURN_POINTER(entries);
944 : : }
945 : :
946 : : Datum
947 : 16 : gin_consistent_jsonb(PG_FUNCTION_ARGS)
948 : : {
949 : 16 : bool *check = (bool *) PG_GETARG_POINTER(0);
950 : 16 : StrategyNumber strategy = PG_GETARG_UINT16(1);
951 : : #ifdef NOT_USED
952 : : Jsonb *query = PG_GETARG_JSONB_P(2);
953 : : #endif
954 : 16 : int32 nkeys = PG_GETARG_INT32(3);
955 : :
2775 akorotkov@postgresql 956 : 16 : Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
4610 andrew@dunslane.net 957 : 16 : bool *recheck = (bool *) PG_GETARG_POINTER(5);
958 : 16 : bool res = true;
959 : : int32 i;
960 : :
961 [ - + ]: 16 : if (strategy == JsonbContainsStrategyNumber)
962 : : {
963 : : /*
964 : : * We must always recheck, since we can't tell from the index whether
965 : : * the positions of the matched items match the structure of the query
966 : : * object. (Even if we could, we'd also have to worry about hashed
967 : : * keys and the index's failure to distinguish keys from string array
968 : : * elements.) However, the tuple certainly doesn't match unless it
969 : : * contains all the query keys.
970 : : */
4610 andrew@dunslane.net 971 :UBC 0 : *recheck = true;
972 [ # # ]: 0 : for (i = 0; i < nkeys; i++)
973 : : {
974 [ # # ]: 0 : if (!check[i])
975 : : {
976 : 0 : res = false;
977 : 0 : break;
978 : : }
979 : : }
980 : : }
4610 andrew@dunslane.net 981 [ - + ]:CBC 16 : else if (strategy == JsonbExistsStrategyNumber)
982 : : {
983 : : /*
984 : : * Although the key is certainly present in the index, we must recheck
985 : : * because (1) the key might be hashed, and (2) the index match might
986 : : * be for a key that's not at top level of the JSON object. For (1),
987 : : * we could look at the query key to see if it's hashed and not
988 : : * recheck if not, but the index lacks enough info to tell about (2).
989 : : */
4563 tgl@sss.pgh.pa.us 990 :UBC 0 : *recheck = true;
4610 andrew@dunslane.net 991 : 0 : res = true;
992 : : }
4610 andrew@dunslane.net 993 [ - + ]:CBC 16 : else if (strategy == JsonbExistsAnyStrategyNumber)
994 : : {
995 : : /* As for plain exists, we must recheck */
4563 tgl@sss.pgh.pa.us 996 :UBC 0 : *recheck = true;
4610 andrew@dunslane.net 997 : 0 : res = true;
998 : : }
4610 andrew@dunslane.net 999 [ - + ]:CBC 16 : else if (strategy == JsonbExistsAllStrategyNumber)
1000 : : {
1001 : : /* As for plain exists, we must recheck */
4563 tgl@sss.pgh.pa.us 1002 :UBC 0 : *recheck = true;
1003 : : /* ... but unless all the keys are present, we can say "false" */
4610 andrew@dunslane.net 1004 [ # # ]: 0 : for (i = 0; i < nkeys; i++)
1005 : : {
1006 [ # # ]: 0 : if (!check[i])
1007 : : {
1008 : 0 : res = false;
1009 : 0 : break;
1010 : : }
1011 : : }
1012 : : }
2775 akorotkov@postgresql 1013 [ + + + - ]:CBC 16 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1014 : : strategy == JsonbJsonpathExistsStrategyNumber)
1015 : : {
1016 : 16 : *recheck = true;
1017 : :
1018 [ + - ]: 16 : if (nkeys > 0)
1019 : : {
1020 [ + - - + ]: 16 : Assert(extra_data && extra_data[0]);
336 peter@eisentraut.org 1021 : 16 : res = execute_jsp_gin_node(extra_data[0], check, false) != GIN_FALSE;
1022 : : }
1023 : : }
1024 : : else
4610 andrew@dunslane.net 1025 [ # # ]:UBC 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1026 : :
4610 andrew@dunslane.net 1027 :CBC 16 : PG_RETURN_BOOL(res);
1028 : : }
1029 : :
1030 : : Datum
1031 : 50532 : gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
1032 : : {
4602 heikki.linnakangas@i 1033 : 50532 : GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0);
4610 andrew@dunslane.net 1034 : 50532 : StrategyNumber strategy = PG_GETARG_UINT16(1);
1035 : : #ifdef NOT_USED
1036 : : Jsonb *query = PG_GETARG_JSONB_P(2);
1037 : : #endif
1038 : 50532 : int32 nkeys = PG_GETARG_INT32(3);
2775 akorotkov@postgresql 1039 : 50532 : Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
4563 tgl@sss.pgh.pa.us 1040 : 50532 : GinTernaryValue res = GIN_MAYBE;
1041 : : int32 i;
1042 : :
1043 : : /*
1044 : : * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this
1045 : : * corresponds to always forcing recheck in the regular consistent
1046 : : * function, for the reasons listed there.
1047 : : */
1048 [ + + + + ]: 50532 : if (strategy == JsonbContainsStrategyNumber ||
1049 : : strategy == JsonbExistsAllStrategyNumber)
1050 : : {
1051 : : /* All extracted keys must be present */
4610 andrew@dunslane.net 1052 [ + + ]: 6760 : for (i = 0; i < nkeys; i++)
1053 : : {
1054 [ + + ]: 2356 : if (check[i] == GIN_FALSE)
1055 : : {
1056 : 1368 : res = GIN_FALSE;
1057 : 1368 : break;
1058 : : }
1059 : : }
1060 : : }
1061 [ + + + + ]: 44760 : else if (strategy == JsonbExistsStrategyNumber ||
1062 : : strategy == JsonbExistsAnyStrategyNumber)
1063 : : {
1064 : : /* At least one extracted key must be present */
1065 : 2160 : res = GIN_FALSE;
1066 [ + - ]: 2732 : for (i = 0; i < nkeys; i++)
1067 : : {
4563 tgl@sss.pgh.pa.us 1068 [ + + ]: 2732 : if (check[i] == GIN_TRUE ||
1069 [ + + ]: 576 : check[i] == GIN_MAYBE)
1070 : : {
4610 andrew@dunslane.net 1071 : 2160 : res = GIN_MAYBE;
1072 : 2160 : break;
1073 : : }
1074 : : }
1075 : : }
2775 akorotkov@postgresql 1076 [ + + + - ]: 42600 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1077 : : strategy == JsonbJsonpathExistsStrategyNumber)
1078 : : {
1079 [ + + ]: 42600 : if (nkeys > 0)
1080 : : {
1081 [ + - - + ]: 2120 : Assert(extra_data && extra_data[0]);
336 peter@eisentraut.org 1082 : 2120 : res = execute_jsp_gin_node(extra_data[0], check, true);
1083 : :
1084 : : /* Should always recheck the result */
2775 akorotkov@postgresql 1085 [ + + ]: 2120 : if (res == GIN_TRUE)
1086 : 424 : res = GIN_MAYBE;
1087 : : }
1088 : : }
1089 : : else
4610 andrew@dunslane.net 1090 [ # # ]:UBC 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1091 : :
4602 heikki.linnakangas@i 1092 :CBC 50532 : PG_RETURN_GIN_TERNARY_VALUE(res);
1093 : : }
1094 : :
1095 : : /*
1096 : : *
1097 : : * jsonb_path_ops GIN opclass support functions
1098 : : *
1099 : : * In a jsonb_path_ops index, the GIN keys are uint32 hashes, one per JSON
1100 : : * value; but the JSON key(s) leading to each value are also included in its
1101 : : * hash computation. This means we can only support containment queries,
1102 : : * but the index can distinguish, for example, {"foo": 42} from {"bar": 42}
1103 : : * since different hashes will be generated.
1104 : : *
1105 : : */
1106 : :
1107 : : Datum
4561 tgl@sss.pgh.pa.us 1108 : 4159 : gin_extract_jsonb_path(PG_FUNCTION_ARGS)
1109 : : {
3335 1110 : 4159 : Jsonb *jb = PG_GETARG_JSONB_P(0);
4610 andrew@dunslane.net 1111 : 4159 : int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
2775 akorotkov@postgresql 1112 : 4159 : int total = JB_ROOT_COUNT(jb);
1113 : : JsonbIterator *it;
1114 : : JsonbValue v;
1115 : : JsonbIteratorToken r;
1116 : : PathHashStack tail;
1117 : : PathHashStack *stack;
1118 : : GinEntries entries;
1119 : :
1120 : : /* If the root level is empty, we certainly have no keys */
4610 andrew@dunslane.net 1121 [ + + ]: 4159 : if (total == 0)
1122 : : {
1123 : 481 : *nentries = 0;
1124 : 481 : PG_RETURN_POINTER(NULL);
1125 : : }
1126 : :
1127 : : /* Otherwise, use 2 * root count as initial estimate of result size */
2775 akorotkov@postgresql 1128 : 3678 : init_gin_entries(&entries, 2 * total);
1129 : :
1130 : : /* We keep a stack of partial hashes corresponding to parent key levels */
4610 andrew@dunslane.net 1131 : 3678 : tail.parent = NULL;
1132 : 3678 : tail.hash = 0;
1133 : 3678 : stack = &tail;
1134 : :
4563 tgl@sss.pgh.pa.us 1135 : 3678 : it = JsonbIteratorInit(&jb->root);
1136 : :
4610 andrew@dunslane.net 1137 [ + + ]: 49916 : while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
1138 : : {
1139 : : PathHashStack *parent;
1140 : :
1141 [ + + + + : 46238 : switch (r)
- ]
1142 : : {
1143 : 3801 : case WJB_BEGIN_ARRAY:
1144 : : case WJB_BEGIN_OBJECT:
1145 : : /* Push a stack level for this object */
4563 tgl@sss.pgh.pa.us 1146 : 3801 : parent = stack;
330 michael@paquier.xyz 1147 : 3801 : stack = palloc_object(PathHashStack);
1148 : :
1149 : : /*
1150 : : * We pass forward hashes from outer nesting levels so that
1151 : : * the hashes for nested values will include outer keys as
1152 : : * well as their own keys.
1153 : : *
1154 : : * Nesting an array within another array will not alter
1155 : : * innermost scalar element hash values, but that seems
1156 : : * inconsequential.
1157 : : */
4018 tgl@sss.pgh.pa.us 1158 : 3801 : stack->hash = parent->hash;
4563 1159 : 3801 : stack->parent = parent;
4610 andrew@dunslane.net 1160 : 3801 : break;
1161 : 19292 : case WJB_KEY:
1162 : : /* mix this key into the current outer hash */
1163 : 19292 : JsonbHashScalarValue(&v, &stack->hash);
1164 : : /* hash is now ready to incorporate the value */
1165 : 19292 : break;
1166 : 19344 : case WJB_ELEM:
1167 : : case WJB_VALUE:
1168 : : /* mix the element or value's hash into the prepared hash */
1169 : 19344 : JsonbHashScalarValue(&v, &stack->hash);
1170 : : /* and emit an index entry */
2775 akorotkov@postgresql 1171 : 19344 : add_gin_entry(&entries, UInt32GetDatum(stack->hash));
1172 : : /* reset hash for next key, value, or sub-object */
4018 tgl@sss.pgh.pa.us 1173 : 19344 : stack->hash = stack->parent->hash;
4610 andrew@dunslane.net 1174 : 19344 : break;
1175 : 3801 : case WJB_END_ARRAY:
1176 : : case WJB_END_OBJECT:
1177 : : /* Pop the stack */
4563 tgl@sss.pgh.pa.us 1178 : 3801 : parent = stack->parent;
4610 andrew@dunslane.net 1179 : 3801 : pfree(stack);
4563 tgl@sss.pgh.pa.us 1180 : 3801 : stack = parent;
1181 : : /* reset hash for next key, value, or sub-object */
4018 1182 [ + + ]: 3801 : if (stack->parent)
1183 : 123 : stack->hash = stack->parent->hash;
1184 : : else
1185 : 3678 : stack->hash = 0;
4610 andrew@dunslane.net 1186 : 3801 : break;
4610 andrew@dunslane.net 1187 :UBC 0 : default:
4043 noah@leadboat.com 1188 [ # # ]: 0 : elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r);
1189 : : }
1190 : : }
1191 : :
2775 akorotkov@postgresql 1192 :CBC 3678 : *nentries = entries.count;
1193 : :
1194 : 3678 : PG_RETURN_POINTER(entries.buf);
1195 : : }
1196 : :
1197 : : Datum
4561 tgl@sss.pgh.pa.us 1198 : 335 : gin_extract_jsonb_query_path(PG_FUNCTION_ARGS)
1199 : : {
4610 andrew@dunslane.net 1200 : 335 : int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
1201 : 335 : StrategyNumber strategy = PG_GETARG_UINT16(2);
1202 : 335 : int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
1203 : : Datum *entries;
1204 : :
2775 akorotkov@postgresql 1205 [ + + ]: 335 : if (strategy == JsonbContainsStrategyNumber)
1206 : : {
1207 : : /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */
1208 : : entries = (Datum *)
1209 : 96 : DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
1210 : : PG_GETARG_DATUM(0),
1211 : : PointerGetDatum(nentries)));
1212 : :
1213 : : /* ... although "contains {}" requires a full index scan */
1214 [ + + ]: 96 : if (*nentries == 0)
1215 : 9 : *searchMode = GIN_SEARCH_MODE_ALL;
1216 : : }
1217 [ + + + - ]: 239 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1218 : : strategy == JsonbJsonpathExistsStrategyNumber)
1219 : 239 : {
1220 : 239 : JsonPath *jp = PG_GETARG_JSONPATH_P(0);
1221 : 239 : Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4);
1222 : :
1223 : 239 : entries = extract_jsp_query(jp, strategy, true, nentries, extra_data);
1224 : :
1225 [ + + ]: 239 : if (!entries)
1226 : 54 : *searchMode = GIN_SEARCH_MODE_ALL;
1227 : : }
1228 : : else
1229 : : {
2775 akorotkov@postgresql 1230 [ # # ]:UBC 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1231 : : entries = NULL;
1232 : : }
1233 : :
4610 andrew@dunslane.net 1234 :CBC 335 : PG_RETURN_POINTER(entries);
1235 : : }
1236 : :
1237 : : Datum
4561 tgl@sss.pgh.pa.us 1238 :UBC 0 : gin_consistent_jsonb_path(PG_FUNCTION_ARGS)
1239 : : {
4563 1240 : 0 : bool *check = (bool *) PG_GETARG_POINTER(0);
1241 : 0 : StrategyNumber strategy = PG_GETARG_UINT16(1);
1242 : : #ifdef NOT_USED
1243 : : Jsonb *query = PG_GETARG_JSONB_P(2);
1244 : : #endif
1245 : 0 : int32 nkeys = PG_GETARG_INT32(3);
2775 akorotkov@postgresql 1246 : 0 : Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
4563 tgl@sss.pgh.pa.us 1247 : 0 : bool *recheck = (bool *) PG_GETARG_POINTER(5);
1248 : 0 : bool res = true;
1249 : : int32 i;
1250 : :
2775 akorotkov@postgresql 1251 [ # # ]: 0 : if (strategy == JsonbContainsStrategyNumber)
1252 : : {
1253 : : /*
1254 : : * jsonb_path_ops is necessarily lossy, not only because of hash
1255 : : * collisions but also because it doesn't preserve complete
1256 : : * information about the structure of the JSON object. Besides, there
1257 : : * are some special rules around the containment of raw scalars in
1258 : : * arrays that are not handled here. So we must always recheck a
1259 : : * match. However, if not all of the keys are present, the tuple
1260 : : * certainly doesn't match.
1261 : : */
1262 : 0 : *recheck = true;
1263 [ # # ]: 0 : for (i = 0; i < nkeys; i++)
1264 : : {
1265 [ # # ]: 0 : if (!check[i])
1266 : : {
1267 : 0 : res = false;
1268 : 0 : break;
1269 : : }
1270 : : }
1271 : : }
1272 [ # # # # ]: 0 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1273 : : strategy == JsonbJsonpathExistsStrategyNumber)
1274 : : {
1275 : 0 : *recheck = true;
1276 : :
1277 [ # # ]: 0 : if (nkeys > 0)
1278 : : {
1279 [ # # # # ]: 0 : Assert(extra_data && extra_data[0]);
336 peter@eisentraut.org 1280 : 0 : res = execute_jsp_gin_node(extra_data[0], check, false) != GIN_FALSE;
1281 : : }
1282 : : }
1283 : : else
2775 akorotkov@postgresql 1284 [ # # ]: 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1285 : :
4563 tgl@sss.pgh.pa.us 1286 : 0 : PG_RETURN_BOOL(res);
1287 : : }
1288 : :
1289 : : Datum
4561 tgl@sss.pgh.pa.us 1290 :CBC 28888 : gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS)
1291 : : {
4563 1292 : 28888 : GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0);
1293 : 28888 : StrategyNumber strategy = PG_GETARG_UINT16(1);
1294 : : #ifdef NOT_USED
1295 : : Jsonb *query = PG_GETARG_JSONB_P(2);
1296 : : #endif
1297 : 28888 : int32 nkeys = PG_GETARG_INT32(3);
2775 akorotkov@postgresql 1298 : 28888 : Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
4563 tgl@sss.pgh.pa.us 1299 : 28888 : GinTernaryValue res = GIN_MAYBE;
1300 : : int32 i;
1301 : :
2775 akorotkov@postgresql 1302 [ + + ]: 28888 : if (strategy == JsonbContainsStrategyNumber)
1303 : : {
1304 : : /*
1305 : : * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE;
1306 : : * this corresponds to always forcing recheck in the regular
1307 : : * consistent function, for the reasons listed there.
1308 : : */
1309 [ + + ]: 4372 : for (i = 0; i < nkeys; i++)
1310 : : {
1311 [ + + ]: 220 : if (check[i] == GIN_FALSE)
1312 : : {
1313 : 56 : res = GIN_FALSE;
1314 : 56 : break;
1315 : : }
1316 : : }
1317 : : }
1318 [ + + + - ]: 24680 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1319 : : strategy == JsonbJsonpathExistsStrategyNumber)
1320 : : {
1321 [ + + ]: 24680 : if (nkeys > 0)
1322 : : {
1323 [ + - - + ]: 392 : Assert(extra_data && extra_data[0]);
336 peter@eisentraut.org 1324 : 392 : res = execute_jsp_gin_node(extra_data[0], check, true);
1325 : :
1326 : : /* Should always recheck the result */
2775 akorotkov@postgresql 1327 [ + + ]: 392 : if (res == GIN_TRUE)
1328 : 280 : res = GIN_MAYBE;
1329 : : }
1330 : : }
1331 : : else
2775 akorotkov@postgresql 1332 [ # # ]:UBC 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1333 : :
4563 tgl@sss.pgh.pa.us 1334 :CBC 28888 : PG_RETURN_GIN_TERNARY_VALUE(res);
1335 : : }
1336 : :
1337 : : /*
1338 : : * Construct a jsonb_ops GIN key from a flag byte and a textual representation
1339 : : * (which need not be null-terminated). This function is responsible
1340 : : * for hashing overlength text representations; it will add the
1341 : : * JGINFLAG_HASHED bit to the flag value if it does that.
1342 : : */
1343 : : static Datum
1344 : 71277 : make_text_key(char flag, const char *str, int len)
1345 : : {
1346 : : text *item;
1347 : : char hashbuf[10];
1348 : :
1349 [ - + ]: 71277 : if (len > JGIN_MAXLENGTH)
1350 : : {
1351 : : uint32 hashval;
1352 : :
43 john.naylor@postgres 1353 :UNC 0 : hashval = hash_bytes((const unsigned char *) str, len);
4563 tgl@sss.pgh.pa.us 1354 :UBC 0 : snprintf(hashbuf, sizeof(hashbuf), "%08x", hashval);
1355 : 0 : str = hashbuf;
1356 : 0 : len = 8;
1357 : 0 : flag |= JGINFLAG_HASHED;
1358 : : }
1359 : :
1360 : : /*
1361 : : * Now build the text Datum. For simplicity we build a 4-byte-header
1362 : : * varlena text Datum here, but we expect it will get converted to short
1363 : : * header format when stored in the index.
1364 : : */
4610 andrew@dunslane.net 1365 :CBC 71277 : item = (text *) palloc(VARHDRSZ + len + 1);
1366 : 71277 : SET_VARSIZE(item, VARHDRSZ + len + 1);
1367 : :
1368 : 71277 : *VARDATA(item) = flag;
1369 : :
1370 : 71277 : memcpy(VARDATA(item) + 1, str, len);
1371 : :
4563 tgl@sss.pgh.pa.us 1372 : 71277 : return PointerGetDatum(item);
1373 : : }
1374 : :
1375 : : /*
1376 : : * Create a textual representation of a JsonbValue that will serve as a GIN
1377 : : * key in a jsonb_ops index. is_key is true if the JsonbValue is a key,
1378 : : * or if it is a string array element (since we pretend those are keys,
1379 : : * see jsonb.h).
1380 : : */
1381 : : static Datum
1382 : 70916 : make_scalar_key(const JsonbValue *scalarVal, bool is_key)
1383 : : {
1384 : : Datum item;
1385 : : char *cstr;
1386 : :
4610 andrew@dunslane.net 1387 [ + + + + : 70916 : switch (scalarVal->type)
- ]
1388 : : {
1389 : 1107 : case jbvNull:
4563 tgl@sss.pgh.pa.us 1390 [ - + ]: 1107 : Assert(!is_key);
1391 : 1107 : item = make_text_key(JGINFLAG_NULL, "", 0);
4610 andrew@dunslane.net 1392 : 1107 : break;
1393 : 4598 : case jbvBool:
4563 tgl@sss.pgh.pa.us 1394 [ - + ]: 4598 : Assert(!is_key);
1395 : 4598 : item = make_text_key(JGINFLAG_BOOL,
1396 [ + + ]: 4598 : scalarVal->val.boolean ? "t" : "f", 1);
4610 andrew@dunslane.net 1397 : 4598 : break;
1398 : 22354 : case jbvNumeric:
4563 tgl@sss.pgh.pa.us 1399 [ - + ]: 22354 : Assert(!is_key);
1400 : :
1401 : : /*
1402 : : * A normalized textual representation, free of trailing zeroes,
1403 : : * is required so that numerically equal values will produce equal
1404 : : * strings.
1405 : : *
1406 : : * It isn't ideal that numerics are stored in a relatively bulky
1407 : : * textual format. However, it's a notationally convenient way of
1408 : : * storing a "union" type in the GIN B-Tree, and indexing Jsonb
1409 : : * strings takes precedence.
1410 : : */
4600 1411 : 22354 : cstr = numeric_normalize(scalarVal->val.numeric);
4563 1412 : 22354 : item = make_text_key(JGINFLAG_NUM, cstr, strlen(cstr));
4610 andrew@dunslane.net 1413 : 22354 : pfree(cstr);
1414 : 22354 : break;
1415 : 42857 : case jbvString:
4563 tgl@sss.pgh.pa.us 1416 : 42857 : item = make_text_key(is_key ? JGINFLAG_KEY : JGINFLAG_STR,
1417 : 42857 : scalarVal->val.string.val,
1418 [ + + ]: 42857 : scalarVal->val.string.len);
4610 andrew@dunslane.net 1419 : 42857 : break;
4610 andrew@dunslane.net 1420 :UBC 0 : default:
4563 tgl@sss.pgh.pa.us 1421 [ # # ]: 0 : elog(ERROR, "unrecognized jsonb scalar type: %d", scalarVal->type);
1422 : : item = 0; /* keep compiler quiet */
1423 : : break;
1424 : : }
1425 : :
4610 andrew@dunslane.net 1426 :CBC 70916 : return item;
1427 : : }
|