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-2024, 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
1840 akorotkov@postgresql 163 :CBC 5993 : init_gin_entries(GinEntries *entries, int preallocated)
164 : : {
165 : 5993 : entries->allocated = preallocated;
166 [ + - ]: 5993 : entries->buf = preallocated ? palloc(sizeof(Datum) * preallocated) : NULL;
167 : 5993 : entries->count = 0;
168 : 5993 : }
169 : :
170 : : /* Add new entry to GinEntries */
171 : : static int
172 : 46881 : add_gin_entry(GinEntries *entries, Datum entry)
173 : : {
174 : 46881 : int id = entries->count;
175 : :
176 [ + + ]: 46881 : 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 : 46881 : entries->buf[entries->count++] = entry;
192 : :
193 : 46881 : return id;
194 : : }
195 : :
196 : : /*
197 : : *
198 : : * jsonb_ops GIN opclass support functions
199 : : *
200 : : */
201 : :
202 : : Datum
3675 andrew@dunslane.net 203 : 378779 : gin_compare_jsonb(PG_FUNCTION_ARGS)
204 : : {
205 : 378779 : text *arg1 = PG_GETARG_TEXT_PP(0);
206 : 378779 : text *arg2 = PG_GETARG_TEXT_PP(1);
207 : : int32 result;
208 : : char *a1p,
209 : : *a2p;
210 : : int len1,
211 : : len2;
212 : :
213 [ + + ]: 378779 : a1p = VARDATA_ANY(arg1);
214 [ + + ]: 378779 : a2p = VARDATA_ANY(arg2);
215 : :
216 [ - + - - : 378779 : len1 = VARSIZE_ANY_EXHDR(arg1);
- - - - +
+ ]
217 [ - + - - : 378779 : len2 = VARSIZE_ANY_EXHDR(arg2);
- - - - +
+ ]
218 : :
219 : : /* Compare text as bttextcmp does, but always using C collation */
220 : 378779 : result = varstr_cmp(a1p, len1, a2p, len2, C_COLLATION_OID);
221 : :
222 [ - + ]: 378779 : PG_FREE_IF_COPY(arg1, 0);
223 [ - + ]: 378779 : PG_FREE_IF_COPY(arg2, 1);
224 : :
225 : 378779 : PG_RETURN_INT32(result);
226 : : }
227 : :
228 : : Datum
229 : 3605 : gin_extract_jsonb(PG_FUNCTION_ARGS)
230 : : {
2400 tgl@sss.pgh.pa.us 231 : 3605 : Jsonb *jb = (Jsonb *) PG_GETARG_JSONB_P(0);
3675 andrew@dunslane.net 232 : 3605 : int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
1840 akorotkov@postgresql 233 : 3605 : 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 */
3675 andrew@dunslane.net 240 [ + + ]: 3605 : 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 */
1840 akorotkov@postgresql 247 : 3245 : init_gin_entries(&entries, 2 * total);
248 : :
3630 heikki.linnakangas@i 249 : 3245 : it = JsonbIteratorInit(&jb->root);
250 : :
3675 andrew@dunslane.net 251 [ + + ]: 41715 : while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
252 : : {
253 [ + + + + ]: 38470 : switch (r)
254 : : {
255 : 15930 : case WJB_KEY:
1840 akorotkov@postgresql 256 : 15930 : add_gin_entry(&entries, make_scalar_key(&v, true));
3675 andrew@dunslane.net 257 : 15930 : break;
258 : 84 : case WJB_ELEM:
259 : : /* Pretend string array elements are keys, see jsonb.h */
1840 akorotkov@postgresql 260 : 84 : add_gin_entry(&entries, make_scalar_key(&v, v.type == jbvString));
3675 andrew@dunslane.net 261 : 84 : break;
262 : 15894 : case WJB_VALUE:
1840 akorotkov@postgresql 263 : 15894 : add_gin_entry(&entries, make_scalar_key(&v, false));
3675 andrew@dunslane.net 264 : 15894 : break;
265 : 6562 : default:
266 : : /* we can ignore structural items */
3628 tgl@sss.pgh.pa.us 267 : 6562 : break;
268 : : }
269 : : }
270 : :
1840 akorotkov@postgresql 271 : 3245 : *nentries = entries.count;
272 : :
273 : 3245 : 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 : :
1840 akorotkov@postgresql 305 :UBC 0 : default:
306 : : /* other path items like item methods are not supported */
307 : 0 : return false;
308 : : }
309 : :
1840 akorotkov@postgresql 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 : :
1840 akorotkov@postgresql 346 :UBC 0 : default:
347 : : /* other items (wildcard paths, item methods) are not supported */
348 : 0 : return false;
349 : : }
350 : : }
351 : :
352 : : static JsonPathGinNode *
1840 akorotkov@postgresql 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;
1840 akorotkov@postgresql 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 : :
1840 akorotkov@postgresql 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 : : {
1840 akorotkov@postgresql 461 :UBC 0 : node = make_jsp_entry_node_scalar(scalar,
462 : : key_entry == GIN_TRUE);
463 : : }
464 : : }
465 : : else
466 : : {
1840 akorotkov@postgresql 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 : : */
1840 akorotkov@postgresql 539 :UBC 0 : return nodes;
1840 akorotkov@postgresql 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 : :
606 tgl@sss.pgh.pa.us 570 [ + + ]: 519 : if (nodes == NIL)
571 : : /* no nodes were extracted => full scan is needed for this path */
1840 akorotkov@postgresql 572 : 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 : :
1840 akorotkov@postgresql 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 : :
1840 akorotkov@postgresql 627 :CBC 108 : case jpiExists: /* EXISTS(path) */
628 : : {
629 : : JsonPathItem arg;
630 : :
631 [ - + ]: 108 : if (not)
1840 akorotkov@postgresql 632 :UBC 0 : return NULL; /* NOT EXISTS is not supported */
633 : :
1840 akorotkov@postgresql 634 :CBC 108 : jspGetArg(jsp, &arg);
635 : :
636 : 108 : return extract_jsp_path_expr(cxt, path, &arg, NULL);
637 : : }
638 : :
1840 akorotkov@postgresql 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 : :
1840 akorotkov@postgresql 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)
1840 akorotkov@postgresql 665 :UBC 0 : return NULL;
666 : :
1840 akorotkov@postgresql 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
1840 akorotkov@postgresql 681 :UBC 0 : return NULL; /* at least one operand should be a scalar */
682 : :
1840 akorotkov@postgresql 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;
1840 akorotkov@postgresql 703 :UBC 0 : default:
704 [ # # ]: 0 : elog(ERROR, "invalid scalar jsonpath item type: %d",
705 : : scalar_item->type);
706 : : return NULL;
707 : : }
708 : :
1840 akorotkov@postgresql 709 :CBC 273 : return extract_jsp_path_expr(cxt, path, path_item, &scalar);
710 : : }
711 : :
1840 akorotkov@postgresql 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
1840 akorotkov@postgresql 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)
1840 akorotkov@postgresql 786 :UBC 0 : return NULL;
787 : :
1840 akorotkov@postgresql 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
1840 akorotkov@postgresql 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
3675 andrew@dunslane.net 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 */
3628 tgl@sss.pgh.pa.us 863 [ + + ]: 54 : if (*nentries == 0)
3675 andrew@dunslane.net 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));
3628 tgl@sss.pgh.pa.us 873 :UBC 0 : entries[0] = make_text_key(JGINFLAG_KEY,
3628 tgl@sss.pgh.pa.us 874 [ - + ]:CBC 24 : VARDATA_ANY(query),
875 [ - + - - : 24 : VARSIZE_ANY_EXHDR(query));
- - - - -
+ ]
876 : : }
3675 andrew@dunslane.net 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 : :
653 peter@eisentraut.org 888 : 12 : deconstruct_array_builtin(query, TEXTOID, &key_datums, &key_nulls, &key_count);
889 : :
3675 andrew@dunslane.net 890 : 12 : entries = (Datum *) palloc(sizeof(Datum) * key_count);
891 : :
3628 tgl@sss.pgh.pa.us 892 [ + + ]: 36 : for (i = 0, j = 0; i < key_count; i++)
893 : : {
894 : : /* Nulls in the array are ignored */
3675 andrew@dunslane.net 895 [ - + ]: 24 : if (key_nulls[i])
3675 andrew@dunslane.net 896 :UBC 0 : continue;
897 : : /* We rely on the array elements not being toasted */
3628 tgl@sss.pgh.pa.us 898 : 0 : entries[j++] = make_text_key(JGINFLAG_KEY,
489 tgl@sss.pgh.pa.us 899 [ - + ]:CBC 24 : VARDATA_ANY(key_datums[i]),
900 [ - + - - : 24 : VARSIZE_ANY_EXHDR(key_datums[i]));
- - - - -
+ ]
901 : : }
902 : :
3675 andrew@dunslane.net 903 : 12 : *nentries = j;
904 : : /* ExistsAll with no keys should match everything */
905 [ - + - - ]: 12 : if (j == 0 && strategy == JsonbExistsAllStrategyNumber)
3675 andrew@dunslane.net 906 :UBC 0 : *searchMode = GIN_SEARCH_MODE_ALL;
907 : : }
1840 akorotkov@postgresql 908 [ + + + - ]:CBC 174 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
909 : : strategy == JsonbJsonpathExistsStrategyNumber)
910 : 174 : {
911 : 174 : JsonPath *jp = PG_GETARG_JSONPATH_P(0);
912 : 174 : Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4);
913 : :
914 : 174 : entries = extract_jsp_query(jp, strategy, false, nentries, extra_data);
915 : :
916 [ + + ]: 174 : if (!entries)
917 : 48 : *searchMode = GIN_SEARCH_MODE_ALL;
918 : : }
919 : : else
920 : : {
3675 andrew@dunslane.net 921 [ # # ]:UBC 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
922 : : entries = NULL; /* keep compiler quiet */
923 : : }
924 : :
3675 andrew@dunslane.net 925 :CBC 264 : PG_RETURN_POINTER(entries);
926 : : }
927 : :
928 : : Datum
3675 andrew@dunslane.net 929 :UBC 0 : gin_consistent_jsonb(PG_FUNCTION_ARGS)
930 : : {
931 : 0 : bool *check = (bool *) PG_GETARG_POINTER(0);
932 : 0 : StrategyNumber strategy = PG_GETARG_UINT16(1);
933 : :
934 : : /* Jsonb *query = PG_GETARG_JSONB_P(2); */
935 : 0 : int32 nkeys = PG_GETARG_INT32(3);
936 : :
1840 akorotkov@postgresql 937 : 0 : Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
3675 andrew@dunslane.net 938 : 0 : bool *recheck = (bool *) PG_GETARG_POINTER(5);
939 : 0 : bool res = true;
940 : : int32 i;
941 : :
942 [ # # ]: 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
950 : : * contains all the query keys.
951 : : */
952 : 0 : *recheck = true;
953 [ # # ]: 0 : for (i = 0; i < nkeys; i++)
954 : : {
955 [ # # ]: 0 : if (!check[i])
956 : : {
957 : 0 : res = false;
958 : 0 : break;
959 : : }
960 : : }
961 : : }
962 [ # # ]: 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
969 : : * recheck if not, but the index lacks enough info to tell about (2).
970 : : */
3628 tgl@sss.pgh.pa.us 971 : 0 : *recheck = true;
3675 andrew@dunslane.net 972 : 0 : res = true;
973 : : }
974 [ # # ]: 0 : else if (strategy == JsonbExistsAnyStrategyNumber)
975 : : {
976 : : /* As for plain exists, we must recheck */
3628 tgl@sss.pgh.pa.us 977 : 0 : *recheck = true;
3675 andrew@dunslane.net 978 : 0 : res = true;
979 : : }
980 [ # # ]: 0 : else if (strategy == JsonbExistsAllStrategyNumber)
981 : : {
982 : : /* As for plain exists, we must recheck */
3628 tgl@sss.pgh.pa.us 983 : 0 : *recheck = true;
984 : : /* ... but unless all the keys are present, we can say "false" */
3675 andrew@dunslane.net 985 [ # # ]: 0 : for (i = 0; i < nkeys; i++)
986 : : {
987 [ # # ]: 0 : if (!check[i])
988 : : {
989 : 0 : res = false;
990 : 0 : break;
991 : : }
992 : : }
993 : : }
1840 akorotkov@postgresql 994 [ # # # # ]: 0 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
995 : : strategy == JsonbJsonpathExistsStrategyNumber)
996 : : {
997 : 0 : *recheck = true;
998 : :
999 [ # # ]: 0 : if (nkeys > 0)
1000 : : {
1001 [ # # # # ]: 0 : Assert(extra_data && extra_data[0]);
1002 : 0 : res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check,
1003 : : false) != GIN_FALSE;
1004 : : }
1005 : : }
1006 : : else
3675 andrew@dunslane.net 1007 [ # # ]: 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1008 : :
1009 : 0 : PG_RETURN_BOOL(res);
1010 : : }
1011 : :
1012 : : Datum
3675 andrew@dunslane.net 1013 :CBC 31821 : gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
1014 : : {
3667 heikki.linnakangas@i 1015 : 31821 : GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0);
3675 andrew@dunslane.net 1016 : 31821 : StrategyNumber strategy = PG_GETARG_UINT16(1);
1017 : :
1018 : : /* Jsonb *query = PG_GETARG_JSONB_P(2); */
1019 : 31821 : int32 nkeys = PG_GETARG_INT32(3);
1840 akorotkov@postgresql 1020 : 31821 : Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
3628 tgl@sss.pgh.pa.us 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
1027 : : * function, for the reasons listed there.
1028 : : */
1029 [ + + + + ]: 31821 : if (strategy == JsonbContainsStrategyNumber ||
1030 : : strategy == JsonbExistsAllStrategyNumber)
1031 : : {
1032 : : /* All extracted keys must be present */
3675 andrew@dunslane.net 1033 [ + + ]: 5070 : for (i = 0; i < nkeys; i++)
1034 : : {
1035 [ + + ]: 1767 : if (check[i] == GIN_FALSE)
1036 : : {
1037 : 1026 : res = GIN_FALSE;
1038 : 1026 : break;
1039 : : }
1040 : : }
1041 : : }
1042 [ + + + + ]: 27492 : else if (strategy == JsonbExistsStrategyNumber ||
1043 : : strategy == JsonbExistsAnyStrategyNumber)
1044 : : {
1045 : : /* At least one extracted key must be present */
1046 : 1620 : res = GIN_FALSE;
1047 [ + - ]: 2049 : for (i = 0; i < nkeys; i++)
1048 : : {
3628 tgl@sss.pgh.pa.us 1049 [ + + ]: 2049 : if (check[i] == GIN_TRUE ||
1050 [ + + ]: 432 : check[i] == GIN_MAYBE)
1051 : : {
3675 andrew@dunslane.net 1052 : 1620 : res = GIN_MAYBE;
1053 : 1620 : break;
1054 : : }
1055 : : }
1056 : : }
1840 akorotkov@postgresql 1057 [ + + + - ]: 25872 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1058 : : strategy == JsonbJsonpathExistsStrategyNumber)
1059 : : {
1060 [ + + ]: 25872 : if (nkeys > 0)
1061 : : {
1062 [ + - - + ]: 1584 : Assert(extra_data && extra_data[0]);
1063 : 1584 : res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check,
1064 : : true);
1065 : :
1066 : : /* Should always recheck the result */
1067 [ + + ]: 1584 : if (res == GIN_TRUE)
1068 : 318 : res = GIN_MAYBE;
1069 : : }
1070 : : }
1071 : : else
3675 andrew@dunslane.net 1072 [ # # ]:UBC 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1073 : :
3667 heikki.linnakangas@i 1074 :CBC 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 : : */
1088 : :
1089 : : Datum
3626 tgl@sss.pgh.pa.us 1090 : 3108 : gin_extract_jsonb_path(PG_FUNCTION_ARGS)
1091 : : {
2400 1092 : 3108 : Jsonb *jb = PG_GETARG_JSONB_P(0);
3675 andrew@dunslane.net 1093 : 3108 : int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
1840 akorotkov@postgresql 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;
1101 : :
1102 : : /* If the root level is empty, we certainly have no keys */
3675 andrew@dunslane.net 1103 [ + + ]: 3108 : if (total == 0)
1104 : : {
1105 : 360 : *nentries = 0;
1106 : 360 : PG_RETURN_POINTER(NULL);
1107 : : }
1108 : :
1109 : : /* Otherwise, use 2 * root count as initial estimate of result size */
1840 akorotkov@postgresql 1110 : 2748 : init_gin_entries(&entries, 2 * total);
1111 : :
1112 : : /* We keep a stack of partial hashes corresponding to parent key levels */
3675 andrew@dunslane.net 1113 : 2748 : tail.parent = NULL;
1114 : 2748 : tail.hash = 0;
1115 : 2748 : stack = &tail;
1116 : :
3628 tgl@sss.pgh.pa.us 1117 : 2748 : it = JsonbIteratorInit(&jb->root);
1118 : :
3675 andrew@dunslane.net 1119 [ + + ]: 37353 : while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
1120 : : {
1121 : : PathHashStack *parent;
1122 : :
1123 [ + + + + : 34605 : switch (r)
- ]
1124 : : {
1125 : 2829 : case WJB_BEGIN_ARRAY:
1126 : : case WJB_BEGIN_OBJECT:
1127 : : /* Push a stack level for this object */
3628 tgl@sss.pgh.pa.us 1128 : 2829 : parent = stack;
3675 andrew@dunslane.net 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
1138 : : * inconsequential.
1139 : : */
3083 tgl@sss.pgh.pa.us 1140 : 2829 : stack->hash = parent->hash;
3628 1141 : 2829 : stack->parent = parent;
3675 andrew@dunslane.net 1142 : 2829 : break;
1143 : 14457 : case WJB_KEY:
1144 : : /* mix this key into the current outer hash */
1145 : 14457 : JsonbHashScalarValue(&v, &stack->hash);
1146 : : /* hash is now ready to incorporate the value */
1147 : 14457 : break;
1148 : 14490 : case WJB_ELEM:
1149 : : case WJB_VALUE:
1150 : : /* mix the element or value's hash into the prepared hash */
1151 : 14490 : JsonbHashScalarValue(&v, &stack->hash);
1152 : : /* and emit an index entry */
1840 akorotkov@postgresql 1153 : 14490 : add_gin_entry(&entries, UInt32GetDatum(stack->hash));
1154 : : /* reset hash for next key, value, or sub-object */
3083 tgl@sss.pgh.pa.us 1155 : 14490 : stack->hash = stack->parent->hash;
3675 andrew@dunslane.net 1156 : 14490 : break;
1157 : 2829 : case WJB_END_ARRAY:
1158 : : case WJB_END_OBJECT:
1159 : : /* Pop the stack */
3628 tgl@sss.pgh.pa.us 1160 : 2829 : parent = stack->parent;
3675 andrew@dunslane.net 1161 : 2829 : pfree(stack);
3628 tgl@sss.pgh.pa.us 1162 : 2829 : stack = parent;
1163 : : /* reset hash for next key, value, or sub-object */
3083 1164 [ + + ]: 2829 : if (stack->parent)
1165 : 81 : stack->hash = stack->parent->hash;
1166 : : else
1167 : 2748 : stack->hash = 0;
3675 andrew@dunslane.net 1168 : 2829 : break;
3675 andrew@dunslane.net 1169 :UBC 0 : default:
3108 noah@leadboat.com 1170 [ # # ]: 0 : elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r);
1171 : : }
1172 : : }
1173 : :
1840 akorotkov@postgresql 1174 :CBC 2748 : *nentries = entries.count;
1175 : :
1176 : 2748 : PG_RETURN_POINTER(entries.buf);
1177 : : }
1178 : :
1179 : : Datum
3626 tgl@sss.pgh.pa.us 1180 : 210 : gin_extract_jsonb_query_path(PG_FUNCTION_ARGS)
1181 : : {
3675 andrew@dunslane.net 1182 : 210 : int32 *nentries = (int32 *) PG_GETARG_POINTER(1);
1183 : 210 : StrategyNumber strategy = PG_GETARG_UINT16(2);
1184 : 210 : int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
1185 : : Datum *entries;
1186 : :
1840 akorotkov@postgresql 1187 [ + + ]: 210 : if (strategy == JsonbContainsStrategyNumber)
1188 : : {
1189 : : /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */
1190 : : entries = (Datum *)
1191 : 63 : DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
1192 : : PG_GETARG_DATUM(0),
1193 : : PointerGetDatum(nentries)));
1194 : :
1195 : : /* ... although "contains {}" requires a full index scan */
1196 [ + + ]: 63 : if (*nentries == 0)
1197 : 6 : *searchMode = GIN_SEARCH_MODE_ALL;
1198 : : }
1199 [ + + + - ]: 147 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1200 : : strategy == JsonbJsonpathExistsStrategyNumber)
1201 : 147 : {
1202 : 147 : JsonPath *jp = PG_GETARG_JSONPATH_P(0);
1203 : 147 : Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4);
1204 : :
1205 : 147 : entries = extract_jsp_query(jp, strategy, true, nentries, extra_data);
1206 : :
1207 [ + + ]: 147 : if (!entries)
1208 : 24 : *searchMode = GIN_SEARCH_MODE_ALL;
1209 : : }
1210 : : else
1211 : : {
1840 akorotkov@postgresql 1212 [ # # ]:UBC 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1213 : : entries = NULL;
1214 : : }
1215 : :
3675 andrew@dunslane.net 1216 :CBC 210 : PG_RETURN_POINTER(entries);
1217 : : }
1218 : :
1219 : : Datum
3626 tgl@sss.pgh.pa.us 1220 :UBC 0 : gin_consistent_jsonb_path(PG_FUNCTION_ARGS)
1221 : : {
3628 1222 : 0 : bool *check = (bool *) PG_GETARG_POINTER(0);
1223 : 0 : StrategyNumber strategy = PG_GETARG_UINT16(1);
1224 : :
1225 : : /* Jsonb *query = PG_GETARG_JSONB_P(2); */
1226 : 0 : int32 nkeys = PG_GETARG_INT32(3);
1840 akorotkov@postgresql 1227 : 0 : Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
3628 tgl@sss.pgh.pa.us 1228 : 0 : bool *recheck = (bool *) PG_GETARG_POINTER(5);
1229 : 0 : bool res = true;
1230 : : int32 i;
1231 : :
1840 akorotkov@postgresql 1232 [ # # ]: 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
1241 : : * certainly doesn't match.
1242 : : */
1243 : 0 : *recheck = true;
1244 [ # # ]: 0 : for (i = 0; i < nkeys; i++)
1245 : : {
1246 [ # # ]: 0 : if (!check[i])
1247 : : {
1248 : 0 : res = false;
1249 : 0 : break;
1250 : : }
1251 : : }
1252 : : }
1253 [ # # # # ]: 0 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1254 : : strategy == JsonbJsonpathExistsStrategyNumber)
1255 : : {
1256 : 0 : *recheck = true;
1257 : :
1258 [ # # ]: 0 : if (nkeys > 0)
1259 : : {
1260 [ # # # # ]: 0 : Assert(extra_data && extra_data[0]);
1261 : 0 : res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check,
1262 : : false) != GIN_FALSE;
1263 : : }
1264 : : }
1265 : : else
1266 [ # # ]: 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1267 : :
3628 tgl@sss.pgh.pa.us 1268 : 0 : PG_RETURN_BOOL(res);
1269 : : }
1270 : :
1271 : : Datum
3626 tgl@sss.pgh.pa.us 1272 :CBC 15594 : gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS)
1273 : : {
3628 1274 : 15594 : GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0);
1275 : 15594 : StrategyNumber strategy = PG_GETARG_UINT16(1);
1276 : :
1277 : : /* Jsonb *query = PG_GETARG_JSONB_P(2); */
1278 : 15594 : int32 nkeys = PG_GETARG_INT32(3);
1840 akorotkov@postgresql 1279 : 15594 : Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4);
3628 tgl@sss.pgh.pa.us 1280 : 15594 : GinTernaryValue res = GIN_MAYBE;
1281 : : int32 i;
1282 : :
1840 akorotkov@postgresql 1283 [ + + ]: 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
1288 : : * consistent function, for the reasons listed there.
1289 : : */
1290 [ + + ]: 3279 : for (i = 0; i < nkeys; i++)
1291 : : {
1292 [ + + ]: 165 : if (check[i] == GIN_FALSE)
1293 : : {
1294 : 42 : res = GIN_FALSE;
1295 : 42 : break;
1296 : : }
1297 : : }
1298 : : }
1299 [ + + + - ]: 12438 : else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1300 : : strategy == JsonbJsonpathExistsStrategyNumber)
1301 : : {
1302 [ + + ]: 12438 : if (nkeys > 0)
1303 : : {
1304 [ + - - + ]: 294 : Assert(extra_data && extra_data[0]);
1305 : 294 : res = execute_jsp_gin_node((JsonPathGinNode *) extra_data[0], check,
1306 : : true);
1307 : :
1308 : : /* Should always recheck the result */
1309 [ + + ]: 294 : if (res == GIN_TRUE)
1310 : 210 : res = GIN_MAYBE;
1311 : : }
1312 : : }
1313 : : else
1840 akorotkov@postgresql 1314 [ # # ]:UBC 0 : elog(ERROR, "unrecognized strategy number: %d", strategy);
1315 : :
3628 tgl@sss.pgh.pa.us 1316 :CBC 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.
1324 : : */
1325 : : static Datum
1326 : 32352 : make_text_key(char flag, const char *str, int len)
1327 : : {
1328 : : text *item;
1329 : : char hashbuf[10];
1330 : :
1331 [ - + ]: 32352 : if (len > JGIN_MAXLENGTH)
1332 : : {
1333 : : uint32 hashval;
1334 : :
3628 tgl@sss.pgh.pa.us 1335 :UBC 0 : hashval = DatumGetUInt32(hash_any((const unsigned char *) str, len));
1336 : 0 : snprintf(hashbuf, sizeof(hashbuf), "%08x", hashval);
1337 : 0 : str = hashbuf;
1338 : 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
1345 : : * header format when stored in the index.
1346 : : */
3675 andrew@dunslane.net 1347 :CBC 32352 : item = (text *) palloc(VARHDRSZ + len + 1);
1348 : 32352 : SET_VARSIZE(item, VARHDRSZ + len + 1);
1349 : :
1350 : 32352 : *VARDATA(item) = flag;
1351 : :
1352 : 32352 : memcpy(VARDATA(item) + 1, str, len);
1353 : :
3628 tgl@sss.pgh.pa.us 1354 : 32352 : 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).
1362 : : */
1363 : : static Datum
1364 : 32118 : make_scalar_key(const JsonbValue *scalarVal, bool is_key)
1365 : : {
1366 : : Datum item;
1367 : : char *cstr;
1368 : :
3675 andrew@dunslane.net 1369 [ + + + + : 32118 : switch (scalarVal->type)
- ]
1370 : : {
1371 : 39 : case jbvNull:
3628 tgl@sss.pgh.pa.us 1372 [ - + ]: 39 : Assert(!is_key);
1373 : 39 : item = make_text_key(JGINFLAG_NULL, "", 0);
3675 andrew@dunslane.net 1374 : 39 : break;
1375 : 2784 : case jbvBool:
3628 tgl@sss.pgh.pa.us 1376 [ - + ]: 2784 : Assert(!is_key);
1377 : 2784 : item = make_text_key(JGINFLAG_BOOL,
1378 [ + + ]: 2784 : scalarVal->val.boolean ? "t" : "f", 1);
3675 andrew@dunslane.net 1379 : 2784 : break;
1380 : 6483 : case jbvNumeric:
3628 tgl@sss.pgh.pa.us 1381 [ - + ]: 6483 : 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
1391 : : * strings takes precedence.
1392 : : */
3665 1393 : 6483 : cstr = numeric_normalize(scalarVal->val.numeric);
3628 1394 : 6483 : item = make_text_key(JGINFLAG_NUM, cstr, strlen(cstr));
3675 andrew@dunslane.net 1395 : 6483 : pfree(cstr);
1396 : 6483 : break;
1397 : 22812 : case jbvString:
3628 tgl@sss.pgh.pa.us 1398 : 22812 : item = make_text_key(is_key ? JGINFLAG_KEY : JGINFLAG_STR,
1399 : 22812 : scalarVal->val.string.val,
1400 [ + + ]: 22812 : scalarVal->val.string.len);
3675 andrew@dunslane.net 1401 : 22812 : break;
3675 andrew@dunslane.net 1402 :UBC 0 : default:
3628 tgl@sss.pgh.pa.us 1403 [ # # ]: 0 : elog(ERROR, "unrecognized jsonb scalar type: %d", scalarVal->type);
1404 : : item = 0; /* keep compiler quiet */
1405 : : break;
1406 : : }
1407 : :
3675 andrew@dunslane.net 1408 :CBC 32118 : return item;
1409 : : }
|