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 10700 : jsonpath_in(PG_FUNCTION_ARGS)
99 : {
100 10700 : char *in = PG_GETARG_CSTRING(0);
101 10700 : int len = strlen(in);
102 :
103 10700 : 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 1886 : jsonpath_out(PG_FUNCTION_ARGS)
135 : {
136 1886 : JsonPath *in = PG_GETARG_JSONPATH_P(0);
137 :
138 1886 : 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 10700 : jsonPathFromCstring(char *in, int len, struct Node *escontext)
174 : {
175 10700 : JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
176 : JsonPath *res;
177 : StringInfoData buf;
178 :
179 10334 : if (SOFT_ERROR_OCCURRED(escontext))
180 42 : return (Datum) 0;
181 :
182 10292 : 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 10286 : initStringInfo(&buf);
189 10286 : enlargeStringInfo(&buf, 4 * len /* estimation */ );
190 :
191 10286 : appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
192 :
193 10286 : if (!flattenJsonPathParseItem(&buf, NULL, escontext,
194 : jsonpath->expr, 0, false))
195 12 : return (Datum) 0;
196 :
197 10256 : res = (JsonPath *) buf.data;
198 10256 : SET_VARSIZE(res, buf.len);
199 10256 : res->header = JSONPATH_VERSION;
200 10256 : if (jsonpath->lax)
201 9608 : res->header |= JSONPATH_LAX;
202 :
203 10256 : 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 1886 : jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
214 : {
215 : StringInfoData buf;
216 : JsonPathItem v;
217 :
218 1886 : if (!out)
219 : {
220 1886 : out = &buf;
221 1886 : initStringInfo(out);
222 : }
223 1886 : enlargeStringInfo(out, estimated_len);
224 :
225 1886 : if (!(in->header & JSONPATH_LAX))
226 18 : appendStringInfoString(out, "strict ");
227 :
228 1886 : jspInit(&v, in);
229 1886 : printJsonPathItem(out, &v, false, true);
230 :
231 1886 : 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 35084 : 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 35084 : int32 pos = buf->len - JSONPATH_HDRSZ;
245 : int32 chld;
246 : int32 next;
247 35084 : int argNestingLevel = 0;
248 :
249 35084 : check_stack_depth();
250 35084 : CHECK_FOR_INTERRUPTS();
251 :
252 35084 : 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 35084 : 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 35084 : next = reserveSpaceForItemPointer(buf);
266 :
267 35084 : switch (item->type)
268 : {
269 5936 : case jpiString:
270 : case jpiVariable:
271 : case jpiKey:
272 5936 : appendBinaryStringInfo(buf, &item->value.string.len,
273 : sizeof(item->value.string.len));
274 5936 : appendBinaryStringInfo(buf, item->value.string.val,
275 5936 : item->value.string.len);
276 5936 : appendStringInfoChar(buf, '\0');
277 5936 : 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 5130 : 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 5130 : int32 arg = reserveSpaceForItemPointer(buf);
367 :
368 5130 : if (!item->value.arg)
369 1536 : chld = pos;
370 3594 : else if (!flattenJsonPathParseItem(buf, &chld, escontext,
371 : item->value.arg,
372 : nestingLevel + argNestingLevel,
373 : insideArraySubscript))
374 0 : return false;
375 5124 : *(int32 *) (buf->data + arg) = chld - pos;
376 : }
377 5124 : break;
378 114 : case jpiNull:
379 114 : break;
380 9950 : case jpiRoot:
381 9950 : break;
382 2182 : case jpiAnyArray:
383 : case jpiAnyKey:
384 2182 : 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 1752 : 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 1752 : break;
461 0 : default:
462 0 : elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
463 : }
464 :
465 35024 : if (item->next)
466 : {
467 13518 : if (!flattenJsonPathParseItem(buf, &chld, escontext,
468 : item->next, nestingLevel,
469 : insideArraySubscript))
470 0 : return false;
471 13512 : chld -= pos;
472 13512 : *(int32 *) (buf->data + next) = chld;
473 : }
474 :
475 35018 : if (result)
476 24762 : *result = pos;
477 35018 : return true;
478 : }
479 :
480 : /*
481 : * Align StringInfo to int by adding zero padding bytes
482 : */
483 : static void
484 35084 : alignStringInfoInt(StringInfo buf)
485 : {
486 35084 : switch (INTALIGN(buf->len) - buf->len)
487 : {
488 31916 : case 3:
489 31916 : appendStringInfoCharMacro(buf, 0);
490 : /* FALLTHROUGH */
491 : case 2:
492 32324 : appendStringInfoCharMacro(buf, 0);
493 : /* FALLTHROUGH */
494 : case 1:
495 34634 : appendStringInfoCharMacro(buf, 0);
496 : /* FALLTHROUGH */
497 : default:
498 35084 : break;
499 : }
500 35084 : }
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 47654 : reserveSpaceForItemPointer(StringInfo buf)
508 : {
509 47654 : int32 pos = buf->len;
510 47654 : int32 ptr = 0;
511 :
512 47654 : appendBinaryStringInfo(buf, &ptr, sizeof(ptr));
513 :
514 47654 : return pos;
515 : }
516 :
517 : /*
518 : * Prints text representation of given jsonpath item and all its children.
519 : */
520 : static void
521 6656 : 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 6656 : check_stack_depth();
530 6656 : CHECK_FOR_INTERRUPTS();
531 :
532 6656 : switch (v->type)
533 : {
534 42 : case jpiNull:
535 42 : appendStringInfoString(buf, "null");
536 42 : break;
537 84 : case jpiString:
538 84 : str = jspGetString(v, &len);
539 84 : escape_json_with_len(buf, str, len);
540 84 : break;
541 980 : case jpiNumeric:
542 980 : if (jspHasNext(v))
543 84 : appendStringInfoChar(buf, '(');
544 980 : appendStringInfoString(buf,
545 980 : DatumGetCString(DirectFunctionCall1(numeric_out,
546 : NumericGetDatum(jspGetNumeric(v)))));
547 980 : if (jspHasNext(v))
548 84 : appendStringInfoChar(buf, ')');
549 980 : break;
550 12 : case jpiBool:
551 12 : if (jspGetBool(v))
552 6 : appendStringInfoString(buf, "true");
553 : else
554 6 : appendStringInfoString(buf, "false");
555 12 : break;
556 788 : 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 788 : if (printBracketes)
571 114 : appendStringInfoChar(buf, '(');
572 788 : jspGetLeftArg(v, &elem);
573 788 : printJsonPathItem(buf, &elem, false,
574 788 : operationPriority(elem.type) <=
575 788 : operationPriority(v->type));
576 788 : appendStringInfoChar(buf, ' ');
577 788 : appendStringInfoString(buf, jspOperationName(v->type));
578 788 : appendStringInfoChar(buf, ' ');
579 788 : jspGetRightArg(v, &elem);
580 788 : printJsonPathItem(buf, &elem, false,
581 788 : operationPriority(elem.type) <=
582 788 : operationPriority(v->type));
583 788 : if (printBracketes)
584 114 : appendStringInfoChar(buf, ')');
585 788 : break;
586 12 : case jpiNot:
587 12 : appendStringInfoString(buf, "!(");
588 12 : jspGetArg(v, &elem);
589 12 : printJsonPathItem(buf, &elem, false, false);
590 12 : appendStringInfoChar(buf, ')');
591 12 : break;
592 6 : case jpiIsUnknown:
593 6 : appendStringInfoChar(buf, '(');
594 6 : jspGetArg(v, &elem);
595 6 : printJsonPathItem(buf, &elem, false, false);
596 6 : appendStringInfoString(buf, ") is unknown");
597 6 : break;
598 48 : case jpiPlus:
599 : case jpiMinus:
600 48 : if (printBracketes)
601 18 : appendStringInfoChar(buf, '(');
602 48 : appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
603 48 : jspGetArg(v, &elem);
604 48 : printJsonPathItem(buf, &elem, false,
605 48 : operationPriority(elem.type) <=
606 48 : operationPriority(v->type));
607 48 : if (printBracketes)
608 18 : appendStringInfoChar(buf, ')');
609 48 : break;
610 218 : case jpiAnyArray:
611 218 : appendStringInfoString(buf, "[*]");
612 218 : break;
613 12 : case jpiAnyKey:
614 12 : if (inKey)
615 12 : appendStringInfoChar(buf, '.');
616 12 : appendStringInfoChar(buf, '*');
617 12 : break;
618 96 : case jpiIndexArray:
619 96 : appendStringInfoChar(buf, '[');
620 210 : for (i = 0; i < v->content.array.nelems; i++)
621 : {
622 : JsonPathItem from;
623 : JsonPathItem to;
624 114 : bool range = jspGetArraySubscript(v, &from, &to, i);
625 :
626 114 : if (i)
627 18 : appendStringInfoChar(buf, ',');
628 :
629 114 : printJsonPathItem(buf, &from, false, false);
630 :
631 114 : if (range)
632 : {
633 12 : appendStringInfoString(buf, " to ");
634 12 : printJsonPathItem(buf, &to, false, false);
635 : }
636 : }
637 96 : appendStringInfoChar(buf, ']');
638 96 : break;
639 48 : case jpiAny:
640 48 : if (inKey)
641 48 : appendStringInfoChar(buf, '.');
642 :
643 48 : if (v->content.anybounds.first == 0 &&
644 12 : v->content.anybounds.last == PG_UINT32_MAX)
645 6 : appendStringInfoString(buf, "**");
646 42 : else if (v->content.anybounds.first == v->content.anybounds.last)
647 : {
648 18 : if (v->content.anybounds.first == PG_UINT32_MAX)
649 6 : appendStringInfoString(buf, "**{last}");
650 : else
651 12 : appendStringInfo(buf, "**{%u}",
652 : v->content.anybounds.first);
653 : }
654 24 : else if (v->content.anybounds.first == PG_UINT32_MAX)
655 6 : appendStringInfo(buf, "**{last to %u}",
656 : v->content.anybounds.last);
657 18 : else if (v->content.anybounds.last == PG_UINT32_MAX)
658 6 : appendStringInfo(buf, "**{%u to last}",
659 : v->content.anybounds.first);
660 : else
661 12 : appendStringInfo(buf, "**{%u to %u}",
662 : v->content.anybounds.first,
663 : v->content.anybounds.last);
664 48 : break;
665 1280 : case jpiKey:
666 1280 : if (inKey)
667 1280 : appendStringInfoChar(buf, '.');
668 1280 : str = jspGetString(v, &len);
669 1280 : escape_json_with_len(buf, str, len);
670 1280 : break;
671 602 : case jpiCurrent:
672 : Assert(!inKey);
673 602 : appendStringInfoChar(buf, '@');
674 602 : break;
675 1568 : case jpiRoot:
676 : Assert(!inKey);
677 1568 : appendStringInfoChar(buf, '$');
678 1568 : break;
679 72 : case jpiVariable:
680 72 : appendStringInfoChar(buf, '$');
681 72 : str = jspGetString(v, &len);
682 72 : escape_json_with_len(buf, str, len);
683 72 : break;
684 530 : case jpiFilter:
685 530 : appendStringInfoString(buf, "?(");
686 530 : jspGetArg(v, &elem);
687 530 : printJsonPathItem(buf, &elem, false, false);
688 530 : appendStringInfoChar(buf, ')');
689 530 : break;
690 24 : case jpiExists:
691 24 : appendStringInfoString(buf, "exists (");
692 24 : jspGetArg(v, &elem);
693 24 : printJsonPathItem(buf, &elem, false, false);
694 24 : appendStringInfoChar(buf, ')');
695 24 : break;
696 30 : case jpiType:
697 30 : appendStringInfoString(buf, ".type()");
698 30 : break;
699 6 : case jpiSize:
700 6 : appendStringInfoString(buf, ".size()");
701 6 : break;
702 6 : case jpiAbs:
703 6 : appendStringInfoString(buf, ".abs()");
704 6 : break;
705 6 : case jpiFloor:
706 6 : appendStringInfoString(buf, ".floor()");
707 6 : break;
708 6 : case jpiCeiling:
709 6 : appendStringInfoString(buf, ".ceiling()");
710 6 : break;
711 6 : case jpiDouble:
712 6 : appendStringInfoString(buf, ".double()");
713 6 : break;
714 12 : case jpiDatetime:
715 12 : appendStringInfoString(buf, ".datetime(");
716 12 : if (v->content.arg)
717 : {
718 6 : jspGetArg(v, &elem);
719 6 : printJsonPathItem(buf, &elem, false, false);
720 : }
721 12 : appendStringInfoChar(buf, ')');
722 12 : break;
723 6 : case jpiKeyValue:
724 6 : appendStringInfoString(buf, ".keyvalue()");
725 6 : break;
726 12 : case jpiLast:
727 12 : appendStringInfoString(buf, "last");
728 12 : break;
729 48 : case jpiLikeRegex:
730 48 : if (printBracketes)
731 0 : appendStringInfoChar(buf, '(');
732 :
733 48 : jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
734 48 : printJsonPathItem(buf, &elem, false,
735 48 : operationPriority(elem.type) <=
736 48 : operationPriority(v->type));
737 :
738 48 : appendStringInfoString(buf, " like_regex ");
739 :
740 48 : escape_json_with_len(buf,
741 48 : v->content.like_regex.pattern,
742 : v->content.like_regex.patternlen);
743 :
744 48 : if (v->content.like_regex.flags)
745 : {
746 36 : appendStringInfoString(buf, " flag \"");
747 :
748 36 : if (v->content.like_regex.flags & JSP_REGEX_ICASE)
749 30 : appendStringInfoChar(buf, 'i');
750 36 : if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
751 18 : appendStringInfoChar(buf, 's');
752 36 : if (v->content.like_regex.flags & JSP_REGEX_MLINE)
753 12 : appendStringInfoChar(buf, 'm');
754 36 : if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
755 6 : appendStringInfoChar(buf, 'x');
756 36 : if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
757 18 : appendStringInfoChar(buf, 'q');
758 :
759 36 : appendStringInfoChar(buf, '"');
760 : }
761 :
762 48 : if (printBracketes)
763 0 : appendStringInfoChar(buf, ')');
764 48 : break;
765 6 : case jpiBigint:
766 6 : appendStringInfoString(buf, ".bigint()");
767 6 : break;
768 6 : case jpiBoolean:
769 6 : appendStringInfoString(buf, ".boolean()");
770 6 : break;
771 6 : case jpiDate:
772 6 : appendStringInfoString(buf, ".date()");
773 6 : break;
774 12 : case jpiDecimal:
775 12 : appendStringInfoString(buf, ".decimal(");
776 12 : if (v->content.args.left)
777 : {
778 6 : jspGetLeftArg(v, &elem);
779 6 : printJsonPathItem(buf, &elem, false, false);
780 : }
781 12 : if (v->content.args.right)
782 : {
783 6 : appendStringInfoChar(buf, ',');
784 6 : jspGetRightArg(v, &elem);
785 6 : printJsonPathItem(buf, &elem, false, false);
786 : }
787 12 : appendStringInfoChar(buf, ')');
788 12 : break;
789 6 : case jpiInteger:
790 6 : appendStringInfoString(buf, ".integer()");
791 6 : break;
792 6 : case jpiNumber:
793 6 : appendStringInfoString(buf, ".number()");
794 6 : break;
795 6 : case jpiStringFunc:
796 6 : appendStringInfoString(buf, ".string()");
797 6 : break;
798 12 : case jpiTime:
799 12 : appendStringInfoString(buf, ".time(");
800 12 : if (v->content.arg)
801 : {
802 6 : jspGetArg(v, &elem);
803 6 : printJsonPathItem(buf, &elem, false, false);
804 : }
805 12 : appendStringInfoChar(buf, ')');
806 12 : break;
807 12 : case jpiTimeTz:
808 12 : appendStringInfoString(buf, ".time_tz(");
809 12 : if (v->content.arg)
810 : {
811 6 : jspGetArg(v, &elem);
812 6 : printJsonPathItem(buf, &elem, false, false);
813 : }
814 12 : appendStringInfoChar(buf, ')');
815 12 : break;
816 12 : case jpiTimestamp:
817 12 : appendStringInfoString(buf, ".timestamp(");
818 12 : if (v->content.arg)
819 : {
820 6 : jspGetArg(v, &elem);
821 6 : printJsonPathItem(buf, &elem, false, false);
822 : }
823 12 : appendStringInfoChar(buf, ')');
824 12 : break;
825 12 : case jpiTimestampTz:
826 12 : appendStringInfoString(buf, ".timestamp_tz(");
827 12 : if (v->content.arg)
828 : {
829 6 : jspGetArg(v, &elem);
830 6 : printJsonPathItem(buf, &elem, false, false);
831 : }
832 12 : appendStringInfoChar(buf, ')');
833 12 : break;
834 0 : default:
835 0 : elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
836 : }
837 :
838 6656 : if (jspGetNext(v, &elem))
839 2358 : printJsonPathItem(buf, &elem, true, true);
840 6656 : }
841 :
842 : const char *
843 1532 : jspOperationName(JsonPathItemType type)
844 : {
845 1532 : switch (type)
846 : {
847 30 : case jpiAnd:
848 30 : return "&&";
849 54 : case jpiOr:
850 54 : return "||";
851 168 : case jpiEqual:
852 168 : return "==";
853 6 : case jpiNotEqual:
854 6 : return "!=";
855 300 : case jpiLess:
856 300 : return "<";
857 44 : case jpiGreater:
858 44 : return ">";
859 6 : case jpiLessOrEqual:
860 6 : return "<=";
861 42 : case jpiGreaterOrEqual:
862 42 : return ">=";
863 84 : case jpiAdd:
864 : case jpiPlus:
865 84 : return "+";
866 36 : case jpiSub:
867 : case jpiMinus:
868 36 : return "-";
869 24 : case jpiMul:
870 24 : return "*";
871 6 : case jpiDiv:
872 6 : return "/";
873 6 : case jpiMod:
874 6 : return "%";
875 0 : case jpiType:
876 0 : return "type";
877 6 : case jpiSize:
878 6 : return "size";
879 6 : case jpiAbs:
880 6 : return "abs";
881 6 : case jpiFloor:
882 6 : return "floor";
883 6 : case jpiCeiling:
884 6 : return "ceiling";
885 60 : case jpiDouble:
886 60 : return "double";
887 30 : case jpiDatetime:
888 30 : return "datetime";
889 18 : case jpiKeyValue:
890 18 : return "keyvalue";
891 12 : case jpiStartsWith:
892 12 : return "starts with";
893 0 : case jpiLikeRegex:
894 0 : return "like_regex";
895 78 : case jpiBigint:
896 78 : return "bigint";
897 72 : case jpiBoolean:
898 72 : return "boolean";
899 36 : case jpiDate:
900 36 : return "date";
901 78 : case jpiDecimal:
902 78 : return "decimal";
903 78 : case jpiInteger:
904 78 : return "integer";
905 54 : case jpiNumber:
906 54 : return "number";
907 18 : case jpiStringFunc:
908 18 : return "string";
909 42 : case jpiTime:
910 42 : return "time";
911 42 : case jpiTimeTz:
912 42 : return "time_tz";
913 42 : case jpiTimestamp:
914 42 : return "timestamp";
915 42 : case jpiTimestampTz:
916 42 : 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 3344 : operationPriority(JsonPathItemType op)
925 : {
926 3344 : switch (op)
927 : {
928 114 : case jpiOr:
929 114 : return 0;
930 78 : case jpiAnd:
931 78 : return 1;
932 1282 : case jpiEqual:
933 : case jpiNotEqual:
934 : case jpiLess:
935 : case jpiGreater:
936 : case jpiLessOrEqual:
937 : case jpiGreaterOrEqual:
938 : case jpiStartsWith:
939 1282 : return 2;
940 252 : case jpiAdd:
941 : case jpiSub:
942 252 : return 3;
943 66 : case jpiMul:
944 : case jpiDiv:
945 : case jpiMod:
946 66 : return 4;
947 84 : case jpiPlus:
948 : case jpiMinus:
949 84 : return 5;
950 1468 : default:
951 1468 : 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 201518 : jspInit(JsonPathItem *v, JsonPath *js)
981 : {
982 : Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
983 201518 : jspInitByBuffer(v, js->data, 0);
984 201518 : }
985 :
986 : /*
987 : * Read node from buffer and fill its representation
988 : */
989 : void
990 687588 : jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
991 : {
992 687588 : v->base = base + pos;
993 :
994 687588 : read_byte(v->type, base, pos);
995 687588 : pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
996 687588 : read_int32(v->nextPos, base, pos);
997 :
998 687588 : switch (v->type)
999 : {
1000 252074 : 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 252074 : break;
1020 203194 : case jpiString:
1021 : case jpiKey:
1022 : case jpiVariable:
1023 203194 : read_int32(v->content.value.datalen, base, pos);
1024 : /* FALLTHROUGH */
1025 226668 : case jpiNumeric:
1026 : case jpiBool:
1027 226668 : v->content.value.data = base + pos;
1028 226668 : break;
1029 101168 : 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 101168 : read_int32(v->content.args.left, base, pos);
1045 101168 : read_int32(v->content.args.right, base, pos);
1046 101168 : break;
1047 106274 : 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 106274 : read_int32(v->content.arg, base, pos);
1059 106274 : break;
1060 606 : case jpiIndexArray:
1061 606 : read_int32(v->content.array.nelems, base, pos);
1062 606 : read_int32_n(v->content.array.elems, base, pos,
1063 : v->content.array.nelems * 2);
1064 606 : break;
1065 354 : case jpiAny:
1066 354 : read_int32(v->content.anybounds.first, base, pos);
1067 354 : read_int32(v->content.anybounds.last, base, pos);
1068 354 : break;
1069 444 : case jpiLikeRegex:
1070 444 : read_int32(v->content.like_regex.flags, base, pos);
1071 444 : read_int32(v->content.like_regex.expr, base, pos);
1072 444 : read_int32(v->content.like_regex.patternlen, base, pos);
1073 444 : v->content.like_regex.pattern = base + pos;
1074 444 : break;
1075 0 : default:
1076 0 : elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
1077 : }
1078 687588 : }
1079 :
1080 : void
1081 106070 : 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 106070 : jspInitByBuffer(a, v->base, v->content.arg);
1096 106070 : }
1097 :
1098 : bool
1099 452676 : jspGetNext(JsonPathItem *v, JsonPathItem *a)
1100 : {
1101 452676 : 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 202168 : if (a)
1158 202168 : jspInitByBuffer(a, v->base, v->nextPos);
1159 202168 : return true;
1160 : }
1161 :
1162 250508 : return false;
1163 : }
1164 :
1165 : void
1166 101000 : 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 101000 : jspInitByBuffer(a, v->base, v->content.args.left);
1185 101000 : }
1186 :
1187 : void
1188 75698 : 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 75698 : jspInitByBuffer(a, v->base, v->content.args.right);
1207 75698 : }
1208 :
1209 : bool
1210 1434 : jspGetBool(JsonPathItem *v)
1211 : {
1212 : Assert(v->type == jpiBool);
1213 :
1214 1434 : return (bool) *v->content.value.data;
1215 : }
1216 :
1217 : Numeric
1218 21836 : jspGetNumeric(JsonPathItem *v)
1219 : {
1220 : Assert(v->type == jpiNumeric);
1221 :
1222 21836 : return (Numeric) v->content.value.data;
1223 : }
1224 :
1225 : char *
1226 202258 : jspGetString(JsonPathItem *v, int32 *len)
1227 : {
1228 : Assert(v->type == jpiKey ||
1229 : v->type == jpiString ||
1230 : v->type == jpiVariable);
1231 :
1232 202258 : if (len)
1233 202192 : *len = v->content.value.datalen;
1234 202258 : return v->content.value.data;
1235 : }
1236 :
1237 : bool
1238 642 : jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
1239 : int i)
1240 : {
1241 : Assert(v->type == jpiIndexArray);
1242 :
1243 642 : jspInitByBuffer(from, v->base, v->content.array.elems[i].from);
1244 :
1245 642 : if (!v->content.array.elems[i].to)
1246 594 : return false;
1247 :
1248 48 : jspInitByBuffer(to, v->base, v->content.array.elems[i].to);
1249 :
1250 48 : 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 234 : jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
1281 : {
1282 : struct JsonPathMutableContext cxt;
1283 : JsonPathItem jpi;
1284 :
1285 234 : cxt.varnames = varnames;
1286 234 : cxt.varexprs = varexprs;
1287 234 : cxt.current = jpdsNonDateTime;
1288 234 : cxt.lax = (path->header & JSONPATH_LAX) != 0;
1289 234 : cxt.mutable = false;
1290 :
1291 234 : jspInit(&jpi, path);
1292 234 : (void) jspIsMutableWalker(&jpi, &cxt);
1293 :
1294 234 : return cxt.mutable;
1295 : }
1296 :
1297 : /*
1298 : * Recursive walker for jspIsMutable()
1299 : */
1300 : static enum JsonPathDatatypeStatus
1301 786 : jspIsMutableWalker(JsonPathItem *jpi, struct JsonPathMutableContext *cxt)
1302 : {
1303 : JsonPathItem next;
1304 786 : enum JsonPathDatatypeStatus status = jpdsNonDateTime;
1305 :
1306 1338 : while (!cxt->mutable)
1307 : {
1308 : JsonPathItem arg;
1309 : enum JsonPathDatatypeStatus leftStatus;
1310 : enum JsonPathDatatypeStatus rightStatus;
1311 :
1312 1242 : switch (jpi->type)
1313 : {
1314 288 : case jpiRoot:
1315 : Assert(status == jpdsNonDateTime);
1316 288 : break;
1317 :
1318 144 : case jpiCurrent:
1319 : Assert(status == jpdsNonDateTime);
1320 144 : status = cxt->current;
1321 144 : break;
1322 :
1323 144 : case jpiFilter:
1324 : {
1325 144 : enum JsonPathDatatypeStatus prevStatus = cxt->current;
1326 :
1327 144 : cxt->current = status;
1328 144 : jspGetArg(jpi, &arg);
1329 144 : jspIsMutableWalker(&arg, cxt);
1330 :
1331 144 : cxt->current = prevStatus;
1332 144 : break;
1333 : }
1334 :
1335 54 : case jpiVariable:
1336 : {
1337 : int32 len;
1338 54 : const char *name = jspGetString(jpi, &len);
1339 : ListCell *lc1;
1340 : ListCell *lc2;
1341 :
1342 : Assert(status == jpdsNonDateTime);
1343 :
1344 60 : forboth(lc1, cxt->varnames, lc2, cxt->varexprs)
1345 : {
1346 54 : String *varname = lfirst_node(String, lc1);
1347 54 : Node *varexpr = lfirst(lc2);
1348 :
1349 54 : if (strncmp(varname->sval, name, len))
1350 6 : continue;
1351 :
1352 48 : switch (exprType(varexpr))
1353 : {
1354 30 : case DATEOID:
1355 : case TIMEOID:
1356 : case TIMESTAMPOID:
1357 30 : status = jpdsDateTimeNonZoned;
1358 30 : break;
1359 :
1360 12 : case TIMETZOID:
1361 : case TIMESTAMPTZOID:
1362 12 : status = jpdsDateTimeZoned;
1363 12 : break;
1364 :
1365 6 : default:
1366 6 : status = jpdsNonDateTime;
1367 6 : break;
1368 : }
1369 :
1370 48 : break;
1371 : }
1372 54 : break;
1373 : }
1374 :
1375 180 : case jpiEqual:
1376 : case jpiNotEqual:
1377 : case jpiLess:
1378 : case jpiGreater:
1379 : case jpiLessOrEqual:
1380 : case jpiGreaterOrEqual:
1381 : Assert(status == jpdsNonDateTime);
1382 180 : jspGetLeftArg(jpi, &arg);
1383 180 : leftStatus = jspIsMutableWalker(&arg, cxt);
1384 :
1385 180 : jspGetRightArg(jpi, &arg);
1386 180 : rightStatus = jspIsMutableWalker(&arg, cxt);
1387 :
1388 : /*
1389 : * Comparison of datetime type with different timezone status
1390 : * is mutable.
1391 : */
1392 180 : if (leftStatus != jpdsNonDateTime &&
1393 72 : rightStatus != jpdsNonDateTime &&
1394 36 : (leftStatus == jpdsUnknownDateTime ||
1395 36 : rightStatus == jpdsUnknownDateTime ||
1396 : leftStatus != rightStatus))
1397 42 : cxt->mutable = true;
1398 180 : 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 24 : case jpiIndexArray:
1426 66 : for (int i = 0; i < jpi->content.array.nelems; i++)
1427 : {
1428 : JsonPathItem from;
1429 : JsonPathItem to;
1430 :
1431 42 : if (jspGetArraySubscript(jpi, &from, &to, i))
1432 6 : jspIsMutableWalker(&to, cxt);
1433 :
1434 42 : jspIsMutableWalker(&from, cxt);
1435 : }
1436 : /* FALLTHROUGH */
1437 :
1438 : case jpiAnyArray:
1439 24 : if (!cxt->lax)
1440 0 : status = jpdsNonDateTime;
1441 24 : break;
1442 :
1443 0 : case jpiAny:
1444 0 : if (jpi->content.anybounds.first > 0)
1445 0 : status = jpdsNonDateTime;
1446 0 : break;
1447 :
1448 126 : case jpiDatetime:
1449 126 : if (jpi->content.arg)
1450 : {
1451 : char *template;
1452 :
1453 66 : jspGetArg(jpi, &arg);
1454 66 : if (arg.type != jpiString)
1455 : {
1456 0 : status = jpdsNonDateTime;
1457 0 : break; /* there will be runtime error */
1458 : }
1459 :
1460 66 : template = jspGetString(&arg, NULL);
1461 66 : if (datetime_format_has_tz(template))
1462 36 : status = jpdsDateTimeZoned;
1463 : else
1464 30 : status = jpdsDateTimeNonZoned;
1465 : }
1466 : else
1467 : {
1468 60 : status = jpdsUnknownDateTime;
1469 : }
1470 126 : 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 24 : case jpiNull:
1480 : case jpiString:
1481 : case jpiNumeric:
1482 : case jpiBool:
1483 24 : break;
1484 : /* accessors */
1485 138 : 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 138 : status = jpdsNonDateTime;
1505 138 : break;
1506 :
1507 90 : case jpiTime:
1508 : case jpiDate:
1509 : case jpiTimestamp:
1510 90 : status = jpdsDateTimeNonZoned;
1511 90 : cxt->mutable = true;
1512 90 : break;
1513 :
1514 30 : case jpiTimeTz:
1515 : case jpiTimestampTz:
1516 30 : status = jpdsDateTimeNonZoned;
1517 30 : cxt->mutable = true;
1518 30 : break;
1519 :
1520 : }
1521 :
1522 1242 : if (!jspGetNext(jpi, &next))
1523 690 : break;
1524 :
1525 552 : jpi = &next;
1526 : }
1527 :
1528 786 : return status;
1529 : }
|