Coverage Report

Created: 2026-08-14 06:37

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