Coverage Report

Created: 2025-06-15 06:31

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