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-2022, 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 "funcapi.h"
67 : #include "lib/stringinfo.h"
68 : #include "libpq/pqformat.h"
69 : #include "miscadmin.h"
70 : #include "nodes/nodeFuncs.h"
71 : #include "utils/builtins.h"
72 : #include "utils/formatting.h"
73 : #include "utils/json.h"
74 : #include "utils/jsonpath.h"
75 :
76 :
77 : static Datum jsonPathFromCstring(char *in, int len);
78 : static char *jsonPathToCstring(StringInfo out, JsonPath *in,
79 : int estimated_len);
80 : static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
81 : int nestingLevel, bool insideArraySubscript);
82 : static void alignStringInfoInt(StringInfo buf);
83 : static int32 reserveSpaceForItemPointer(StringInfo buf);
84 : static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
85 : bool printBracketes);
86 : static int operationPriority(JsonPathItemType op);
87 :
88 :
89 : /**************************** INPUT/OUTPUT ********************************/
90 :
91 : /*
92 : * jsonpath type input function
93 : */
94 : Datum
95 6822 : jsonpath_in(PG_FUNCTION_ARGS)
96 : {
97 6822 : char *in = PG_GETARG_CSTRING(0);
98 6822 : int len = strlen(in);
99 :
100 6822 : return jsonPathFromCstring(in, len);
101 : }
102 :
103 : /*
104 : * jsonpath type recv function
105 : *
106 : * The type is sent as text in binary mode, so this is almost the same
107 : * as the input function, but it's prefixed with a version number so we
108 : * can change the binary format sent in future if necessary. For now,
109 : * only version 1 is supported.
110 : */
111 : Datum
112 0 : jsonpath_recv(PG_FUNCTION_ARGS)
113 : {
114 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
115 0 : int version = pq_getmsgint(buf, 1);
116 : char *str;
117 : int nbytes;
118 :
119 0 : if (version == JSONPATH_VERSION)
120 0 : str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
121 : else
122 0 : elog(ERROR, "unsupported jsonpath version number: %d", version);
123 :
124 0 : return jsonPathFromCstring(str, nbytes);
125 : }
126 :
127 : /*
128 : * jsonpath type output function
129 : */
130 : Datum
131 1592 : jsonpath_out(PG_FUNCTION_ARGS)
132 : {
133 1592 : JsonPath *in = PG_GETARG_JSONPATH_P(0);
134 :
135 1592 : PG_RETURN_CSTRING(jsonPathToCstring(NULL, in, VARSIZE(in)));
136 : }
137 :
138 : /*
139 : * jsonpath type send function
140 : *
141 : * Just send jsonpath as a version number, then a string of text
142 : */
143 : Datum
144 0 : jsonpath_send(PG_FUNCTION_ARGS)
145 : {
146 0 : JsonPath *in = PG_GETARG_JSONPATH_P(0);
147 : StringInfoData buf;
148 : StringInfoData jtext;
149 0 : int version = JSONPATH_VERSION;
150 :
151 0 : initStringInfo(&jtext);
152 0 : (void) jsonPathToCstring(&jtext, in, VARSIZE(in));
153 :
154 0 : pq_begintypsend(&buf);
155 0 : pq_sendint8(&buf, version);
156 0 : pq_sendtext(&buf, jtext.data, jtext.len);
157 0 : pfree(jtext.data);
158 :
159 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
160 : }
161 :
162 : /*
163 : * Converts C-string to a jsonpath value.
164 : *
165 : * Uses jsonpath parser to turn string into an AST, then
166 : * flattenJsonPathParseItem() does second pass turning AST into binary
167 : * representation of jsonpath.
168 : */
169 : static Datum
170 6822 : jsonPathFromCstring(char *in, int len)
171 : {
172 6822 : JsonPathParseResult *jsonpath = parsejsonpath(in, len);
173 : JsonPath *res;
174 : StringInfoData buf;
175 :
176 6642 : initStringInfo(&buf);
177 6642 : enlargeStringInfo(&buf, 4 * len /* estimation */ );
178 :
179 6642 : appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
180 :
181 6642 : if (!jsonpath)
182 6 : ereport(ERROR,
183 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
184 : errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
185 : in)));
186 :
187 6636 : flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false);
188 :
189 6618 : res = (JsonPath *) buf.data;
190 6618 : SET_VARSIZE(res, buf.len);
191 6618 : res->header = JSONPATH_VERSION;
192 6618 : if (jsonpath->lax)
193 5934 : res->header |= JSONPATH_LAX;
194 :
195 6618 : PG_RETURN_JSONPATH_P(res);
196 : }
197 :
198 : /*
199 : * Converts jsonpath value to a C-string.
200 : *
201 : * If 'out' argument is non-null, the resulting C-string is stored inside the
202 : * StringBuffer. The resulting string is always returned.
203 : */
204 : static char *
205 1592 : jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
206 : {
207 : StringInfoData buf;
208 : JsonPathItem v;
209 :
210 1592 : if (!out)
211 : {
212 1592 : out = &buf;
213 1592 : initStringInfo(out);
214 : }
215 1592 : enlargeStringInfo(out, estimated_len);
216 :
217 1592 : if (!(in->header & JSONPATH_LAX))
218 30 : appendBinaryStringInfo(out, "strict ", 7);
219 :
220 1592 : jspInit(&v, in);
221 1592 : printJsonPathItem(out, &v, false, true);
222 :
223 1592 : return out->data;
224 : }
225 :
226 : /*
227 : * Recursive function converting given jsonpath parse item and all its
228 : * children into a binary representation.
229 : */
230 : static int
231 24336 : flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
232 : int nestingLevel, bool insideArraySubscript)
233 : {
234 : /* position from beginning of jsonpath data */
235 24336 : int32 pos = buf->len - JSONPATH_HDRSZ;
236 : int32 chld;
237 : int32 next;
238 24336 : int argNestingLevel = 0;
239 :
240 24336 : check_stack_depth();
241 24336 : CHECK_FOR_INTERRUPTS();
242 :
243 24336 : appendStringInfoChar(buf, (char) (item->type));
244 :
245 : /*
246 : * We align buffer to int32 because a series of int32 values often goes
247 : * after the header, and we want to read them directly by dereferencing
248 : * int32 pointer (see jspInitByBuffer()).
249 : */
250 24336 : alignStringInfoInt(buf);
251 :
252 : /*
253 : * Reserve space for next item pointer. Actual value will be recorded
254 : * later, after next and children items processing.
255 : */
256 24336 : next = reserveSpaceForItemPointer(buf);
257 :
258 24336 : switch (item->type)
259 : {
260 4848 : case jpiString:
261 : case jpiVariable:
262 : case jpiKey:
263 4848 : appendBinaryStringInfo(buf, (char *) &item->value.string.len,
264 : sizeof(item->value.string.len));
265 4848 : appendBinaryStringInfo(buf, item->value.string.val,
266 4848 : item->value.string.len);
267 4848 : appendStringInfoChar(buf, '\0');
268 4848 : break;
269 1782 : case jpiNumeric:
270 1782 : appendBinaryStringInfo(buf, (char *) item->value.numeric,
271 1782 : VARSIZE(item->value.numeric));
272 1782 : break;
273 156 : case jpiBool:
274 156 : appendBinaryStringInfo(buf, (char *) &item->value.boolean,
275 : sizeof(item->value.boolean));
276 156 : break;
277 2730 : case jpiAnd:
278 : case jpiOr:
279 : case jpiEqual:
280 : case jpiNotEqual:
281 : case jpiLess:
282 : case jpiGreater:
283 : case jpiLessOrEqual:
284 : case jpiGreaterOrEqual:
285 : case jpiAdd:
286 : case jpiSub:
287 : case jpiMul:
288 : case jpiDiv:
289 : case jpiMod:
290 : case jpiStartsWith:
291 : {
292 : /*
293 : * First, reserve place for left/right arg's positions, then
294 : * record both args and sets actual position in reserved
295 : * places.
296 : */
297 2730 : int32 left = reserveSpaceForItemPointer(buf);
298 2730 : int32 right = reserveSpaceForItemPointer(buf);
299 :
300 2730 : chld = !item->value.args.left ? pos :
301 2730 : flattenJsonPathParseItem(buf, item->value.args.left,
302 : nestingLevel + argNestingLevel,
303 : insideArraySubscript);
304 2718 : *(int32 *) (buf->data + left) = chld - pos;
305 :
306 2718 : chld = !item->value.args.right ? pos :
307 2718 : flattenJsonPathParseItem(buf, item->value.args.right,
308 : nestingLevel + argNestingLevel,
309 : insideArraySubscript);
310 2718 : *(int32 *) (buf->data + right) = chld - pos;
311 : }
312 2718 : break;
313 108 : case jpiLikeRegex:
314 : {
315 : int32 offs;
316 :
317 108 : appendBinaryStringInfo(buf,
318 108 : (char *) &item->value.like_regex.flags,
319 : sizeof(item->value.like_regex.flags));
320 108 : offs = reserveSpaceForItemPointer(buf);
321 108 : appendBinaryStringInfo(buf,
322 108 : (char *) &item->value.like_regex.patternlen,
323 : sizeof(item->value.like_regex.patternlen));
324 108 : appendBinaryStringInfo(buf, item->value.like_regex.pattern,
325 108 : item->value.like_regex.patternlen);
326 108 : appendStringInfoChar(buf, '\0');
327 :
328 108 : chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr,
329 : nestingLevel,
330 : insideArraySubscript);
331 108 : *(int32 *) (buf->data + offs) = chld - pos;
332 : }
333 108 : break;
334 1752 : case jpiFilter:
335 1752 : argNestingLevel++;
336 : /* FALLTHROUGH */
337 3150 : case jpiIsUnknown:
338 : case jpiNot:
339 : case jpiPlus:
340 : case jpiMinus:
341 : case jpiExists:
342 : case jpiDatetime:
343 : {
344 3150 : int32 arg = reserveSpaceForItemPointer(buf);
345 :
346 3150 : chld = !item->value.arg ? pos :
347 2754 : flattenJsonPathParseItem(buf, item->value.arg,
348 : nestingLevel + argNestingLevel,
349 : insideArraySubscript);
350 3144 : *(int32 *) (buf->data + arg) = chld - pos;
351 : }
352 3144 : break;
353 114 : case jpiNull:
354 114 : break;
355 6402 : case jpiRoot:
356 6402 : break;
357 1626 : case jpiAnyArray:
358 : case jpiAnyKey:
359 1626 : break;
360 2028 : case jpiCurrent:
361 2028 : if (nestingLevel <= 0)
362 6 : ereport(ERROR,
363 : (errcode(ERRCODE_SYNTAX_ERROR),
364 : errmsg("@ is not allowed in root expressions")));
365 2022 : break;
366 90 : case jpiLast:
367 90 : if (!insideArraySubscript)
368 12 : ereport(ERROR,
369 : (errcode(ERRCODE_SYNTAX_ERROR),
370 : errmsg("LAST is allowed only in array subscripts")));
371 78 : break;
372 432 : case jpiIndexArray:
373 : {
374 432 : int32 nelems = item->value.array.nelems;
375 : int offset;
376 : int i;
377 :
378 432 : appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
379 :
380 432 : offset = buf->len;
381 :
382 432 : appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems);
383 :
384 912 : for (i = 0; i < nelems; i++)
385 : {
386 : int32 *ppos;
387 : int32 topos;
388 480 : int32 frompos =
389 480 : flattenJsonPathParseItem(buf,
390 480 : item->value.array.elems[i].from,
391 : nestingLevel, true) - pos;
392 :
393 480 : if (item->value.array.elems[i].to)
394 48 : topos = flattenJsonPathParseItem(buf,
395 48 : item->value.array.elems[i].to,
396 : nestingLevel, true) - pos;
397 : else
398 432 : topos = 0;
399 :
400 480 : ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
401 :
402 480 : ppos[0] = frompos;
403 480 : ppos[1] = topos;
404 : }
405 : }
406 432 : break;
407 354 : case jpiAny:
408 354 : appendBinaryStringInfo(buf,
409 354 : (char *) &item->value.anybounds.first,
410 : sizeof(item->value.anybounds.first));
411 354 : appendBinaryStringInfo(buf,
412 354 : (char *) &item->value.anybounds.last,
413 : sizeof(item->value.anybounds.last));
414 354 : break;
415 516 : case jpiType:
416 : case jpiSize:
417 : case jpiAbs:
418 : case jpiFloor:
419 : case jpiCeiling:
420 : case jpiDouble:
421 : case jpiKeyValue:
422 516 : break;
423 0 : default:
424 0 : elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
425 : }
426 :
427 24300 : if (item->next)
428 : {
429 8862 : chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
430 : insideArraySubscript) - pos;
431 8856 : *(int32 *) (buf->data + next) = chld;
432 : }
433 :
434 24294 : return pos;
435 : }
436 :
437 : /*
438 : * Align StringInfo to int by adding zero padding bytes
439 : */
440 : static void
441 24336 : alignStringInfoInt(StringInfo buf)
442 : {
443 24336 : switch (INTALIGN(buf->len) - buf->len)
444 : {
445 21666 : case 3:
446 21666 : appendStringInfoCharMacro(buf, 0);
447 : /* FALLTHROUGH */
448 : case 2:
449 22008 : appendStringInfoCharMacro(buf, 0);
450 : /* FALLTHROUGH */
451 : case 1:
452 24144 : appendStringInfoCharMacro(buf, 0);
453 : /* FALLTHROUGH */
454 : default:
455 24336 : break;
456 : }
457 24336 : }
458 :
459 : /*
460 : * Reserve space for int32 JsonPathItem pointer. Now zero pointer is written,
461 : * actual value will be recorded at '(int32 *) &buf->data[pos]' later.
462 : */
463 : static int32
464 33054 : reserveSpaceForItemPointer(StringInfo buf)
465 : {
466 33054 : int32 pos = buf->len;
467 33054 : int32 ptr = 0;
468 :
469 33054 : appendBinaryStringInfo(buf, (char *) &ptr, sizeof(ptr));
470 :
471 33054 : return pos;
472 : }
473 :
474 : /*
475 : * Prints text representation of given jsonpath item and all its children.
476 : */
477 : static void
478 5954 : printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
479 : bool printBracketes)
480 : {
481 : JsonPathItem elem;
482 : int i;
483 :
484 5954 : check_stack_depth();
485 5954 : CHECK_FOR_INTERRUPTS();
486 :
487 5954 : switch (v->type)
488 : {
489 42 : case jpiNull:
490 42 : appendStringInfoString(buf, "null");
491 42 : break;
492 1244 : case jpiKey:
493 1244 : if (inKey)
494 1244 : appendStringInfoChar(buf, '.');
495 1244 : escape_json(buf, jspGetString(v, NULL));
496 1244 : break;
497 84 : case jpiString:
498 84 : escape_json(buf, jspGetString(v, NULL));
499 84 : break;
500 48 : case jpiVariable:
501 48 : appendStringInfoChar(buf, '$');
502 48 : escape_json(buf, jspGetString(v, NULL));
503 48 : break;
504 836 : case jpiNumeric:
505 836 : if (jspHasNext(v))
506 84 : appendStringInfoChar(buf, '(');
507 836 : appendStringInfoString(buf,
508 836 : DatumGetCString(DirectFunctionCall1(numeric_out,
509 : NumericGetDatum(jspGetNumeric(v)))));
510 836 : if (jspHasNext(v))
511 84 : appendStringInfoChar(buf, ')');
512 836 : break;
513 12 : case jpiBool:
514 12 : if (jspGetBool(v))
515 6 : appendBinaryStringInfo(buf, "true", 4);
516 : else
517 6 : appendBinaryStringInfo(buf, "false", 5);
518 12 : break;
519 740 : case jpiAnd:
520 : case jpiOr:
521 : case jpiEqual:
522 : case jpiNotEqual:
523 : case jpiLess:
524 : case jpiGreater:
525 : case jpiLessOrEqual:
526 : case jpiGreaterOrEqual:
527 : case jpiAdd:
528 : case jpiSub:
529 : case jpiMul:
530 : case jpiDiv:
531 : case jpiMod:
532 : case jpiStartsWith:
533 740 : if (printBracketes)
534 114 : appendStringInfoChar(buf, '(');
535 740 : jspGetLeftArg(v, &elem);
536 740 : printJsonPathItem(buf, &elem, false,
537 740 : operationPriority(elem.type) <=
538 740 : operationPriority(v->type));
539 740 : appendStringInfoChar(buf, ' ');
540 740 : appendStringInfoString(buf, jspOperationName(v->type));
541 740 : appendStringInfoChar(buf, ' ');
542 740 : jspGetRightArg(v, &elem);
543 740 : printJsonPathItem(buf, &elem, false,
544 740 : operationPriority(elem.type) <=
545 740 : operationPriority(v->type));
546 740 : if (printBracketes)
547 114 : appendStringInfoChar(buf, ')');
548 740 : break;
549 48 : case jpiLikeRegex:
550 48 : if (printBracketes)
551 0 : appendStringInfoChar(buf, '(');
552 :
553 48 : jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
554 48 : printJsonPathItem(buf, &elem, false,
555 48 : operationPriority(elem.type) <=
556 48 : operationPriority(v->type));
557 :
558 48 : appendBinaryStringInfo(buf, " like_regex ", 12);
559 :
560 48 : escape_json(buf, v->content.like_regex.pattern);
561 :
562 48 : if (v->content.like_regex.flags)
563 : {
564 36 : appendBinaryStringInfo(buf, " flag \"", 7);
565 :
566 36 : if (v->content.like_regex.flags & JSP_REGEX_ICASE)
567 30 : appendStringInfoChar(buf, 'i');
568 36 : if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
569 18 : appendStringInfoChar(buf, 's');
570 36 : if (v->content.like_regex.flags & JSP_REGEX_MLINE)
571 12 : appendStringInfoChar(buf, 'm');
572 36 : if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
573 6 : appendStringInfoChar(buf, 'x');
574 36 : if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
575 18 : appendStringInfoChar(buf, 'q');
576 :
577 36 : appendStringInfoChar(buf, '"');
578 : }
579 :
580 48 : if (printBracketes)
581 0 : appendStringInfoChar(buf, ')');
582 48 : break;
583 48 : case jpiPlus:
584 : case jpiMinus:
585 48 : if (printBracketes)
586 18 : appendStringInfoChar(buf, '(');
587 48 : appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
588 48 : jspGetArg(v, &elem);
589 48 : printJsonPathItem(buf, &elem, false,
590 48 : operationPriority(elem.type) <=
591 48 : operationPriority(v->type));
592 48 : if (printBracketes)
593 18 : appendStringInfoChar(buf, ')');
594 48 : break;
595 506 : case jpiFilter:
596 506 : appendBinaryStringInfo(buf, "?(", 2);
597 506 : jspGetArg(v, &elem);
598 506 : printJsonPathItem(buf, &elem, false, false);
599 506 : appendStringInfoChar(buf, ')');
600 506 : break;
601 12 : case jpiNot:
602 12 : appendBinaryStringInfo(buf, "!(", 2);
603 12 : jspGetArg(v, &elem);
604 12 : printJsonPathItem(buf, &elem, false, false);
605 12 : appendStringInfoChar(buf, ')');
606 12 : break;
607 6 : case jpiIsUnknown:
608 6 : appendStringInfoChar(buf, '(');
609 6 : jspGetArg(v, &elem);
610 6 : printJsonPathItem(buf, &elem, false, false);
611 6 : appendBinaryStringInfo(buf, ") is unknown", 12);
612 6 : break;
613 24 : case jpiExists:
614 24 : appendBinaryStringInfo(buf, "exists (", 8);
615 24 : jspGetArg(v, &elem);
616 24 : printJsonPathItem(buf, &elem, false, false);
617 24 : appendStringInfoChar(buf, ')');
618 24 : break;
619 578 : case jpiCurrent:
620 : Assert(!inKey);
621 578 : appendStringInfoChar(buf, '@');
622 578 : break;
623 1346 : case jpiRoot:
624 : Assert(!inKey);
625 1346 : appendStringInfoChar(buf, '$');
626 1346 : break;
627 12 : case jpiLast:
628 12 : appendBinaryStringInfo(buf, "last", 4);
629 12 : break;
630 146 : case jpiAnyArray:
631 146 : appendBinaryStringInfo(buf, "[*]", 3);
632 146 : break;
633 12 : case jpiAnyKey:
634 12 : if (inKey)
635 12 : appendStringInfoChar(buf, '.');
636 12 : appendStringInfoChar(buf, '*');
637 12 : break;
638 84 : case jpiIndexArray:
639 84 : appendStringInfoChar(buf, '[');
640 186 : for (i = 0; i < v->content.array.nelems; i++)
641 : {
642 : JsonPathItem from;
643 : JsonPathItem to;
644 102 : bool range = jspGetArraySubscript(v, &from, &to, i);
645 :
646 102 : if (i)
647 18 : appendStringInfoChar(buf, ',');
648 :
649 102 : printJsonPathItem(buf, &from, false, false);
650 :
651 102 : if (range)
652 : {
653 12 : appendBinaryStringInfo(buf, " to ", 4);
654 12 : printJsonPathItem(buf, &to, false, false);
655 : }
656 : }
657 84 : appendStringInfoChar(buf, ']');
658 84 : break;
659 48 : case jpiAny:
660 48 : if (inKey)
661 48 : appendStringInfoChar(buf, '.');
662 :
663 48 : if (v->content.anybounds.first == 0 &&
664 12 : v->content.anybounds.last == PG_UINT32_MAX)
665 6 : appendBinaryStringInfo(buf, "**", 2);
666 42 : else if (v->content.anybounds.first == v->content.anybounds.last)
667 : {
668 18 : if (v->content.anybounds.first == PG_UINT32_MAX)
669 6 : appendStringInfoString(buf, "**{last}");
670 : else
671 12 : appendStringInfo(buf, "**{%u}",
672 : v->content.anybounds.first);
673 : }
674 24 : else if (v->content.anybounds.first == PG_UINT32_MAX)
675 6 : appendStringInfo(buf, "**{last to %u}",
676 : v->content.anybounds.last);
677 18 : else if (v->content.anybounds.last == PG_UINT32_MAX)
678 6 : appendStringInfo(buf, "**{%u to last}",
679 : v->content.anybounds.first);
680 : else
681 12 : appendStringInfo(buf, "**{%u to %u}",
682 : v->content.anybounds.first,
683 : v->content.anybounds.last);
684 48 : break;
685 30 : case jpiType:
686 30 : appendBinaryStringInfo(buf, ".type()", 7);
687 30 : break;
688 6 : case jpiSize:
689 6 : appendBinaryStringInfo(buf, ".size()", 7);
690 6 : break;
691 6 : case jpiAbs:
692 6 : appendBinaryStringInfo(buf, ".abs()", 6);
693 6 : break;
694 6 : case jpiFloor:
695 6 : appendBinaryStringInfo(buf, ".floor()", 8);
696 6 : break;
697 6 : case jpiCeiling:
698 6 : appendBinaryStringInfo(buf, ".ceiling()", 10);
699 6 : break;
700 6 : case jpiDouble:
701 6 : appendBinaryStringInfo(buf, ".double()", 9);
702 6 : break;
703 12 : case jpiDatetime:
704 12 : appendBinaryStringInfo(buf, ".datetime(", 10);
705 12 : if (v->content.arg)
706 : {
707 6 : jspGetArg(v, &elem);
708 6 : printJsonPathItem(buf, &elem, false, false);
709 : }
710 12 : appendStringInfoChar(buf, ')');
711 12 : break;
712 6 : case jpiKeyValue:
713 6 : appendBinaryStringInfo(buf, ".keyvalue()", 11);
714 6 : break;
715 0 : default:
716 0 : elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
717 : }
718 :
719 5954 : if (jspGetNext(v, &elem))
720 2118 : printJsonPathItem(buf, &elem, true, true);
721 5954 : }
722 :
723 : const char *
724 902 : jspOperationName(JsonPathItemType type)
725 : {
726 902 : switch (type)
727 : {
728 30 : case jpiAnd:
729 30 : return "&&";
730 54 : case jpiOr:
731 54 : return "||";
732 162 : case jpiEqual:
733 162 : return "==";
734 6 : case jpiNotEqual:
735 6 : return "!=";
736 300 : case jpiLess:
737 300 : return "<";
738 38 : case jpiGreater:
739 38 : return ">";
740 6 : case jpiLessOrEqual:
741 6 : return "<=";
742 30 : case jpiGreaterOrEqual:
743 30 : return ">=";
744 72 : case jpiPlus:
745 : case jpiAdd:
746 72 : return "+";
747 24 : case jpiMinus:
748 : case jpiSub:
749 24 : return "-";
750 24 : case jpiMul:
751 24 : return "*";
752 6 : case jpiDiv:
753 6 : return "/";
754 6 : case jpiMod:
755 6 : return "%";
756 12 : case jpiStartsWith:
757 12 : return "starts with";
758 0 : case jpiLikeRegex:
759 0 : return "like_regex";
760 0 : case jpiType:
761 0 : return "type";
762 6 : case jpiSize:
763 6 : return "size";
764 18 : case jpiKeyValue:
765 18 : return "keyvalue";
766 60 : case jpiDouble:
767 60 : return "double";
768 6 : case jpiAbs:
769 6 : return "abs";
770 6 : case jpiFloor:
771 6 : return "floor";
772 6 : case jpiCeiling:
773 6 : return "ceiling";
774 30 : case jpiDatetime:
775 30 : return "datetime";
776 0 : default:
777 0 : elog(ERROR, "unrecognized jsonpath item type: %d", type);
778 : return NULL;
779 : }
780 : }
781 :
782 : static int
783 3152 : operationPriority(JsonPathItemType op)
784 : {
785 3152 : switch (op)
786 : {
787 114 : case jpiOr:
788 114 : return 0;
789 78 : case jpiAnd:
790 78 : return 1;
791 1234 : case jpiEqual:
792 : case jpiNotEqual:
793 : case jpiLess:
794 : case jpiGreater:
795 : case jpiLessOrEqual:
796 : case jpiGreaterOrEqual:
797 : case jpiStartsWith:
798 1234 : return 2;
799 180 : case jpiAdd:
800 : case jpiSub:
801 180 : return 3;
802 66 : case jpiMul:
803 : case jpiDiv:
804 : case jpiMod:
805 66 : return 4;
806 84 : case jpiPlus:
807 : case jpiMinus:
808 84 : return 5;
809 1396 : default:
810 1396 : return 6;
811 : }
812 : }
813 :
814 : /******************* Support functions for JsonPath *************************/
815 :
816 : /*
817 : * Support macros to read stored values
818 : */
819 :
820 : #define read_byte(v, b, p) do { \
821 : (v) = *(uint8*)((b) + (p)); \
822 : (p) += 1; \
823 : } while(0) \
824 :
825 : #define read_int32(v, b, p) do { \
826 : (v) = *(uint32*)((b) + (p)); \
827 : (p) += sizeof(int32); \
828 : } while(0) \
829 :
830 : #define read_int32_n(v, b, p, n) do { \
831 : (v) = (void *)((b) + (p)); \
832 : (p) += sizeof(int32) * (n); \
833 : } while(0) \
834 :
835 : /*
836 : * Read root node and fill root node representation
837 : */
838 : void
839 799646 : jspInit(JsonPathItem *v, JsonPath *js)
840 : {
841 : Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
842 799646 : jspInitByBuffer(v, js->data, 0);
843 799646 : }
844 :
845 : /*
846 : * Read node from buffer and fill its representation
847 : */
848 : void
849 1268396 : jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
850 : {
851 1268396 : v->base = base + pos;
852 :
853 1268396 : read_byte(v->type, base, pos);
854 1268396 : pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
855 1268396 : read_int32(v->nextPos, base, pos);
856 :
857 1268396 : switch (v->type)
858 : {
859 846132 : case jpiNull:
860 : case jpiRoot:
861 : case jpiCurrent:
862 : case jpiAnyArray:
863 : case jpiAnyKey:
864 : case jpiType:
865 : case jpiSize:
866 : case jpiAbs:
867 : case jpiFloor:
868 : case jpiCeiling:
869 : case jpiDouble:
870 : case jpiKeyValue:
871 : case jpiLast:
872 846132 : break;
873 200432 : case jpiKey:
874 : case jpiString:
875 : case jpiVariable:
876 200432 : read_int32(v->content.value.datalen, base, pos);
877 : /* FALLTHROUGH */
878 222292 : case jpiNumeric:
879 : case jpiBool:
880 222292 : v->content.value.data = base + pos;
881 222292 : break;
882 97946 : case jpiAnd:
883 : case jpiOr:
884 : case jpiAdd:
885 : case jpiSub:
886 : case jpiMul:
887 : case jpiDiv:
888 : case jpiMod:
889 : case jpiEqual:
890 : case jpiNotEqual:
891 : case jpiLess:
892 : case jpiGreater:
893 : case jpiLessOrEqual:
894 : case jpiGreaterOrEqual:
895 : case jpiStartsWith:
896 97946 : read_int32(v->content.args.left, base, pos);
897 97946 : read_int32(v->content.args.right, base, pos);
898 97946 : break;
899 444 : case jpiLikeRegex:
900 444 : read_int32(v->content.like_regex.flags, base, pos);
901 444 : read_int32(v->content.like_regex.expr, base, pos);
902 444 : read_int32(v->content.like_regex.patternlen, base, pos);
903 444 : v->content.like_regex.pattern = base + pos;
904 444 : break;
905 100784 : case jpiNot:
906 : case jpiExists:
907 : case jpiIsUnknown:
908 : case jpiPlus:
909 : case jpiMinus:
910 : case jpiFilter:
911 : case jpiDatetime:
912 100784 : read_int32(v->content.arg, base, pos);
913 100784 : break;
914 444 : case jpiIndexArray:
915 444 : read_int32(v->content.array.nelems, base, pos);
916 444 : read_int32_n(v->content.array.elems, base, pos,
917 : v->content.array.nelems * 2);
918 444 : break;
919 354 : case jpiAny:
920 354 : read_int32(v->content.anybounds.first, base, pos);
921 354 : read_int32(v->content.anybounds.last, base, pos);
922 354 : break;
923 0 : default:
924 0 : elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
925 : }
926 1268396 : }
927 :
928 : void
929 102668 : jspGetArg(JsonPathItem *v, JsonPathItem *a)
930 : {
931 : Assert(v->type == jpiFilter ||
932 : v->type == jpiNot ||
933 : v->type == jpiIsUnknown ||
934 : v->type == jpiExists ||
935 : v->type == jpiPlus ||
936 : v->type == jpiMinus ||
937 : v->type == jpiDatetime);
938 :
939 102668 : jspInitByBuffer(a, v->base, v->content.arg);
940 102668 : }
941 :
942 : bool
943 1037444 : jspGetNext(JsonPathItem *v, JsonPathItem *a)
944 : {
945 1037444 : if (jspHasNext(v))
946 : {
947 : Assert(v->type == jpiString ||
948 : v->type == jpiNumeric ||
949 : v->type == jpiBool ||
950 : v->type == jpiNull ||
951 : v->type == jpiKey ||
952 : v->type == jpiAny ||
953 : v->type == jpiAnyArray ||
954 : v->type == jpiAnyKey ||
955 : v->type == jpiIndexArray ||
956 : v->type == jpiFilter ||
957 : v->type == jpiCurrent ||
958 : v->type == jpiExists ||
959 : v->type == jpiRoot ||
960 : v->type == jpiVariable ||
961 : v->type == jpiLast ||
962 : v->type == jpiAdd ||
963 : v->type == jpiSub ||
964 : v->type == jpiMul ||
965 : v->type == jpiDiv ||
966 : v->type == jpiMod ||
967 : v->type == jpiPlus ||
968 : v->type == jpiMinus ||
969 : v->type == jpiEqual ||
970 : v->type == jpiNotEqual ||
971 : v->type == jpiGreater ||
972 : v->type == jpiGreaterOrEqual ||
973 : v->type == jpiLess ||
974 : v->type == jpiLessOrEqual ||
975 : v->type == jpiAnd ||
976 : v->type == jpiOr ||
977 : v->type == jpiNot ||
978 : v->type == jpiIsUnknown ||
979 : v->type == jpiType ||
980 : v->type == jpiSize ||
981 : v->type == jpiAbs ||
982 : v->type == jpiFloor ||
983 : v->type == jpiCeiling ||
984 : v->type == jpiDouble ||
985 : v->type == jpiDatetime ||
986 : v->type == jpiKeyValue ||
987 : v->type == jpiStartsWith);
988 :
989 194520 : if (a)
990 194520 : jspInitByBuffer(a, v->base, v->nextPos);
991 194520 : return true;
992 : }
993 :
994 842924 : return false;
995 : }
996 :
997 : void
998 97946 : jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
999 : {
1000 : Assert(v->type == jpiAnd ||
1001 : v->type == jpiOr ||
1002 : v->type == jpiEqual ||
1003 : v->type == jpiNotEqual ||
1004 : v->type == jpiLess ||
1005 : v->type == jpiGreater ||
1006 : v->type == jpiLessOrEqual ||
1007 : v->type == jpiGreaterOrEqual ||
1008 : v->type == jpiAdd ||
1009 : v->type == jpiSub ||
1010 : v->type == jpiMul ||
1011 : v->type == jpiDiv ||
1012 : v->type == jpiMod ||
1013 : v->type == jpiStartsWith);
1014 :
1015 97946 : jspInitByBuffer(a, v->base, v->content.args.left);
1016 97946 : }
1017 :
1018 : void
1019 72650 : jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
1020 : {
1021 : Assert(v->type == jpiAnd ||
1022 : v->type == jpiOr ||
1023 : v->type == jpiEqual ||
1024 : v->type == jpiNotEqual ||
1025 : v->type == jpiLess ||
1026 : v->type == jpiGreater ||
1027 : v->type == jpiLessOrEqual ||
1028 : v->type == jpiGreaterOrEqual ||
1029 : v->type == jpiAdd ||
1030 : v->type == jpiSub ||
1031 : v->type == jpiMul ||
1032 : v->type == jpiDiv ||
1033 : v->type == jpiMod ||
1034 : v->type == jpiStartsWith);
1035 :
1036 72650 : jspInitByBuffer(a, v->base, v->content.args.right);
1037 72650 : }
1038 :
1039 : bool
1040 1416 : jspGetBool(JsonPathItem *v)
1041 : {
1042 : Assert(v->type == jpiBool);
1043 :
1044 1416 : return (bool) *v->content.value.data;
1045 : }
1046 :
1047 : Numeric
1048 20246 : jspGetNumeric(JsonPathItem *v)
1049 : {
1050 : Assert(v->type == jpiNumeric);
1051 :
1052 20246 : return (Numeric) v->content.value.data;
1053 : }
1054 :
1055 : char *
1056 199658 : jspGetString(JsonPathItem *v, int32 *len)
1057 : {
1058 : Assert(v->type == jpiKey ||
1059 : v->type == jpiString ||
1060 : v->type == jpiVariable);
1061 :
1062 199658 : if (len)
1063 198216 : *len = v->content.value.datalen;
1064 199658 : return v->content.value.data;
1065 : }
1066 :
1067 : bool
1068 474 : jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
1069 : int i)
1070 : {
1071 : Assert(v->type == jpiIndexArray);
1072 :
1073 474 : jspInitByBuffer(from, v->base, v->content.array.elems[i].from);
1074 :
1075 474 : if (!v->content.array.elems[i].to)
1076 426 : return false;
1077 :
1078 48 : jspInitByBuffer(to, v->base, v->content.array.elems[i].to);
1079 :
1080 48 : return true;
1081 : }
1082 :
1083 : /* SQL/JSON datatype status: */
1084 : typedef enum JsonPathDatatypeStatus
1085 : {
1086 : jpdsNonDateTime, /* null, bool, numeric, string, array, object */
1087 : jpdsUnknownDateTime, /* unknown datetime type */
1088 : jpdsDateTimeZoned, /* timetz, timestamptz */
1089 : jpdsDateTimeNonZoned /* time, timestamp, date */
1090 : } JsonPathDatatypeStatus;
1091 :
1092 : /* Context for jspIsMutableWalker() */
1093 : typedef struct JsonPathMutableContext
1094 : {
1095 : List *varnames; /* list of variable names */
1096 : List *varexprs; /* list of variable expressions */
1097 : JsonPathDatatypeStatus current; /* status of @ item */
1098 : bool lax; /* jsonpath is lax or strict */
1099 : bool mutable; /* resulting mutability status */
1100 : } JsonPathMutableContext;
1101 :
1102 : /*
1103 : * Recursive walker for jspIsMutable()
1104 : */
1105 : static JsonPathDatatypeStatus
1106 402 : jspIsMutableWalker(JsonPathItem *jpi, JsonPathMutableContext *cxt)
1107 : {
1108 : JsonPathItem next;
1109 402 : JsonPathDatatypeStatus status = jpdsNonDateTime;
1110 :
1111 678 : while (!cxt->mutable)
1112 : {
1113 : JsonPathItem arg;
1114 : JsonPathDatatypeStatus leftStatus;
1115 : JsonPathDatatypeStatus rightStatus;
1116 :
1117 672 : switch (jpi->type)
1118 : {
1119 162 : case jpiRoot:
1120 : Assert(status == jpdsNonDateTime);
1121 162 : break;
1122 :
1123 66 : case jpiCurrent:
1124 : Assert(status == jpdsNonDateTime);
1125 66 : status = cxt->current;
1126 66 : break;
1127 :
1128 66 : case jpiFilter:
1129 : {
1130 66 : JsonPathDatatypeStatus prevStatus = cxt->current;
1131 :
1132 66 : cxt->current = status;
1133 66 : jspGetArg(jpi, &arg);
1134 66 : jspIsMutableWalker(&arg, cxt);
1135 :
1136 66 : cxt->current = prevStatus;
1137 66 : break;
1138 : }
1139 :
1140 54 : case jpiVariable:
1141 : {
1142 : int32 len;
1143 54 : const char *name = jspGetString(jpi, &len);
1144 : ListCell *lc1;
1145 : ListCell *lc2;
1146 :
1147 : Assert(status == jpdsNonDateTime);
1148 :
1149 60 : forboth(lc1, cxt->varnames, lc2, cxt->varexprs)
1150 : {
1151 54 : String *varname = lfirst_node(String, lc1);
1152 54 : Node *varexpr = lfirst(lc2);
1153 :
1154 54 : if (strncmp(varname->sval, name, len))
1155 6 : continue;
1156 :
1157 48 : switch (exprType(varexpr))
1158 : {
1159 30 : case DATEOID:
1160 : case TIMEOID:
1161 : case TIMESTAMPOID:
1162 30 : status = jpdsDateTimeNonZoned;
1163 30 : break;
1164 :
1165 12 : case TIMETZOID:
1166 : case TIMESTAMPTZOID:
1167 12 : status = jpdsDateTimeZoned;
1168 12 : break;
1169 :
1170 6 : default:
1171 6 : status = jpdsNonDateTime;
1172 6 : break;
1173 : }
1174 :
1175 48 : break;
1176 : }
1177 54 : break;
1178 : }
1179 :
1180 90 : case jpiEqual:
1181 : case jpiNotEqual:
1182 : case jpiLess:
1183 : case jpiGreater:
1184 : case jpiLessOrEqual:
1185 : case jpiGreaterOrEqual:
1186 : Assert(status == jpdsNonDateTime);
1187 90 : jspGetLeftArg(jpi, &arg);
1188 90 : leftStatus = jspIsMutableWalker(&arg, cxt);
1189 :
1190 90 : jspGetRightArg(jpi, &arg);
1191 90 : rightStatus = jspIsMutableWalker(&arg, cxt);
1192 :
1193 : /*
1194 : * Comparison of datetime type with different timezone status
1195 : * is mutable.
1196 : */
1197 90 : if (leftStatus != jpdsNonDateTime &&
1198 72 : rightStatus != jpdsNonDateTime &&
1199 36 : (leftStatus == jpdsUnknownDateTime ||
1200 36 : rightStatus == jpdsUnknownDateTime ||
1201 : leftStatus != rightStatus))
1202 42 : cxt->mutable = true;
1203 90 : break;
1204 :
1205 0 : case jpiNot:
1206 : case jpiIsUnknown:
1207 : case jpiExists:
1208 : case jpiPlus:
1209 : case jpiMinus:
1210 : Assert(status == jpdsNonDateTime);
1211 0 : jspGetArg(jpi, &arg);
1212 0 : jspIsMutableWalker(&arg, cxt);
1213 0 : break;
1214 :
1215 0 : case jpiAnd:
1216 : case jpiOr:
1217 : case jpiAdd:
1218 : case jpiSub:
1219 : case jpiMul:
1220 : case jpiDiv:
1221 : case jpiMod:
1222 : case jpiStartsWith:
1223 : Assert(status == jpdsNonDateTime);
1224 0 : jspGetLeftArg(jpi, &arg);
1225 0 : jspIsMutableWalker(&arg, cxt);
1226 0 : jspGetRightArg(jpi, &arg);
1227 0 : jspIsMutableWalker(&arg, cxt);
1228 0 : break;
1229 :
1230 24 : case jpiIndexArray:
1231 66 : for (int i = 0; i < jpi->content.array.nelems; i++)
1232 : {
1233 : JsonPathItem from;
1234 : JsonPathItem to;
1235 :
1236 42 : if (jspGetArraySubscript(jpi, &from, &to, i))
1237 6 : jspIsMutableWalker(&to, cxt);
1238 :
1239 42 : jspIsMutableWalker(&from, cxt);
1240 : }
1241 : /* FALLTHROUGH */
1242 :
1243 : case jpiAnyArray:
1244 24 : if (!cxt->lax)
1245 0 : status = jpdsNonDateTime;
1246 24 : break;
1247 :
1248 0 : case jpiAny:
1249 0 : if (jpi->content.anybounds.first > 0)
1250 0 : status = jpdsNonDateTime;
1251 0 : break;
1252 :
1253 126 : case jpiDatetime:
1254 126 : if (jpi->content.arg)
1255 : {
1256 : char *template;
1257 : int flags;
1258 :
1259 66 : jspGetArg(jpi, &arg);
1260 66 : if (arg.type != jpiString)
1261 : {
1262 0 : status = jpdsNonDateTime;
1263 0 : break; /* there will be runtime error */
1264 : }
1265 :
1266 66 : template = jspGetString(&arg, NULL);
1267 66 : flags = datetime_format_flags(template, NULL);
1268 66 : if (flags & DCH_ZONED)
1269 36 : status = jpdsDateTimeZoned;
1270 : else
1271 30 : status = jpdsDateTimeNonZoned;
1272 : }
1273 : else
1274 : {
1275 60 : status = jpdsUnknownDateTime;
1276 : }
1277 126 : break;
1278 :
1279 0 : case jpiLikeRegex:
1280 : Assert(status == jpdsNonDateTime);
1281 0 : jspInitByBuffer(&arg, jpi->base, jpi->content.like_regex.expr);
1282 0 : jspIsMutableWalker(&arg, cxt);
1283 0 : break;
1284 :
1285 : /* literals */
1286 84 : case jpiNull:
1287 : case jpiString:
1288 : case jpiNumeric:
1289 : case jpiBool:
1290 : /* accessors */
1291 : case jpiKey:
1292 : case jpiAnyKey:
1293 : /* special items */
1294 : case jpiSubscript:
1295 : case jpiLast:
1296 : /* item methods */
1297 : case jpiType:
1298 : case jpiSize:
1299 : case jpiAbs:
1300 : case jpiFloor:
1301 : case jpiCeiling:
1302 : case jpiDouble:
1303 : case jpiKeyValue:
1304 84 : status = jpdsNonDateTime;
1305 84 : break;
1306 : }
1307 :
1308 672 : if (!jspGetNext(jpi, &next))
1309 396 : break;
1310 :
1311 276 : jpi = &next;
1312 : }
1313 :
1314 402 : return status;
1315 : }
1316 :
1317 : /*
1318 : * Check whether jsonpath expression is immutable or not.
1319 : */
1320 : bool
1321 108 : jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
1322 : {
1323 : JsonPathMutableContext cxt;
1324 : JsonPathItem jpi;
1325 :
1326 108 : cxt.varnames = varnames;
1327 108 : cxt.varexprs = varexprs;
1328 108 : cxt.current = jpdsNonDateTime;
1329 108 : cxt.lax = (path->header & JSONPATH_LAX) != 0;
1330 108 : cxt.mutable = false;
1331 :
1332 108 : jspInit(&jpi, path);
1333 108 : jspIsMutableWalker(&jpi, &cxt);
1334 :
1335 108 : return cxt.mutable;
1336 : }
|