LCOV - code coverage report
Current view: top level - src/backend/utils/adt - jsonpath.c (source / functions) Hit Total Coverage
Test: PostgreSQL 16beta1 Lines: 458 497 92.2 %
Date: 2023-05-31 04:12:22 Functions: 20 22 90.9 %
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-2023, 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 "funcapi.h"
      67             : #include "lib/stringinfo.h"
      68             : #include "libpq/pqformat.h"
      69             : #include "nodes/miscnodes.h"
      70             : #include "miscadmin.h"
      71             : #include "utils/builtins.h"
      72             : #include "utils/json.h"
      73             : #include "utils/jsonpath.h"
      74             : 
      75             : 
      76             : static Datum jsonPathFromCstring(char *in, int len, struct Node *escontext);
      77             : static char *jsonPathToCstring(StringInfo out, JsonPath *in,
      78             :                                int estimated_len);
      79             : static bool flattenJsonPathParseItem(StringInfo buf, int *result,
      80             :                                      struct Node *escontext,
      81             :                                      JsonPathParseItem *item,
      82             :                                      int nestingLevel, bool insideArraySubscript);
      83             : static void alignStringInfoInt(StringInfo buf);
      84             : static int32 reserveSpaceForItemPointer(StringInfo buf);
      85             : static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
      86             :                               bool printBracketes);
      87             : static int  operationPriority(JsonPathItemType op);
      88             : 
      89             : 
      90             : /**************************** INPUT/OUTPUT ********************************/
      91             : 
      92             : /*
      93             :  * jsonpath type input function
      94             :  */
      95             : Datum
      96        4560 : jsonpath_in(PG_FUNCTION_ARGS)
      97             : {
      98        4560 :     char       *in = PG_GETARG_CSTRING(0);
      99        4560 :     int         len = strlen(in);
     100             : 
     101        4560 :     return jsonPathFromCstring(in, len, fcinfo->context);
     102             : }
     103             : 
     104             : /*
     105             :  * jsonpath type recv function
     106             :  *
     107             :  * The type is sent as text in binary mode, so this is almost the same
     108             :  * as the input function, but it's prefixed with a version number so we
     109             :  * can change the binary format sent in future if necessary. For now,
     110             :  * only version 1 is supported.
     111             :  */
     112             : Datum
     113           0 : jsonpath_recv(PG_FUNCTION_ARGS)
     114             : {
     115           0 :     StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
     116           0 :     int         version = pq_getmsgint(buf, 1);
     117             :     char       *str;
     118             :     int         nbytes;
     119             : 
     120           0 :     if (version == JSONPATH_VERSION)
     121           0 :         str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
     122             :     else
     123           0 :         elog(ERROR, "unsupported jsonpath version number: %d", version);
     124             : 
     125           0 :     return jsonPathFromCstring(str, nbytes, NULL);
     126             : }
     127             : 
     128             : /*
     129             :  * jsonpath type output function
     130             :  */
     131             : Datum
     132        1172 : jsonpath_out(PG_FUNCTION_ARGS)
     133             : {
     134        1172 :     JsonPath   *in = PG_GETARG_JSONPATH_P(0);
     135             : 
     136        1172 :     PG_RETURN_CSTRING(jsonPathToCstring(NULL, in, VARSIZE(in)));
     137             : }
     138             : 
     139             : /*
     140             :  * jsonpath type send function
     141             :  *
     142             :  * Just send jsonpath as a version number, then a string of text
     143             :  */
     144             : Datum
     145           0 : jsonpath_send(PG_FUNCTION_ARGS)
     146             : {
     147           0 :     JsonPath   *in = PG_GETARG_JSONPATH_P(0);
     148             :     StringInfoData buf;
     149             :     StringInfoData jtext;
     150           0 :     int         version = JSONPATH_VERSION;
     151             : 
     152           0 :     initStringInfo(&jtext);
     153           0 :     (void) jsonPathToCstring(&jtext, in, VARSIZE(in));
     154             : 
     155           0 :     pq_begintypsend(&buf);
     156           0 :     pq_sendint8(&buf, version);
     157           0 :     pq_sendtext(&buf, jtext.data, jtext.len);
     158           0 :     pfree(jtext.data);
     159             : 
     160           0 :     PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
     161             : }
     162             : 
     163             : /*
     164             :  * Converts C-string to a jsonpath value.
     165             :  *
     166             :  * Uses jsonpath parser to turn string into an AST, then
     167             :  * flattenJsonPathParseItem() does second pass turning AST into binary
     168             :  * representation of jsonpath.
     169             :  */
     170             : static Datum
     171        4560 : jsonPathFromCstring(char *in, int len, struct Node *escontext)
     172             : {
     173        4560 :     JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
     174             :     JsonPath   *res;
     175             :     StringInfoData buf;
     176             : 
     177        4260 :     if (SOFT_ERROR_OCCURRED(escontext))
     178          36 :         return (Datum) 0;
     179             : 
     180        4224 :     if (!jsonpath)
     181           6 :         ereturn(escontext, (Datum) 0,
     182             :                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     183             :                  errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
     184             :                         in)));
     185             : 
     186        4218 :     initStringInfo(&buf);
     187        4218 :     enlargeStringInfo(&buf, 4 * len /* estimation */ );
     188             : 
     189        4218 :     appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
     190             : 
     191        4218 :     if (!flattenJsonPathParseItem(&buf, NULL, escontext,
     192             :                                   jsonpath->expr, 0, false))
     193          12 :         return (Datum) 0;
     194             : 
     195        4188 :     res = (JsonPath *) buf.data;
     196        4188 :     SET_VARSIZE(res, buf.len);
     197        4188 :     res->header = JSONPATH_VERSION;
     198        4188 :     if (jsonpath->lax)
     199        3834 :         res->header |= JSONPATH_LAX;
     200             : 
     201        4188 :     PG_RETURN_JSONPATH_P(res);
     202             : }
     203             : 
     204             : /*
     205             :  * Converts jsonpath value to a C-string.
     206             :  *
     207             :  * If 'out' argument is non-null, the resulting C-string is stored inside the
     208             :  * StringBuffer.  The resulting string is always returned.
     209             :  */
     210             : static char *
     211        1172 : jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
     212             : {
     213             :     StringInfoData buf;
     214             :     JsonPathItem v;
     215             : 
     216        1172 :     if (!out)
     217             :     {
     218        1172 :         out = &buf;
     219        1172 :         initStringInfo(out);
     220             :     }
     221        1172 :     enlargeStringInfo(out, estimated_len);
     222             : 
     223        1172 :     if (!(in->header & JSONPATH_LAX))
     224           6 :         appendStringInfoString(out, "strict ");
     225             : 
     226        1172 :     jspInit(&v, in);
     227        1172 :     printJsonPathItem(out, &v, false, true);
     228             : 
     229        1172 :     return out->data;
     230             : }
     231             : 
     232             : /*
     233             :  * Recursive function converting given jsonpath parse item and all its
     234             :  * children into a binary representation.
     235             :  */
     236             : static bool
     237       19686 : flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
     238             :                          JsonPathParseItem *item, int nestingLevel,
     239             :                          bool insideArraySubscript)
     240             : {
     241             :     /* position from beginning of jsonpath data */
     242       19686 :     int32       pos = buf->len - JSONPATH_HDRSZ;
     243             :     int32       chld;
     244             :     int32       next;
     245       19686 :     int         argNestingLevel = 0;
     246             : 
     247       19686 :     check_stack_depth();
     248       19686 :     CHECK_FOR_INTERRUPTS();
     249             : 
     250       19686 :     appendStringInfoChar(buf, (char) (item->type));
     251             : 
     252             :     /*
     253             :      * We align buffer to int32 because a series of int32 values often goes
     254             :      * after the header, and we want to read them directly by dereferencing
     255             :      * int32 pointer (see jspInitByBuffer()).
     256             :      */
     257       19686 :     alignStringInfoInt(buf);
     258             : 
     259             :     /*
     260             :      * Reserve space for next item pointer.  Actual value will be recorded
     261             :      * later, after next and children items processing.
     262             :      */
     263       19686 :     next = reserveSpaceForItemPointer(buf);
     264             : 
     265       19686 :     switch (item->type)
     266             :     {
     267        3894 :         case jpiString:
     268             :         case jpiVariable:
     269             :         case jpiKey:
     270        3894 :             appendBinaryStringInfo(buf, &item->value.string.len,
     271             :                                    sizeof(item->value.string.len));
     272        3894 :             appendBinaryStringInfo(buf, item->value.string.val,
     273        3894 :                                    item->value.string.len);
     274        3894 :             appendStringInfoChar(buf, '\0');
     275        3894 :             break;
     276        1740 :         case jpiNumeric:
     277        1740 :             appendBinaryStringInfo(buf, item->value.numeric,
     278        1740 :                                    VARSIZE(item->value.numeric));
     279        1740 :             break;
     280         180 :         case jpiBool:
     281         180 :             appendBinaryStringInfo(buf, &item->value.boolean,
     282             :                                    sizeof(item->value.boolean));
     283         180 :             break;
     284        2544 :         case jpiAnd:
     285             :         case jpiOr:
     286             :         case jpiEqual:
     287             :         case jpiNotEqual:
     288             :         case jpiLess:
     289             :         case jpiGreater:
     290             :         case jpiLessOrEqual:
     291             :         case jpiGreaterOrEqual:
     292             :         case jpiAdd:
     293             :         case jpiSub:
     294             :         case jpiMul:
     295             :         case jpiDiv:
     296             :         case jpiMod:
     297             :         case jpiStartsWith:
     298             :             {
     299             :                 /*
     300             :                  * First, reserve place for left/right arg's positions, then
     301             :                  * record both args and sets actual position in reserved
     302             :                  * places.
     303             :                  */
     304        2544 :                 int32       left = reserveSpaceForItemPointer(buf);
     305        2544 :                 int32       right = reserveSpaceForItemPointer(buf);
     306             : 
     307        2544 :                 if (!item->value.args.left)
     308           0 :                     chld = pos;
     309        2544 :                 else if (!flattenJsonPathParseItem(buf, &chld, escontext,
     310             :                                                    item->value.args.left,
     311             :                                                    nestingLevel + argNestingLevel,
     312             :                                                    insideArraySubscript))
     313          12 :                     return false;
     314        2520 :                 *(int32 *) (buf->data + left) = chld - pos;
     315             : 
     316        2520 :                 if (!item->value.args.right)
     317           0 :                     chld = pos;
     318        2520 :                 else if (!flattenJsonPathParseItem(buf, &chld, escontext,
     319             :                                                    item->value.args.right,
     320             :                                                    nestingLevel + argNestingLevel,
     321             :                                                    insideArraySubscript))
     322           0 :                     return false;
     323        2520 :                 *(int32 *) (buf->data + right) = chld - pos;
     324             :             }
     325        2520 :             break;
     326         120 :         case jpiLikeRegex:
     327             :             {
     328             :                 int32       offs;
     329             : 
     330         120 :                 appendBinaryStringInfo(buf,
     331         120 :                                        &item->value.like_regex.flags,
     332             :                                        sizeof(item->value.like_regex.flags));
     333         120 :                 offs = reserveSpaceForItemPointer(buf);
     334         120 :                 appendBinaryStringInfo(buf,
     335         120 :                                        &item->value.like_regex.patternlen,
     336             :                                        sizeof(item->value.like_regex.patternlen));
     337         120 :                 appendBinaryStringInfo(buf, item->value.like_regex.pattern,
     338         120 :                                        item->value.like_regex.patternlen);
     339         120 :                 appendStringInfoChar(buf, '\0');
     340             : 
     341         120 :                 if (!flattenJsonPathParseItem(buf, &chld, escontext,
     342             :                                               item->value.like_regex.expr,
     343             :                                               nestingLevel,
     344             :                                               insideArraySubscript))
     345           0 :                     return false;
     346         120 :                 *(int32 *) (buf->data + offs) = chld - pos;
     347             :             }
     348         120 :             break;
     349        1638 :         case jpiFilter:
     350        1638 :             argNestingLevel++;
     351             :             /* FALLTHROUGH */
     352        2910 :         case jpiIsUnknown:
     353             :         case jpiNot:
     354             :         case jpiPlus:
     355             :         case jpiMinus:
     356             :         case jpiExists:
     357             :         case jpiDatetime:
     358             :             {
     359        2910 :                 int32       arg = reserveSpaceForItemPointer(buf);
     360             : 
     361        2910 :                 if (!item->value.arg)
     362         336 :                     chld = pos;
     363        2574 :                 else if (!flattenJsonPathParseItem(buf, &chld, escontext,
     364             :                                                    item->value.arg,
     365             :                                                    nestingLevel + argNestingLevel,
     366             :                                                    insideArraySubscript))
     367           0 :                     return false;
     368        2904 :                 *(int32 *) (buf->data + arg) = chld - pos;
     369             :             }
     370        2904 :             break;
     371         114 :         case jpiNull:
     372         114 :             break;
     373        3858 :         case jpiRoot:
     374        3858 :             break;
     375        1122 :         case jpiAnyArray:
     376             :         case jpiAnyKey:
     377        1122 :             break;
     378        1908 :         case jpiCurrent:
     379        1908 :             if (nestingLevel <= 0)
     380          18 :                 ereturn(escontext, false,
     381             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     382             :                          errmsg("@ is not allowed in root expressions")));
     383        1890 :             break;
     384          90 :         case jpiLast:
     385          90 :             if (!insideArraySubscript)
     386          12 :                 ereturn(escontext, false,
     387             :                         (errcode(ERRCODE_SYNTAX_ERROR),
     388             :                          errmsg("LAST is allowed only in array subscripts")));
     389          78 :             break;
     390         336 :         case jpiIndexArray:
     391             :             {
     392         336 :                 int32       nelems = item->value.array.nelems;
     393             :                 int         offset;
     394             :                 int         i;
     395             : 
     396         336 :                 appendBinaryStringInfo(buf, &nelems, sizeof(nelems));
     397             : 
     398         336 :                 offset = buf->len;
     399             : 
     400         336 :                 appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems);
     401             : 
     402         702 :                 for (i = 0; i < nelems; i++)
     403             :                 {
     404             :                     int32      *ppos;
     405             :                     int32       topos;
     406             :                     int32       frompos;
     407             : 
     408         366 :                     if (!flattenJsonPathParseItem(buf, &frompos, escontext,
     409         366 :                                                   item->value.array.elems[i].from,
     410             :                                                   nestingLevel, true))
     411           0 :                         return false;
     412         366 :                     frompos -= pos;
     413             : 
     414         366 :                     if (item->value.array.elems[i].to)
     415             :                     {
     416          42 :                         if (!flattenJsonPathParseItem(buf, &topos, escontext,
     417          42 :                                                       item->value.array.elems[i].to,
     418             :                                                       nestingLevel, true))
     419           0 :                             return false;
     420          42 :                         topos -= pos;
     421             :                     }
     422             :                     else
     423         324 :                         topos = 0;
     424             : 
     425         366 :                     ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
     426             : 
     427         366 :                     ppos[0] = frompos;
     428         366 :                     ppos[1] = topos;
     429             :                 }
     430             :             }
     431         336 :             break;
     432         354 :         case jpiAny:
     433         354 :             appendBinaryStringInfo(buf,
     434         354 :                                    &item->value.anybounds.first,
     435             :                                    sizeof(item->value.anybounds.first));
     436         354 :             appendBinaryStringInfo(buf,
     437         354 :                                    &item->value.anybounds.last,
     438             :                                    sizeof(item->value.anybounds.last));
     439         354 :             break;
     440         516 :         case jpiType:
     441             :         case jpiSize:
     442             :         case jpiAbs:
     443             :         case jpiFloor:
     444             :         case jpiCeiling:
     445             :         case jpiDouble:
     446             :         case jpiKeyValue:
     447         516 :             break;
     448           0 :         default:
     449           0 :             elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
     450             :     }
     451             : 
     452       19626 :     if (item->next)
     453             :     {
     454        7302 :         if (!flattenJsonPathParseItem(buf, &chld, escontext,
     455             :                                       item->next, nestingLevel,
     456             :                                       insideArraySubscript))
     457           0 :             return false;
     458        7296 :         chld -= pos;
     459        7296 :         *(int32 *) (buf->data + next) = chld;
     460             :     }
     461             : 
     462       19620 :     if (result)
     463       15432 :         *result = pos;
     464       19620 :     return true;
     465             : }
     466             : 
     467             : /*
     468             :  * Align StringInfo to int by adding zero padding bytes
     469             :  */
     470             : static void
     471       19686 : alignStringInfoInt(StringInfo buf)
     472             : {
     473       19686 :     switch (INTALIGN(buf->len) - buf->len)
     474             :     {
     475       17298 :         case 3:
     476       17298 :             appendStringInfoCharMacro(buf, 0);
     477             :             /* FALLTHROUGH */
     478             :         case 2:
     479       17634 :             appendStringInfoCharMacro(buf, 0);
     480             :             /* FALLTHROUGH */
     481             :         case 1:
     482       19494 :             appendStringInfoCharMacro(buf, 0);
     483             :             /* FALLTHROUGH */
     484             :         default:
     485       19686 :             break;
     486             :     }
     487       19686 : }
     488             : 
     489             : /*
     490             :  * Reserve space for int32 JsonPathItem pointer.  Now zero pointer is written,
     491             :  * actual value will be recorded at '(int32 *) &buf->data[pos]' later.
     492             :  */
     493             : static int32
     494       27804 : reserveSpaceForItemPointer(StringInfo buf)
     495             : {
     496       27804 :     int32       pos = buf->len;
     497       27804 :     int32       ptr = 0;
     498             : 
     499       27804 :     appendBinaryStringInfo(buf, &ptr, sizeof(ptr));
     500             : 
     501       27804 :     return pos;
     502             : }
     503             : 
     504             : /*
     505             :  * Prints text representation of given jsonpath item and all its children.
     506             :  */
     507             : static void
     508        5246 : printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
     509             :                   bool printBracketes)
     510             : {
     511             :     JsonPathItem elem;
     512             :     int         i;
     513             : 
     514        5246 :     check_stack_depth();
     515        5246 :     CHECK_FOR_INTERRUPTS();
     516             : 
     517        5246 :     switch (v->type)
     518             :     {
     519          42 :         case jpiNull:
     520          42 :             appendStringInfoString(buf, "null");
     521          42 :             break;
     522        1064 :         case jpiKey:
     523        1064 :             if (inKey)
     524        1064 :                 appendStringInfoChar(buf, '.');
     525        1064 :             escape_json(buf, jspGetString(v, NULL));
     526        1064 :             break;
     527          84 :         case jpiString:
     528          84 :             escape_json(buf, jspGetString(v, NULL));
     529          84 :             break;
     530          48 :         case jpiVariable:
     531          48 :             appendStringInfoChar(buf, '$');
     532          48 :             escape_json(buf, jspGetString(v, NULL));
     533          48 :             break;
     534         884 :         case jpiNumeric:
     535         884 :             if (jspHasNext(v))
     536          84 :                 appendStringInfoChar(buf, '(');
     537         884 :             appendStringInfoString(buf,
     538         884 :                                    DatumGetCString(DirectFunctionCall1(numeric_out,
     539             :                                                                        NumericGetDatum(jspGetNumeric(v)))));
     540         884 :             if (jspHasNext(v))
     541          84 :                 appendStringInfoChar(buf, ')');
     542         884 :             break;
     543          12 :         case jpiBool:
     544          12 :             if (jspGetBool(v))
     545           6 :                 appendStringInfoString(buf, "true");
     546             :             else
     547           6 :                 appendStringInfoString(buf, "false");
     548          12 :             break;
     549         740 :         case jpiAnd:
     550             :         case jpiOr:
     551             :         case jpiEqual:
     552             :         case jpiNotEqual:
     553             :         case jpiLess:
     554             :         case jpiGreater:
     555             :         case jpiLessOrEqual:
     556             :         case jpiGreaterOrEqual:
     557             :         case jpiAdd:
     558             :         case jpiSub:
     559             :         case jpiMul:
     560             :         case jpiDiv:
     561             :         case jpiMod:
     562             :         case jpiStartsWith:
     563         740 :             if (printBracketes)
     564         114 :                 appendStringInfoChar(buf, '(');
     565         740 :             jspGetLeftArg(v, &elem);
     566         740 :             printJsonPathItem(buf, &elem, false,
     567         740 :                               operationPriority(elem.type) <=
     568         740 :                               operationPriority(v->type));
     569         740 :             appendStringInfoChar(buf, ' ');
     570         740 :             appendStringInfoString(buf, jspOperationName(v->type));
     571         740 :             appendStringInfoChar(buf, ' ');
     572         740 :             jspGetRightArg(v, &elem);
     573         740 :             printJsonPathItem(buf, &elem, false,
     574         740 :                               operationPriority(elem.type) <=
     575         740 :                               operationPriority(v->type));
     576         740 :             if (printBracketes)
     577         114 :                 appendStringInfoChar(buf, ')');
     578         740 :             break;
     579          48 :         case jpiLikeRegex:
     580          48 :             if (printBracketes)
     581           0 :                 appendStringInfoChar(buf, '(');
     582             : 
     583          48 :             jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
     584          48 :             printJsonPathItem(buf, &elem, false,
     585          48 :                               operationPriority(elem.type) <=
     586          48 :                               operationPriority(v->type));
     587             : 
     588          48 :             appendStringInfoString(buf, " like_regex ");
     589             : 
     590          48 :             escape_json(buf, v->content.like_regex.pattern);
     591             : 
     592          48 :             if (v->content.like_regex.flags)
     593             :             {
     594          36 :                 appendStringInfoString(buf, " flag \"");
     595             : 
     596          36 :                 if (v->content.like_regex.flags & JSP_REGEX_ICASE)
     597          30 :                     appendStringInfoChar(buf, 'i');
     598          36 :                 if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
     599          18 :                     appendStringInfoChar(buf, 's');
     600          36 :                 if (v->content.like_regex.flags & JSP_REGEX_MLINE)
     601          12 :                     appendStringInfoChar(buf, 'm');
     602          36 :                 if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
     603           6 :                     appendStringInfoChar(buf, 'x');
     604          36 :                 if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
     605          18 :                     appendStringInfoChar(buf, 'q');
     606             : 
     607          36 :                 appendStringInfoChar(buf, '"');
     608             :             }
     609             : 
     610          48 :             if (printBracketes)
     611           0 :                 appendStringInfoChar(buf, ')');
     612          48 :             break;
     613          48 :         case jpiPlus:
     614             :         case jpiMinus:
     615          48 :             if (printBracketes)
     616          18 :                 appendStringInfoChar(buf, '(');
     617          48 :             appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
     618          48 :             jspGetArg(v, &elem);
     619          48 :             printJsonPathItem(buf, &elem, false,
     620          48 :                               operationPriority(elem.type) <=
     621          48 :                               operationPriority(v->type));
     622          48 :             if (printBracketes)
     623          18 :                 appendStringInfoChar(buf, ')');
     624          48 :             break;
     625         506 :         case jpiFilter:
     626         506 :             appendStringInfoString(buf, "?(");
     627         506 :             jspGetArg(v, &elem);
     628         506 :             printJsonPathItem(buf, &elem, false, false);
     629         506 :             appendStringInfoChar(buf, ')');
     630         506 :             break;
     631          12 :         case jpiNot:
     632          12 :             appendStringInfoString(buf, "!(");
     633          12 :             jspGetArg(v, &elem);
     634          12 :             printJsonPathItem(buf, &elem, false, false);
     635          12 :             appendStringInfoChar(buf, ')');
     636          12 :             break;
     637           6 :         case jpiIsUnknown:
     638           6 :             appendStringInfoChar(buf, '(');
     639           6 :             jspGetArg(v, &elem);
     640           6 :             printJsonPathItem(buf, &elem, false, false);
     641           6 :             appendStringInfoString(buf, ") is unknown");
     642           6 :             break;
     643          24 :         case jpiExists:
     644          24 :             appendStringInfoString(buf, "exists (");
     645          24 :             jspGetArg(v, &elem);
     646          24 :             printJsonPathItem(buf, &elem, false, false);
     647          24 :             appendStringInfoChar(buf, ')');
     648          24 :             break;
     649         578 :         case jpiCurrent:
     650             :             Assert(!inKey);
     651         578 :             appendStringInfoChar(buf, '@');
     652         578 :             break;
     653         854 :         case jpiRoot:
     654             :             Assert(!inKey);
     655         854 :             appendStringInfoChar(buf, '$');
     656         854 :             break;
     657          12 :         case jpiLast:
     658          12 :             appendStringInfoString(buf, "last");
     659          12 :             break;
     660          86 :         case jpiAnyArray:
     661          86 :             appendStringInfoString(buf, "[*]");
     662          86 :             break;
     663          12 :         case jpiAnyKey:
     664          12 :             if (inKey)
     665          12 :                 appendStringInfoChar(buf, '.');
     666          12 :             appendStringInfoChar(buf, '*');
     667          12 :             break;
     668          60 :         case jpiIndexArray:
     669          60 :             appendStringInfoChar(buf, '[');
     670         138 :             for (i = 0; i < v->content.array.nelems; i++)
     671             :             {
     672             :                 JsonPathItem from;
     673             :                 JsonPathItem to;
     674          78 :                 bool        range = jspGetArraySubscript(v, &from, &to, i);
     675             : 
     676          78 :                 if (i)
     677          18 :                     appendStringInfoChar(buf, ',');
     678             : 
     679          78 :                 printJsonPathItem(buf, &from, false, false);
     680             : 
     681          78 :                 if (range)
     682             :                 {
     683          12 :                     appendStringInfoString(buf, " to ");
     684          12 :                     printJsonPathItem(buf, &to, false, false);
     685             :                 }
     686             :             }
     687          60 :             appendStringInfoChar(buf, ']');
     688          60 :             break;
     689          48 :         case jpiAny:
     690          48 :             if (inKey)
     691          48 :                 appendStringInfoChar(buf, '.');
     692             : 
     693          48 :             if (v->content.anybounds.first == 0 &&
     694          12 :                 v->content.anybounds.last == PG_UINT32_MAX)
     695           6 :                 appendStringInfoString(buf, "**");
     696          42 :             else if (v->content.anybounds.first == v->content.anybounds.last)
     697             :             {
     698          18 :                 if (v->content.anybounds.first == PG_UINT32_MAX)
     699           6 :                     appendStringInfoString(buf, "**{last}");
     700             :                 else
     701          12 :                     appendStringInfo(buf, "**{%u}",
     702             :                                      v->content.anybounds.first);
     703             :             }
     704          24 :             else if (v->content.anybounds.first == PG_UINT32_MAX)
     705           6 :                 appendStringInfo(buf, "**{last to %u}",
     706             :                                  v->content.anybounds.last);
     707          18 :             else if (v->content.anybounds.last == PG_UINT32_MAX)
     708           6 :                 appendStringInfo(buf, "**{%u to last}",
     709             :                                  v->content.anybounds.first);
     710             :             else
     711          12 :                 appendStringInfo(buf, "**{%u to %u}",
     712             :                                  v->content.anybounds.first,
     713             :                                  v->content.anybounds.last);
     714          48 :             break;
     715          30 :         case jpiType:
     716          30 :             appendStringInfoString(buf, ".type()");
     717          30 :             break;
     718           6 :         case jpiSize:
     719           6 :             appendStringInfoString(buf, ".size()");
     720           6 :             break;
     721           6 :         case jpiAbs:
     722           6 :             appendStringInfoString(buf, ".abs()");
     723           6 :             break;
     724           6 :         case jpiFloor:
     725           6 :             appendStringInfoString(buf, ".floor()");
     726           6 :             break;
     727           6 :         case jpiCeiling:
     728           6 :             appendStringInfoString(buf, ".ceiling()");
     729           6 :             break;
     730           6 :         case jpiDouble:
     731           6 :             appendStringInfoString(buf, ".double()");
     732           6 :             break;
     733          12 :         case jpiDatetime:
     734          12 :             appendStringInfoString(buf, ".datetime(");
     735          12 :             if (v->content.arg)
     736             :             {
     737           6 :                 jspGetArg(v, &elem);
     738           6 :                 printJsonPathItem(buf, &elem, false, false);
     739             :             }
     740          12 :             appendStringInfoChar(buf, ')');
     741          12 :             break;
     742           6 :         case jpiKeyValue:
     743           6 :             appendStringInfoString(buf, ".keyvalue()");
     744           6 :             break;
     745           0 :         default:
     746           0 :             elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
     747             :     }
     748             : 
     749        5246 :     if (jspGetNext(v, &elem))
     750        1854 :         printJsonPathItem(buf, &elem, true, true);
     751        5246 : }
     752             : 
     753             : const char *
     754         902 : jspOperationName(JsonPathItemType type)
     755             : {
     756         902 :     switch (type)
     757             :     {
     758          30 :         case jpiAnd:
     759          30 :             return "&&";
     760          54 :         case jpiOr:
     761          54 :             return "||";
     762         162 :         case jpiEqual:
     763         162 :             return "==";
     764           6 :         case jpiNotEqual:
     765           6 :             return "!=";
     766         300 :         case jpiLess:
     767         300 :             return "<";
     768          38 :         case jpiGreater:
     769          38 :             return ">";
     770           6 :         case jpiLessOrEqual:
     771           6 :             return "<=";
     772          30 :         case jpiGreaterOrEqual:
     773          30 :             return ">=";
     774          72 :         case jpiPlus:
     775             :         case jpiAdd:
     776          72 :             return "+";
     777          24 :         case jpiMinus:
     778             :         case jpiSub:
     779          24 :             return "-";
     780          24 :         case jpiMul:
     781          24 :             return "*";
     782           6 :         case jpiDiv:
     783           6 :             return "/";
     784           6 :         case jpiMod:
     785           6 :             return "%";
     786          12 :         case jpiStartsWith:
     787          12 :             return "starts with";
     788           0 :         case jpiLikeRegex:
     789           0 :             return "like_regex";
     790           0 :         case jpiType:
     791           0 :             return "type";
     792           6 :         case jpiSize:
     793           6 :             return "size";
     794          18 :         case jpiKeyValue:
     795          18 :             return "keyvalue";
     796          60 :         case jpiDouble:
     797          60 :             return "double";
     798           6 :         case jpiAbs:
     799           6 :             return "abs";
     800           6 :         case jpiFloor:
     801           6 :             return "floor";
     802           6 :         case jpiCeiling:
     803           6 :             return "ceiling";
     804          30 :         case jpiDatetime:
     805          30 :             return "datetime";
     806           0 :         default:
     807           0 :             elog(ERROR, "unrecognized jsonpath item type: %d", type);
     808             :             return NULL;
     809             :     }
     810             : }
     811             : 
     812             : static int
     813        3152 : operationPriority(JsonPathItemType op)
     814             : {
     815        3152 :     switch (op)
     816             :     {
     817         114 :         case jpiOr:
     818         114 :             return 0;
     819          78 :         case jpiAnd:
     820          78 :             return 1;
     821        1234 :         case jpiEqual:
     822             :         case jpiNotEqual:
     823             :         case jpiLess:
     824             :         case jpiGreater:
     825             :         case jpiLessOrEqual:
     826             :         case jpiGreaterOrEqual:
     827             :         case jpiStartsWith:
     828        1234 :             return 2;
     829         180 :         case jpiAdd:
     830             :         case jpiSub:
     831         180 :             return 3;
     832          66 :         case jpiMul:
     833             :         case jpiDiv:
     834             :         case jpiMod:
     835          66 :             return 4;
     836          84 :         case jpiPlus:
     837             :         case jpiMinus:
     838          84 :             return 5;
     839        1396 :         default:
     840        1396 :             return 6;
     841             :     }
     842             : }
     843             : 
     844             : /******************* Support functions for JsonPath *************************/
     845             : 
     846             : /*
     847             :  * Support macros to read stored values
     848             :  */
     849             : 
     850             : #define read_byte(v, b, p) do {         \
     851             :     (v) = *(uint8*)((b) + (p));         \
     852             :     (p) += 1;                           \
     853             : } while(0)                              \
     854             : 
     855             : #define read_int32(v, b, p) do {        \
     856             :     (v) = *(uint32*)((b) + (p));        \
     857             :     (p) += sizeof(int32);               \
     858             : } while(0)                              \
     859             : 
     860             : #define read_int32_n(v, b, p, n) do {   \
     861             :     (v) = (void *)((b) + (p));          \
     862             :     (p) += sizeof(int32) * (n);         \
     863             : } while(0)                              \
     864             : 
     865             : /*
     866             :  * Read root node and fill root node representation
     867             :  */
     868             : void
     869      192032 : jspInit(JsonPathItem *v, JsonPath *js)
     870             : {
     871             :     Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
     872      192032 :     jspInitByBuffer(v, js->data, 0);
     873      192032 : }
     874             : 
     875             : /*
     876             :  * Read node from buffer and fill its representation
     877             :  */
     878             : void
     879      648062 : jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
     880             : {
     881      648062 :     v->base = base + pos;
     882             : 
     883      648062 :     read_byte(v->type, base, pos);
     884      648062 :     pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
     885      648062 :     read_int32(v->nextPos, base, pos);
     886             : 
     887      648062 :     switch (v->type)
     888             :     {
     889      234306 :         case jpiNull:
     890             :         case jpiRoot:
     891             :         case jpiCurrent:
     892             :         case jpiAnyArray:
     893             :         case jpiAnyKey:
     894             :         case jpiType:
     895             :         case jpiSize:
     896             :         case jpiAbs:
     897             :         case jpiFloor:
     898             :         case jpiCeiling:
     899             :         case jpiDouble:
     900             :         case jpiKeyValue:
     901             :         case jpiLast:
     902      234306 :             break;
     903      195812 :         case jpiKey:
     904             :         case jpiString:
     905             :         case jpiVariable:
     906      195812 :             read_int32(v->content.value.datalen, base, pos);
     907             :             /* FALLTHROUGH */
     908      217660 :         case jpiNumeric:
     909             :         case jpiBool:
     910      217660 :             v->content.value.data = base + pos;
     911      217660 :             break;
     912       94742 :         case jpiAnd:
     913             :         case jpiOr:
     914             :         case jpiAdd:
     915             :         case jpiSub:
     916             :         case jpiMul:
     917             :         case jpiDiv:
     918             :         case jpiMod:
     919             :         case jpiEqual:
     920             :         case jpiNotEqual:
     921             :         case jpiLess:
     922             :         case jpiGreater:
     923             :         case jpiLessOrEqual:
     924             :         case jpiGreaterOrEqual:
     925             :         case jpiStartsWith:
     926       94742 :             read_int32(v->content.args.left, base, pos);
     927       94742 :             read_int32(v->content.args.right, base, pos);
     928       94742 :             break;
     929         444 :         case jpiLikeRegex:
     930         444 :             read_int32(v->content.like_regex.flags, base, pos);
     931         444 :             read_int32(v->content.like_regex.expr, base, pos);
     932         444 :             read_int32(v->content.like_regex.patternlen, base, pos);
     933         444 :             v->content.like_regex.pattern = base + pos;
     934         444 :             break;
     935      100208 :         case jpiNot:
     936             :         case jpiExists:
     937             :         case jpiIsUnknown:
     938             :         case jpiPlus:
     939             :         case jpiMinus:
     940             :         case jpiFilter:
     941             :         case jpiDatetime:
     942      100208 :             read_int32(v->content.arg, base, pos);
     943      100208 :             break;
     944         348 :         case jpiIndexArray:
     945         348 :             read_int32(v->content.array.nelems, base, pos);
     946         348 :             read_int32_n(v->content.array.elems, base, pos,
     947             :                          v->content.array.nelems * 2);
     948         348 :             break;
     949         354 :         case jpiAny:
     950         354 :             read_int32(v->content.anybounds.first, base, pos);
     951         354 :             read_int32(v->content.anybounds.last, base, pos);
     952         354 :             break;
     953           0 :         default:
     954           0 :             elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
     955             :     }
     956      648062 : }
     957             : 
     958             : void
     959      100940 : jspGetArg(JsonPathItem *v, JsonPathItem *a)
     960             : {
     961             :     Assert(v->type == jpiFilter ||
     962             :            v->type == jpiNot ||
     963             :            v->type == jpiIsUnknown ||
     964             :            v->type == jpiExists ||
     965             :            v->type == jpiPlus ||
     966             :            v->type == jpiMinus ||
     967             :            v->type == jpiDatetime);
     968             : 
     969      100940 :     jspInitByBuffer(a, v->base, v->content.arg);
     970      100940 : }
     971             : 
     972             : bool
     973      421010 : jspGetNext(JsonPathItem *v, JsonPathItem *a)
     974             : {
     975      421010 :     if (jspHasNext(v))
     976             :     {
     977             :         Assert(v->type == jpiString ||
     978             :                v->type == jpiNumeric ||
     979             :                v->type == jpiBool ||
     980             :                v->type == jpiNull ||
     981             :                v->type == jpiKey ||
     982             :                v->type == jpiAny ||
     983             :                v->type == jpiAnyArray ||
     984             :                v->type == jpiAnyKey ||
     985             :                v->type == jpiIndexArray ||
     986             :                v->type == jpiFilter ||
     987             :                v->type == jpiCurrent ||
     988             :                v->type == jpiExists ||
     989             :                v->type == jpiRoot ||
     990             :                v->type == jpiVariable ||
     991             :                v->type == jpiLast ||
     992             :                v->type == jpiAdd ||
     993             :                v->type == jpiSub ||
     994             :                v->type == jpiMul ||
     995             :                v->type == jpiDiv ||
     996             :                v->type == jpiMod ||
     997             :                v->type == jpiPlus ||
     998             :                v->type == jpiMinus ||
     999             :                v->type == jpiEqual ||
    1000             :                v->type == jpiNotEqual ||
    1001             :                v->type == jpiGreater ||
    1002             :                v->type == jpiGreaterOrEqual ||
    1003             :                v->type == jpiLess ||
    1004             :                v->type == jpiLessOrEqual ||
    1005             :                v->type == jpiAnd ||
    1006             :                v->type == jpiOr ||
    1007             :                v->type == jpiNot ||
    1008             :                v->type == jpiIsUnknown ||
    1009             :                v->type == jpiType ||
    1010             :                v->type == jpiSize ||
    1011             :                v->type == jpiAbs ||
    1012             :                v->type == jpiFloor ||
    1013             :                v->type == jpiCeiling ||
    1014             :                v->type == jpiDouble ||
    1015             :                v->type == jpiDatetime ||
    1016             :                v->type == jpiKeyValue ||
    1017             :                v->type == jpiStartsWith);
    1018             : 
    1019      189720 :         if (a)
    1020      189720 :             jspInitByBuffer(a, v->base, v->nextPos);
    1021      189720 :         return true;
    1022             :     }
    1023             : 
    1024      231290 :     return false;
    1025             : }
    1026             : 
    1027             : void
    1028       94742 : jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
    1029             : {
    1030             :     Assert(v->type == jpiAnd ||
    1031             :            v->type == jpiOr ||
    1032             :            v->type == jpiEqual ||
    1033             :            v->type == jpiNotEqual ||
    1034             :            v->type == jpiLess ||
    1035             :            v->type == jpiGreater ||
    1036             :            v->type == jpiLessOrEqual ||
    1037             :            v->type == jpiGreaterOrEqual ||
    1038             :            v->type == jpiAdd ||
    1039             :            v->type == jpiSub ||
    1040             :            v->type == jpiMul ||
    1041             :            v->type == jpiDiv ||
    1042             :            v->type == jpiMod ||
    1043             :            v->type == jpiStartsWith);
    1044             : 
    1045       94742 :     jspInitByBuffer(a, v->base, v->content.args.left);
    1046       94742 : }
    1047             : 
    1048             : void
    1049       69776 : jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
    1050             : {
    1051             :     Assert(v->type == jpiAnd ||
    1052             :            v->type == jpiOr ||
    1053             :            v->type == jpiEqual ||
    1054             :            v->type == jpiNotEqual ||
    1055             :            v->type == jpiLess ||
    1056             :            v->type == jpiGreater ||
    1057             :            v->type == jpiLessOrEqual ||
    1058             :            v->type == jpiGreaterOrEqual ||
    1059             :            v->type == jpiAdd ||
    1060             :            v->type == jpiSub ||
    1061             :            v->type == jpiMul ||
    1062             :            v->type == jpiDiv ||
    1063             :            v->type == jpiMod ||
    1064             :            v->type == jpiStartsWith);
    1065             : 
    1066       69776 :     jspInitByBuffer(a, v->base, v->content.args.right);
    1067       69776 : }
    1068             : 
    1069             : bool
    1070        1434 : jspGetBool(JsonPathItem *v)
    1071             : {
    1072             :     Assert(v->type == jpiBool);
    1073             : 
    1074        1434 :     return (bool) *v->content.value.data;
    1075             : }
    1076             : 
    1077             : Numeric
    1078       20240 : jspGetNumeric(JsonPathItem *v)
    1079             : {
    1080             :     Assert(v->type == jpiNumeric);
    1081             : 
    1082       20240 :     return (Numeric) v->content.value.data;
    1083             : }
    1084             : 
    1085             : char *
    1086      195692 : jspGetString(JsonPathItem *v, int32 *len)
    1087             : {
    1088             :     Assert(v->type == jpiKey ||
    1089             :            v->type == jpiString ||
    1090             :            v->type == jpiVariable);
    1091             : 
    1092      195692 :     if (len)
    1093      194496 :         *len = v->content.value.datalen;
    1094      195692 :     return v->content.value.data;
    1095             : }
    1096             : 
    1097             : bool
    1098         366 : jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
    1099             :                      int i)
    1100             : {
    1101             :     Assert(v->type == jpiIndexArray);
    1102             : 
    1103         366 :     jspInitByBuffer(from, v->base, v->content.array.elems[i].from);
    1104             : 
    1105         366 :     if (!v->content.array.elems[i].to)
    1106         324 :         return false;
    1107             : 
    1108          42 :     jspInitByBuffer(to, v->base, v->content.array.elems[i].to);
    1109             : 
    1110          42 :     return true;
    1111             : }

Generated by: LCOV version 1.14