LCOV - code coverage report
Current view: top level - src/backend/utils/adt - jsonpath.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 628 686 91.5 %
Date: 2024-07-18 17:11:54 Functions: 22 24 91.7 %
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-2024, 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       10286 : jsonpath_in(PG_FUNCTION_ARGS)
      99             : {
     100       10286 :     char       *in = PG_GETARG_CSTRING(0);
     101       10286 :     int         len = strlen(in);
     102             : 
     103       10286 :     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        1838 : jsonpath_out(PG_FUNCTION_ARGS)
     135             : {
     136        1838 :     JsonPath   *in = PG_GETARG_JSONPATH_P(0);
     137             : 
     138        1838 :     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       10286 : jsonPathFromCstring(char *in, int len, struct Node *escontext)
     174             : {
     175       10286 :     JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
     176             :     JsonPath   *res;
     177             :     StringInfoData buf;
     178             : 
     179        9920 :     if (SOFT_ERROR_OCCURRED(escontext))
     180          42 :         return (Datum) 0;
     181             : 
     182        9878 :     if (!jsonpath)
     183           6 :         ereturn(escontext, (Datum) 0,
     184             :                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     185             :                  errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
     186             :                         in)));
     187             : 
     188        9872 :     initStringInfo(&buf);
     189        9872 :     enlargeStringInfo(&buf, 4 * len /* estimation */ );
     190             : 
     191        9872 :     appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
     192             : 
     193        9872 :     if (!flattenJsonPathParseItem(&buf, NULL, escontext,
     194             :                                   jsonpath->expr, 0, false))
     195          12 :         return (Datum) 0;
     196             : 
     197        9842 :     res = (JsonPath *) buf.data;
     198        9842 :     SET_VARSIZE(res, buf.len);
     199        9842 :     res->header = JSONPATH_VERSION;
     200        9842 :     if (jsonpath->lax)
     201        9212 :         res->header |= JSONPATH_LAX;
     202             : 
     203        9842 :     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        1838 : jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
     214             : {
     215             :     StringInfoData buf;
     216             :     JsonPathItem v;
     217             : 
     218        1838 :     if (!out)
     219             :     {
     220        1838 :         out = &buf;
     221        1838 :         initStringInfo(out);
     222             :     }
     223        1838 :     enlargeStringInfo(out, estimated_len);
     224             : 
     225        1838 :     if (!(in->header & JSONPATH_LAX))
     226          18 :         appendStringInfoString(out, "strict ");
     227             : 
     228        1838 :     jspInit(&v, in);
     229        1838 :     printJsonPathItem(out, &v, false, true);
     230             : 
     231        1838 :     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       34448 : 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       34448 :     int32       pos = buf->len - JSONPATH_HDRSZ;
     245             :     int32       chld;
     246             :     int32       next;
     247       34448 :     int         argNestingLevel = 0;
     248             : 
     249       34448 :     check_stack_depth();
     250       34448 :     CHECK_FOR_INTERRUPTS();
     251             : 
     252       34448 :     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       34448 :     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       34448 :     next = reserveSpaceForItemPointer(buf);
     266             : 
     267       34448 :     switch (item->type)
     268             :     {
     269        5888 :         case jpiString:
     270             :         case jpiVariable:
     271             :         case jpiKey:
     272        5888 :             appendBinaryStringInfo(buf, &item->value.string.len,
     273             :                                    sizeof(item->value.string.len));
     274        5888 :             appendBinaryStringInfo(buf, item->value.string.val,
     275        5888 :                                    item->value.string.len);
     276        5888 :             appendStringInfoChar(buf, '\0');
     277        5888 :             break;
     278        2538 :         case jpiNumeric:
     279        2538 :             appendBinaryStringInfo(buf, item->value.numeric,
     280        2538 :                                    VARSIZE(item->value.numeric));
     281        2538 :             break;
     282         180 :         case jpiBool:
     283         180 :             appendBinaryStringInfo(buf, &item->value.boolean,
     284             :                                    sizeof(item->value.boolean));
     285         180 :             break;
     286        3660 :         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        3660 :                 int32       left = reserveSpaceForItemPointer(buf);
     308        3660 :                 int32       right = reserveSpaceForItemPointer(buf);
     309             : 
     310        3660 :                 if (!item->value.args.left)
     311         168 :                     chld = pos;
     312        3492 :                 else if (!flattenJsonPathParseItem(buf, &chld, escontext,
     313             :                                                    item->value.args.left,
     314             :                                                    nestingLevel + argNestingLevel,
     315             :                                                    insideArraySubscript))
     316          12 :                     return false;
     317        3636 :                 *(int32 *) (buf->data + left) = chld - pos;
     318             : 
     319        3636 :                 if (!item->value.args.right)
     320         168 :                     chld = pos;
     321        3468 :                 else if (!flattenJsonPathParseItem(buf, &chld, escontext,
     322             :                                                    item->value.args.right,
     323             :                                                    nestingLevel + argNestingLevel,
     324             :                                                    insideArraySubscript))
     325           0 :                     return false;
     326        3636 :                 *(int32 *) (buf->data + right) = chld - pos;
     327             :             }
     328        3636 :             break;
     329         120 :         case jpiLikeRegex:
     330             :             {
     331             :                 int32       offs;
     332             : 
     333         120 :                 appendBinaryStringInfo(buf,
     334         120 :                                        &item->value.like_regex.flags,
     335             :                                        sizeof(item->value.like_regex.flags));
     336         120 :                 offs = reserveSpaceForItemPointer(buf);
     337         120 :                 appendBinaryStringInfo(buf,
     338         120 :                                        &item->value.like_regex.patternlen,
     339             :                                        sizeof(item->value.like_regex.patternlen));
     340         120 :                 appendBinaryStringInfo(buf, item->value.like_regex.pattern,
     341         120 :                                        item->value.like_regex.patternlen);
     342         120 :                 appendStringInfoChar(buf, '\0');
     343             : 
     344         120 :                 if (!flattenJsonPathParseItem(buf, &chld, escontext,
     345             :                                               item->value.like_regex.expr,
     346             :                                               nestingLevel,
     347             :                                               insideArraySubscript))
     348           0 :                     return false;
     349         120 :                 *(int32 *) (buf->data + offs) = chld - pos;
     350             :             }
     351         120 :             break;
     352        2280 :         case jpiFilter:
     353        2280 :             argNestingLevel++;
     354             :             /* FALLTHROUGH */
     355        5076 :         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        5076 :                 int32       arg = reserveSpaceForItemPointer(buf);
     367             : 
     368        5076 :                 if (!item->value.arg)
     369        1482 :                     chld = pos;
     370        3594 :                 else if (!flattenJsonPathParseItem(buf, &chld, escontext,
     371             :                                                    item->value.arg,
     372             :                                                    nestingLevel + argNestingLevel,
     373             :                                                    insideArraySubscript))
     374           0 :                     return false;
     375        5070 :                 *(int32 *) (buf->data + arg) = chld - pos;
     376             :             }
     377        5070 :             break;
     378         114 :         case jpiNull:
     379         114 :             break;
     380        9536 :         case jpiRoot:
     381        9536 :             break;
     382        2128 :         case jpiAnyArray:
     383             :         case jpiAnyKey:
     384        2128 :             break;
     385        2568 :         case jpiCurrent:
     386        2568 :             if (nestingLevel <= 0)
     387          18 :                 ereturn(escontext, false,
     388             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     389             :                          errmsg("@ is not allowed in root expressions")));
     390        2550 :             break;
     391          90 :         case jpiLast:
     392          90 :             if (!insideArraySubscript)
     393          12 :                 ereturn(escontext, false,
     394             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     395             :                          errmsg("LAST is allowed only in array subscripts")));
     396          78 :             break;
     397         510 :         case jpiIndexArray:
     398             :             {
     399         510 :                 int32       nelems = item->value.array.nelems;
     400             :                 int         offset;
     401             :                 int         i;
     402             : 
     403         510 :                 appendBinaryStringInfo(buf, &nelems, sizeof(nelems));
     404             : 
     405         510 :                 offset = buf->len;
     406             : 
     407         510 :                 appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems);
     408             : 
     409        1068 :                 for (i = 0; i < nelems; i++)
     410             :                 {
     411             :                     int32      *ppos;
     412             :                     int32       topos;
     413             :                     int32       frompos;
     414             : 
     415         558 :                     if (!flattenJsonPathParseItem(buf, &frompos, escontext,
     416         558 :                                                   item->value.array.elems[i].from,
     417             :                                                   nestingLevel, true))
     418           0 :                         return false;
     419         558 :                     frompos -= pos;
     420             : 
     421         558 :                     if (item->value.array.elems[i].to)
     422             :                     {
     423          48 :                         if (!flattenJsonPathParseItem(buf, &topos, escontext,
     424          48 :                                                       item->value.array.elems[i].to,
     425             :                                                       nestingLevel, true))
     426           0 :                             return false;
     427          48 :                         topos -= pos;
     428             :                     }
     429             :                     else
     430         510 :                         topos = 0;
     431             : 
     432         558 :                     ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
     433             : 
     434         558 :                     ppos[0] = frompos;
     435         558 :                     ppos[1] = topos;
     436             :                 }
     437             :             }
     438         510 :             break;
     439         354 :         case jpiAny:
     440         354 :             appendBinaryStringInfo(buf,
     441         354 :                                    &item->value.anybounds.first,
     442             :                                    sizeof(item->value.anybounds.first));
     443         354 :             appendBinaryStringInfo(buf,
     444         354 :                                    &item->value.anybounds.last,
     445             :                                    sizeof(item->value.anybounds.last));
     446         354 :             break;
     447        1686 :         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        1686 :             break;
     461           0 :         default:
     462           0 :             elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
     463             :     }
     464             : 
     465       34388 :     if (item->next)
     466             :     {
     467       13296 :         if (!flattenJsonPathParseItem(buf, &chld, escontext,
     468             :                                       item->next, nestingLevel,
     469             :                                       insideArraySubscript))
     470           0 :             return false;
     471       13290 :         chld -= pos;
     472       13290 :         *(int32 *) (buf->data + next) = chld;
     473             :     }
     474             : 
     475       34382 :     if (result)
     476       24540 :         *result = pos;
     477       34382 :     return true;
     478             : }
     479             : 
     480             : /*
     481             :  * Align StringInfo to int by adding zero padding bytes
     482             :  */
     483             : static void
     484       34448 : alignStringInfoInt(StringInfo buf)
     485             : {
     486       34448 :     switch (INTALIGN(buf->len) - buf->len)
     487             :     {
     488       31280 :         case 3:
     489       31280 :             appendStringInfoCharMacro(buf, 0);
     490             :             /* FALLTHROUGH */
     491             :         case 2:
     492       31688 :             appendStringInfoCharMacro(buf, 0);
     493             :             /* FALLTHROUGH */
     494             :         case 1:
     495       33998 :             appendStringInfoCharMacro(buf, 0);
     496             :             /* FALLTHROUGH */
     497             :         default:
     498       34448 :             break;
     499             :     }
     500       34448 : }
     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       46964 : reserveSpaceForItemPointer(StringInfo buf)
     508             : {
     509       46964 :     int32       pos = buf->len;
     510       46964 :     int32       ptr = 0;
     511             : 
     512       46964 :     appendBinaryStringInfo(buf, &ptr, sizeof(ptr));
     513             : 
     514       46964 :     return pos;
     515             : }
     516             : 
     517             : /*
     518             :  * Prints text representation of given jsonpath item and all its children.
     519             :  */
     520             : static void
     521        6608 : printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
     522             :                   bool printBracketes)
     523             : {
     524             :     JsonPathItem elem;
     525             :     int         i;
     526             : 
     527        6608 :     check_stack_depth();
     528        6608 :     CHECK_FOR_INTERRUPTS();
     529             : 
     530        6608 :     switch (v->type)
     531             :     {
     532          42 :         case jpiNull:
     533          42 :             appendStringInfoString(buf, "null");
     534          42 :             break;
     535          84 :         case jpiString:
     536          84 :             escape_json(buf, jspGetString(v, NULL));
     537          84 :             break;
     538         980 :         case jpiNumeric:
     539         980 :             if (jspHasNext(v))
     540          84 :                 appendStringInfoChar(buf, '(');
     541         980 :             appendStringInfoString(buf,
     542         980 :                                    DatumGetCString(DirectFunctionCall1(numeric_out,
     543             :                                                                        NumericGetDatum(jspGetNumeric(v)))));
     544         980 :             if (jspHasNext(v))
     545          84 :                 appendStringInfoChar(buf, ')');
     546         980 :             break;
     547          12 :         case jpiBool:
     548          12 :             if (jspGetBool(v))
     549           6 :                 appendStringInfoString(buf, "true");
     550             :             else
     551           6 :                 appendStringInfoString(buf, "false");
     552          12 :             break;
     553         788 :         case jpiAnd:
     554             :         case jpiOr:
     555             :         case jpiEqual:
     556             :         case jpiNotEqual:
     557             :         case jpiLess:
     558             :         case jpiGreater:
     559             :         case jpiLessOrEqual:
     560             :         case jpiGreaterOrEqual:
     561             :         case jpiAdd:
     562             :         case jpiSub:
     563             :         case jpiMul:
     564             :         case jpiDiv:
     565             :         case jpiMod:
     566             :         case jpiStartsWith:
     567         788 :             if (printBracketes)
     568         114 :                 appendStringInfoChar(buf, '(');
     569         788 :             jspGetLeftArg(v, &elem);
     570         788 :             printJsonPathItem(buf, &elem, false,
     571         788 :                               operationPriority(elem.type) <=
     572         788 :                               operationPriority(v->type));
     573         788 :             appendStringInfoChar(buf, ' ');
     574         788 :             appendStringInfoString(buf, jspOperationName(v->type));
     575         788 :             appendStringInfoChar(buf, ' ');
     576         788 :             jspGetRightArg(v, &elem);
     577         788 :             printJsonPathItem(buf, &elem, false,
     578         788 :                               operationPriority(elem.type) <=
     579         788 :                               operationPriority(v->type));
     580         788 :             if (printBracketes)
     581         114 :                 appendStringInfoChar(buf, ')');
     582         788 :             break;
     583          12 :         case jpiNot:
     584          12 :             appendStringInfoString(buf, "!(");
     585          12 :             jspGetArg(v, &elem);
     586          12 :             printJsonPathItem(buf, &elem, false, false);
     587          12 :             appendStringInfoChar(buf, ')');
     588          12 :             break;
     589           6 :         case jpiIsUnknown:
     590           6 :             appendStringInfoChar(buf, '(');
     591           6 :             jspGetArg(v, &elem);
     592           6 :             printJsonPathItem(buf, &elem, false, false);
     593           6 :             appendStringInfoString(buf, ") is unknown");
     594           6 :             break;
     595          48 :         case jpiPlus:
     596             :         case jpiMinus:
     597          48 :             if (printBracketes)
     598          18 :                 appendStringInfoChar(buf, '(');
     599          48 :             appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
     600          48 :             jspGetArg(v, &elem);
     601          48 :             printJsonPathItem(buf, &elem, false,
     602          48 :                               operationPriority(elem.type) <=
     603          48 :                               operationPriority(v->type));
     604          48 :             if (printBracketes)
     605          18 :                 appendStringInfoChar(buf, ')');
     606          48 :             break;
     607         218 :         case jpiAnyArray:
     608         218 :             appendStringInfoString(buf, "[*]");
     609         218 :             break;
     610          12 :         case jpiAnyKey:
     611          12 :             if (inKey)
     612          12 :                 appendStringInfoChar(buf, '.');
     613          12 :             appendStringInfoChar(buf, '*');
     614          12 :             break;
     615          96 :         case jpiIndexArray:
     616          96 :             appendStringInfoChar(buf, '[');
     617         210 :             for (i = 0; i < v->content.array.nelems; i++)
     618             :             {
     619             :                 JsonPathItem from;
     620             :                 JsonPathItem to;
     621         114 :                 bool        range = jspGetArraySubscript(v, &from, &to, i);
     622             : 
     623         114 :                 if (i)
     624          18 :                     appendStringInfoChar(buf, ',');
     625             : 
     626         114 :                 printJsonPathItem(buf, &from, false, false);
     627             : 
     628         114 :                 if (range)
     629             :                 {
     630          12 :                     appendStringInfoString(buf, " to ");
     631          12 :                     printJsonPathItem(buf, &to, false, false);
     632             :                 }
     633             :             }
     634          96 :             appendStringInfoChar(buf, ']');
     635          96 :             break;
     636          48 :         case jpiAny:
     637          48 :             if (inKey)
     638          48 :                 appendStringInfoChar(buf, '.');
     639             : 
     640          48 :             if (v->content.anybounds.first == 0 &&
     641          12 :                 v->content.anybounds.last == PG_UINT32_MAX)
     642           6 :                 appendStringInfoString(buf, "**");
     643          42 :             else if (v->content.anybounds.first == v->content.anybounds.last)
     644             :             {
     645          18 :                 if (v->content.anybounds.first == PG_UINT32_MAX)
     646           6 :                     appendStringInfoString(buf, "**{last}");
     647             :                 else
     648          12 :                     appendStringInfo(buf, "**{%u}",
     649             :                                      v->content.anybounds.first);
     650             :             }
     651          24 :             else if (v->content.anybounds.first == PG_UINT32_MAX)
     652           6 :                 appendStringInfo(buf, "**{last to %u}",
     653             :                                  v->content.anybounds.last);
     654          18 :             else if (v->content.anybounds.last == PG_UINT32_MAX)
     655           6 :                 appendStringInfo(buf, "**{%u to last}",
     656             :                                  v->content.anybounds.first);
     657             :             else
     658          12 :                 appendStringInfo(buf, "**{%u to %u}",
     659             :                                  v->content.anybounds.first,
     660             :                                  v->content.anybounds.last);
     661          48 :             break;
     662        1280 :         case jpiKey:
     663        1280 :             if (inKey)
     664        1280 :                 appendStringInfoChar(buf, '.');
     665        1280 :             escape_json(buf, jspGetString(v, NULL));
     666        1280 :             break;
     667         602 :         case jpiCurrent:
     668             :             Assert(!inKey);
     669         602 :             appendStringInfoChar(buf, '@');
     670         602 :             break;
     671        1520 :         case jpiRoot:
     672             :             Assert(!inKey);
     673        1520 :             appendStringInfoChar(buf, '$');
     674        1520 :             break;
     675          72 :         case jpiVariable:
     676          72 :             appendStringInfoChar(buf, '$');
     677          72 :             escape_json(buf, jspGetString(v, NULL));
     678          72 :             break;
     679         530 :         case jpiFilter:
     680         530 :             appendStringInfoString(buf, "?(");
     681         530 :             jspGetArg(v, &elem);
     682         530 :             printJsonPathItem(buf, &elem, false, false);
     683         530 :             appendStringInfoChar(buf, ')');
     684         530 :             break;
     685          24 :         case jpiExists:
     686          24 :             appendStringInfoString(buf, "exists (");
     687          24 :             jspGetArg(v, &elem);
     688          24 :             printJsonPathItem(buf, &elem, false, false);
     689          24 :             appendStringInfoChar(buf, ')');
     690          24 :             break;
     691          30 :         case jpiType:
     692          30 :             appendStringInfoString(buf, ".type()");
     693          30 :             break;
     694           6 :         case jpiSize:
     695           6 :             appendStringInfoString(buf, ".size()");
     696           6 :             break;
     697           6 :         case jpiAbs:
     698           6 :             appendStringInfoString(buf, ".abs()");
     699           6 :             break;
     700           6 :         case jpiFloor:
     701           6 :             appendStringInfoString(buf, ".floor()");
     702           6 :             break;
     703           6 :         case jpiCeiling:
     704           6 :             appendStringInfoString(buf, ".ceiling()");
     705           6 :             break;
     706           6 :         case jpiDouble:
     707           6 :             appendStringInfoString(buf, ".double()");
     708           6 :             break;
     709          12 :         case jpiDatetime:
     710          12 :             appendStringInfoString(buf, ".datetime(");
     711          12 :             if (v->content.arg)
     712             :             {
     713           6 :                 jspGetArg(v, &elem);
     714           6 :                 printJsonPathItem(buf, &elem, false, false);
     715             :             }
     716          12 :             appendStringInfoChar(buf, ')');
     717          12 :             break;
     718           6 :         case jpiKeyValue:
     719           6 :             appendStringInfoString(buf, ".keyvalue()");
     720           6 :             break;
     721          12 :         case jpiLast:
     722          12 :             appendStringInfoString(buf, "last");
     723          12 :             break;
     724          48 :         case jpiLikeRegex:
     725          48 :             if (printBracketes)
     726           0 :                 appendStringInfoChar(buf, '(');
     727             : 
     728          48 :             jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
     729          48 :             printJsonPathItem(buf, &elem, false,
     730          48 :                               operationPriority(elem.type) <=
     731          48 :                               operationPriority(v->type));
     732             : 
     733          48 :             appendStringInfoString(buf, " like_regex ");
     734             : 
     735          48 :             escape_json(buf, v->content.like_regex.pattern);
     736             : 
     737          48 :             if (v->content.like_regex.flags)
     738             :             {
     739          36 :                 appendStringInfoString(buf, " flag \"");
     740             : 
     741          36 :                 if (v->content.like_regex.flags & JSP_REGEX_ICASE)
     742          30 :                     appendStringInfoChar(buf, 'i');
     743          36 :                 if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
     744          18 :                     appendStringInfoChar(buf, 's');
     745          36 :                 if (v->content.like_regex.flags & JSP_REGEX_MLINE)
     746          12 :                     appendStringInfoChar(buf, 'm');
     747          36 :                 if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
     748           6 :                     appendStringInfoChar(buf, 'x');
     749          36 :                 if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
     750          18 :                     appendStringInfoChar(buf, 'q');
     751             : 
     752          36 :                 appendStringInfoChar(buf, '"');
     753             :             }
     754             : 
     755          48 :             if (printBracketes)
     756           0 :                 appendStringInfoChar(buf, ')');
     757          48 :             break;
     758           6 :         case jpiBigint:
     759           6 :             appendStringInfoString(buf, ".bigint()");
     760           6 :             break;
     761           6 :         case jpiBoolean:
     762           6 :             appendStringInfoString(buf, ".boolean()");
     763           6 :             break;
     764           6 :         case jpiDate:
     765           6 :             appendStringInfoString(buf, ".date()");
     766           6 :             break;
     767          12 :         case jpiDecimal:
     768          12 :             appendStringInfoString(buf, ".decimal(");
     769          12 :             if (v->content.args.left)
     770             :             {
     771           6 :                 jspGetLeftArg(v, &elem);
     772           6 :                 printJsonPathItem(buf, &elem, false, false);
     773             :             }
     774          12 :             if (v->content.args.right)
     775             :             {
     776           6 :                 appendStringInfoChar(buf, ',');
     777           6 :                 jspGetRightArg(v, &elem);
     778           6 :                 printJsonPathItem(buf, &elem, false, false);
     779             :             }
     780          12 :             appendStringInfoChar(buf, ')');
     781          12 :             break;
     782           6 :         case jpiInteger:
     783           6 :             appendStringInfoString(buf, ".integer()");
     784           6 :             break;
     785           6 :         case jpiNumber:
     786           6 :             appendStringInfoString(buf, ".number()");
     787           6 :             break;
     788           6 :         case jpiStringFunc:
     789           6 :             appendStringInfoString(buf, ".string()");
     790           6 :             break;
     791          12 :         case jpiTime:
     792          12 :             appendStringInfoString(buf, ".time(");
     793          12 :             if (v->content.arg)
     794             :             {
     795           6 :                 jspGetArg(v, &elem);
     796           6 :                 printJsonPathItem(buf, &elem, false, false);
     797             :             }
     798          12 :             appendStringInfoChar(buf, ')');
     799          12 :             break;
     800          12 :         case jpiTimeTz:
     801          12 :             appendStringInfoString(buf, ".time_tz(");
     802          12 :             if (v->content.arg)
     803             :             {
     804           6 :                 jspGetArg(v, &elem);
     805           6 :                 printJsonPathItem(buf, &elem, false, false);
     806             :             }
     807          12 :             appendStringInfoChar(buf, ')');
     808          12 :             break;
     809          12 :         case jpiTimestamp:
     810          12 :             appendStringInfoString(buf, ".timestamp(");
     811          12 :             if (v->content.arg)
     812             :             {
     813           6 :                 jspGetArg(v, &elem);
     814           6 :                 printJsonPathItem(buf, &elem, false, false);
     815             :             }
     816          12 :             appendStringInfoChar(buf, ')');
     817          12 :             break;
     818          12 :         case jpiTimestampTz:
     819          12 :             appendStringInfoString(buf, ".timestamp_tz(");
     820          12 :             if (v->content.arg)
     821             :             {
     822           6 :                 jspGetArg(v, &elem);
     823           6 :                 printJsonPathItem(buf, &elem, false, false);
     824             :             }
     825          12 :             appendStringInfoChar(buf, ')');
     826          12 :             break;
     827           0 :         default:
     828           0 :             elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
     829             :     }
     830             : 
     831        6608 :     if (jspGetNext(v, &elem))
     832        2358 :         printJsonPathItem(buf, &elem, true, true);
     833        6608 : }
     834             : 
     835             : const char *
     836        1532 : jspOperationName(JsonPathItemType type)
     837             : {
     838        1532 :     switch (type)
     839             :     {
     840          30 :         case jpiAnd:
     841          30 :             return "&&";
     842          54 :         case jpiOr:
     843          54 :             return "||";
     844         168 :         case jpiEqual:
     845         168 :             return "==";
     846           6 :         case jpiNotEqual:
     847           6 :             return "!=";
     848         300 :         case jpiLess:
     849         300 :             return "<";
     850          44 :         case jpiGreater:
     851          44 :             return ">";
     852           6 :         case jpiLessOrEqual:
     853           6 :             return "<=";
     854          42 :         case jpiGreaterOrEqual:
     855          42 :             return ">=";
     856          84 :         case jpiAdd:
     857             :         case jpiPlus:
     858          84 :             return "+";
     859          36 :         case jpiSub:
     860             :         case jpiMinus:
     861          36 :             return "-";
     862          24 :         case jpiMul:
     863          24 :             return "*";
     864           6 :         case jpiDiv:
     865           6 :             return "/";
     866           6 :         case jpiMod:
     867           6 :             return "%";
     868           0 :         case jpiType:
     869           0 :             return "type";
     870           6 :         case jpiSize:
     871           6 :             return "size";
     872           6 :         case jpiAbs:
     873           6 :             return "abs";
     874           6 :         case jpiFloor:
     875           6 :             return "floor";
     876           6 :         case jpiCeiling:
     877           6 :             return "ceiling";
     878          60 :         case jpiDouble:
     879          60 :             return "double";
     880          30 :         case jpiDatetime:
     881          30 :             return "datetime";
     882          18 :         case jpiKeyValue:
     883          18 :             return "keyvalue";
     884          12 :         case jpiStartsWith:
     885          12 :             return "starts with";
     886           0 :         case jpiLikeRegex:
     887           0 :             return "like_regex";
     888          78 :         case jpiBigint:
     889          78 :             return "bigint";
     890          72 :         case jpiBoolean:
     891          72 :             return "boolean";
     892          36 :         case jpiDate:
     893          36 :             return "date";
     894          78 :         case jpiDecimal:
     895          78 :             return "decimal";
     896          78 :         case jpiInteger:
     897          78 :             return "integer";
     898          54 :         case jpiNumber:
     899          54 :             return "number";
     900          18 :         case jpiStringFunc:
     901          18 :             return "string";
     902          42 :         case jpiTime:
     903          42 :             return "time";
     904          42 :         case jpiTimeTz:
     905          42 :             return "time_tz";
     906          42 :         case jpiTimestamp:
     907          42 :             return "timestamp";
     908          42 :         case jpiTimestampTz:
     909          42 :             return "timestamp_tz";
     910           0 :         default:
     911           0 :             elog(ERROR, "unrecognized jsonpath item type: %d", type);
     912             :             return NULL;
     913             :     }
     914             : }
     915             : 
     916             : static int
     917        3344 : operationPriority(JsonPathItemType op)
     918             : {
     919        3344 :     switch (op)
     920             :     {
     921         114 :         case jpiOr:
     922         114 :             return 0;
     923          78 :         case jpiAnd:
     924          78 :             return 1;
     925        1282 :         case jpiEqual:
     926             :         case jpiNotEqual:
     927             :         case jpiLess:
     928             :         case jpiGreater:
     929             :         case jpiLessOrEqual:
     930             :         case jpiGreaterOrEqual:
     931             :         case jpiStartsWith:
     932        1282 :             return 2;
     933         252 :         case jpiAdd:
     934             :         case jpiSub:
     935         252 :             return 3;
     936          66 :         case jpiMul:
     937             :         case jpiDiv:
     938             :         case jpiMod:
     939          66 :             return 4;
     940          84 :         case jpiPlus:
     941             :         case jpiMinus:
     942          84 :             return 5;
     943        1468 :         default:
     944        1468 :             return 6;
     945             :     }
     946             : }
     947             : 
     948             : /******************* Support functions for JsonPath *************************/
     949             : 
     950             : /*
     951             :  * Support macros to read stored values
     952             :  */
     953             : 
     954             : #define read_byte(v, b, p) do {         \
     955             :     (v) = *(uint8*)((b) + (p));         \
     956             :     (p) += 1;                           \
     957             : } while(0)                              \
     958             : 
     959             : #define read_int32(v, b, p) do {        \
     960             :     (v) = *(uint32*)((b) + (p));        \
     961             :     (p) += sizeof(int32);               \
     962             : } while(0)                              \
     963             : 
     964             : #define read_int32_n(v, b, p, n) do {   \
     965             :     (v) = (void *)((b) + (p));          \
     966             :     (p) += sizeof(int32) * (n);         \
     967             : } while(0)                              \
     968             : 
     969             : /*
     970             :  * Read root node and fill root node representation
     971             :  */
     972             : void
     973      201170 : jspInit(JsonPathItem *v, JsonPath *js)
     974             : {
     975             :     Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
     976      201170 :     jspInitByBuffer(v, js->data, 0);
     977      201170 : }
     978             : 
     979             : /*
     980             :  * Read node from buffer and fill its representation
     981             :  */
     982             : void
     983      687006 : jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
     984             : {
     985      687006 :     v->base = base + pos;
     986             : 
     987      687006 :     read_byte(v->type, base, pos);
     988      687006 :     pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
     989      687006 :     read_int32(v->nextPos, base, pos);
     990             : 
     991      687006 :     switch (v->type)
     992             :     {
     993      251600 :         case jpiNull:
     994             :         case jpiRoot:
     995             :         case jpiCurrent:
     996             :         case jpiAnyArray:
     997             :         case jpiAnyKey:
     998             :         case jpiType:
     999             :         case jpiSize:
    1000             :         case jpiAbs:
    1001             :         case jpiFloor:
    1002             :         case jpiCeiling:
    1003             :         case jpiDouble:
    1004             :         case jpiKeyValue:
    1005             :         case jpiLast:
    1006             :         case jpiBigint:
    1007             :         case jpiBoolean:
    1008             :         case jpiDate:
    1009             :         case jpiInteger:
    1010             :         case jpiNumber:
    1011             :         case jpiStringFunc:
    1012      251600 :             break;
    1013      203140 :         case jpiString:
    1014             :         case jpiKey:
    1015             :         case jpiVariable:
    1016      203140 :             read_int32(v->content.value.datalen, base, pos);
    1017             :             /* FALLTHROUGH */
    1018      226614 :         case jpiNumeric:
    1019             :         case jpiBool:
    1020      226614 :             v->content.value.data = base + pos;
    1021      226614 :             break;
    1022      101168 :         case jpiAnd:
    1023             :         case jpiOr:
    1024             :         case jpiEqual:
    1025             :         case jpiNotEqual:
    1026             :         case jpiLess:
    1027             :         case jpiGreater:
    1028             :         case jpiLessOrEqual:
    1029             :         case jpiGreaterOrEqual:
    1030             :         case jpiAdd:
    1031             :         case jpiSub:
    1032             :         case jpiMul:
    1033             :         case jpiDiv:
    1034             :         case jpiMod:
    1035             :         case jpiStartsWith:
    1036             :         case jpiDecimal:
    1037      101168 :             read_int32(v->content.args.left, base, pos);
    1038      101168 :             read_int32(v->content.args.right, base, pos);
    1039      101168 :             break;
    1040      106220 :         case jpiNot:
    1041             :         case jpiIsUnknown:
    1042             :         case jpiExists:
    1043             :         case jpiPlus:
    1044             :         case jpiMinus:
    1045             :         case jpiFilter:
    1046             :         case jpiDatetime:
    1047             :         case jpiTime:
    1048             :         case jpiTimeTz:
    1049             :         case jpiTimestamp:
    1050             :         case jpiTimestampTz:
    1051      106220 :             read_int32(v->content.arg, base, pos);
    1052      106220 :             break;
    1053         606 :         case jpiIndexArray:
    1054         606 :             read_int32(v->content.array.nelems, base, pos);
    1055         606 :             read_int32_n(v->content.array.elems, base, pos,
    1056             :                          v->content.array.nelems * 2);
    1057         606 :             break;
    1058         354 :         case jpiAny:
    1059         354 :             read_int32(v->content.anybounds.first, base, pos);
    1060         354 :             read_int32(v->content.anybounds.last, base, pos);
    1061         354 :             break;
    1062         444 :         case jpiLikeRegex:
    1063         444 :             read_int32(v->content.like_regex.flags, base, pos);
    1064         444 :             read_int32(v->content.like_regex.expr, base, pos);
    1065         444 :             read_int32(v->content.like_regex.patternlen, base, pos);
    1066         444 :             v->content.like_regex.pattern = base + pos;
    1067         444 :             break;
    1068           0 :         default:
    1069           0 :             elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
    1070             :     }
    1071      687006 : }
    1072             : 
    1073             : void
    1074      106070 : jspGetArg(JsonPathItem *v, JsonPathItem *a)
    1075             : {
    1076             :     Assert(v->type == jpiNot ||
    1077             :            v->type == jpiIsUnknown ||
    1078             :            v->type == jpiPlus ||
    1079             :            v->type == jpiMinus ||
    1080             :            v->type == jpiFilter ||
    1081             :            v->type == jpiExists ||
    1082             :            v->type == jpiDatetime ||
    1083             :            v->type == jpiTime ||
    1084             :            v->type == jpiTimeTz ||
    1085             :            v->type == jpiTimestamp ||
    1086             :            v->type == jpiTimestampTz);
    1087             : 
    1088      106070 :     jspInitByBuffer(a, v->base, v->content.arg);
    1089      106070 : }
    1090             : 
    1091             : bool
    1092      452202 : jspGetNext(JsonPathItem *v, JsonPathItem *a)
    1093             : {
    1094      452202 :     if (jspHasNext(v))
    1095             :     {
    1096             :         Assert(v->type == jpiNull ||
    1097             :                v->type == jpiString ||
    1098             :                v->type == jpiNumeric ||
    1099             :                v->type == jpiBool ||
    1100             :                v->type == jpiAnd ||
    1101             :                v->type == jpiOr ||
    1102             :                v->type == jpiNot ||
    1103             :                v->type == jpiIsUnknown ||
    1104             :                v->type == jpiEqual ||
    1105             :                v->type == jpiNotEqual ||
    1106             :                v->type == jpiLess ||
    1107             :                v->type == jpiGreater ||
    1108             :                v->type == jpiLessOrEqual ||
    1109             :                v->type == jpiGreaterOrEqual ||
    1110             :                v->type == jpiAdd ||
    1111             :                v->type == jpiSub ||
    1112             :                v->type == jpiMul ||
    1113             :                v->type == jpiDiv ||
    1114             :                v->type == jpiMod ||
    1115             :                v->type == jpiPlus ||
    1116             :                v->type == jpiMinus ||
    1117             :                v->type == jpiAnyArray ||
    1118             :                v->type == jpiAnyKey ||
    1119             :                v->type == jpiIndexArray ||
    1120             :                v->type == jpiAny ||
    1121             :                v->type == jpiKey ||
    1122             :                v->type == jpiCurrent ||
    1123             :                v->type == jpiRoot ||
    1124             :                v->type == jpiVariable ||
    1125             :                v->type == jpiFilter ||
    1126             :                v->type == jpiExists ||
    1127             :                v->type == jpiType ||
    1128             :                v->type == jpiSize ||
    1129             :                v->type == jpiAbs ||
    1130             :                v->type == jpiFloor ||
    1131             :                v->type == jpiCeiling ||
    1132             :                v->type == jpiDouble ||
    1133             :                v->type == jpiDatetime ||
    1134             :                v->type == jpiKeyValue ||
    1135             :                v->type == jpiLast ||
    1136             :                v->type == jpiStartsWith ||
    1137             :                v->type == jpiLikeRegex ||
    1138             :                v->type == jpiBigint ||
    1139             :                v->type == jpiBoolean ||
    1140             :                v->type == jpiDate ||
    1141             :                v->type == jpiDecimal ||
    1142             :                v->type == jpiInteger ||
    1143             :                v->type == jpiNumber ||
    1144             :                v->type == jpiStringFunc ||
    1145             :                v->type == jpiTime ||
    1146             :                v->type == jpiTimeTz ||
    1147             :                v->type == jpiTimestamp ||
    1148             :                v->type == jpiTimestampTz);
    1149             : 
    1150      201934 :         if (a)
    1151      201934 :             jspInitByBuffer(a, v->base, v->nextPos);
    1152      201934 :         return true;
    1153             :     }
    1154             : 
    1155      250268 :     return false;
    1156             : }
    1157             : 
    1158             : void
    1159      101000 : jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
    1160             : {
    1161             :     Assert(v->type == jpiAnd ||
    1162             :            v->type == jpiOr ||
    1163             :            v->type == jpiEqual ||
    1164             :            v->type == jpiNotEqual ||
    1165             :            v->type == jpiLess ||
    1166             :            v->type == jpiGreater ||
    1167             :            v->type == jpiLessOrEqual ||
    1168             :            v->type == jpiGreaterOrEqual ||
    1169             :            v->type == jpiAdd ||
    1170             :            v->type == jpiSub ||
    1171             :            v->type == jpiMul ||
    1172             :            v->type == jpiDiv ||
    1173             :            v->type == jpiMod ||
    1174             :            v->type == jpiStartsWith ||
    1175             :            v->type == jpiDecimal);
    1176             : 
    1177      101000 :     jspInitByBuffer(a, v->base, v->content.args.left);
    1178      101000 : }
    1179             : 
    1180             : void
    1181       75698 : jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
    1182             : {
    1183             :     Assert(v->type == jpiAnd ||
    1184             :            v->type == jpiOr ||
    1185             :            v->type == jpiEqual ||
    1186             :            v->type == jpiNotEqual ||
    1187             :            v->type == jpiLess ||
    1188             :            v->type == jpiGreater ||
    1189             :            v->type == jpiLessOrEqual ||
    1190             :            v->type == jpiGreaterOrEqual ||
    1191             :            v->type == jpiAdd ||
    1192             :            v->type == jpiSub ||
    1193             :            v->type == jpiMul ||
    1194             :            v->type == jpiDiv ||
    1195             :            v->type == jpiMod ||
    1196             :            v->type == jpiStartsWith ||
    1197             :            v->type == jpiDecimal);
    1198             : 
    1199       75698 :     jspInitByBuffer(a, v->base, v->content.args.right);
    1200       75698 : }
    1201             : 
    1202             : bool
    1203        1434 : jspGetBool(JsonPathItem *v)
    1204             : {
    1205             :     Assert(v->type == jpiBool);
    1206             : 
    1207        1434 :     return (bool) *v->content.value.data;
    1208             : }
    1209             : 
    1210             : Numeric
    1211       21836 : jspGetNumeric(JsonPathItem *v)
    1212             : {
    1213             :     Assert(v->type == jpiNumeric);
    1214             : 
    1215       21836 :     return (Numeric) v->content.value.data;
    1216             : }
    1217             : 
    1218             : char *
    1219      202258 : jspGetString(JsonPathItem *v, int32 *len)
    1220             : {
    1221             :     Assert(v->type == jpiKey ||
    1222             :            v->type == jpiString ||
    1223             :            v->type == jpiVariable);
    1224             : 
    1225      202258 :     if (len)
    1226      200756 :         *len = v->content.value.datalen;
    1227      202258 :     return v->content.value.data;
    1228             : }
    1229             : 
    1230             : bool
    1231         642 : jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
    1232             :                      int i)
    1233             : {
    1234             :     Assert(v->type == jpiIndexArray);
    1235             : 
    1236         642 :     jspInitByBuffer(from, v->base, v->content.array.elems[i].from);
    1237             : 
    1238         642 :     if (!v->content.array.elems[i].to)
    1239         594 :         return false;
    1240             : 
    1241          48 :     jspInitByBuffer(to, v->base, v->content.array.elems[i].to);
    1242             : 
    1243          48 :     return true;
    1244             : }
    1245             : 
    1246             : /* SQL/JSON datatype status: */
    1247             : enum JsonPathDatatypeStatus
    1248             : {
    1249             :     jpdsNonDateTime,            /* null, bool, numeric, string, array, object */
    1250             :     jpdsUnknownDateTime,        /* unknown datetime type */
    1251             :     jpdsDateTimeZoned,          /* timetz, timestamptz */
    1252             :     jpdsDateTimeNonZoned,       /* time, timestamp, date */
    1253             : };
    1254             : 
    1255             : /* Context for jspIsMutableWalker() */
    1256             : struct JsonPathMutableContext
    1257             : {
    1258             :     List       *varnames;       /* list of variable names */
    1259             :     List       *varexprs;       /* list of variable expressions */
    1260             :     enum JsonPathDatatypeStatus current;    /* status of @ item */
    1261             :     bool        lax;            /* jsonpath is lax or strict */
    1262             :     bool        mutable;        /* resulting mutability status */
    1263             : };
    1264             : 
    1265             : static enum JsonPathDatatypeStatus jspIsMutableWalker(JsonPathItem *jpi,
    1266             :                                                       struct JsonPathMutableContext *cxt);
    1267             : 
    1268             : /*
    1269             :  * Function to check whether jsonpath expression is mutable to be used in the
    1270             :  * planner function contain_mutable_functions().
    1271             :  */
    1272             : bool
    1273         234 : jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
    1274             : {
    1275             :     struct JsonPathMutableContext cxt;
    1276             :     JsonPathItem jpi;
    1277             : 
    1278         234 :     cxt.varnames = varnames;
    1279         234 :     cxt.varexprs = varexprs;
    1280         234 :     cxt.current = jpdsNonDateTime;
    1281         234 :     cxt.lax = (path->header & JSONPATH_LAX) != 0;
    1282         234 :     cxt.mutable = false;
    1283             : 
    1284         234 :     jspInit(&jpi, path);
    1285         234 :     (void) jspIsMutableWalker(&jpi, &cxt);
    1286             : 
    1287         234 :     return cxt.mutable;
    1288             : }
    1289             : 
    1290             : /*
    1291             :  * Recursive walker for jspIsMutable()
    1292             :  */
    1293             : static enum JsonPathDatatypeStatus
    1294         786 : jspIsMutableWalker(JsonPathItem *jpi, struct JsonPathMutableContext *cxt)
    1295             : {
    1296             :     JsonPathItem next;
    1297         786 :     enum JsonPathDatatypeStatus status = jpdsNonDateTime;
    1298             : 
    1299        1338 :     while (!cxt->mutable)
    1300             :     {
    1301             :         JsonPathItem arg;
    1302             :         enum JsonPathDatatypeStatus leftStatus;
    1303             :         enum JsonPathDatatypeStatus rightStatus;
    1304             : 
    1305        1242 :         switch (jpi->type)
    1306             :         {
    1307         288 :             case jpiRoot:
    1308             :                 Assert(status == jpdsNonDateTime);
    1309         288 :                 break;
    1310             : 
    1311         144 :             case jpiCurrent:
    1312             :                 Assert(status == jpdsNonDateTime);
    1313         144 :                 status = cxt->current;
    1314         144 :                 break;
    1315             : 
    1316         144 :             case jpiFilter:
    1317             :                 {
    1318         144 :                     enum JsonPathDatatypeStatus prevStatus = cxt->current;
    1319             : 
    1320         144 :                     cxt->current = status;
    1321         144 :                     jspGetArg(jpi, &arg);
    1322         144 :                     jspIsMutableWalker(&arg, cxt);
    1323             : 
    1324         144 :                     cxt->current = prevStatus;
    1325         144 :                     break;
    1326             :                 }
    1327             : 
    1328          54 :             case jpiVariable:
    1329             :                 {
    1330             :                     int32       len;
    1331          54 :                     const char *name = jspGetString(jpi, &len);
    1332             :                     ListCell   *lc1;
    1333             :                     ListCell   *lc2;
    1334             : 
    1335             :                     Assert(status == jpdsNonDateTime);
    1336             : 
    1337          60 :                     forboth(lc1, cxt->varnames, lc2, cxt->varexprs)
    1338             :                     {
    1339          54 :                         String     *varname = lfirst_node(String, lc1);
    1340          54 :                         Node       *varexpr = lfirst(lc2);
    1341             : 
    1342          54 :                         if (strncmp(varname->sval, name, len))
    1343           6 :                             continue;
    1344             : 
    1345          48 :                         switch (exprType(varexpr))
    1346             :                         {
    1347          30 :                             case DATEOID:
    1348             :                             case TIMEOID:
    1349             :                             case TIMESTAMPOID:
    1350          30 :                                 status = jpdsDateTimeNonZoned;
    1351          30 :                                 break;
    1352             : 
    1353          12 :                             case TIMETZOID:
    1354             :                             case TIMESTAMPTZOID:
    1355          12 :                                 status = jpdsDateTimeZoned;
    1356          12 :                                 break;
    1357             : 
    1358           6 :                             default:
    1359           6 :                                 status = jpdsNonDateTime;
    1360           6 :                                 break;
    1361             :                         }
    1362             : 
    1363          48 :                         break;
    1364             :                     }
    1365          54 :                     break;
    1366             :                 }
    1367             : 
    1368         180 :             case jpiEqual:
    1369             :             case jpiNotEqual:
    1370             :             case jpiLess:
    1371             :             case jpiGreater:
    1372             :             case jpiLessOrEqual:
    1373             :             case jpiGreaterOrEqual:
    1374             :                 Assert(status == jpdsNonDateTime);
    1375         180 :                 jspGetLeftArg(jpi, &arg);
    1376         180 :                 leftStatus = jspIsMutableWalker(&arg, cxt);
    1377             : 
    1378         180 :                 jspGetRightArg(jpi, &arg);
    1379         180 :                 rightStatus = jspIsMutableWalker(&arg, cxt);
    1380             : 
    1381             :                 /*
    1382             :                  * Comparison of datetime type with different timezone status
    1383             :                  * is mutable.
    1384             :                  */
    1385         180 :                 if (leftStatus != jpdsNonDateTime &&
    1386          72 :                     rightStatus != jpdsNonDateTime &&
    1387          36 :                     (leftStatus == jpdsUnknownDateTime ||
    1388          36 :                      rightStatus == jpdsUnknownDateTime ||
    1389             :                      leftStatus != rightStatus))
    1390          42 :                     cxt->mutable = true;
    1391         180 :                 break;
    1392             : 
    1393           0 :             case jpiNot:
    1394             :             case jpiIsUnknown:
    1395             :             case jpiExists:
    1396             :             case jpiPlus:
    1397             :             case jpiMinus:
    1398             :                 Assert(status == jpdsNonDateTime);
    1399           0 :                 jspGetArg(jpi, &arg);
    1400           0 :                 jspIsMutableWalker(&arg, cxt);
    1401           0 :                 break;
    1402             : 
    1403           0 :             case jpiAnd:
    1404             :             case jpiOr:
    1405             :             case jpiAdd:
    1406             :             case jpiSub:
    1407             :             case jpiMul:
    1408             :             case jpiDiv:
    1409             :             case jpiMod:
    1410             :             case jpiStartsWith:
    1411             :                 Assert(status == jpdsNonDateTime);
    1412           0 :                 jspGetLeftArg(jpi, &arg);
    1413           0 :                 jspIsMutableWalker(&arg, cxt);
    1414           0 :                 jspGetRightArg(jpi, &arg);
    1415           0 :                 jspIsMutableWalker(&arg, cxt);
    1416           0 :                 break;
    1417             : 
    1418          24 :             case jpiIndexArray:
    1419          66 :                 for (int i = 0; i < jpi->content.array.nelems; i++)
    1420             :                 {
    1421             :                     JsonPathItem from;
    1422             :                     JsonPathItem to;
    1423             : 
    1424          42 :                     if (jspGetArraySubscript(jpi, &from, &to, i))
    1425           6 :                         jspIsMutableWalker(&to, cxt);
    1426             : 
    1427          42 :                     jspIsMutableWalker(&from, cxt);
    1428             :                 }
    1429             :                 /* FALLTHROUGH */
    1430             : 
    1431             :             case jpiAnyArray:
    1432          24 :                 if (!cxt->lax)
    1433           0 :                     status = jpdsNonDateTime;
    1434          24 :                 break;
    1435             : 
    1436           0 :             case jpiAny:
    1437           0 :                 if (jpi->content.anybounds.first > 0)
    1438           0 :                     status = jpdsNonDateTime;
    1439           0 :                 break;
    1440             : 
    1441         126 :             case jpiDatetime:
    1442         126 :                 if (jpi->content.arg)
    1443             :                 {
    1444             :                     char       *template;
    1445             : 
    1446          66 :                     jspGetArg(jpi, &arg);
    1447          66 :                     if (arg.type != jpiString)
    1448             :                     {
    1449           0 :                         status = jpdsNonDateTime;
    1450           0 :                         break;  /* there will be runtime error */
    1451             :                     }
    1452             : 
    1453          66 :                     template = jspGetString(&arg, NULL);
    1454          66 :                     if (datetime_format_has_tz(template))
    1455          36 :                         status = jpdsDateTimeZoned;
    1456             :                     else
    1457          30 :                         status = jpdsDateTimeNonZoned;
    1458             :                 }
    1459             :                 else
    1460             :                 {
    1461          60 :                     status = jpdsUnknownDateTime;
    1462             :                 }
    1463         126 :                 break;
    1464             : 
    1465           0 :             case jpiLikeRegex:
    1466             :                 Assert(status == jpdsNonDateTime);
    1467           0 :                 jspInitByBuffer(&arg, jpi->base, jpi->content.like_regex.expr);
    1468           0 :                 jspIsMutableWalker(&arg, cxt);
    1469           0 :                 break;
    1470             : 
    1471             :                 /* literals */
    1472          24 :             case jpiNull:
    1473             :             case jpiString:
    1474             :             case jpiNumeric:
    1475             :             case jpiBool:
    1476          24 :                 break;
    1477             :                 /* accessors */
    1478         138 :             case jpiKey:
    1479             :             case jpiAnyKey:
    1480             :                 /* special items */
    1481             :             case jpiSubscript:
    1482             :             case jpiLast:
    1483             :                 /* item methods */
    1484             :             case jpiType:
    1485             :             case jpiSize:
    1486             :             case jpiAbs:
    1487             :             case jpiFloor:
    1488             :             case jpiCeiling:
    1489             :             case jpiDouble:
    1490             :             case jpiKeyValue:
    1491             :             case jpiBigint:
    1492             :             case jpiBoolean:
    1493             :             case jpiDecimal:
    1494             :             case jpiInteger:
    1495             :             case jpiNumber:
    1496             :             case jpiStringFunc:
    1497         138 :                 status = jpdsNonDateTime;
    1498         138 :                 break;
    1499             : 
    1500          90 :             case jpiTime:
    1501             :             case jpiDate:
    1502             :             case jpiTimestamp:
    1503          90 :                 status = jpdsDateTimeNonZoned;
    1504          90 :                 cxt->mutable = true;
    1505          90 :                 break;
    1506             : 
    1507          30 :             case jpiTimeTz:
    1508             :             case jpiTimestampTz:
    1509          30 :                 status = jpdsDateTimeNonZoned;
    1510          30 :                 cxt->mutable = true;
    1511          30 :                 break;
    1512             : 
    1513             :         }
    1514             : 
    1515        1242 :         if (!jspGetNext(jpi, &next))
    1516         690 :             break;
    1517             : 
    1518         552 :         jpi = &next;
    1519             :     }
    1520             : 
    1521         786 :     return status;
    1522             : }

Generated by: LCOV version 1.14