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