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

Generated by: LCOV version 1.13