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