LCOV - code coverage report
Current view: top level - src/backend/utils/adt - jsonb_gin.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 81.5 % 507 413
Test Date: 2026-02-17 17:20:33 Functions: 92.9 % 28 26
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-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        11608 : init_gin_entries(GinEntries *entries, int preallocated)
     164              : {
     165        11608 :     entries->allocated = preallocated;
     166        11608 :     entries->buf = preallocated ? palloc_array(Datum, preallocated) : NULL;
     167        11608 :     entries->count = 0;
     168        11608 : }
     169              : 
     170              : /* Add new entry to GinEntries */
     171              : static int
     172        75327 : add_gin_entry(GinEntries *entries, Datum entry)
     173              : {
     174        75327 :     int         id = entries->count;
     175              : 
     176        75327 :     if (entries->count >= entries->allocated)
     177              :     {
     178         5642 :         if (entries->allocated)
     179              :         {
     180         5393 :             entries->allocated *= 2;
     181         5393 :             entries->buf = repalloc_array(entries->buf,
     182              :                                           Datum,
     183              :                                           entries->allocated);
     184              :         }
     185              :         else
     186              :         {
     187          249 :             entries->allocated = 8;
     188          249 :             entries->buf = palloc_array(Datum, entries->allocated);
     189              :         }
     190              :     }
     191              : 
     192        75327 :     entries->buf[entries->count++] = entry;
     193              : 
     194        75327 :     return id;
     195              : }
     196              : 
     197              : /*
     198              :  *
     199              :  * jsonb_ops GIN opclass support functions
     200              :  *
     201              :  */
     202              : 
     203              : Datum
     204       518311 : gin_compare_jsonb(PG_FUNCTION_ARGS)
     205              : {
     206       518311 :     text       *arg1 = PG_GETARG_TEXT_PP(0);
     207       518311 :     text       *arg2 = PG_GETARG_TEXT_PP(1);
     208              :     int32       result;
     209              :     char       *a1p,
     210              :                *a2p;
     211              :     int         len1,
     212              :                 len2;
     213              : 
     214       518311 :     a1p = VARDATA_ANY(arg1);
     215       518311 :     a2p = VARDATA_ANY(arg2);
     216              : 
     217       518311 :     len1 = VARSIZE_ANY_EXHDR(arg1);
     218       518311 :     len2 = VARSIZE_ANY_EXHDR(arg2);
     219              : 
     220              :     /* Compare text as bttextcmp does, but always using C collation */
     221       518311 :     result = varstr_cmp(a1p, len1, a2p, len2, C_COLLATION_OID);
     222              : 
     223       518311 :     PG_FREE_IF_COPY(arg1, 0);
     224       518311 :     PG_FREE_IF_COPY(arg2, 1);
     225              : 
     226       518311 :     PG_RETURN_INT32(result);
     227              : }
     228              : 
     229              : Datum
     230         9217 : gin_extract_jsonb(PG_FUNCTION_ARGS)
     231              : {
     232         9217 :     Jsonb      *jb = (Jsonb *) PG_GETARG_JSONB_P(0);
     233         9217 :     int32      *nentries = (int32 *) PG_GETARG_POINTER(1);
     234         9217 :     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         9217 :     if (total == 0)
     242              :     {
     243          360 :         *nentries = 0;
     244          360 :         PG_RETURN_POINTER(NULL);
     245              :     }
     246              : 
     247              :     /* Otherwise, use 2 * root count as initial estimate of result size */
     248         8857 :     init_gin_entries(&entries, 2 * total);
     249              : 
     250         8857 :     it = JsonbIteratorInit(&jb->root);
     251              : 
     252       111335 :     while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
     253              :     {
     254       102478 :         switch (r)
     255              :         {
     256        23511 :             case WJB_KEY:
     257        23511 :                 add_gin_entry(&entries, make_scalar_key(&v, true));
     258        23511 :                 break;
     259        15930 :             case WJB_ELEM:
     260              :                 /* Pretend string array elements are keys, see jsonb.h */
     261        15930 :                 add_gin_entry(&entries, make_scalar_key(&v, v.type == jbvString));
     262        15930 :                 break;
     263        20901 :             case WJB_VALUE:
     264        20901 :                 add_gin_entry(&entries, make_scalar_key(&v, false));
     265        20901 :                 break;
     266        42136 :             default:
     267              :                 /* we can ignore structural items */
     268        42136 :                 break;
     269              :         }
     270              :     }
     271              : 
     272         8857 :     *nentries = entries.count;
     273              : 
     274         8857 :     PG_RETURN_POINTER(entries.buf);
     275              : }
     276              : 
     277              : /* Append JsonPathGinPathItem to JsonPathGinPath (jsonb_ops) */
     278              : static bool
     279          426 : jsonb_ops__add_path_item(JsonPathGinPath *path, JsonPathItem *jsp)
     280              : {
     281              :     JsonPathGinPathItem *pentry;
     282              :     Datum       keyName;
     283              : 
     284          426 :     switch (jsp->type)
     285              :     {
     286          192 :         case jpiRoot:
     287          192 :             path->items = NULL; /* reset path */
     288          192 :             return true;
     289              : 
     290          186 :         case jpiKey:
     291              :             {
     292              :                 int         len;
     293          186 :                 char       *key = jspGetString(jsp, &len);
     294              : 
     295          186 :                 keyName = make_text_key(JGINFLAG_KEY, key, len);
     296          186 :                 break;
     297              :             }
     298              : 
     299           48 :         case jpiAny:
     300              :         case jpiAnyKey:
     301              :         case jpiAnyArray:
     302              :         case jpiIndexArray:
     303           48 :             keyName = PointerGetDatum(NULL);
     304           48 :             break;
     305              : 
     306            0 :         default:
     307              :             /* other path items like item methods are not supported */
     308            0 :             return false;
     309              :     }
     310              : 
     311          234 :     pentry = palloc_object(JsonPathGinPathItem);
     312              : 
     313          234 :     pentry->type = jsp->type;
     314          234 :     pentry->keyName = keyName;
     315          234 :     pentry->parent = path->items;
     316              : 
     317          234 :     path->items = pentry;
     318              : 
     319          234 :     return true;
     320              : }
     321              : 
     322              : /* Combine existing path hash with next key hash (jsonb_path_ops) */
     323              : static bool
     324          348 : jsonb_path_ops__add_path_item(JsonPathGinPath *path, JsonPathItem *jsp)
     325              : {
     326          348 :     switch (jsp->type)
     327              :     {
     328          153 :         case jpiRoot:
     329          153 :             path->hash = 0;      /* reset path hash */
     330          153 :             return true;
     331              : 
     332          147 :         case jpiKey:
     333              :             {
     334              :                 JsonbValue  jbv;
     335              : 
     336          147 :                 jbv.type = jbvString;
     337          147 :                 jbv.val.string.val = jspGetString(jsp, &jbv.val.string.len);
     338              : 
     339          147 :                 JsonbHashScalarValue(&jbv, &path->hash);
     340          147 :                 return true;
     341              :             }
     342              : 
     343           48 :         case jpiIndexArray:
     344              :         case jpiAnyArray:
     345           48 :             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          483 : make_jsp_entry_node(Datum entry)
     355              : {
     356          483 :     JsonPathGinNode *node = palloc(offsetof(JsonPathGinNode, args));
     357              : 
     358          483 :     node->type = JSP_GIN_ENTRY;
     359          483 :     node->val.entryDatum = entry;
     360              : 
     361          483 :     return node;
     362              : }
     363              : 
     364              : static JsonPathGinNode *
     365          210 : make_jsp_entry_node_scalar(JsonbValue *scalar, bool iskey)
     366              : {
     367          210 :     return make_jsp_entry_node(make_scalar_key(scalar, iskey));
     368              : }
     369              : 
     370              : static JsonPathGinNode *
     371          234 : make_jsp_expr_node(JsonPathGinNodeType type, int nargs)
     372              : {
     373          234 :     JsonPathGinNode *node = palloc(offsetof(JsonPathGinNode, args) +
     374              :                                    sizeof(node->args[0]) * nargs);
     375              : 
     376          234 :     node->type = type;
     377          234 :     node->val.nargs = nargs;
     378              : 
     379          234 :     return node;
     380              : }
     381              : 
     382              : static JsonPathGinNode *
     383          138 : make_jsp_expr_node_args(JsonPathGinNodeType type, List *args)
     384              : {
     385          138 :     JsonPathGinNode *node = make_jsp_expr_node(type, list_length(args));
     386              :     ListCell   *lc;
     387          138 :     int         i = 0;
     388              : 
     389          414 :     foreach(lc, args)
     390          276 :         node->args[i++] = lfirst(lc);
     391              : 
     392          138 :     return node;
     393              : }
     394              : 
     395              : static JsonPathGinNode *
     396           96 : make_jsp_expr_node_binary(JsonPathGinNodeType type,
     397              :                           JsonPathGinNode *arg1, JsonPathGinNode *arg2)
     398              : {
     399           96 :     JsonPathGinNode *node = make_jsp_expr_node(type, 2);
     400              : 
     401           96 :     node->args[0] = arg1;
     402           96 :     node->args[1] = arg2;
     403              : 
     404           96 :     return node;
     405              : }
     406              : 
     407              : /* Append a list of nodes from the jsonpath (jsonb_ops). */
     408              : static List *
     409          279 : jsonb_ops__extract_nodes(JsonPathGinContext *cxt, JsonPathGinPath path,
     410              :                          JsonbValue *scalar, List *nodes)
     411              : {
     412              :     JsonPathGinPathItem *pentry;
     413              : 
     414          279 :     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          324 :         for (pentry = path.items; pentry; pentry = pentry->parent)
     423              :         {
     424          186 :             if (pentry->type == jpiKey) /* only keys are indexed */
     425          138 :                 nodes = lappend(nodes, make_jsp_entry_node(pentry->keyName));
     426              :         }
     427              : 
     428              :         /* Append scalar node for equality queries. */
     429          138 :         if (scalar->type == jbvString)
     430              :         {
     431           72 :             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           72 :             if (cxt->lax)
     443           72 :                 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           72 :             if (key_entry == GIN_MAYBE)
     454              :             {
     455           72 :                 JsonPathGinNode *n1 = make_jsp_entry_node_scalar(scalar, true);
     456           72 :                 JsonPathGinNode *n2 = make_jsp_entry_node_scalar(scalar, false);
     457              : 
     458           72 :                 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           66 :             node = make_jsp_entry_node_scalar(scalar, false);
     469              :         }
     470              : 
     471          138 :         nodes = lappend(nodes, node);
     472              :     }
     473              : 
     474          279 :     return nodes;
     475              : }
     476              : 
     477              : /* Append a list of nodes from the jsonpath (jsonb_path_ops). */
     478              : static List *
     479          240 : jsonb_path_ops__extract_nodes(JsonPathGinContext *cxt, JsonPathGinPath path,
     480              :                               JsonbValue *scalar, List *nodes)
     481              : {
     482          240 :     if (scalar)
     483              :     {
     484              :         /* append path hash node for equality queries */
     485          135 :         uint32      hash = path.hash;
     486              : 
     487          135 :         JsonbHashScalarValue(scalar, &hash);
     488              : 
     489          135 :         return lappend(nodes,
     490          135 :                        make_jsp_entry_node(UInt32GetDatum(hash)));
     491              :     }
     492              :     else
     493              :     {
     494              :         /* jsonb_path_ops doesn't support EXISTS queries => nothing to append */
     495          105 :         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          519 : extract_jsp_path_expr_nodes(JsonPathGinContext *cxt, JsonPathGinPath path,
     506              :                             JsonPathItem *jsp, JsonbValue *scalar)
     507              : {
     508              :     JsonPathItem next;
     509          519 :     List       *nodes = NIL;
     510              : 
     511              :     for (;;)
     512              :     {
     513         1110 :         switch (jsp->type)
     514              :         {
     515          174 :             case jpiCurrent:
     516          174 :                 break;
     517              : 
     518          162 :             case jpiFilter:
     519              :                 {
     520              :                     JsonPathItem arg;
     521              :                     JsonPathGinNode *filter;
     522              : 
     523          162 :                     jspGetArg(jsp, &arg);
     524              : 
     525          162 :                     filter = extract_jsp_bool_expr(cxt, path, &arg, false);
     526              : 
     527          162 :                     if (filter)
     528          162 :                         nodes = lappend(nodes, filter);
     529              : 
     530          162 :                     break;
     531              :                 }
     532              : 
     533          774 :             default:
     534          774 :                 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          774 :                 break;
     542              :         }
     543              : 
     544         1110 :         if (!jspGetNext(jsp, &next))
     545          519 :             break;
     546              : 
     547          591 :         jsp = &next;
     548              :     }
     549              : 
     550              :     /*
     551              :      * Append nodes from the path expression itself to the already extracted
     552              :      * list of filter nodes.
     553              :      */
     554          519 :     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          519 : 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          519 :     List       *nodes = extract_jsp_path_expr_nodes(cxt, path, jsp, scalar);
     570              : 
     571          519 :     if (nodes == NIL)
     572              :         /* no nodes were extracted => full scan is needed for this path */
     573           84 :         return NULL;
     574              : 
     575          435 :     if (list_length(nodes) == 1)
     576          297 :         return linitial(nodes); /* avoid extra AND-node */
     577              : 
     578              :     /* construct AND-node for path with filters */
     579          138 :     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          417 : extract_jsp_bool_expr(JsonPathGinContext *cxt, JsonPathGinPath path,
     585              :                       JsonPathItem *jsp, bool not)
     586              : {
     587          417 :     check_stack_depth();
     588              : 
     589          417 :     switch (jsp->type)
     590              :     {
     591           36 :         case jpiAnd:            /* expr && expr */
     592              :         case jpiOr:             /* expr || expr */
     593              :             {
     594              :                 JsonPathItem arg;
     595              :                 JsonPathGinNode *larg;
     596              :                 JsonPathGinNode *rarg;
     597              :                 JsonPathGinNodeType type;
     598              : 
     599           36 :                 jspGetLeftArg(jsp, &arg);
     600           36 :                 larg = extract_jsp_bool_expr(cxt, path, &arg, not);
     601              : 
     602           36 :                 jspGetRightArg(jsp, &arg);
     603           36 :                 rarg = extract_jsp_bool_expr(cxt, path, &arg, not);
     604              : 
     605           36 :                 if (!larg || !rarg)
     606              :                 {
     607           12 :                     if (jsp->type == jpiOr)
     608            6 :                         return NULL;
     609              : 
     610            6 :                     return larg ? larg : rarg;
     611              :                 }
     612              : 
     613           24 :                 type = not ^ (jsp->type == jpiAnd) ? JSP_GIN_AND : JSP_GIN_OR;
     614              : 
     615           24 :                 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          108 :         case jpiExists:         /* EXISTS(path) */
     629              :             {
     630              :                 JsonPathItem arg;
     631              : 
     632          108 :                 if (not)
     633            0 :                     return NULL;    /* NOT EXISTS is not supported */
     634              : 
     635          108 :                 jspGetArg(jsp, &arg);
     636              : 
     637          108 :                 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          273 :         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          273 :                 if (not)
     666            0 :                     return NULL;
     667              : 
     668          273 :                 jspGetLeftArg(jsp, &left_item);
     669          273 :                 jspGetRightArg(jsp, &right_item);
     670              : 
     671          273 :                 if (jspIsScalar(left_item.type))
     672              :                 {
     673           48 :                     scalar_item = &left_item;
     674           48 :                     path_item = &right_item;
     675              :                 }
     676          225 :                 else if (jspIsScalar(right_item.type))
     677              :                 {
     678          225 :                     scalar_item = &right_item;
     679          225 :                     path_item = &left_item;
     680              :                 }
     681              :                 else
     682            0 :                     return NULL;    /* at least one operand should be a scalar */
     683              : 
     684          273 :                 switch (scalar_item->type)
     685              :                 {
     686           57 :                     case jpiNull:
     687           57 :                         scalar.type = jbvNull;
     688           57 :                         break;
     689           24 :                     case jpiBool:
     690           24 :                         scalar.type = jbvBool;
     691           24 :                         scalar.val.boolean = !!*scalar_item->content.value.data;
     692           24 :                         break;
     693           48 :                     case jpiNumeric:
     694           48 :                         scalar.type = jbvNumeric;
     695           48 :                         scalar.val.numeric =
     696           48 :                             (Numeric) scalar_item->content.value.data;
     697           48 :                         break;
     698          144 :                     case jpiString:
     699          144 :                         scalar.type = jbvString;
     700          144 :                         scalar.val.string.val = scalar_item->content.value.data;
     701          144 :                         scalar.val.string.len =
     702          144 :                             scalar_item->content.value.datalen;
     703          144 :                         break;
     704            0 :                     default:
     705            0 :                         elog(ERROR, "invalid scalar jsonpath item type: %d",
     706              :                              scalar_item->type);
     707              :                         return NULL;
     708              :                 }
     709              : 
     710          273 :                 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          717 : emit_jsp_gin_entries(JsonPathGinNode *node, GinEntries *entries)
     721              : {
     722          717 :     check_stack_depth();
     723              : 
     724          717 :     switch (node->type)
     725              :     {
     726          483 :         case JSP_GIN_ENTRY:
     727              :             /* replace datum with its index in the array */
     728          483 :             node->val.entryIndex = add_gin_entry(entries, node->val.entryDatum);
     729          483 :             break;
     730              : 
     731          234 :         case JSP_GIN_OR:
     732              :         case JSP_GIN_AND:
     733              :             {
     734              :                 int         i;
     735              : 
     736          702 :                 for (i = 0; i < node->val.nargs; i++)
     737          468 :                     emit_jsp_gin_entries(node->args[i], entries);
     738              : 
     739          234 :                 break;
     740              :             }
     741              :     }
     742          717 : }
     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          321 : 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          321 :     JsonPathGinPath path = {0};
     756          321 :     GinEntries  entries = {0};
     757              : 
     758          321 :     cxt.lax = (jp->header & JSONPATH_LAX) != 0;
     759              : 
     760          321 :     if (pathOps)
     761              :     {
     762          147 :         cxt.add_path_item = jsonb_path_ops__add_path_item;
     763          147 :         cxt.extract_nodes = jsonb_path_ops__extract_nodes;
     764              :     }
     765              :     else
     766              :     {
     767          174 :         cxt.add_path_item = jsonb_ops__add_path_item;
     768          174 :         cxt.extract_nodes = jsonb_ops__extract_nodes;
     769              :     }
     770              : 
     771          321 :     jspInit(&root, jp);
     772              : 
     773          321 :     node = strat == JsonbJsonpathExistsStrategyNumber
     774          138 :         ? extract_jsp_path_expr(&cxt, path, &root, NULL)
     775          321 :         : extract_jsp_bool_expr(&cxt, path, &root, false);
     776              : 
     777          321 :     if (!node)
     778              :     {
     779           72 :         *nentries = 0;
     780           72 :         return NULL;
     781              :     }
     782              : 
     783          249 :     emit_jsp_gin_entries(node, &entries);
     784              : 
     785          249 :     *nentries = entries.count;
     786          249 :     if (!*nentries)
     787            0 :         return NULL;
     788              : 
     789          249 :     *extra_data = palloc0_array(Pointer, entries.count);
     790          249 :     **extra_data = (Pointer) node;
     791              : 
     792          249 :     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         6306 : execute_jsp_gin_node(JsonPathGinNode *node, void *check, bool ternary)
     801              : {
     802              :     GinTernaryValue res;
     803              :     GinTernaryValue v;
     804              :     int         i;
     805              : 
     806         6306 :     switch (node->type)
     807              :     {
     808         2544 :         case JSP_GIN_AND:
     809         2544 :             res = GIN_TRUE;
     810         4032 :             for (i = 0; i < node->val.nargs; i++)
     811              :             {
     812         3444 :                 v = execute_jsp_gin_node(node->args[i], check, ternary);
     813         3444 :                 if (v == GIN_FALSE)
     814         1956 :                     return GIN_FALSE;
     815         1488 :                 else if (v == GIN_MAYBE)
     816          120 :                     res = GIN_MAYBE;
     817              :             }
     818          588 :             return res;
     819              : 
     820          528 :         case JSP_GIN_OR:
     821          528 :             res = GIN_FALSE;
     822         1080 :             for (i = 0; i < node->val.nargs; i++)
     823              :             {
     824          984 :                 v = execute_jsp_gin_node(node->args[i], check, ternary);
     825          984 :                 if (v == GIN_TRUE)
     826          432 :                     return GIN_TRUE;
     827          552 :                 else if (v == GIN_MAYBE)
     828           36 :                     res = GIN_MAYBE;
     829              :             }
     830           96 :             return res;
     831              : 
     832         3234 :         case JSP_GIN_ENTRY:
     833              :             {
     834         3234 :                 int         index = node->val.entryIndex;
     835              : 
     836         3234 :                 if (ternary)
     837         3234 :                     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          264 : gin_extract_jsonb_query(PG_FUNCTION_ARGS)
     850              : {
     851          264 :     int32      *nentries = (int32 *) PG_GETARG_POINTER(1);
     852          264 :     StrategyNumber strategy = PG_GETARG_UINT16(2);
     853          264 :     int32      *searchMode = (int32 *) PG_GETARG_POINTER(6);
     854              :     Datum      *entries;
     855              : 
     856          264 :     if (strategy == JsonbContainsStrategyNumber)
     857              :     {
     858              :         /* Query is a jsonb, so just apply gin_extract_jsonb... */
     859              :         entries = (Datum *)
     860           54 :             DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb,
     861              :                                                 PG_GETARG_DATUM(0),
     862              :                                                 PointerGetDatum(nentries)));
     863              :         /* ...although "contains {}" requires a full index scan */
     864           54 :         if (*nentries == 0)
     865            6 :             *searchMode = GIN_SEARCH_MODE_ALL;
     866              :     }
     867          210 :     else if (strategy == JsonbExistsStrategyNumber)
     868              :     {
     869              :         /* Query is a text string, which we treat as a key */
     870           24 :         text       *query = PG_GETARG_TEXT_PP(0);
     871              : 
     872           24 :         *nentries = 1;
     873           24 :         entries = palloc_object(Datum);
     874           24 :         entries[0] = make_text_key(JGINFLAG_KEY,
     875           24 :                                    VARDATA_ANY(query),
     876           24 :                                    VARSIZE_ANY_EXHDR(query));
     877              :     }
     878          186 :     else if (strategy == JsonbExistsAnyStrategyNumber ||
     879              :              strategy == JsonbExistsAllStrategyNumber)
     880           12 :     {
     881              :         /* Query is a text array; each element is treated as a key */
     882           12 :         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           12 :         deconstruct_array_builtin(query, TEXTOID, &key_datums, &key_nulls, &key_count);
     890              : 
     891           12 :         entries = palloc_array(Datum, key_count);
     892              : 
     893           36 :         for (i = 0, j = 0; i < key_count; i++)
     894              :         {
     895              :             /* Nulls in the array are ignored */
     896           24 :             if (key_nulls[i])
     897            0 :                 continue;
     898              :             /* We rely on the array elements not being toasted */
     899           24 :             entries[j++] = make_text_key(JGINFLAG_KEY,
     900           24 :                                          VARDATA_ANY(DatumGetPointer(key_datums[i])),
     901           24 :                                          VARSIZE_ANY_EXHDR(DatumGetPointer(key_datums[i])));
     902              :         }
     903              : 
     904           12 :         *nentries = j;
     905              :         /* ExistsAll with no keys should match everything */
     906           12 :         if (j == 0 && strategy == JsonbExistsAllStrategyNumber)
     907            0 :             *searchMode = GIN_SEARCH_MODE_ALL;
     908              :     }
     909          174 :     else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
     910              :              strategy == JsonbJsonpathExistsStrategyNumber)
     911          174 :     {
     912          174 :         JsonPath   *jp = PG_GETARG_JSONPATH_P(0);
     913          174 :         Pointer   **extra_data = (Pointer **) PG_GETARG_POINTER(4);
     914              : 
     915          174 :         entries = extract_jsp_query(jp, strategy, false, nentries, extra_data);
     916              : 
     917          174 :         if (!entries)
     918           48 :             *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          264 :     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              : #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              :     int32       i;
     943              : 
     944            0 :     if (strategy == JsonbContainsStrategyNumber)
     945              :     {
     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              :         {
     957            0 :             if (!check[i])
     958              :             {
     959            0 :                 res = false;
     960            0 :                 break;
     961              :             }
     962              :         }
     963              :     }
     964            0 :     else if (strategy == JsonbExistsStrategyNumber)
     965              :     {
     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              :     }
     976            0 :     else if (strategy == JsonbExistsAnyStrategyNumber)
     977              :     {
     978              :         /* As for plain exists, we must recheck */
     979            0 :         *recheck = true;
     980            0 :         res = true;
     981              :     }
     982            0 :     else if (strategy == JsonbExistsAllStrategyNumber)
     983              :     {
     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              :         {
     989            0 :             if (!check[i])
     990              :             {
     991            0 :                 res = false;
     992            0 :                 break;
     993              :             }
     994              :         }
     995              :     }
     996            0 :     else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
     997              :              strategy == JsonbJsonpathExistsStrategyNumber)
     998              :     {
     999            0 :         *recheck = true;
    1000              : 
    1001            0 :         if (nkeys > 0)
    1002              :         {
    1003              :             Assert(extra_data && extra_data[0]);
    1004            0 :             res = execute_jsp_gin_node(extra_data[0], check, false) != GIN_FALSE;
    1005              :         }
    1006              :     }
    1007              :     else
    1008            0 :         elog(ERROR, "unrecognized strategy number: %d", strategy);
    1009              : 
    1010            0 :     PG_RETURN_BOOL(res);
    1011              : }
    1012              : 
    1013              : Datum
    1014        31821 : gin_triconsistent_jsonb(PG_FUNCTION_ARGS)
    1015              : {
    1016        31821 :     GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0);
    1017        31821 :     StrategyNumber strategy = PG_GETARG_UINT16(1);
    1018              : #ifdef NOT_USED
    1019              :     Jsonb      *query = PG_GETARG_JSONB_P(2);
    1020              : #endif
    1021        31821 :     int32       nkeys = PG_GETARG_INT32(3);
    1022        31821 :     Pointer    *extra_data = (Pointer *) PG_GETARG_POINTER(4);
    1023        31821 :     GinTernaryValue res = GIN_MAYBE;
    1024              :     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        31821 :     if (strategy == JsonbContainsStrategyNumber ||
    1032              :         strategy == JsonbExistsAllStrategyNumber)
    1033              :     {
    1034              :         /* All extracted keys must be present */
    1035         5070 :         for (i = 0; i < nkeys; i++)
    1036              :         {
    1037         1767 :             if (check[i] == GIN_FALSE)
    1038              :             {
    1039         1026 :                 res = GIN_FALSE;
    1040         1026 :                 break;
    1041              :             }
    1042              :         }
    1043              :     }
    1044        27492 :     else if (strategy == JsonbExistsStrategyNumber ||
    1045              :              strategy == JsonbExistsAnyStrategyNumber)
    1046              :     {
    1047              :         /* At least one extracted key must be present */
    1048         1620 :         res = GIN_FALSE;
    1049         2049 :         for (i = 0; i < nkeys; i++)
    1050              :         {
    1051         2049 :             if (check[i] == GIN_TRUE ||
    1052          432 :                 check[i] == GIN_MAYBE)
    1053              :             {
    1054         1620 :                 res = GIN_MAYBE;
    1055         1620 :                 break;
    1056              :             }
    1057              :         }
    1058              :     }
    1059        25872 :     else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
    1060              :              strategy == JsonbJsonpathExistsStrategyNumber)
    1061              :     {
    1062        25872 :         if (nkeys > 0)
    1063              :         {
    1064              :             Assert(extra_data && extra_data[0]);
    1065         1584 :             res = execute_jsp_gin_node(extra_data[0], check, true);
    1066              : 
    1067              :             /* Should always recheck the result */
    1068         1584 :             if (res == GIN_TRUE)
    1069          318 :                 res = GIN_MAYBE;
    1070              :         }
    1071              :     }
    1072              :     else
    1073            0 :         elog(ERROR, "unrecognized strategy number: %d", strategy);
    1074              : 
    1075        31821 :     PG_RETURN_GIN_TERNARY_VALUE(res);
    1076              : }
    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         3111 : gin_extract_jsonb_path(PG_FUNCTION_ARGS)
    1092              : {
    1093         3111 :     Jsonb      *jb = PG_GETARG_JSONB_P(0);
    1094         3111 :     int32      *nentries = (int32 *) PG_GETARG_POINTER(1);
    1095         3111 :     int         total = JB_ROOT_COUNT(jb);
    1096              :     JsonbIterator *it;
    1097              :     JsonbValue  v;
    1098              :     JsonbIteratorToken r;
    1099              :     PathHashStack tail;
    1100              :     PathHashStack *stack;
    1101              :     GinEntries  entries;
    1102              : 
    1103              :     /* If the root level is empty, we certainly have no keys */
    1104         3111 :     if (total == 0)
    1105              :     {
    1106          360 :         *nentries = 0;
    1107          360 :         PG_RETURN_POINTER(NULL);
    1108              :     }
    1109              : 
    1110              :     /* Otherwise, use 2 * root count as initial estimate of result size */
    1111         2751 :     init_gin_entries(&entries, 2 * total);
    1112              : 
    1113              :     /* We keep a stack of partial hashes corresponding to parent key levels */
    1114         2751 :     tail.parent = NULL;
    1115         2751 :     tail.hash = 0;
    1116         2751 :     stack = &tail;
    1117              : 
    1118         2751 :     it = JsonbIteratorInit(&jb->root);
    1119              : 
    1120        37392 :     while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
    1121              :     {
    1122              :         PathHashStack *parent;
    1123              : 
    1124        34641 :         switch (r)
    1125              :         {
    1126         2839 :             case WJB_BEGIN_ARRAY:
    1127              :             case WJB_BEGIN_OBJECT:
    1128              :                 /* Push a stack level for this object */
    1129         2839 :                 parent = stack;
    1130         2839 :                 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         2839 :                 stack->hash = parent->hash;
    1142         2839 :                 stack->parent = parent;
    1143         2839 :                 break;
    1144        14461 :             case WJB_KEY:
    1145              :                 /* mix this key into the current outer hash */
    1146        14461 :                 JsonbHashScalarValue(&v, &stack->hash);
    1147              :                 /* hash is now ready to incorporate the value */
    1148        14461 :                 break;
    1149        14502 :             case WJB_ELEM:
    1150              :             case WJB_VALUE:
    1151              :                 /* mix the element or value's hash into the prepared hash */
    1152        14502 :                 JsonbHashScalarValue(&v, &stack->hash);
    1153              :                 /* and emit an index entry */
    1154        14502 :                 add_gin_entry(&entries, UInt32GetDatum(stack->hash));
    1155              :                 /* reset hash for next key, value, or sub-object */
    1156        14502 :                 stack->hash = stack->parent->hash;
    1157        14502 :                 break;
    1158         2839 :             case WJB_END_ARRAY:
    1159              :             case WJB_END_OBJECT:
    1160              :                 /* Pop the stack */
    1161         2839 :                 parent = stack->parent;
    1162         2839 :                 pfree(stack);
    1163         2839 :                 stack = parent;
    1164              :                 /* reset hash for next key, value, or sub-object */
    1165         2839 :                 if (stack->parent)
    1166           88 :                     stack->hash = stack->parent->hash;
    1167              :                 else
    1168         2751 :                     stack->hash = 0;
    1169         2839 :                 break;
    1170            0 :             default:
    1171            0 :                 elog(ERROR, "invalid JsonbIteratorNext rc: %d", (int) r);
    1172              :         }
    1173              :     }
    1174              : 
    1175         2751 :     *nentries = entries.count;
    1176              : 
    1177         2751 :     PG_RETURN_POINTER(entries.buf);
    1178              : }
    1179              : 
    1180              : Datum
    1181          210 : gin_extract_jsonb_query_path(PG_FUNCTION_ARGS)
    1182              : {
    1183          210 :     int32      *nentries = (int32 *) PG_GETARG_POINTER(1);
    1184          210 :     StrategyNumber strategy = PG_GETARG_UINT16(2);
    1185          210 :     int32      *searchMode = (int32 *) PG_GETARG_POINTER(6);
    1186              :     Datum      *entries;
    1187              : 
    1188          210 :     if (strategy == JsonbContainsStrategyNumber)
    1189              :     {
    1190              :         /* Query is a jsonb, so just apply gin_extract_jsonb_path ... */
    1191              :         entries = (Datum *)
    1192           63 :             DatumGetPointer(DirectFunctionCall2(gin_extract_jsonb_path,
    1193              :                                                 PG_GETARG_DATUM(0),
    1194              :                                                 PointerGetDatum(nentries)));
    1195              : 
    1196              :         /* ... although "contains {}" requires a full index scan */
    1197           63 :         if (*nentries == 0)
    1198            6 :             *searchMode = GIN_SEARCH_MODE_ALL;
    1199              :     }
    1200          147 :     else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
    1201              :              strategy == JsonbJsonpathExistsStrategyNumber)
    1202          147 :     {
    1203          147 :         JsonPath   *jp = PG_GETARG_JSONPATH_P(0);
    1204          147 :         Pointer   **extra_data = (Pointer **) PG_GETARG_POINTER(4);
    1205              : 
    1206          147 :         entries = extract_jsp_query(jp, strategy, true, nentries, extra_data);
    1207              : 
    1208          147 :         if (!entries)
    1209           24 :             *searchMode = GIN_SEARCH_MODE_ALL;
    1210              :     }
    1211              :     else
    1212              :     {
    1213            0 :         elog(ERROR, "unrecognized strategy number: %d", strategy);
    1214              :         entries = NULL;
    1215              :     }
    1216              : 
    1217          210 :     PG_RETURN_POINTER(entries);
    1218              : }
    1219              : 
    1220              : Datum
    1221            0 : gin_consistent_jsonb_path(PG_FUNCTION_ARGS)
    1222              : {
    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              :     int32       i;
    1233              : 
    1234            0 :     if (strategy == JsonbContainsStrategyNumber)
    1235              :     {
    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              :         {
    1248            0 :             if (!check[i])
    1249              :             {
    1250            0 :                 res = false;
    1251            0 :                 break;
    1252              :             }
    1253              :         }
    1254              :     }
    1255            0 :     else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
    1256              :              strategy == JsonbJsonpathExistsStrategyNumber)
    1257              :     {
    1258            0 :         *recheck = true;
    1259              : 
    1260            0 :         if (nkeys > 0)
    1261              :         {
    1262              :             Assert(extra_data && extra_data[0]);
    1263            0 :             res = execute_jsp_gin_node(extra_data[0], check, false) != GIN_FALSE;
    1264              :         }
    1265              :     }
    1266              :     else
    1267            0 :         elog(ERROR, "unrecognized strategy number: %d", strategy);
    1268              : 
    1269            0 :     PG_RETURN_BOOL(res);
    1270              : }
    1271              : 
    1272              : Datum
    1273        15594 : gin_triconsistent_jsonb_path(PG_FUNCTION_ARGS)
    1274              : {
    1275        15594 :     GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0);
    1276        15594 :     StrategyNumber strategy = PG_GETARG_UINT16(1);
    1277              : #ifdef NOT_USED
    1278              :     Jsonb      *query = PG_GETARG_JSONB_P(2);
    1279              : #endif
    1280        15594 :     int32       nkeys = PG_GETARG_INT32(3);
    1281        15594 :     Pointer    *extra_data = (Pointer *) PG_GETARG_POINTER(4);
    1282        15594 :     GinTernaryValue res = GIN_MAYBE;
    1283              :     int32       i;
    1284              : 
    1285        15594 :     if (strategy == JsonbContainsStrategyNumber)
    1286              :     {
    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         3279 :         for (i = 0; i < nkeys; i++)
    1293              :         {
    1294          165 :             if (check[i] == GIN_FALSE)
    1295              :             {
    1296           42 :                 res = GIN_FALSE;
    1297           42 :                 break;
    1298              :             }
    1299              :         }
    1300              :     }
    1301        12438 :     else if (strategy == JsonbJsonpathPredicateStrategyNumber ||
    1302              :              strategy == JsonbJsonpathExistsStrategyNumber)
    1303              :     {
    1304        12438 :         if (nkeys > 0)
    1305              :         {
    1306              :             Assert(extra_data && extra_data[0]);
    1307          294 :             res = execute_jsp_gin_node(extra_data[0], check, true);
    1308              : 
    1309              :             /* Should always recheck the result */
    1310          294 :             if (res == GIN_TRUE)
    1311          210 :                 res = GIN_MAYBE;
    1312              :         }
    1313              :     }
    1314              :     else
    1315            0 :         elog(ERROR, "unrecognized strategy number: %d", strategy);
    1316              : 
    1317        15594 :     PG_RETURN_GIN_TERNARY_VALUE(res);
    1318              : }
    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        60786 : make_text_key(char flag, const char *str, int len)
    1328              : {
    1329              :     text       *item;
    1330              :     char        hashbuf[10];
    1331              : 
    1332        60786 :     if (len > JGIN_MAXLENGTH)
    1333              :     {
    1334              :         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              :     }
    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        60786 :     item = (text *) palloc(VARHDRSZ + len + 1);
    1349        60786 :     SET_VARSIZE(item, VARHDRSZ + len + 1);
    1350              : 
    1351        60786 :     *VARDATA(item) = flag;
    1352              : 
    1353        60786 :     memcpy(VARDATA(item) + 1, str, len);
    1354              : 
    1355        60786 :     return PointerGetDatum(item);
    1356              : }
    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        60552 : make_scalar_key(const JsonbValue *scalarVal, bool is_key)
    1366              : {
    1367              :     Datum       item;
    1368              :     char       *cstr;
    1369              : 
    1370        60552 :     switch (scalarVal->type)
    1371              :     {
    1372         1042 :         case jbvNull:
    1373              :             Assert(!is_key);
    1374         1042 :             item = make_text_key(JGINFLAG_NULL, "", 0);
    1375         1042 :             break;
    1376         3689 :         case jbvBool:
    1377              :             Assert(!is_key);
    1378         3689 :             item = make_text_key(JGINFLAG_BOOL,
    1379         3689 :                                  scalarVal->val.boolean ? "t" : "f", 1);
    1380         3689 :             break;
    1381        21170 :         case jbvNumeric:
    1382              :             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        21170 :             cstr = numeric_normalize(scalarVal->val.numeric);
    1395        21170 :             item = make_text_key(JGINFLAG_NUM, cstr, strlen(cstr));
    1396        21170 :             pfree(cstr);
    1397        21170 :             break;
    1398        34651 :         case jbvString:
    1399        34651 :             item = make_text_key(is_key ? JGINFLAG_KEY : JGINFLAG_STR,
    1400        34651 :                                  scalarVal->val.string.val,
    1401        34651 :                                  scalarVal->val.string.len);
    1402        34651 :             break;
    1403            0 :         default:
    1404            0 :             elog(ERROR, "unrecognized jsonb scalar type: %d", scalarVal->type);
    1405              :             item = 0;           /* keep compiler quiet */
    1406              :             break;
    1407              :     }
    1408              : 
    1409        60552 :     return item;
    1410              : }
        

Generated by: LCOV version 2.0-1