LCOV - code coverage report
Current view: top level - src/backend/utils/adt - jsonb_gin.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 413 507 81.5 %
Date: 2025-12-23 14:18:26 Functions: 26 28 92.9 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.16