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

Generated by: LCOV version 1.14