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