Coverage Report

Created: 2026-10-16 06:48

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