Coverage Report

Created: 2026-08-14 06:37

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
 *
562
 * The current path (@) is passed in 'path'.
563
 */
564
static JsonPathGinNode *
565
extract_jsp_path_expr(JsonPathGinContext *cxt, JsonPathGinPath path,
566
            JsonPathItem *jsp, JsonbValue *scalar)
567
0
{
568
  /* extract a list of nodes to be AND-ed */
569
0
  List     *nodes = extract_jsp_path_expr_nodes(cxt, path, jsp, scalar);
570
571
0
  if (nodes == NIL)
572
    /* no nodes were extracted => full scan is needed for this path */
573
0
    return NULL;
574
575
0
  if (list_length(nodes) == 1)
576
0
    return linitial(nodes); /* avoid extra AND-node */
577
578
  /* construct AND-node for path with filters */
579
0
  return make_jsp_expr_node_args(JSP_GIN_AND, nodes);
580
0
}
581
582
/* Recursively extract nodes from the boolean jsonpath expression. */
583
static JsonPathGinNode *
584
extract_jsp_bool_expr(JsonPathGinContext *cxt, JsonPathGinPath path,
585
            JsonPathItem *jsp, bool not)
586
0
{
587
0
  check_stack_depth();
588
589
0
  switch (jsp->type)
590
0
  {
591
0
    case jpiAnd:      /* expr && expr */
592
0
    case jpiOr:       /* expr || expr */
593
0
      {
594
0
        JsonPathItem arg;
595
0
        JsonPathGinNode *larg;
596
0
        JsonPathGinNode *rarg;
597
0
        JsonPathGinNodeType type;
598
599
0
        jspGetLeftArg(jsp, &arg);
600
0
        larg = extract_jsp_bool_expr(cxt, path, &arg, not);
601
602
0
        jspGetRightArg(jsp, &arg);
603
0
        rarg = extract_jsp_bool_expr(cxt, path, &arg, not);
604
605
0
        if (!larg || !rarg)
606
0
        {
607
0
          if (jsp->type == jpiOr)
608
0
            return NULL;
609
610
0
          return larg ? larg : rarg;
611
0
        }
612
613
0
        type = not ^ (jsp->type == jpiAnd) ? JSP_GIN_AND : JSP_GIN_OR;
614
615
0
        return make_jsp_expr_node_binary(type, larg, rarg);
616
0
      }
617
618
0
    case jpiNot:      /* !expr  */
619
0
      {
620
0
        JsonPathItem arg;
621
622
0
        jspGetArg(jsp, &arg);
623
624
        /* extract child expression inverting 'not' flag */
625
0
        return extract_jsp_bool_expr(cxt, path, &arg, !not);
626
0
      }
627
628
0
    case jpiExists:     /* EXISTS(path) */
629
0
      {
630
0
        JsonPathItem arg;
631
632
0
        if (not)
633
0
          return NULL; /* NOT EXISTS is not supported */
634
635
0
        jspGetArg(jsp, &arg);
636
637
0
        return extract_jsp_path_expr(cxt, path, &arg, NULL);
638
0
      }
639
640
0
    case jpiNotEqual:
641
642
      /*
643
       * 'not' == true case is not supported here because '!(path !=
644
       * scalar)' is not equivalent to 'path == scalar' in the general
645
       * case because of sequence comparison semantics: 'path == scalar'
646
       * === 'EXISTS (path, @ == scalar)', '!(path != scalar)' ===
647
       * 'FOR_ALL(path, @ == scalar)'. So, we should translate '!(path
648
       * != scalar)' into GIN query 'path == scalar || EMPTY(path)', but
649
       * 'EMPTY(path)' queries are not supported by the both jsonb
650
       * opclasses.  However in strict mode we could omit 'EMPTY(path)'
651
       * part if the path can return exactly one item (it does not
652
       * contain wildcard accessors or item methods like .keyvalue()
653
       * etc.).
654
       */
655
0
      return NULL;
656
657
0
    case jpiEqual:      /* path == scalar */
658
0
      {
659
0
        JsonPathItem left_item;
660
0
        JsonPathItem right_item;
661
0
        JsonPathItem *path_item;
662
0
        JsonPathItem *scalar_item;
663
0
        JsonbValue  scalar;
664
665
0
        if (not)
666
0
          return NULL;
667
668
0
        jspGetLeftArg(jsp, &left_item);
669
0
        jspGetRightArg(jsp, &right_item);
670
671
0
        if (jspIsScalar(left_item.type))
672
0
        {
673
0
          scalar_item = &left_item;
674
0
          path_item = &right_item;
675
0
        }
676
0
        else if (jspIsScalar(right_item.type))
677
0
        {
678
0
          scalar_item = &right_item;
679
0
          path_item = &left_item;
680
0
        }
681
0
        else
682
0
          return NULL; /* at least one operand should be a scalar */
683
684
0
        switch (scalar_item->type)
685
0
        {
686
0
          case jpiNull:
687
0
            scalar.type = jbvNull;
688
0
            break;
689
0
          case jpiBool:
690
0
            scalar.type = jbvBool;
691
0
            scalar.val.boolean = !!*scalar_item->content.value.data;
692
0
            break;
693
0
          case jpiNumeric:
694
0
            scalar.type = jbvNumeric;
695
0
            scalar.val.numeric =
696
0
              (Numeric) scalar_item->content.value.data;
697
0
            break;
698
0
          case jpiString:
699
0
            scalar.type = jbvString;
700
0
            scalar.val.string.val = scalar_item->content.value.data;
701
0
            scalar.val.string.len =
702
0
              scalar_item->content.value.datalen;
703
0
            break;
704
0
          default:
705
0
            elog(ERROR, "invalid scalar jsonpath item type: %d",
706
0
               scalar_item->type);
707
0
            return NULL;
708
0
        }
709
710
0
        return extract_jsp_path_expr(cxt, path, path_item, &scalar);
711
0
      }
712
713
0
    default:
714
0
      return NULL;   /* not a boolean expression */
715
0
  }
716
0
}
717
718
/* Recursively emit all GIN entries found in the node tree */
719
static void
720
emit_jsp_gin_entries(JsonPathGinNode *node, GinEntries *entries)
721
0
{
722
0
  check_stack_depth();
723
724
0
  switch (node->type)
725
0
  {
726
0
    case JSP_GIN_ENTRY:
727
      /* replace datum with its index in the array */
728
0
      node->val.entryIndex = add_gin_entry(entries, node->val.entryDatum);
729
0
      break;
730
731
0
    case JSP_GIN_OR:
732
0
    case JSP_GIN_AND:
733
0
      {
734
0
        int     i;
735
736
0
        for (i = 0; i < node->val.nargs; i++)
737
0
          emit_jsp_gin_entries(node->args[i], entries);
738
739
0
        break;
740
0
      }
741
0
  }
742
0
}
743
744
/*
745
 * Recursively extract GIN entries from jsonpath query.
746
 * Root expression node is put into (*extra_data)[0].
747
 */
748
static Datum *
749
extract_jsp_query(JsonPath *jp, StrategyNumber strat, bool pathOps,
750
          int32 *nentries, Pointer **extra_data)
751
0
{
752
0
  JsonPathGinContext cxt;
753
0
  JsonPathItem root;
754
0
  JsonPathGinNode *node;
755
0
  JsonPathGinPath path = {0};
756
0
  GinEntries  entries = {0};
757
758
0
  cxt.lax = (jp->header & JSONPATH_LAX) != 0;
759
760
0
  if (pathOps)
761
0
  {
762
0
    cxt.add_path_item = jsonb_path_ops__add_path_item;
763
0
    cxt.extract_nodes = jsonb_path_ops__extract_nodes;
764
0
  }
765
0
  else
766
0
  {
767
0
    cxt.add_path_item = jsonb_ops__add_path_item;
768
0
    cxt.extract_nodes = jsonb_ops__extract_nodes;
769
0
  }
770
771
0
  jspInit(&root, jp);
772
773
0
  node = strat == JsonbJsonpathExistsStrategyNumber
774
0
    ? extract_jsp_path_expr(&cxt, path, &root, NULL)
775
0
    : extract_jsp_bool_expr(&cxt, path, &root, false);
776
777
0
  if (!node)
778
0
  {
779
0
    *nentries = 0;
780
0
    return NULL;
781
0
  }
782
783
0
  emit_jsp_gin_entries(node, &entries);
784
785
0
  *nentries = entries.count;
786
0
  if (!*nentries)
787
0
    return NULL;
788
789
0
  *extra_data = palloc0_array(Pointer, entries.count);
790
0
  **extra_data = (Pointer) node;
791
792
0
  return entries.buf;
793
0
}
794
795
/*
796
 * Recursively execute jsonpath expression.
797
 * 'check' is a bool[] or a GinTernaryValue[] depending on 'ternary' flag.
798
 */
799
static GinTernaryValue
800
execute_jsp_gin_node(JsonPathGinNode *node, void *check, bool ternary)
801
0
{
802
0
  GinTernaryValue res;
803
0
  GinTernaryValue v;
804
0
  int     i;
805
806
0
  switch (node->type)
807
0
  {
808
0
    case JSP_GIN_AND:
809
0
      res = GIN_TRUE;
810
0
      for (i = 0; i < node->val.nargs; i++)
811
0
      {
812
0
        v = execute_jsp_gin_node(node->args[i], check, ternary);
813
0
        if (v == GIN_FALSE)
814
0
          return GIN_FALSE;
815
0
        else if (v == GIN_MAYBE)
816
0
          res = GIN_MAYBE;
817
0
      }
818
0
      return res;
819
820
0
    case JSP_GIN_OR:
821
0
      res = GIN_FALSE;
822
0
      for (i = 0; i < node->val.nargs; i++)
823
0
      {
824
0
        v = execute_jsp_gin_node(node->args[i], check, ternary);
825
0
        if (v == GIN_TRUE)
826
0
          return GIN_TRUE;
827
0
        else if (v == GIN_MAYBE)
828
0
          res = GIN_MAYBE;
829
0
      }
830
0
      return res;
831
832
0
    case JSP_GIN_ENTRY:
833
0
      {
834
0
        int     index = node->val.entryIndex;
835
836
0
        if (ternary)
837
0
          return ((GinTernaryValue *) check)[index];
838
0
        else
839
0
          return ((bool *) check)[index] ? GIN_TRUE : GIN_FALSE;
840
0
      }
841
842
0
    default:
843
0
      elog(ERROR, "invalid jsonpath gin node type: %d", node->type);
844
0
      return GIN_FALSE; /* keep compiler quiet */
845
0
  }
846
0
}
847
848
Datum
849
gin_extract_jsonb_query(PG_FUNCTION_ARGS)
850
0
{
851
0
  int32    *nentries = (int32 *) PG_GETARG_POINTER(1);
852
0
  StrategyNumber strategy = PG_GETARG_UINT16(2);
853
0
  int32    *searchMode = (int32 *) PG_GETARG_POINTER(6);
854
0
  Datum    *entries;
855
856
0
  if (strategy == JsonbContainsStrategyNumber)
857
0
  {
858
    /* Query is a jsonb, so just apply gin_extract_jsonb... */
859
0
    entries = (Datum *)
860
0
      DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb,
861
0
                        PG_GETARG_DATUM(0),
862
0
                        PointerGetDatum(nentries)));
863
    /* ...although "contains {}" requires a full index scan */
864
0
    if (*nentries == 0)
865
0
      *searchMode = GIN_SEARCH_MODE_ALL;
866
0
  }
867
0
  else if (strategy == JsonbExistsStrategyNumber)
868
0
  {
869
    /* Query is a text string, which we treat as a key */
870
0
    text     *query = PG_GETARG_TEXT_PP(0);
871
872
0
    *nentries = 1;
873
0
    entries = palloc_object(Datum);
874
0
    entries[0] = make_text_key(JGINFLAG_KEY,
875
0
                   VARDATA_ANY(query),
876
0
                   VARSIZE_ANY_EXHDR(query));
877
0
  }
878
0
  else if (strategy == JsonbExistsAnyStrategyNumber ||
879
0
       strategy == JsonbExistsAllStrategyNumber)
880
0
  {
881
    /* Query is a text array; each element is treated as a key */
882
0
    ArrayType  *query = PG_GETARG_ARRAYTYPE_P(0);
883
0
    Datum    *key_datums;
884
0
    bool     *key_nulls;
885
0
    int     key_count;
886
0
    int     i,
887
0
          j;
888
889
0
    deconstruct_array_builtin(query, TEXTOID, &key_datums, &key_nulls, &key_count);
890
891
0
    entries = palloc_array(Datum, key_count);
892
893
0
    for (i = 0, j = 0; i < key_count; i++)
894
0
    {
895
      /* Nulls in the array are ignored */
896
0
      if (key_nulls[i])
897
0
        continue;
898
      /* We rely on the array elements not being toasted */
899
0
      entries[j++] = make_text_key(JGINFLAG_KEY,
900
0
                     VARDATA_ANY(DatumGetPointer(key_datums[i])),
901
0
                     VARSIZE_ANY_EXHDR(DatumGetPointer(key_datums[i])));
902
0
    }
903
904
0
    *nentries = j;
905
    /* ExistsAll with no keys should match everything */
906
0
    if (j == 0 && strategy == JsonbExistsAllStrategyNumber)
907
0
      *searchMode = GIN_SEARCH_MODE_ALL;
908
0
  }
909
0
  else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
910
0
       strategy == JsonbJsonpathExistsStrategyNumber)
911
0
  {
912
0
    JsonPath   *jp = PG_GETARG_JSONPATH_P(0);
913
0
    Pointer   **extra_data = (Pointer **) PG_GETARG_POINTER(4);
914
915
0
    entries = extract_jsp_query(jp, strategy, false, nentries, extra_data);
916
917
0
    if (!entries)
918
0
      *searchMode = GIN_SEARCH_MODE_ALL;
919
0
  }
920
0
  else
921
0
  {
922
0
    elog(ERROR, "unrecognized strategy number: %d", strategy);
923
0
    entries = NULL;     /* keep compiler quiet */
924
0
  }
925
926
0
  PG_RETURN_POINTER(entries);
927
0
}
928
929
Datum
930
gin_consistent_jsonb(PG_FUNCTION_ARGS)
931
0
{
932
0
  bool     *check = (bool *) PG_GETARG_POINTER(0);
933
0
  StrategyNumber strategy = PG_GETARG_UINT16(1);
934
#ifdef NOT_USED
935
  Jsonb    *query = PG_GETARG_JSONB_P(2);
936
#endif
937
0
  int32   nkeys = PG_GETARG_INT32(3);
938
939
0
  Pointer    *extra_data = (Pointer *) PG_GETARG_POINTER(4);
940
0
  bool     *recheck = (bool *) PG_GETARG_POINTER(5);
941
0
  bool    res = true;
942
0
  int32   i;
943
944
0
  if (strategy == JsonbContainsStrategyNumber)
945
0
  {
946
    /*
947
     * We must always recheck, since we can't tell from the index whether
948
     * the positions of the matched items match the structure of the query
949
     * object.  (Even if we could, we'd also have to worry about hashed
950
     * keys and the index's failure to distinguish keys from string array
951
     * elements.)  However, the tuple certainly doesn't match unless it
952
     * contains all the query keys.
953
     */
954
0
    *recheck = true;
955
0
    for (i = 0; i < nkeys; i++)
956
0
    {
957
0
      if (!check[i])
958
0
      {
959
0
        res = false;
960
0
        break;
961
0
      }
962
0
    }
963
0
  }
964
0
  else if (strategy == JsonbExistsStrategyNumber)
965
0
  {
966
    /*
967
     * Although the key is certainly present in the index, we must recheck
968
     * because (1) the key might be hashed, and (2) the index match might
969
     * be for a key that's not at top level of the JSON object.  For (1),
970
     * we could look at the query key to see if it's hashed and not
971
     * recheck if not, but the index lacks enough info to tell about (2).
972
     */
973
0
    *recheck = true;
974
0
    res = true;
975
0
  }
976
0
  else if (strategy == JsonbExistsAnyStrategyNumber)
977
0
  {
978
    /* As for plain exists, we must recheck */
979
0
    *recheck = true;
980
0
    res = true;
981
0
  }
982
0
  else if (strategy == JsonbExistsAllStrategyNumber)
983
0
  {
984
    /* As for plain exists, we must recheck */
985
0
    *recheck = true;
986
    /* ... but unless all the keys are present, we can say "false" */
987
0
    for (i = 0; i < nkeys; i++)
988
0
    {
989
0
      if (!check[i])
990
0
      {
991
0
        res = false;
992
0
        break;
993
0
      }
994
0
    }
995
0
  }
996
0
  else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
997
0
       strategy == JsonbJsonpathExistsStrategyNumber)
998
0
  {
999
0
    *recheck = true;
1000
1001
0
    if (nkeys > 0)
1002
0
    {
1003
0
      Assert(extra_data && extra_data[0]);
1004
0
      res = execute_jsp_gin_node(extra_data[0], check, false) != GIN_FALSE;
1005
0
    }
1006
0
  }
1007
0
  else
1008
0
    elog(ERROR, "unrecognized strategy number: %d", strategy);
1009
1010
0
  PG_RETURN_BOOL(res);
1011
0
}
1012
1013
Datum
1014
gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
1015
0
{
1016
0
  GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0);
1017
0
  StrategyNumber strategy = PG_GETARG_UINT16(1);
1018
#ifdef NOT_USED
1019
  Jsonb    *query = PG_GETARG_JSONB_P(2);
1020
#endif
1021
0
  int32   nkeys = PG_GETARG_INT32(3);
1022
0
  Pointer    *extra_data = (Pointer *) PG_GETARG_POINTER(4);
1023
0
  GinTernaryValue res = GIN_MAYBE;
1024
0
  int32   i;
1025
1026
  /*
1027
   * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE; this
1028
   * corresponds to always forcing recheck in the regular consistent
1029
   * function, for the reasons listed there.
1030
   */
1031
0
  if (strategy == JsonbContainsStrategyNumber ||
1032
0
    strategy == JsonbExistsAllStrategyNumber)
1033
0
  {
1034
    /* All extracted keys must be present */
1035
0
    for (i = 0; i < nkeys; i++)
1036
0
    {
1037
0
      if (check[i] == GIN_FALSE)
1038
0
      {
1039
0
        res = GIN_FALSE;
1040
0
        break;
1041
0
      }
1042
0
    }
1043
0
  }
1044
0
  else if (strategy == JsonbExistsStrategyNumber ||
1045
0
       strategy == JsonbExistsAnyStrategyNumber)
1046
0
  {
1047
    /* At least one extracted key must be present */
1048
0
    res = GIN_FALSE;
1049
0
    for (i = 0; i < nkeys; i++)
1050
0
    {
1051
0
      if (check[i] == GIN_TRUE ||
1052
0
        check[i] == GIN_MAYBE)
1053
0
      {
1054
0
        res = GIN_MAYBE;
1055
0
        break;
1056
0
      }
1057
0
    }
1058
0
  }
1059
0
  else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1060
0
       strategy == JsonbJsonpathExistsStrategyNumber)
1061
0
  {
1062
0
    if (nkeys > 0)
1063
0
    {
1064
0
      Assert(extra_data && extra_data[0]);
1065
0
      res = execute_jsp_gin_node(extra_data[0], check, true);
1066
1067
      /* Should always recheck the result */
1068
0
      if (res == GIN_TRUE)
1069
0
        res = GIN_MAYBE;
1070
0
    }
1071
0
  }
1072
0
  else
1073
0
    elog(ERROR, "unrecognized strategy number: %d", strategy);
1074
1075
0
  PG_RETURN_GIN_TERNARY_VALUE(res);
1076
0
}
1077
1078
/*
1079
 *
1080
 * jsonb_path_ops GIN opclass support functions
1081
 *
1082
 * In a jsonb_path_ops index, the GIN keys are uint32 hashes, one per JSON
1083
 * value; but the JSON key(s) leading to each value are also included in its
1084
 * hash computation.  This means we can only support containment queries,
1085
 * but the index can distinguish, for example, {"foo": 42} from {"bar": 42}
1086
 * since different hashes will be generated.
1087
 *
1088
 */
1089
1090
Datum
1091
gin_extract_jsonb_path(PG_FUNCTION_ARGS)
1092
0
{
1093
0
  Jsonb    *jb = PG_GETARG_JSONB_P(0);
1094
0
  int32    *nentries = (int32 *) PG_GETARG_POINTER(1);
1095
0
  int     total = JB_ROOT_COUNT(jb);
1096
0
  JsonbIterator *it;
1097
0
  JsonbValue  v;
1098
0
  JsonbIteratorToken r;
1099
0
  PathHashStack tail;
1100
0
  PathHashStack *stack;
1101
0
  GinEntries  entries;
1102
1103
  /* If the root level is empty, we certainly have no keys */
1104
0
  if (total == 0)
1105
0
  {
1106
0
    *nentries = 0;
1107
0
    PG_RETURN_POINTER(NULL);
1108
0
  }
1109
1110
  /* Otherwise, use 2 * root count as initial estimate of result size */
1111
0
  init_gin_entries(&entries, 2 * total);
1112
1113
  /* We keep a stack of partial hashes corresponding to parent key levels */
1114
0
  tail.parent = NULL;
1115
0
  tail.hash = 0;
1116
0
  stack = &tail;
1117
1118
0
  it = JsonbIteratorInit(&jb->root);
1119
1120
0
  while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
1121
0
  {
1122
0
    PathHashStack *parent;
1123
1124
0
    switch (r)
1125
0
    {
1126
0
      case WJB_BEGIN_ARRAY:
1127
0
      case WJB_BEGIN_OBJECT:
1128
        /* Push a stack level for this object */
1129
0
        parent = stack;
1130
0
        stack = palloc_object(PathHashStack);
1131
1132
        /*
1133
         * We pass forward hashes from outer nesting levels so that
1134
         * the hashes for nested values will include outer keys as
1135
         * well as their own keys.
1136
         *
1137
         * Nesting an array within another array will not alter
1138
         * innermost scalar element hash values, but that seems
1139
         * inconsequential.
1140
         */
1141
0
        stack->hash = parent->hash;
1142
0
        stack->parent = parent;
1143
0
        break;
1144
0
      case WJB_KEY:
1145
        /* mix this key into the current outer hash */
1146
0
        JsonbHashScalarValue(&v, &stack->hash);
1147
        /* hash is now ready to incorporate the value */
1148
0
        break;
1149
0
      case WJB_ELEM:
1150
0
      case WJB_VALUE:
1151
        /* mix the element or value's hash into the prepared hash */
1152
0
        JsonbHashScalarValue(&v, &stack->hash);
1153
        /* and emit an index entry */
1154
0
        add_gin_entry(&entries, UInt32GetDatum(stack->hash));
1155
        /* reset hash for next key, value, or sub-object */
1156
0
        stack->hash = stack->parent->hash;
1157
0
        break;
1158
0
      case WJB_END_ARRAY:
1159
0
      case WJB_END_OBJECT:
1160
        /* Pop the stack */
1161
0
        parent = stack->parent;
1162
0
        pfree(stack);
1163
0
        stack = parent;
1164
        /* reset hash for next key, value, or sub-object */
1165
0
        if (stack->parent)
1166
0
          stack->hash = stack->parent->hash;
1167
0
        else
1168
0
          stack->hash = 0;
1169
0
        break;
1170
0
      default:
1171
0
        elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r);
1172
0
    }
1173
0
  }
1174
1175
0
  *nentries = entries.count;
1176
1177
0
  PG_RETURN_POINTER(entries.buf);
1178
0
}
1179
1180
Datum
1181
gin_extract_jsonb_query_path(PG_FUNCTION_ARGS)
1182
0
{
1183
0
  int32    *nentries = (int32 *) PG_GETARG_POINTER(1);
1184
0
  StrategyNumber strategy = PG_GETARG_UINT16(2);
1185
0
  int32    *searchMode = (int32 *) PG_GETARG_POINTER(6);
1186
0
  Datum    *entries;
1187
1188
0
  if (strategy == JsonbContainsStrategyNumber)
1189
0
  {
1190
    /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */
1191
0
    entries = (Datum *)
1192
0
      DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
1193
0
                        PG_GETARG_DATUM(0),
1194
0
                        PointerGetDatum(nentries)));
1195
1196
    /* ... although "contains {}" requires a full index scan */
1197
0
    if (*nentries == 0)
1198
0
      *searchMode = GIN_SEARCH_MODE_ALL;
1199
0
  }
1200
0
  else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1201
0
       strategy == JsonbJsonpathExistsStrategyNumber)
1202
0
  {
1203
0
    JsonPath   *jp = PG_GETARG_JSONPATH_P(0);
1204
0
    Pointer   **extra_data = (Pointer **) PG_GETARG_POINTER(4);
1205
1206
0
    entries = extract_jsp_query(jp, strategy, true, nentries, extra_data);
1207
1208
0
    if (!entries)
1209
0
      *searchMode = GIN_SEARCH_MODE_ALL;
1210
0
  }
1211
0
  else
1212
0
  {
1213
0
    elog(ERROR, "unrecognized strategy number: %d", strategy);
1214
0
    entries = NULL;
1215
0
  }
1216
1217
0
  PG_RETURN_POINTER(entries);
1218
0
}
1219
1220
Datum
1221
gin_consistent_jsonb_path(PG_FUNCTION_ARGS)
1222
0
{
1223
0
  bool     *check = (bool *) PG_GETARG_POINTER(0);
1224
0
  StrategyNumber strategy = PG_GETARG_UINT16(1);
1225
#ifdef NOT_USED
1226
  Jsonb    *query = PG_GETARG_JSONB_P(2);
1227
#endif
1228
0
  int32   nkeys = PG_GETARG_INT32(3);
1229
0
  Pointer    *extra_data = (Pointer *) PG_GETARG_POINTER(4);
1230
0
  bool     *recheck = (bool *) PG_GETARG_POINTER(5);
1231
0
  bool    res = true;
1232
0
  int32   i;
1233
1234
0
  if (strategy == JsonbContainsStrategyNumber)
1235
0
  {
1236
    /*
1237
     * jsonb_path_ops is necessarily lossy, not only because of hash
1238
     * collisions but also because it doesn't preserve complete
1239
     * information about the structure of the JSON object.  Besides, there
1240
     * are some special rules around the containment of raw scalars in
1241
     * arrays that are not handled here.  So we must always recheck a
1242
     * match.  However, if not all of the keys are present, the tuple
1243
     * certainly doesn't match.
1244
     */
1245
0
    *recheck = true;
1246
0
    for (i = 0; i < nkeys; i++)
1247
0
    {
1248
0
      if (!check[i])
1249
0
      {
1250
0
        res = false;
1251
0
        break;
1252
0
      }
1253
0
    }
1254
0
  }
1255
0
  else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1256
0
       strategy == JsonbJsonpathExistsStrategyNumber)
1257
0
  {
1258
0
    *recheck = true;
1259
1260
0
    if (nkeys > 0)
1261
0
    {
1262
0
      Assert(extra_data && extra_data[0]);
1263
0
      res = execute_jsp_gin_node(extra_data[0], check, false) != GIN_FALSE;
1264
0
    }
1265
0
  }
1266
0
  else
1267
0
    elog(ERROR, "unrecognized strategy number: %d", strategy);
1268
1269
0
  PG_RETURN_BOOL(res);
1270
0
}
1271
1272
Datum
1273
gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS)
1274
0
{
1275
0
  GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0);
1276
0
  StrategyNumber strategy = PG_GETARG_UINT16(1);
1277
#ifdef NOT_USED
1278
  Jsonb    *query = PG_GETARG_JSONB_P(2);
1279
#endif
1280
0
  int32   nkeys = PG_GETARG_INT32(3);
1281
0
  Pointer    *extra_data = (Pointer *) PG_GETARG_POINTER(4);
1282
0
  GinTernaryValue res = GIN_MAYBE;
1283
0
  int32   i;
1284
1285
0
  if (strategy == JsonbContainsStrategyNumber)
1286
0
  {
1287
    /*
1288
     * Note that we never return GIN_TRUE, only GIN_MAYBE or GIN_FALSE;
1289
     * this corresponds to always forcing recheck in the regular
1290
     * consistent function, for the reasons listed there.
1291
     */
1292
0
    for (i = 0; i < nkeys; i++)
1293
0
    {
1294
0
      if (check[i] == GIN_FALSE)
1295
0
      {
1296
0
        res = GIN_FALSE;
1297
0
        break;
1298
0
      }
1299
0
    }
1300
0
  }
1301
0
  else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
1302
0
       strategy == JsonbJsonpathExistsStrategyNumber)
1303
0
  {
1304
0
    if (nkeys > 0)
1305
0
    {
1306
0
      Assert(extra_data && extra_data[0]);
1307
0
      res = execute_jsp_gin_node(extra_data[0], check, true);
1308
1309
      /* Should always recheck the result */
1310
0
      if (res == GIN_TRUE)
1311
0
        res = GIN_MAYBE;
1312
0
    }
1313
0
  }
1314
0
  else
1315
0
    elog(ERROR, "unrecognized strategy number: %d", strategy);
1316
1317
0
  PG_RETURN_GIN_TERNARY_VALUE(res);
1318
0
}
1319
1320
/*
1321
 * Construct a jsonb_ops GIN key from a flag byte and a textual representation
1322
 * (which need not be null-terminated).  This function is responsible
1323
 * for hashing overlength text representations; it will add the
1324
 * JGINFLAG_HASHED bit to the flag value if it does that.
1325
 */
1326
static Datum
1327
make_text_key(char flag, const char *str, int len)
1328
0
{
1329
0
  text     *item;
1330
0
  char    hashbuf[10];
1331
1332
0
  if (len > JGIN_MAXLENGTH)
1333
0
  {
1334
0
    uint32    hashval;
1335
1336
0
    hashval = DatumGetUInt32(hash_any((const unsigned char *) str, len));
1337
0
    snprintf(hashbuf, sizeof(hashbuf), "%08x", hashval);
1338
0
    str = hashbuf;
1339
0
    len = 8;
1340
0
    flag |= JGINFLAG_HASHED;
1341
0
  }
1342
1343
  /*
1344
   * Now build the text Datum.  For simplicity we build a 4-byte-header
1345
   * varlena text Datum here, but we expect it will get converted to short
1346
   * header format when stored in the index.
1347
   */
1348
0
  item = (text *) palloc(VARHDRSZ + len + 1);
1349
0
  SET_VARSIZE(item, VARHDRSZ + len + 1);
1350
1351
0
  *VARDATA(item) = flag;
1352
1353
0
  memcpy(VARDATA(item) + 1, str, len);
1354
1355
0
  return PointerGetDatum(item);
1356
0
}
1357
1358
/*
1359
 * Create a textual representation of a JsonbValue that will serve as a GIN
1360
 * key in a jsonb_ops index.  is_key is true if the JsonbValue is a key,
1361
 * or if it is a string array element (since we pretend those are keys,
1362
 * see jsonb.h).
1363
 */
1364
static Datum
1365
make_scalar_key(const JsonbValue *scalarVal, bool is_key)
1366
0
{
1367
0
  Datum   item;
1368
0
  char     *cstr;
1369
1370
0
  switch (scalarVal->type)
1371
0
  {
1372
0
    case jbvNull:
1373
0
      Assert(!is_key);
1374
0
      item = make_text_key(JGINFLAG_NULL, "", 0);
1375
0
      break;
1376
0
    case jbvBool:
1377
0
      Assert(!is_key);
1378
0
      item = make_text_key(JGINFLAG_BOOL,
1379
0
                 scalarVal->val.boolean ? "t" : "f", 1);
1380
0
      break;
1381
0
    case jbvNumeric:
1382
0
      Assert(!is_key);
1383
1384
      /*
1385
       * A normalized textual representation, free of trailing zeroes,
1386
       * is required so that numerically equal values will produce equal
1387
       * strings.
1388
       *
1389
       * It isn't ideal that numerics are stored in a relatively bulky
1390
       * textual format.  However, it's a notationally convenient way of
1391
       * storing a "union" type in the GIN B-Tree, and indexing Jsonb
1392
       * strings takes precedence.
1393
       */
1394
0
      cstr = numeric_normalize(scalarVal->val.numeric);
1395
0
      item = make_text_key(JGINFLAG_NUM, cstr, strlen(cstr));
1396
0
      pfree(cstr);
1397
0
      break;
1398
0
    case jbvString:
1399
0
      item = make_text_key(is_key ? JGINFLAG_KEY : JGINFLAG_STR,
1400
0
                 scalarVal->val.string.val,
1401
0
                 scalarVal->val.string.len);
1402
0
      break;
1403
0
    default:
1404
0
      elog(ERROR, "unrecognized jsonb scalar type: %d", scalarVal->type);
1405
0
      item = 0;     /* keep compiler quiet */
1406
0
      break;
1407
0
  }
1408
1409
0
  return item;
1410
0
}