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