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