LCOV - code coverage report
Current view: top level - src/backend/utils/adt - jsonpath.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 91.6 % 690 632
Test Date: 2026-03-10 15:14:48 Functions: 91.7 % 24 22
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * jsonpath.c
       4              :  *   Input/output and supporting routines for jsonpath
       5              :  *
       6              :  * jsonpath expression is a chain of path items.  First path item is $, $var,
       7              :  * literal or arithmetic expression.  Subsequent path items are accessors
       8              :  * (.key, .*, [subscripts], [*]), filters (? (predicate)) and methods (.type(),
       9              :  * .size() etc).
      10              :  *
      11              :  * For instance, structure of path items for simple expression:
      12              :  *
      13              :  *      $.a[*].type()
      14              :  *
      15              :  * is pretty evident:
      16              :  *
      17              :  *      $ => .a => [*] => .type()
      18              :  *
      19              :  * Some path items such as arithmetic operations, predicates or array
      20              :  * subscripts may comprise subtrees.  For instance, more complex expression
      21              :  *
      22              :  *      ($.a + $[1 to 5, 7] ? (@ > 3).double()).type()
      23              :  *
      24              :  * have following structure of path items:
      25              :  *
      26              :  *            +  =>  .type()
      27              :  *        ___/ \___
      28              :  *       /         \
      29              :  *      $ => .a  $  =>  []  => ?  =>  .double()
      30              :  *                        _||_      |
      31              :  *                       /    \     >
      32              :  *                      to    to   / \
      33              :  *                     / \    /   @   3
      34              :  *                    1   5  7
      35              :  *
      36              :  * Binary encoding of jsonpath constitutes a sequence of 4-bytes aligned
      37              :  * variable-length path items connected by links.  Every item has a header
      38              :  * consisting of item type (enum JsonPathItemType) and offset of next item
      39              :  * (zero means no next item).  After the header, item may have payload
      40              :  * depending on item type.  For instance, payload of '.key' accessor item is
      41              :  * length of key name and key name itself.  Payload of '>' arithmetic operator
      42              :  * item is offsets of right and left operands.
      43              :  *
      44              :  * So, binary representation of sample expression above is:
      45              :  * (bottom arrows are next links, top lines are argument links)
      46              :  *
      47              :  *                                _____
      48              :  *       _____                ___/____ \                __
      49              :  *    _ /_    \         _____/__/____ \ \      __    _ /_ \
      50              :  *   / /  \    \       /    /  /     \ \ \    /  \  / /  \ \
      51              :  * +(LR)  $ .a  $  [](* to *, * to *) 1 5 7 ?(A)  >(LR)   @ 3 .double() .type()
      52              :  * |      |  ^  |  ^|                        ^|                   ^        ^
      53              :  * |      |__|  |__||________________________||___________________|        |
      54              :  * |_______________________________________________________________________|
      55              :  *
      56              :  * Copyright (c) 2019-2026, PostgreSQL Global Development Group
      57              :  *
      58              :  * IDENTIFICATION
      59              :  *  src/backend/utils/adt/jsonpath.c
      60              :  *
      61              :  *-------------------------------------------------------------------------
      62              :  */
      63              : 
      64              : #include "postgres.h"
      65              : 
      66              : #include "catalog/pg_type.h"
      67              : #include "lib/stringinfo.h"
      68              : #include "libpq/pqformat.h"
      69              : #include "miscadmin.h"
      70              : #include "nodes/miscnodes.h"
      71              : #include "nodes/nodeFuncs.h"
      72              : #include "utils/fmgrprotos.h"
      73              : #include "utils/formatting.h"
      74              : #include "utils/json.h"
      75              : #include "utils/jsonpath.h"
      76              : 
      77              : 
      78              : static Datum jsonPathFromCstring(char *in, int len, struct Node *escontext);
      79              : static char *jsonPathToCstring(StringInfo out, JsonPath *in,
      80              :                                int estimated_len);
      81              : static bool flattenJsonPathParseItem(StringInfo buf, int *result,
      82              :                                      struct Node *escontext,
      83              :                                      JsonPathParseItem *item,
      84              :                                      int nestingLevel, bool insideArraySubscript);
      85              : static void alignStringInfoInt(StringInfo buf);
      86              : static int32 reserveSpaceForItemPointer(StringInfo buf);
      87              : static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
      88              :                               bool printBracketes);
      89              : static int  operationPriority(JsonPathItemType op);
      90              : 
      91              : 
      92              : /**************************** INPUT/OUTPUT ********************************/
      93              : 
      94              : /*
      95              :  * jsonpath type input function
      96              :  */
      97              : Datum
      98         5383 : jsonpath_in(PG_FUNCTION_ARGS)
      99              : {
     100         5383 :     char       *in = PG_GETARG_CSTRING(0);
     101         5383 :     int         len = strlen(in);
     102              : 
     103         5383 :     return jsonPathFromCstring(in, len, fcinfo->context);
     104              : }
     105              : 
     106              : /*
     107              :  * jsonpath type recv function
     108              :  *
     109              :  * The type is sent as text in binary mode, so this is almost the same
     110              :  * as the input function, but it's prefixed with a version number so we
     111              :  * can change the binary format sent in future if necessary. For now,
     112              :  * only version 1 is supported.
     113              :  */
     114              : Datum
     115            0 : jsonpath_recv(PG_FUNCTION_ARGS)
     116              : {
     117            0 :     StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
     118            0 :     int         version = pq_getmsgint(buf, 1);
     119              :     char       *str;
     120              :     int         nbytes;
     121              : 
     122            0 :     if (version == JSONPATH_VERSION)
     123            0 :         str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
     124              :     else
     125            0 :         elog(ERROR, "unsupported jsonpath version number: %d", version);
     126              : 
     127            0 :     return jsonPathFromCstring(str, nbytes, NULL);
     128              : }
     129              : 
     130              : /*
     131              :  * jsonpath type output function
     132              :  */
     133              : Datum
     134          943 : jsonpath_out(PG_FUNCTION_ARGS)
     135              : {
     136          943 :     JsonPath   *in = PG_GETARG_JSONPATH_P(0);
     137              : 
     138          943 :     PG_RETURN_CSTRING(jsonPathToCstring(NULL, in, VARSIZE(in)));
     139              : }
     140              : 
     141              : /*
     142              :  * jsonpath type send function
     143              :  *
     144              :  * Just send jsonpath as a version number, then a string of text
     145              :  */
     146              : Datum
     147            0 : jsonpath_send(PG_FUNCTION_ARGS)
     148              : {
     149            0 :     JsonPath   *in = PG_GETARG_JSONPATH_P(0);
     150              :     StringInfoData buf;
     151              :     StringInfoData jtext;
     152            0 :     int         version = JSONPATH_VERSION;
     153              : 
     154            0 :     initStringInfo(&jtext);
     155            0 :     (void) jsonPathToCstring(&jtext, in, VARSIZE(in));
     156              : 
     157            0 :     pq_begintypsend(&buf);
     158            0 :     pq_sendint8(&buf, version);
     159            0 :     pq_sendtext(&buf, jtext.data, jtext.len);
     160            0 :     pfree(jtext.data);
     161              : 
     162            0 :     PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
     163              : }
     164              : 
     165              : /*
     166              :  * Converts C-string to a jsonpath value.
     167              :  *
     168              :  * Uses jsonpath parser to turn string into an AST, then
     169              :  * flattenJsonPathParseItem() does second pass turning AST into binary
     170              :  * representation of jsonpath.
     171              :  */
     172              : static Datum
     173         5383 : jsonPathFromCstring(char *in, int len, struct Node *escontext)
     174              : {
     175         5383 :     JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
     176              :     JsonPath   *res;
     177              :     StringInfoData buf;
     178              : 
     179         5200 :     if (SOFT_ERROR_OCCURRED(escontext))
     180           21 :         return (Datum) 0;
     181              : 
     182         5179 :     if (!jsonpath)
     183            3 :         ereturn(escontext, (Datum) 0,
     184              :                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     185              :                  errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
     186              :                         in)));
     187              : 
     188         5176 :     initStringInfo(&buf);
     189         5176 :     enlargeStringInfo(&buf, 4 * len /* estimation */ );
     190              : 
     191         5176 :     appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
     192              : 
     193         5176 :     if (!flattenJsonPathParseItem(&buf, NULL, escontext,
     194              :                                   jsonpath->expr, 0, false))
     195            6 :         return (Datum) 0;
     196              : 
     197         5161 :     res = (JsonPath *) buf.data;
     198         5161 :     SET_VARSIZE(res, buf.len);
     199         5161 :     res->header = JSONPATH_VERSION;
     200         5161 :     if (jsonpath->lax)
     201         4837 :         res->header |= JSONPATH_LAX;
     202              : 
     203         5161 :     PG_RETURN_JSONPATH_P(res);
     204              : }
     205              : 
     206              : /*
     207              :  * Converts jsonpath value to a C-string.
     208              :  *
     209              :  * If 'out' argument is non-null, the resulting C-string is stored inside the
     210              :  * StringBuffer.  The resulting string is always returned.
     211              :  */
     212              : static char *
     213          943 : jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
     214              : {
     215              :     StringInfoData buf;
     216              :     JsonPathItem v;
     217              : 
     218          943 :     if (!out)
     219              :     {
     220          943 :         out = &buf;
     221          943 :         initStringInfo(out);
     222              :     }
     223          943 :     enlargeStringInfo(out, estimated_len);
     224              : 
     225          943 :     if (!(in->header & JSONPATH_LAX))
     226            9 :         appendStringInfoString(out, "strict ");
     227              : 
     228          943 :     jspInit(&v, in);
     229          943 :     printJsonPathItem(out, &v, false, true);
     230              : 
     231          943 :     return out->data;
     232              : }
     233              : 
     234              : /*
     235              :  * Recursive function converting given jsonpath parse item and all its
     236              :  * children into a binary representation.
     237              :  */
     238              : static bool
     239        17599 : flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
     240              :                          JsonPathParseItem *item, int nestingLevel,
     241              :                          bool insideArraySubscript)
     242              : {
     243              :     /* position from beginning of jsonpath data */
     244        17599 :     int32       pos = buf->len - JSONPATH_HDRSZ;
     245              :     int32       chld;
     246              :     int32       next;
     247        17599 :     int         argNestingLevel = 0;
     248              : 
     249        17599 :     check_stack_depth();
     250        17599 :     CHECK_FOR_INTERRUPTS();
     251              : 
     252        17599 :     appendStringInfoChar(buf, (char) (item->type));
     253              : 
     254              :     /*
     255              :      * We align buffer to int32 because a series of int32 values often goes
     256              :      * after the header, and we want to read them directly by dereferencing
     257              :      * int32 pointer (see jspInitByBuffer()).
     258              :      */
     259        17599 :     alignStringInfoInt(buf);
     260              : 
     261              :     /*
     262              :      * Reserve space for next item pointer.  Actual value will be recorded
     263              :      * later, after next and children items processing.
     264              :      */
     265        17599 :     next = reserveSpaceForItemPointer(buf);
     266              : 
     267        17599 :     switch (item->type)
     268              :     {
     269         3001 :         case jpiString:
     270              :         case jpiVariable:
     271              :         case jpiKey:
     272         3001 :             appendBinaryStringInfo(buf, &item->value.string.len,
     273              :                                    sizeof(item->value.string.len));
     274         3001 :             appendBinaryStringInfo(buf, item->value.string.val,
     275         3001 :                                    item->value.string.len);
     276         3001 :             appendStringInfoChar(buf, '\0');
     277         3001 :             break;
     278         1269 :         case jpiNumeric:
     279         1269 :             appendBinaryStringInfo(buf, item->value.numeric,
     280         1269 :                                    VARSIZE(item->value.numeric));
     281         1269 :             break;
     282           90 :         case jpiBool:
     283           90 :             appendBinaryStringInfo(buf, &item->value.boolean,
     284              :                                    sizeof(item->value.boolean));
     285           90 :             break;
     286         1830 :         case jpiAnd:
     287              :         case jpiOr:
     288              :         case jpiEqual:
     289              :         case jpiNotEqual:
     290              :         case jpiLess:
     291              :         case jpiGreater:
     292              :         case jpiLessOrEqual:
     293              :         case jpiGreaterOrEqual:
     294              :         case jpiAdd:
     295              :         case jpiSub:
     296              :         case jpiMul:
     297              :         case jpiDiv:
     298              :         case jpiMod:
     299              :         case jpiStartsWith:
     300              :         case jpiDecimal:
     301              :             {
     302              :                 /*
     303              :                  * First, reserve place for left/right arg's positions, then
     304              :                  * record both args and sets actual position in reserved
     305              :                  * places.
     306              :                  */
     307         1830 :                 int32       left = reserveSpaceForItemPointer(buf);
     308         1830 :                 int32       right = reserveSpaceForItemPointer(buf);
     309              : 
     310         1830 :                 if (!item->value.args.left)
     311           84 :                     chld = pos;
     312         1746 :                 else if (!flattenJsonPathParseItem(buf, &chld, escontext,
     313              :                                                    item->value.args.left,
     314              :                                                    nestingLevel + argNestingLevel,
     315              :                                                    insideArraySubscript))
     316            6 :                     return false;
     317         1818 :                 *(int32 *) (buf->data + left) = chld - pos;
     318              : 
     319         1818 :                 if (!item->value.args.right)
     320           84 :                     chld = pos;
     321         1734 :                 else if (!flattenJsonPathParseItem(buf, &chld, escontext,
     322              :                                                    item->value.args.right,
     323              :                                                    nestingLevel + argNestingLevel,
     324              :                                                    insideArraySubscript))
     325            0 :                     return false;
     326         1818 :                 *(int32 *) (buf->data + right) = chld - pos;
     327              :             }
     328         1818 :             break;
     329           60 :         case jpiLikeRegex:
     330              :             {
     331              :                 int32       offs;
     332              : 
     333           60 :                 appendBinaryStringInfo(buf,
     334           60 :                                        &item->value.like_regex.flags,
     335              :                                        sizeof(item->value.like_regex.flags));
     336           60 :                 offs = reserveSpaceForItemPointer(buf);
     337           60 :                 appendBinaryStringInfo(buf,
     338           60 :                                        &item->value.like_regex.patternlen,
     339              :                                        sizeof(item->value.like_regex.patternlen));
     340           60 :                 appendBinaryStringInfo(buf, item->value.like_regex.pattern,
     341           60 :                                        item->value.like_regex.patternlen);
     342           60 :                 appendStringInfoChar(buf, '\0');
     343              : 
     344           60 :                 if (!flattenJsonPathParseItem(buf, &chld, escontext,
     345              :                                               item->value.like_regex.expr,
     346              :                                               nestingLevel,
     347              :                                               insideArraySubscript))
     348            0 :                     return false;
     349           60 :                 *(int32 *) (buf->data + offs) = chld - pos;
     350              :             }
     351           60 :             break;
     352         1140 :         case jpiFilter:
     353         1140 :             argNestingLevel++;
     354              :             pg_fallthrough;
     355         2565 :         case jpiIsUnknown:
     356              :         case jpiNot:
     357              :         case jpiPlus:
     358              :         case jpiMinus:
     359              :         case jpiExists:
     360              :         case jpiDatetime:
     361              :         case jpiTime:
     362              :         case jpiTimeTz:
     363              :         case jpiTimestamp:
     364              :         case jpiTimestampTz:
     365              :             {
     366         2565 :                 int32       arg = reserveSpaceForItemPointer(buf);
     367              : 
     368         2565 :                 if (!item->value.arg)
     369          768 :                     chld = pos;
     370         1797 :                 else if (!flattenJsonPathParseItem(buf, &chld, escontext,
     371              :                                                    item->value.arg,
     372              :                                                    nestingLevel + argNestingLevel,
     373              :                                                    insideArraySubscript))
     374            0 :                     return false;
     375         2562 :                 *(int32 *) (buf->data + arg) = chld - pos;
     376              :             }
     377         2562 :             break;
     378           57 :         case jpiNull:
     379           57 :             break;
     380         4999 :         case jpiRoot:
     381         4999 :             break;
     382         1091 :         case jpiAnyArray:
     383              :         case jpiAnyKey:
     384         1091 :             break;
     385         1284 :         case jpiCurrent:
     386         1284 :             if (nestingLevel <= 0)
     387            9 :                 ereturn(escontext, false,
     388              :                         (errcode(ERRCODE_SYNTAX_ERROR),
     389              :                          errmsg("@ is not allowed in root expressions")));
     390         1275 :             break;
     391           45 :         case jpiLast:
     392           45 :             if (!insideArraySubscript)
     393            6 :                 ereturn(escontext, false,
     394              :                         (errcode(ERRCODE_SYNTAX_ERROR),
     395              :                          errmsg("LAST is allowed only in array subscripts")));
     396           39 :             break;
     397          255 :         case jpiIndexArray:
     398              :             {
     399          255 :                 int32       nelems = item->value.array.nelems;
     400              :                 int         offset;
     401              :                 int         i;
     402              : 
     403          255 :                 appendBinaryStringInfo(buf, &nelems, sizeof(nelems));
     404              : 
     405          255 :                 offset = buf->len;
     406              : 
     407          255 :                 appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems);
     408              : 
     409          534 :                 for (i = 0; i < nelems; i++)
     410              :                 {
     411              :                     int32      *ppos;
     412              :                     int32       topos;
     413              :                     int32       frompos;
     414              : 
     415          279 :                     if (!flattenJsonPathParseItem(buf, &frompos, escontext,
     416          279 :                                                   item->value.array.elems[i].from,
     417              :                                                   nestingLevel, true))
     418            0 :                         return false;
     419          279 :                     frompos -= pos;
     420              : 
     421          279 :                     if (item->value.array.elems[i].to)
     422              :                     {
     423           24 :                         if (!flattenJsonPathParseItem(buf, &topos, escontext,
     424           24 :                                                       item->value.array.elems[i].to,
     425              :                                                       nestingLevel, true))
     426            0 :                             return false;
     427           24 :                         topos -= pos;
     428              :                     }
     429              :                     else
     430          255 :                         topos = 0;
     431              : 
     432          279 :                     ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
     433              : 
     434          279 :                     ppos[0] = frompos;
     435          279 :                     ppos[1] = topos;
     436              :                 }
     437              :             }
     438          255 :             break;
     439          177 :         case jpiAny:
     440          177 :             appendBinaryStringInfo(buf,
     441          177 :                                    &item->value.anybounds.first,
     442              :                                    sizeof(item->value.anybounds.first));
     443          177 :             appendBinaryStringInfo(buf,
     444          177 :                                    &item->value.anybounds.last,
     445              :                                    sizeof(item->value.anybounds.last));
     446          177 :             break;
     447          876 :         case jpiType:
     448              :         case jpiSize:
     449              :         case jpiAbs:
     450              :         case jpiFloor:
     451              :         case jpiCeiling:
     452              :         case jpiDouble:
     453              :         case jpiKeyValue:
     454              :         case jpiBigint:
     455              :         case jpiBoolean:
     456              :         case jpiDate:
     457              :         case jpiInteger:
     458              :         case jpiNumber:
     459              :         case jpiStringFunc:
     460          876 :             break;
     461            0 :         default:
     462            0 :             elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
     463              :     }
     464              : 
     465        17569 :     if (item->next)
     466              :     {
     467         6783 :         if (!flattenJsonPathParseItem(buf, &chld, escontext,
     468              :                                       item->next, nestingLevel,
     469              :                                       insideArraySubscript))
     470            0 :             return false;
     471         6780 :         chld -= pos;
     472         6780 :         *(int32 *) (buf->data + next) = chld;
     473              :     }
     474              : 
     475        17566 :     if (result)
     476        12405 :         *result = pos;
     477        17566 :     return true;
     478              : }
     479              : 
     480              : /*
     481              :  * Align StringInfo to int by adding zero padding bytes
     482              :  */
     483              : static void
     484        17599 : alignStringInfoInt(StringInfo buf)
     485              : {
     486        17599 :     switch (INTALIGN(buf->len) - buf->len)
     487              :     {
     488        16015 :         case 3:
     489        16015 :             appendStringInfoCharMacro(buf, 0);
     490              :             pg_fallthrough;
     491              :         case 2:
     492        16219 :             appendStringInfoCharMacro(buf, 0);
     493              :             pg_fallthrough;
     494              :         case 1:
     495        17374 :             appendStringInfoCharMacro(buf, 0);
     496              :             pg_fallthrough;
     497              :         default:
     498        17599 :             break;
     499              :     }
     500        17599 : }
     501              : 
     502              : /*
     503              :  * Reserve space for int32 JsonPathItem pointer.  Now zero pointer is written,
     504              :  * actual value will be recorded at '(int32 *) &buf->data[pos]' later.
     505              :  */
     506              : static int32
     507        23884 : reserveSpaceForItemPointer(StringInfo buf)
     508              : {
     509        23884 :     int32       pos = buf->len;
     510        23884 :     int32       ptr = 0;
     511              : 
     512        23884 :     appendBinaryStringInfo(buf, &ptr, sizeof(ptr));
     513              : 
     514        23884 :     return pos;
     515              : }
     516              : 
     517              : /*
     518              :  * Prints text representation of given jsonpath item and all its children.
     519              :  */
     520              : static void
     521         3328 : printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
     522              :                   bool printBracketes)
     523              : {
     524              :     JsonPathItem elem;
     525              :     int         i;
     526              :     int32       len;
     527              :     char       *str;
     528              : 
     529         3328 :     check_stack_depth();
     530         3328 :     CHECK_FOR_INTERRUPTS();
     531              : 
     532         3328 :     switch (v->type)
     533              :     {
     534           21 :         case jpiNull:
     535           21 :             appendStringInfoString(buf, "null");
     536           21 :             break;
     537           42 :         case jpiString:
     538           42 :             str = jspGetString(v, &len);
     539           42 :             escape_json_with_len(buf, str, len);
     540           42 :             break;
     541          490 :         case jpiNumeric:
     542          490 :             if (jspHasNext(v))
     543           42 :                 appendStringInfoChar(buf, '(');
     544          490 :             appendStringInfoString(buf,
     545          490 :                                    DatumGetCString(DirectFunctionCall1(numeric_out,
     546              :                                                                        NumericGetDatum(jspGetNumeric(v)))));
     547          490 :             if (jspHasNext(v))
     548           42 :                 appendStringInfoChar(buf, ')');
     549          490 :             break;
     550            6 :         case jpiBool:
     551            6 :             if (jspGetBool(v))
     552            3 :                 appendStringInfoString(buf, "true");
     553              :             else
     554            3 :                 appendStringInfoString(buf, "false");
     555            6 :             break;
     556          394 :         case jpiAnd:
     557              :         case jpiOr:
     558              :         case jpiEqual:
     559              :         case jpiNotEqual:
     560              :         case jpiLess:
     561              :         case jpiGreater:
     562              :         case jpiLessOrEqual:
     563              :         case jpiGreaterOrEqual:
     564              :         case jpiAdd:
     565              :         case jpiSub:
     566              :         case jpiMul:
     567              :         case jpiDiv:
     568              :         case jpiMod:
     569              :         case jpiStartsWith:
     570          394 :             if (printBracketes)
     571           57 :                 appendStringInfoChar(buf, '(');
     572          394 :             jspGetLeftArg(v, &elem);
     573          394 :             printJsonPathItem(buf, &elem, false,
     574          394 :                               operationPriority(elem.type) <=
     575          394 :                               operationPriority(v->type));
     576          394 :             appendStringInfoChar(buf, ' ');
     577          394 :             appendStringInfoString(buf, jspOperationName(v->type));
     578          394 :             appendStringInfoChar(buf, ' ');
     579          394 :             jspGetRightArg(v, &elem);
     580          394 :             printJsonPathItem(buf, &elem, false,
     581          394 :                               operationPriority(elem.type) <=
     582          394 :                               operationPriority(v->type));
     583          394 :             if (printBracketes)
     584           57 :                 appendStringInfoChar(buf, ')');
     585          394 :             break;
     586            6 :         case jpiNot:
     587            6 :             appendStringInfoString(buf, "!(");
     588            6 :             jspGetArg(v, &elem);
     589            6 :             printJsonPathItem(buf, &elem, false, false);
     590            6 :             appendStringInfoChar(buf, ')');
     591            6 :             break;
     592            3 :         case jpiIsUnknown:
     593            3 :             appendStringInfoChar(buf, '(');
     594            3 :             jspGetArg(v, &elem);
     595            3 :             printJsonPathItem(buf, &elem, false, false);
     596            3 :             appendStringInfoString(buf, ") is unknown");
     597            3 :             break;
     598           24 :         case jpiPlus:
     599              :         case jpiMinus:
     600           24 :             if (printBracketes)
     601            9 :                 appendStringInfoChar(buf, '(');
     602           24 :             appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
     603           24 :             jspGetArg(v, &elem);
     604           24 :             printJsonPathItem(buf, &elem, false,
     605           24 :                               operationPriority(elem.type) <=
     606           24 :                               operationPriority(v->type));
     607           24 :             if (printBracketes)
     608            9 :                 appendStringInfoChar(buf, ')');
     609           24 :             break;
     610          109 :         case jpiAnyArray:
     611          109 :             appendStringInfoString(buf, "[*]");
     612          109 :             break;
     613            6 :         case jpiAnyKey:
     614            6 :             if (inKey)
     615            6 :                 appendStringInfoChar(buf, '.');
     616            6 :             appendStringInfoChar(buf, '*');
     617            6 :             break;
     618           48 :         case jpiIndexArray:
     619           48 :             appendStringInfoChar(buf, '[');
     620          105 :             for (i = 0; i < v->content.array.nelems; i++)
     621              :             {
     622              :                 JsonPathItem from;
     623              :                 JsonPathItem to;
     624           57 :                 bool        range = jspGetArraySubscript(v, &from, &to, i);
     625              : 
     626           57 :                 if (i)
     627            9 :                     appendStringInfoChar(buf, ',');
     628              : 
     629           57 :                 printJsonPathItem(buf, &from, false, false);
     630              : 
     631           57 :                 if (range)
     632              :                 {
     633            6 :                     appendStringInfoString(buf, " to ");
     634            6 :                     printJsonPathItem(buf, &to, false, false);
     635              :                 }
     636              :             }
     637           48 :             appendStringInfoChar(buf, ']');
     638           48 :             break;
     639           24 :         case jpiAny:
     640           24 :             if (inKey)
     641           24 :                 appendStringInfoChar(buf, '.');
     642              : 
     643           24 :             if (v->content.anybounds.first == 0 &&
     644            6 :                 v->content.anybounds.last == PG_UINT32_MAX)
     645            3 :                 appendStringInfoString(buf, "**");
     646           21 :             else if (v->content.anybounds.first == v->content.anybounds.last)
     647              :             {
     648            9 :                 if (v->content.anybounds.first == PG_UINT32_MAX)
     649            3 :                     appendStringInfoString(buf, "**{last}");
     650              :                 else
     651            6 :                     appendStringInfo(buf, "**{%u}",
     652              :                                      v->content.anybounds.first);
     653              :             }
     654           12 :             else if (v->content.anybounds.first == PG_UINT32_MAX)
     655            3 :                 appendStringInfo(buf, "**{last to %u}",
     656              :                                  v->content.anybounds.last);
     657            9 :             else if (v->content.anybounds.last == PG_UINT32_MAX)
     658            3 :                 appendStringInfo(buf, "**{%u to last}",
     659              :                                  v->content.anybounds.first);
     660              :             else
     661            6 :                 appendStringInfo(buf, "**{%u to %u}",
     662              :                                  v->content.anybounds.first,
     663              :                                  v->content.anybounds.last);
     664           24 :             break;
     665          640 :         case jpiKey:
     666          640 :             if (inKey)
     667          640 :                 appendStringInfoChar(buf, '.');
     668          640 :             str = jspGetString(v, &len);
     669          640 :             escape_json_with_len(buf, str, len);
     670          640 :             break;
     671          301 :         case jpiCurrent:
     672              :             Assert(!inKey);
     673          301 :             appendStringInfoChar(buf, '@');
     674          301 :             break;
     675          784 :         case jpiRoot:
     676              :             Assert(!inKey);
     677          784 :             appendStringInfoChar(buf, '$');
     678          784 :             break;
     679           36 :         case jpiVariable:
     680           36 :             appendStringInfoChar(buf, '$');
     681           36 :             str = jspGetString(v, &len);
     682           36 :             escape_json_with_len(buf, str, len);
     683           36 :             break;
     684          265 :         case jpiFilter:
     685          265 :             appendStringInfoString(buf, "?(");
     686          265 :             jspGetArg(v, &elem);
     687          265 :             printJsonPathItem(buf, &elem, false, false);
     688          265 :             appendStringInfoChar(buf, ')');
     689          265 :             break;
     690           12 :         case jpiExists:
     691           12 :             appendStringInfoString(buf, "exists (");
     692           12 :             jspGetArg(v, &elem);
     693           12 :             printJsonPathItem(buf, &elem, false, false);
     694           12 :             appendStringInfoChar(buf, ')');
     695           12 :             break;
     696           15 :         case jpiType:
     697           15 :             appendStringInfoString(buf, ".type()");
     698           15 :             break;
     699            3 :         case jpiSize:
     700            3 :             appendStringInfoString(buf, ".size()");
     701            3 :             break;
     702            3 :         case jpiAbs:
     703            3 :             appendStringInfoString(buf, ".abs()");
     704            3 :             break;
     705            3 :         case jpiFloor:
     706            3 :             appendStringInfoString(buf, ".floor()");
     707            3 :             break;
     708            3 :         case jpiCeiling:
     709            3 :             appendStringInfoString(buf, ".ceiling()");
     710            3 :             break;
     711            3 :         case jpiDouble:
     712            3 :             appendStringInfoString(buf, ".double()");
     713            3 :             break;
     714            6 :         case jpiDatetime:
     715            6 :             appendStringInfoString(buf, ".datetime(");
     716            6 :             if (v->content.arg)
     717              :             {
     718            3 :                 jspGetArg(v, &elem);
     719            3 :                 printJsonPathItem(buf, &elem, false, false);
     720              :             }
     721            6 :             appendStringInfoChar(buf, ')');
     722            6 :             break;
     723            3 :         case jpiKeyValue:
     724            3 :             appendStringInfoString(buf, ".keyvalue()");
     725            3 :             break;
     726            6 :         case jpiLast:
     727            6 :             appendStringInfoString(buf, "last");
     728            6 :             break;
     729           24 :         case jpiLikeRegex:
     730           24 :             if (printBracketes)
     731            0 :                 appendStringInfoChar(buf, '(');
     732              : 
     733           24 :             jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
     734           24 :             printJsonPathItem(buf, &elem, false,
     735           24 :                               operationPriority(elem.type) <=
     736           24 :                               operationPriority(v->type));
     737              : 
     738           24 :             appendStringInfoString(buf, " like_regex ");
     739              : 
     740           24 :             escape_json_with_len(buf,
     741           24 :                                  v->content.like_regex.pattern,
     742              :                                  v->content.like_regex.patternlen);
     743              : 
     744           24 :             if (v->content.like_regex.flags)
     745              :             {
     746           18 :                 appendStringInfoString(buf, " flag \"");
     747              : 
     748           18 :                 if (v->content.like_regex.flags & JSP_REGEX_ICASE)
     749           15 :                     appendStringInfoChar(buf, 'i');
     750           18 :                 if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
     751            9 :                     appendStringInfoChar(buf, 's');
     752           18 :                 if (v->content.like_regex.flags & JSP_REGEX_MLINE)
     753            6 :                     appendStringInfoChar(buf, 'm');
     754           18 :                 if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
     755            3 :                     appendStringInfoChar(buf, 'x');
     756           18 :                 if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
     757            9 :                     appendStringInfoChar(buf, 'q');
     758              : 
     759           18 :                 appendStringInfoChar(buf, '"');
     760              :             }
     761              : 
     762           24 :             if (printBracketes)
     763            0 :                 appendStringInfoChar(buf, ')');
     764           24 :             break;
     765            3 :         case jpiBigint:
     766            3 :             appendStringInfoString(buf, ".bigint()");
     767            3 :             break;
     768            3 :         case jpiBoolean:
     769            3 :             appendStringInfoString(buf, ".boolean()");
     770            3 :             break;
     771            3 :         case jpiDate:
     772            3 :             appendStringInfoString(buf, ".date()");
     773            3 :             break;
     774            6 :         case jpiDecimal:
     775            6 :             appendStringInfoString(buf, ".decimal(");
     776            6 :             if (v->content.args.left)
     777              :             {
     778            3 :                 jspGetLeftArg(v, &elem);
     779            3 :                 printJsonPathItem(buf, &elem, false, false);
     780              :             }
     781            6 :             if (v->content.args.right)
     782              :             {
     783            3 :                 appendStringInfoChar(buf, ',');
     784            3 :                 jspGetRightArg(v, &elem);
     785            3 :                 printJsonPathItem(buf, &elem, false, false);
     786              :             }
     787            6 :             appendStringInfoChar(buf, ')');
     788            6 :             break;
     789            3 :         case jpiInteger:
     790            3 :             appendStringInfoString(buf, ".integer()");
     791            3 :             break;
     792            3 :         case jpiNumber:
     793            3 :             appendStringInfoString(buf, ".number()");
     794            3 :             break;
     795            3 :         case jpiStringFunc:
     796            3 :             appendStringInfoString(buf, ".string()");
     797            3 :             break;
     798            6 :         case jpiTime:
     799            6 :             appendStringInfoString(buf, ".time(");
     800            6 :             if (v->content.arg)
     801              :             {
     802            3 :                 jspGetArg(v, &elem);
     803            3 :                 printJsonPathItem(buf, &elem, false, false);
     804              :             }
     805            6 :             appendStringInfoChar(buf, ')');
     806            6 :             break;
     807            6 :         case jpiTimeTz:
     808            6 :             appendStringInfoString(buf, ".time_tz(");
     809            6 :             if (v->content.arg)
     810              :             {
     811            3 :                 jspGetArg(v, &elem);
     812            3 :                 printJsonPathItem(buf, &elem, false, false);
     813              :             }
     814            6 :             appendStringInfoChar(buf, ')');
     815            6 :             break;
     816            6 :         case jpiTimestamp:
     817            6 :             appendStringInfoString(buf, ".timestamp(");
     818            6 :             if (v->content.arg)
     819              :             {
     820            3 :                 jspGetArg(v, &elem);
     821            3 :                 printJsonPathItem(buf, &elem, false, false);
     822              :             }
     823            6 :             appendStringInfoChar(buf, ')');
     824            6 :             break;
     825            6 :         case jpiTimestampTz:
     826            6 :             appendStringInfoString(buf, ".timestamp_tz(");
     827            6 :             if (v->content.arg)
     828              :             {
     829            3 :                 jspGetArg(v, &elem);
     830            3 :                 printJsonPathItem(buf, &elem, false, false);
     831              :             }
     832            6 :             appendStringInfoChar(buf, ')');
     833            6 :             break;
     834            0 :         default:
     835            0 :             elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
     836              :     }
     837              : 
     838         3328 :     if (jspGetNext(v, &elem))
     839         1179 :         printJsonPathItem(buf, &elem, true, true);
     840         3328 : }
     841              : 
     842              : const char *
     843          766 : jspOperationName(JsonPathItemType type)
     844              : {
     845          766 :     switch (type)
     846              :     {
     847           15 :         case jpiAnd:
     848           15 :             return "&&";
     849           27 :         case jpiOr:
     850           27 :             return "||";
     851           84 :         case jpiEqual:
     852           84 :             return "==";
     853            3 :         case jpiNotEqual:
     854            3 :             return "!=";
     855          150 :         case jpiLess:
     856          150 :             return "<";
     857           22 :         case jpiGreater:
     858           22 :             return ">";
     859            3 :         case jpiLessOrEqual:
     860            3 :             return "<=";
     861           21 :         case jpiGreaterOrEqual:
     862           21 :             return ">=";
     863           42 :         case jpiAdd:
     864              :         case jpiPlus:
     865           42 :             return "+";
     866           18 :         case jpiSub:
     867              :         case jpiMinus:
     868           18 :             return "-";
     869           12 :         case jpiMul:
     870           12 :             return "*";
     871            3 :         case jpiDiv:
     872            3 :             return "/";
     873            3 :         case jpiMod:
     874            3 :             return "%";
     875            0 :         case jpiType:
     876            0 :             return "type";
     877            3 :         case jpiSize:
     878            3 :             return "size";
     879            3 :         case jpiAbs:
     880            3 :             return "abs";
     881            3 :         case jpiFloor:
     882            3 :             return "floor";
     883            3 :         case jpiCeiling:
     884            3 :             return "ceiling";
     885           30 :         case jpiDouble:
     886           30 :             return "double";
     887           15 :         case jpiDatetime:
     888           15 :             return "datetime";
     889            9 :         case jpiKeyValue:
     890            9 :             return "keyvalue";
     891            6 :         case jpiStartsWith:
     892            6 :             return "starts with";
     893            0 :         case jpiLikeRegex:
     894            0 :             return "like_regex";
     895           39 :         case jpiBigint:
     896           39 :             return "bigint";
     897           36 :         case jpiBoolean:
     898           36 :             return "boolean";
     899           18 :         case jpiDate:
     900           18 :             return "date";
     901           39 :         case jpiDecimal:
     902           39 :             return "decimal";
     903           39 :         case jpiInteger:
     904           39 :             return "integer";
     905           27 :         case jpiNumber:
     906           27 :             return "number";
     907            9 :         case jpiStringFunc:
     908            9 :             return "string";
     909           21 :         case jpiTime:
     910           21 :             return "time";
     911           21 :         case jpiTimeTz:
     912           21 :             return "time_tz";
     913           21 :         case jpiTimestamp:
     914           21 :             return "timestamp";
     915           21 :         case jpiTimestampTz:
     916           21 :             return "timestamp_tz";
     917            0 :         default:
     918            0 :             elog(ERROR, "unrecognized jsonpath item type: %d", type);
     919              :             return NULL;
     920              :     }
     921              : }
     922              : 
     923              : static int
     924         1672 : operationPriority(JsonPathItemType op)
     925              : {
     926         1672 :     switch (op)
     927              :     {
     928           57 :         case jpiOr:
     929           57 :             return 0;
     930           39 :         case jpiAnd:
     931           39 :             return 1;
     932          641 :         case jpiEqual:
     933              :         case jpiNotEqual:
     934              :         case jpiLess:
     935              :         case jpiGreater:
     936              :         case jpiLessOrEqual:
     937              :         case jpiGreaterOrEqual:
     938              :         case jpiStartsWith:
     939          641 :             return 2;
     940          126 :         case jpiAdd:
     941              :         case jpiSub:
     942          126 :             return 3;
     943           33 :         case jpiMul:
     944              :         case jpiDiv:
     945              :         case jpiMod:
     946           33 :             return 4;
     947           42 :         case jpiPlus:
     948              :         case jpiMinus:
     949           42 :             return 5;
     950          734 :         default:
     951          734 :             return 6;
     952              :     }
     953              : }
     954              : 
     955              : /******************* Support functions for JsonPath *************************/
     956              : 
     957              : /*
     958              :  * Support macros to read stored values
     959              :  */
     960              : 
     961              : #define read_byte(v, b, p) do {         \
     962              :     (v) = *(uint8*)((b) + (p));         \
     963              :     (p) += 1;                           \
     964              : } while(0)                              \
     965              : 
     966              : #define read_int32(v, b, p) do {        \
     967              :     (v) = *(uint32*)((b) + (p));        \
     968              :     (p) += sizeof(int32);               \
     969              : } while(0)                              \
     970              : 
     971              : #define read_int32_n(v, b, p, n) do {   \
     972              :     (v) = (void *)((b) + (p));          \
     973              :     (p) += sizeof(int32) * (n);         \
     974              : } while(0)                              \
     975              : 
     976              : /*
     977              :  * Read root node and fill root node representation
     978              :  */
     979              : void
     980       100780 : jspInit(JsonPathItem *v, JsonPath *js)
     981              : {
     982              :     Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
     983       100780 :     jspInitByBuffer(v, js->data, 0);
     984       100780 : }
     985              : 
     986              : /*
     987              :  * Read node from buffer and fill its representation
     988              :  */
     989              : void
     990       343827 : jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
     991              : {
     992       343827 :     v->base = base + pos;
     993              : 
     994       343827 :     read_byte(v->type, base, pos);
     995       343827 :     pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
     996       343827 :     read_int32(v->nextPos, base, pos);
     997              : 
     998       343827 :     switch (v->type)
     999              :     {
    1000       126049 :         case jpiNull:
    1001              :         case jpiRoot:
    1002              :         case jpiCurrent:
    1003              :         case jpiAnyArray:
    1004              :         case jpiAnyKey:
    1005              :         case jpiType:
    1006              :         case jpiSize:
    1007              :         case jpiAbs:
    1008              :         case jpiFloor:
    1009              :         case jpiCeiling:
    1010              :         case jpiDouble:
    1011              :         case jpiKeyValue:
    1012              :         case jpiLast:
    1013              :         case jpiBigint:
    1014              :         case jpiBoolean:
    1015              :         case jpiDate:
    1016              :         case jpiInteger:
    1017              :         case jpiNumber:
    1018              :         case jpiStringFunc:
    1019       126049 :             break;
    1020       101618 :         case jpiString:
    1021              :         case jpiKey:
    1022              :         case jpiVariable:
    1023       101618 :             read_int32(v->content.value.datalen, base, pos);
    1024              :             pg_fallthrough;
    1025       113355 :         case jpiNumeric:
    1026              :         case jpiBool:
    1027       113355 :             v->content.value.data = base + pos;
    1028       113355 :             break;
    1029        50584 :         case jpiAnd:
    1030              :         case jpiOr:
    1031              :         case jpiEqual:
    1032              :         case jpiNotEqual:
    1033              :         case jpiLess:
    1034              :         case jpiGreater:
    1035              :         case jpiLessOrEqual:
    1036              :         case jpiGreaterOrEqual:
    1037              :         case jpiAdd:
    1038              :         case jpiSub:
    1039              :         case jpiMul:
    1040              :         case jpiDiv:
    1041              :         case jpiMod:
    1042              :         case jpiStartsWith:
    1043              :         case jpiDecimal:
    1044        50584 :             read_int32(v->content.args.left, base, pos);
    1045        50584 :             read_int32(v->content.args.right, base, pos);
    1046        50584 :             break;
    1047        53137 :         case jpiNot:
    1048              :         case jpiIsUnknown:
    1049              :         case jpiExists:
    1050              :         case jpiPlus:
    1051              :         case jpiMinus:
    1052              :         case jpiFilter:
    1053              :         case jpiDatetime:
    1054              :         case jpiTime:
    1055              :         case jpiTimeTz:
    1056              :         case jpiTimestamp:
    1057              :         case jpiTimestampTz:
    1058        53137 :             read_int32(v->content.arg, base, pos);
    1059        53137 :             break;
    1060          303 :         case jpiIndexArray:
    1061          303 :             read_int32(v->content.array.nelems, base, pos);
    1062          303 :             read_int32_n(v->content.array.elems, base, pos,
    1063              :                          v->content.array.nelems * 2);
    1064          303 :             break;
    1065          177 :         case jpiAny:
    1066          177 :             read_int32(v->content.anybounds.first, base, pos);
    1067          177 :             read_int32(v->content.anybounds.last, base, pos);
    1068          177 :             break;
    1069          222 :         case jpiLikeRegex:
    1070          222 :             read_int32(v->content.like_regex.flags, base, pos);
    1071          222 :             read_int32(v->content.like_regex.expr, base, pos);
    1072          222 :             read_int32(v->content.like_regex.patternlen, base, pos);
    1073          222 :             v->content.like_regex.pattern = base + pos;
    1074          222 :             break;
    1075            0 :         default:
    1076            0 :             elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
    1077              :     }
    1078       343827 : }
    1079              : 
    1080              : void
    1081        53035 : jspGetArg(JsonPathItem *v, JsonPathItem *a)
    1082              : {
    1083              :     Assert(v->type == jpiNot ||
    1084              :            v->type == jpiIsUnknown ||
    1085              :            v->type == jpiPlus ||
    1086              :            v->type == jpiMinus ||
    1087              :            v->type == jpiFilter ||
    1088              :            v->type == jpiExists ||
    1089              :            v->type == jpiDatetime ||
    1090              :            v->type == jpiTime ||
    1091              :            v->type == jpiTimeTz ||
    1092              :            v->type == jpiTimestamp ||
    1093              :            v->type == jpiTimestampTz);
    1094              : 
    1095        53035 :     jspInitByBuffer(a, v->base, v->content.arg);
    1096        53035 : }
    1097              : 
    1098              : bool
    1099       226365 : jspGetNext(JsonPathItem *v, JsonPathItem *a)
    1100              : {
    1101       226365 :     if (jspHasNext(v))
    1102              :     {
    1103              :         Assert(v->type == jpiNull ||
    1104              :                v->type == jpiString ||
    1105              :                v->type == jpiNumeric ||
    1106              :                v->type == jpiBool ||
    1107              :                v->type == jpiAnd ||
    1108              :                v->type == jpiOr ||
    1109              :                v->type == jpiNot ||
    1110              :                v->type == jpiIsUnknown ||
    1111              :                v->type == jpiEqual ||
    1112              :                v->type == jpiNotEqual ||
    1113              :                v->type == jpiLess ||
    1114              :                v->type == jpiGreater ||
    1115              :                v->type == jpiLessOrEqual ||
    1116              :                v->type == jpiGreaterOrEqual ||
    1117              :                v->type == jpiAdd ||
    1118              :                v->type == jpiSub ||
    1119              :                v->type == jpiMul ||
    1120              :                v->type == jpiDiv ||
    1121              :                v->type == jpiMod ||
    1122              :                v->type == jpiPlus ||
    1123              :                v->type == jpiMinus ||
    1124              :                v->type == jpiAnyArray ||
    1125              :                v->type == jpiAnyKey ||
    1126              :                v->type == jpiIndexArray ||
    1127              :                v->type == jpiAny ||
    1128              :                v->type == jpiKey ||
    1129              :                v->type == jpiCurrent ||
    1130              :                v->type == jpiRoot ||
    1131              :                v->type == jpiVariable ||
    1132              :                v->type == jpiFilter ||
    1133              :                v->type == jpiExists ||
    1134              :                v->type == jpiType ||
    1135              :                v->type == jpiSize ||
    1136              :                v->type == jpiAbs ||
    1137              :                v->type == jpiFloor ||
    1138              :                v->type == jpiCeiling ||
    1139              :                v->type == jpiDouble ||
    1140              :                v->type == jpiDatetime ||
    1141              :                v->type == jpiKeyValue ||
    1142              :                v->type == jpiLast ||
    1143              :                v->type == jpiStartsWith ||
    1144              :                v->type == jpiLikeRegex ||
    1145              :                v->type == jpiBigint ||
    1146              :                v->type == jpiBoolean ||
    1147              :                v->type == jpiDate ||
    1148              :                v->type == jpiDecimal ||
    1149              :                v->type == jpiInteger ||
    1150              :                v->type == jpiNumber ||
    1151              :                v->type == jpiStringFunc ||
    1152              :                v->type == jpiTime ||
    1153              :                v->type == jpiTimeTz ||
    1154              :                v->type == jpiTimestamp ||
    1155              :                v->type == jpiTimestampTz);
    1156              : 
    1157       101096 :         if (a)
    1158       101096 :             jspInitByBuffer(a, v->base, v->nextPos);
    1159       101096 :         return true;
    1160              :     }
    1161              : 
    1162       125269 :     return false;
    1163              : }
    1164              : 
    1165              : void
    1166        50500 : jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
    1167              : {
    1168              :     Assert(v->type == jpiAnd ||
    1169              :            v->type == jpiOr ||
    1170              :            v->type == jpiEqual ||
    1171              :            v->type == jpiNotEqual ||
    1172              :            v->type == jpiLess ||
    1173              :            v->type == jpiGreater ||
    1174              :            v->type == jpiLessOrEqual ||
    1175              :            v->type == jpiGreaterOrEqual ||
    1176              :            v->type == jpiAdd ||
    1177              :            v->type == jpiSub ||
    1178              :            v->type == jpiMul ||
    1179              :            v->type == jpiDiv ||
    1180              :            v->type == jpiMod ||
    1181              :            v->type == jpiStartsWith ||
    1182              :            v->type == jpiDecimal);
    1183              : 
    1184        50500 :     jspInitByBuffer(a, v->base, v->content.args.left);
    1185        50500 : }
    1186              : 
    1187              : void
    1188        37849 : jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
    1189              : {
    1190              :     Assert(v->type == jpiAnd ||
    1191              :            v->type == jpiOr ||
    1192              :            v->type == jpiEqual ||
    1193              :            v->type == jpiNotEqual ||
    1194              :            v->type == jpiLess ||
    1195              :            v->type == jpiGreater ||
    1196              :            v->type == jpiLessOrEqual ||
    1197              :            v->type == jpiGreaterOrEqual ||
    1198              :            v->type == jpiAdd ||
    1199              :            v->type == jpiSub ||
    1200              :            v->type == jpiMul ||
    1201              :            v->type == jpiDiv ||
    1202              :            v->type == jpiMod ||
    1203              :            v->type == jpiStartsWith ||
    1204              :            v->type == jpiDecimal);
    1205              : 
    1206        37849 :     jspInitByBuffer(a, v->base, v->content.args.right);
    1207        37849 : }
    1208              : 
    1209              : bool
    1210          717 : jspGetBool(JsonPathItem *v)
    1211              : {
    1212              :     Assert(v->type == jpiBool);
    1213              : 
    1214          717 :     return (bool) *v->content.value.data;
    1215              : }
    1216              : 
    1217              : Numeric
    1218        10918 : jspGetNumeric(JsonPathItem *v)
    1219              : {
    1220              :     Assert(v->type == jpiNumeric);
    1221              : 
    1222        10918 :     return (Numeric) v->content.value.data;
    1223              : }
    1224              : 
    1225              : char *
    1226       101150 : jspGetString(JsonPathItem *v, int32 *len)
    1227              : {
    1228              :     Assert(v->type == jpiKey ||
    1229              :            v->type == jpiString ||
    1230              :            v->type == jpiVariable);
    1231              : 
    1232       101150 :     if (len)
    1233       101117 :         *len = v->content.value.datalen;
    1234       101150 :     return v->content.value.data;
    1235              : }
    1236              : 
    1237              : bool
    1238          321 : jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
    1239              :                      int i)
    1240              : {
    1241              :     Assert(v->type == jpiIndexArray);
    1242              : 
    1243          321 :     jspInitByBuffer(from, v->base, v->content.array.elems[i].from);
    1244              : 
    1245          321 :     if (!v->content.array.elems[i].to)
    1246          297 :         return false;
    1247              : 
    1248           24 :     jspInitByBuffer(to, v->base, v->content.array.elems[i].to);
    1249              : 
    1250           24 :     return true;
    1251              : }
    1252              : 
    1253              : /* SQL/JSON datatype status: */
    1254              : enum JsonPathDatatypeStatus
    1255              : {
    1256              :     jpdsNonDateTime,            /* null, bool, numeric, string, array, object */
    1257              :     jpdsUnknownDateTime,        /* unknown datetime type */
    1258              :     jpdsDateTimeZoned,          /* timetz, timestamptz */
    1259              :     jpdsDateTimeNonZoned,       /* time, timestamp, date */
    1260              : };
    1261              : 
    1262              : /* Context for jspIsMutableWalker() */
    1263              : struct JsonPathMutableContext
    1264              : {
    1265              :     List       *varnames;       /* list of variable names */
    1266              :     List       *varexprs;       /* list of variable expressions */
    1267              :     enum JsonPathDatatypeStatus current;    /* status of @ item */
    1268              :     bool        lax;            /* jsonpath is lax or strict */
    1269              :     bool        mutable;        /* resulting mutability status */
    1270              : };
    1271              : 
    1272              : static enum JsonPathDatatypeStatus jspIsMutableWalker(JsonPathItem *jpi,
    1273              :                                                       struct JsonPathMutableContext *cxt);
    1274              : 
    1275              : /*
    1276              :  * Function to check whether jsonpath expression is mutable to be used in the
    1277              :  * planner function contain_mutable_functions().
    1278              :  */
    1279              : bool
    1280          117 : jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
    1281              : {
    1282              :     struct JsonPathMutableContext cxt;
    1283              :     JsonPathItem jpi;
    1284              : 
    1285          117 :     cxt.varnames = varnames;
    1286          117 :     cxt.varexprs = varexprs;
    1287          117 :     cxt.current = jpdsNonDateTime;
    1288          117 :     cxt.lax = (path->header & JSONPATH_LAX) != 0;
    1289          117 :     cxt.mutable = false;
    1290              : 
    1291          117 :     jspInit(&jpi, path);
    1292          117 :     (void) jspIsMutableWalker(&jpi, &cxt);
    1293              : 
    1294          117 :     return cxt.mutable;
    1295              : }
    1296              : 
    1297              : /*
    1298              :  * Recursive walker for jspIsMutable()
    1299              :  */
    1300              : static enum JsonPathDatatypeStatus
    1301          393 : jspIsMutableWalker(JsonPathItem *jpi, struct JsonPathMutableContext *cxt)
    1302              : {
    1303              :     JsonPathItem next;
    1304          393 :     enum JsonPathDatatypeStatus status = jpdsNonDateTime;
    1305              : 
    1306          669 :     while (!cxt->mutable)
    1307              :     {
    1308              :         JsonPathItem arg;
    1309              :         enum JsonPathDatatypeStatus leftStatus;
    1310              :         enum JsonPathDatatypeStatus rightStatus;
    1311              : 
    1312          621 :         switch (jpi->type)
    1313              :         {
    1314          144 :             case jpiRoot:
    1315              :                 Assert(status == jpdsNonDateTime);
    1316          144 :                 break;
    1317              : 
    1318           72 :             case jpiCurrent:
    1319              :                 Assert(status == jpdsNonDateTime);
    1320           72 :                 status = cxt->current;
    1321           72 :                 break;
    1322              : 
    1323           72 :             case jpiFilter:
    1324              :                 {
    1325           72 :                     enum JsonPathDatatypeStatus prevStatus = cxt->current;
    1326              : 
    1327           72 :                     cxt->current = status;
    1328           72 :                     jspGetArg(jpi, &arg);
    1329           72 :                     jspIsMutableWalker(&arg, cxt);
    1330              : 
    1331           72 :                     cxt->current = prevStatus;
    1332           72 :                     break;
    1333              :                 }
    1334              : 
    1335           27 :             case jpiVariable:
    1336              :                 {
    1337              :                     int32       len;
    1338           27 :                     const char *name = jspGetString(jpi, &len);
    1339              :                     ListCell   *lc1;
    1340              :                     ListCell   *lc2;
    1341              : 
    1342              :                     Assert(status == jpdsNonDateTime);
    1343              : 
    1344           30 :                     forboth(lc1, cxt->varnames, lc2, cxt->varexprs)
    1345              :                     {
    1346           27 :                         String     *varname = lfirst_node(String, lc1);
    1347           27 :                         Node       *varexpr = lfirst(lc2);
    1348              : 
    1349           27 :                         if (strncmp(varname->sval, name, len))
    1350            3 :                             continue;
    1351              : 
    1352           24 :                         switch (exprType(varexpr))
    1353              :                         {
    1354           15 :                             case DATEOID:
    1355              :                             case TIMEOID:
    1356              :                             case TIMESTAMPOID:
    1357           15 :                                 status = jpdsDateTimeNonZoned;
    1358           15 :                                 break;
    1359              : 
    1360            6 :                             case TIMETZOID:
    1361              :                             case TIMESTAMPTZOID:
    1362            6 :                                 status = jpdsDateTimeZoned;
    1363            6 :                                 break;
    1364              : 
    1365            3 :                             default:
    1366            3 :                                 status = jpdsNonDateTime;
    1367            3 :                                 break;
    1368              :                         }
    1369              : 
    1370           24 :                         break;
    1371              :                     }
    1372           27 :                     break;
    1373              :                 }
    1374              : 
    1375           90 :             case jpiEqual:
    1376              :             case jpiNotEqual:
    1377              :             case jpiLess:
    1378              :             case jpiGreater:
    1379              :             case jpiLessOrEqual:
    1380              :             case jpiGreaterOrEqual:
    1381              :                 Assert(status == jpdsNonDateTime);
    1382           90 :                 jspGetLeftArg(jpi, &arg);
    1383           90 :                 leftStatus = jspIsMutableWalker(&arg, cxt);
    1384              : 
    1385           90 :                 jspGetRightArg(jpi, &arg);
    1386           90 :                 rightStatus = jspIsMutableWalker(&arg, cxt);
    1387              : 
    1388              :                 /*
    1389              :                  * Comparison of datetime type with different timezone status
    1390              :                  * is mutable.
    1391              :                  */
    1392           90 :                 if (leftStatus != jpdsNonDateTime &&
    1393           36 :                     rightStatus != jpdsNonDateTime &&
    1394           18 :                     (leftStatus == jpdsUnknownDateTime ||
    1395           18 :                      rightStatus == jpdsUnknownDateTime ||
    1396              :                      leftStatus != rightStatus))
    1397           21 :                     cxt->mutable = true;
    1398           90 :                 break;
    1399              : 
    1400            0 :             case jpiNot:
    1401              :             case jpiIsUnknown:
    1402              :             case jpiExists:
    1403              :             case jpiPlus:
    1404              :             case jpiMinus:
    1405              :                 Assert(status == jpdsNonDateTime);
    1406            0 :                 jspGetArg(jpi, &arg);
    1407            0 :                 jspIsMutableWalker(&arg, cxt);
    1408            0 :                 break;
    1409              : 
    1410            0 :             case jpiAnd:
    1411              :             case jpiOr:
    1412              :             case jpiAdd:
    1413              :             case jpiSub:
    1414              :             case jpiMul:
    1415              :             case jpiDiv:
    1416              :             case jpiMod:
    1417              :             case jpiStartsWith:
    1418              :                 Assert(status == jpdsNonDateTime);
    1419            0 :                 jspGetLeftArg(jpi, &arg);
    1420            0 :                 jspIsMutableWalker(&arg, cxt);
    1421            0 :                 jspGetRightArg(jpi, &arg);
    1422            0 :                 jspIsMutableWalker(&arg, cxt);
    1423            0 :                 break;
    1424              : 
    1425           12 :             case jpiIndexArray:
    1426           33 :                 for (int i = 0; i < jpi->content.array.nelems; i++)
    1427              :                 {
    1428              :                     JsonPathItem from;
    1429              :                     JsonPathItem to;
    1430              : 
    1431           21 :                     if (jspGetArraySubscript(jpi, &from, &to, i))
    1432            3 :                         jspIsMutableWalker(&to, cxt);
    1433              : 
    1434           21 :                     jspIsMutableWalker(&from, cxt);
    1435              :                 }
    1436              :                 pg_fallthrough;
    1437              : 
    1438              :             case jpiAnyArray:
    1439           12 :                 if (!cxt->lax)
    1440            0 :                     status = jpdsNonDateTime;
    1441           12 :                 break;
    1442              : 
    1443            0 :             case jpiAny:
    1444            0 :                 if (jpi->content.anybounds.first > 0)
    1445            0 :                     status = jpdsNonDateTime;
    1446            0 :                 break;
    1447              : 
    1448           63 :             case jpiDatetime:
    1449           63 :                 if (jpi->content.arg)
    1450              :                 {
    1451              :                     char       *template;
    1452              : 
    1453           33 :                     jspGetArg(jpi, &arg);
    1454           33 :                     if (arg.type != jpiString)
    1455              :                     {
    1456            0 :                         status = jpdsNonDateTime;
    1457            0 :                         break;  /* there will be runtime error */
    1458              :                     }
    1459              : 
    1460           33 :                     template = jspGetString(&arg, NULL);
    1461           33 :                     if (datetime_format_has_tz(template))
    1462           18 :                         status = jpdsDateTimeZoned;
    1463              :                     else
    1464           15 :                         status = jpdsDateTimeNonZoned;
    1465              :                 }
    1466              :                 else
    1467              :                 {
    1468           30 :                     status = jpdsUnknownDateTime;
    1469              :                 }
    1470           63 :                 break;
    1471              : 
    1472            0 :             case jpiLikeRegex:
    1473              :                 Assert(status == jpdsNonDateTime);
    1474            0 :                 jspInitByBuffer(&arg, jpi->base, jpi->content.like_regex.expr);
    1475            0 :                 jspIsMutableWalker(&arg, cxt);
    1476            0 :                 break;
    1477              : 
    1478              :                 /* literals */
    1479           12 :             case jpiNull:
    1480              :             case jpiString:
    1481              :             case jpiNumeric:
    1482              :             case jpiBool:
    1483           12 :                 break;
    1484              :                 /* accessors */
    1485           69 :             case jpiKey:
    1486              :             case jpiAnyKey:
    1487              :                 /* special items */
    1488              :             case jpiSubscript:
    1489              :             case jpiLast:
    1490              :                 /* item methods */
    1491              :             case jpiType:
    1492              :             case jpiSize:
    1493              :             case jpiAbs:
    1494              :             case jpiFloor:
    1495              :             case jpiCeiling:
    1496              :             case jpiDouble:
    1497              :             case jpiKeyValue:
    1498              :             case jpiBigint:
    1499              :             case jpiBoolean:
    1500              :             case jpiDecimal:
    1501              :             case jpiInteger:
    1502              :             case jpiNumber:
    1503              :             case jpiStringFunc:
    1504           69 :                 status = jpdsNonDateTime;
    1505           69 :                 break;
    1506              : 
    1507           45 :             case jpiTime:
    1508              :             case jpiDate:
    1509              :             case jpiTimestamp:
    1510           45 :                 status = jpdsDateTimeNonZoned;
    1511           45 :                 cxt->mutable = true;
    1512           45 :                 break;
    1513              : 
    1514           15 :             case jpiTimeTz:
    1515              :             case jpiTimestampTz:
    1516           15 :                 status = jpdsDateTimeNonZoned;
    1517           15 :                 cxt->mutable = true;
    1518           15 :                 break;
    1519              : 
    1520              :         }
    1521              : 
    1522          621 :         if (!jspGetNext(jpi, &next))
    1523          345 :             break;
    1524              : 
    1525          276 :         jpi = &next;
    1526              :     }
    1527              : 
    1528          393 :     return status;
    1529              : }
        

Generated by: LCOV version 2.0-1