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-2023, 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 "nodes/miscnodes.h"
70 : #include "miscadmin.h"
71 : #include "utils/builtins.h"
72 : #include "utils/json.h"
73 : #include "utils/jsonpath.h"
74 :
75 :
76 : static Datum jsonPathFromCstring(char *in, int len, struct Node *escontext);
77 : static char *jsonPathToCstring(StringInfo out, JsonPath *in,
78 : int estimated_len);
79 : static bool flattenJsonPathParseItem(StringInfo buf, int *result,
80 : struct Node *escontext,
81 : JsonPathParseItem *item,
82 : int nestingLevel, bool insideArraySubscript);
83 : static void alignStringInfoInt(StringInfo buf);
84 : static int32 reserveSpaceForItemPointer(StringInfo buf);
85 : static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
86 : bool printBracketes);
87 : static int operationPriority(JsonPathItemType op);
88 :
89 :
90 : /**************************** INPUT/OUTPUT ********************************/
91 :
92 : /*
93 : * jsonpath type input function
94 : */
95 : Datum
96 4578 : jsonpath_in(PG_FUNCTION_ARGS)
97 : {
98 4578 : char *in = PG_GETARG_CSTRING(0);
99 4578 : int len = strlen(in);
100 :
101 4578 : return jsonPathFromCstring(in, len, fcinfo->context);
102 : }
103 :
104 : /*
105 : * jsonpath type recv function
106 : *
107 : * The type is sent as text in binary mode, so this is almost the same
108 : * as the input function, but it's prefixed with a version number so we
109 : * can change the binary format sent in future if necessary. For now,
110 : * only version 1 is supported.
111 : */
112 : Datum
113 0 : jsonpath_recv(PG_FUNCTION_ARGS)
114 : {
115 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
116 0 : int version = pq_getmsgint(buf, 1);
117 : char *str;
118 : int nbytes;
119 :
120 0 : if (version == JSONPATH_VERSION)
121 0 : str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
122 : else
123 0 : elog(ERROR, "unsupported jsonpath version number: %d", version);
124 :
125 0 : return jsonPathFromCstring(str, nbytes, NULL);
126 : }
127 :
128 : /*
129 : * jsonpath type output function
130 : */
131 : Datum
132 1148 : jsonpath_out(PG_FUNCTION_ARGS)
133 : {
134 1148 : JsonPath *in = PG_GETARG_JSONPATH_P(0);
135 :
136 1148 : PG_RETURN_CSTRING(jsonPathToCstring(NULL, in, VARSIZE(in)));
137 : }
138 :
139 : /*
140 : * jsonpath type send function
141 : *
142 : * Just send jsonpath as a version number, then a string of text
143 : */
144 : Datum
145 0 : jsonpath_send(PG_FUNCTION_ARGS)
146 : {
147 0 : JsonPath *in = PG_GETARG_JSONPATH_P(0);
148 : StringInfoData buf;
149 : StringInfoData jtext;
150 0 : int version = JSONPATH_VERSION;
151 :
152 0 : initStringInfo(&jtext);
153 0 : (void) jsonPathToCstring(&jtext, in, VARSIZE(in));
154 :
155 0 : pq_begintypsend(&buf);
156 0 : pq_sendint8(&buf, version);
157 0 : pq_sendtext(&buf, jtext.data, jtext.len);
158 0 : pfree(jtext.data);
159 :
160 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
161 : }
162 :
163 : /*
164 : * Converts C-string to a jsonpath value.
165 : *
166 : * Uses jsonpath parser to turn string into an AST, then
167 : * flattenJsonPathParseItem() does second pass turning AST into binary
168 : * representation of jsonpath.
169 : */
170 : static Datum
171 4578 : jsonPathFromCstring(char *in, int len, struct Node *escontext)
172 : {
173 4578 : JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
174 : JsonPath *res;
175 : StringInfoData buf;
176 :
177 4254 : if (SOFT_ERROR_OCCURRED(escontext))
178 36 : return (Datum) 0;
179 :
180 4218 : if (!jsonpath)
181 6 : ereturn(escontext, (Datum) 0,
182 : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
183 : errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
184 : in)));
185 :
186 4212 : initStringInfo(&buf);
187 4212 : enlargeStringInfo(&buf, 4 * len /* estimation */ );
188 :
189 4212 : appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
190 :
191 4212 : if (!flattenJsonPathParseItem(&buf, NULL, escontext,
192 : jsonpath->expr, 0, false))
193 12 : return (Datum) 0;
194 :
195 4182 : res = (JsonPath *) buf.data;
196 4182 : SET_VARSIZE(res, buf.len);
197 4182 : res->header = JSONPATH_VERSION;
198 4182 : if (jsonpath->lax)
199 3828 : res->header |= JSONPATH_LAX;
200 :
201 4182 : PG_RETURN_JSONPATH_P(res);
202 : }
203 :
204 : /*
205 : * Converts jsonpath value to a C-string.
206 : *
207 : * If 'out' argument is non-null, the resulting C-string is stored inside the
208 : * StringBuffer. The resulting string is always returned.
209 : */
210 : static char *
211 1148 : jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
212 : {
213 : StringInfoData buf;
214 : JsonPathItem v;
215 :
216 1148 : if (!out)
217 : {
218 1148 : out = &buf;
219 1148 : initStringInfo(out);
220 : }
221 1148 : enlargeStringInfo(out, estimated_len);
222 :
223 1148 : if (!(in->header & JSONPATH_LAX))
224 6 : appendStringInfoString(out, "strict ");
225 :
226 1148 : jspInit(&v, in);
227 1148 : printJsonPathItem(out, &v, false, true);
228 :
229 1148 : return out->data;
230 : }
231 :
232 : /*
233 : * Recursive function converting given jsonpath parse item and all its
234 : * children into a binary representation.
235 : */
236 : static bool
237 19686 : flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
238 : JsonPathParseItem *item, int nestingLevel,
239 : bool insideArraySubscript)
240 : {
241 : /* position from beginning of jsonpath data */
242 19686 : int32 pos = buf->len - JSONPATH_HDRSZ;
243 : int32 chld;
244 : int32 next;
245 19686 : int argNestingLevel = 0;
246 :
247 19686 : check_stack_depth();
248 19686 : CHECK_FOR_INTERRUPTS();
249 :
250 19686 : appendStringInfoChar(buf, (char) (item->type));
251 :
252 : /*
253 : * We align buffer to int32 because a series of int32 values often goes
254 : * after the header, and we want to read them directly by dereferencing
255 : * int32 pointer (see jspInitByBuffer()).
256 : */
257 19686 : alignStringInfoInt(buf);
258 :
259 : /*
260 : * Reserve space for next item pointer. Actual value will be recorded
261 : * later, after next and children items processing.
262 : */
263 19686 : next = reserveSpaceForItemPointer(buf);
264 :
265 19686 : switch (item->type)
266 : {
267 3870 : case jpiString:
268 : case jpiVariable:
269 : case jpiKey:
270 3870 : appendBinaryStringInfo(buf, &item->value.string.len,
271 : sizeof(item->value.string.len));
272 3870 : appendBinaryStringInfo(buf, item->value.string.val,
273 3870 : item->value.string.len);
274 3870 : appendStringInfoChar(buf, '\0');
275 3870 : break;
276 1740 : case jpiNumeric:
277 1740 : appendBinaryStringInfo(buf, item->value.numeric,
278 1740 : VARSIZE(item->value.numeric));
279 1740 : break;
280 180 : case jpiBool:
281 180 : appendBinaryStringInfo(buf, &item->value.boolean,
282 : sizeof(item->value.boolean));
283 180 : break;
284 2544 : case jpiAnd:
285 : case jpiOr:
286 : case jpiEqual:
287 : case jpiNotEqual:
288 : case jpiLess:
289 : case jpiGreater:
290 : case jpiLessOrEqual:
291 : case jpiGreaterOrEqual:
292 : case jpiAdd:
293 : case jpiSub:
294 : case jpiMul:
295 : case jpiDiv:
296 : case jpiMod:
297 : case jpiStartsWith:
298 : {
299 : /*
300 : * First, reserve place for left/right arg's positions, then
301 : * record both args and sets actual position in reserved
302 : * places.
303 : */
304 2544 : int32 left = reserveSpaceForItemPointer(buf);
305 2544 : int32 right = reserveSpaceForItemPointer(buf);
306 :
307 2544 : if (!item->value.args.left)
308 0 : chld = pos;
309 2544 : else if (!flattenJsonPathParseItem(buf, &chld, escontext,
310 : item->value.args.left,
311 : nestingLevel + argNestingLevel,
312 : insideArraySubscript))
313 12 : return false;
314 2520 : *(int32 *) (buf->data + left) = chld - pos;
315 :
316 2520 : if (!item->value.args.right)
317 0 : chld = pos;
318 2520 : else if (!flattenJsonPathParseItem(buf, &chld, escontext,
319 : item->value.args.right,
320 : nestingLevel + argNestingLevel,
321 : insideArraySubscript))
322 0 : return false;
323 2520 : *(int32 *) (buf->data + right) = chld - pos;
324 : }
325 2520 : break;
326 120 : case jpiLikeRegex:
327 : {
328 : int32 offs;
329 :
330 120 : appendBinaryStringInfo(buf,
331 120 : &item->value.like_regex.flags,
332 : sizeof(item->value.like_regex.flags));
333 120 : offs = reserveSpaceForItemPointer(buf);
334 120 : appendBinaryStringInfo(buf,
335 120 : &item->value.like_regex.patternlen,
336 : sizeof(item->value.like_regex.patternlen));
337 120 : appendBinaryStringInfo(buf, item->value.like_regex.pattern,
338 120 : item->value.like_regex.patternlen);
339 120 : appendStringInfoChar(buf, '\0');
340 :
341 120 : if (!flattenJsonPathParseItem(buf, &chld, escontext,
342 : item->value.like_regex.expr,
343 : nestingLevel,
344 : insideArraySubscript))
345 0 : return false;
346 120 : *(int32 *) (buf->data + offs) = chld - pos;
347 : }
348 120 : break;
349 1638 : case jpiFilter:
350 1638 : argNestingLevel++;
351 : /* FALLTHROUGH */
352 2928 : case jpiIsUnknown:
353 : case jpiNot:
354 : case jpiPlus:
355 : case jpiMinus:
356 : case jpiExists:
357 : case jpiDatetime:
358 : {
359 2928 : int32 arg = reserveSpaceForItemPointer(buf);
360 :
361 2928 : if (!item->value.arg)
362 354 : chld = pos;
363 2574 : else if (!flattenJsonPathParseItem(buf, &chld, escontext,
364 : item->value.arg,
365 : nestingLevel + argNestingLevel,
366 : insideArraySubscript))
367 0 : return false;
368 2922 : *(int32 *) (buf->data + arg) = chld - pos;
369 : }
370 2922 : break;
371 114 : case jpiNull:
372 114 : break;
373 3864 : case jpiRoot:
374 3864 : break;
375 1122 : case jpiAnyArray:
376 : case jpiAnyKey:
377 1122 : break;
378 1908 : case jpiCurrent:
379 1908 : if (nestingLevel <= 0)
380 18 : ereturn(escontext, false,
381 : (errcode(ERRCODE_SYNTAX_ERROR),
382 : errmsg("@ is not allowed in root expressions")));
383 1890 : break;
384 90 : case jpiLast:
385 90 : if (!insideArraySubscript)
386 12 : ereturn(escontext, false,
387 : (errcode(ERRCODE_SYNTAX_ERROR),
388 : errmsg("LAST is allowed only in array subscripts")));
389 78 : break;
390 336 : case jpiIndexArray:
391 : {
392 336 : int32 nelems = item->value.array.nelems;
393 : int offset;
394 : int i;
395 :
396 336 : appendBinaryStringInfo(buf, &nelems, sizeof(nelems));
397 :
398 336 : offset = buf->len;
399 :
400 336 : appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems);
401 :
402 702 : for (i = 0; i < nelems; i++)
403 : {
404 : int32 *ppos;
405 : int32 topos;
406 : int32 frompos;
407 :
408 366 : if (!flattenJsonPathParseItem(buf, &frompos, escontext,
409 366 : item->value.array.elems[i].from,
410 : nestingLevel, true))
411 0 : return false;
412 366 : frompos -= pos;
413 :
414 366 : if (item->value.array.elems[i].to)
415 : {
416 42 : if (!flattenJsonPathParseItem(buf, &topos, escontext,
417 42 : item->value.array.elems[i].to,
418 : nestingLevel, true))
419 0 : return false;
420 42 : topos -= pos;
421 : }
422 : else
423 324 : topos = 0;
424 :
425 366 : ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
426 :
427 366 : ppos[0] = frompos;
428 366 : ppos[1] = topos;
429 : }
430 : }
431 336 : break;
432 354 : case jpiAny:
433 354 : appendBinaryStringInfo(buf,
434 354 : &item->value.anybounds.first,
435 : sizeof(item->value.anybounds.first));
436 354 : appendBinaryStringInfo(buf,
437 354 : &item->value.anybounds.last,
438 : sizeof(item->value.anybounds.last));
439 354 : break;
440 516 : case jpiType:
441 : case jpiSize:
442 : case jpiAbs:
443 : case jpiFloor:
444 : case jpiCeiling:
445 : case jpiDouble:
446 : case jpiKeyValue:
447 516 : break;
448 0 : default:
449 0 : elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
450 : }
451 :
452 19626 : if (item->next)
453 : {
454 7308 : if (!flattenJsonPathParseItem(buf, &chld, escontext,
455 : item->next, nestingLevel,
456 : insideArraySubscript))
457 0 : return false;
458 7302 : chld -= pos;
459 7302 : *(int32 *) (buf->data + next) = chld;
460 : }
461 :
462 19620 : if (result)
463 15438 : *result = pos;
464 19620 : return true;
465 : }
466 :
467 : /*
468 : * Align StringInfo to int by adding zero padding bytes
469 : */
470 : static void
471 19686 : alignStringInfoInt(StringInfo buf)
472 : {
473 19686 : switch (INTALIGN(buf->len) - buf->len)
474 : {
475 17298 : case 3:
476 17298 : appendStringInfoCharMacro(buf, 0);
477 : /* FALLTHROUGH */
478 : case 2:
479 17634 : appendStringInfoCharMacro(buf, 0);
480 : /* FALLTHROUGH */
481 : case 1:
482 19494 : appendStringInfoCharMacro(buf, 0);
483 : /* FALLTHROUGH */
484 : default:
485 19686 : break;
486 : }
487 19686 : }
488 :
489 : /*
490 : * Reserve space for int32 JsonPathItem pointer. Now zero pointer is written,
491 : * actual value will be recorded at '(int32 *) &buf->data[pos]' later.
492 : */
493 : static int32
494 27822 : reserveSpaceForItemPointer(StringInfo buf)
495 : {
496 27822 : int32 pos = buf->len;
497 27822 : int32 ptr = 0;
498 :
499 27822 : appendBinaryStringInfo(buf, &ptr, sizeof(ptr));
500 :
501 27822 : return pos;
502 : }
503 :
504 : /*
505 : * Prints text representation of given jsonpath item and all its children.
506 : */
507 : static void
508 5210 : printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
509 : bool printBracketes)
510 : {
511 : JsonPathItem elem;
512 : int i;
513 :
514 5210 : check_stack_depth();
515 5210 : CHECK_FOR_INTERRUPTS();
516 :
517 5210 : switch (v->type)
518 : {
519 42 : case jpiNull:
520 42 : appendStringInfoString(buf, "null");
521 42 : break;
522 1052 : case jpiKey:
523 1052 : if (inKey)
524 1052 : appendStringInfoChar(buf, '.');
525 1052 : escape_json(buf, jspGetString(v, NULL));
526 1052 : break;
527 72 : case jpiString:
528 72 : escape_json(buf, jspGetString(v, NULL));
529 72 : break;
530 48 : case jpiVariable:
531 48 : appendStringInfoChar(buf, '$');
532 48 : escape_json(buf, jspGetString(v, NULL));
533 48 : break;
534 884 : case jpiNumeric:
535 884 : if (jspHasNext(v))
536 84 : appendStringInfoChar(buf, '(');
537 884 : appendStringInfoString(buf,
538 884 : DatumGetCString(DirectFunctionCall1(numeric_out,
539 : NumericGetDatum(jspGetNumeric(v)))));
540 884 : if (jspHasNext(v))
541 84 : appendStringInfoChar(buf, ')');
542 884 : break;
543 12 : case jpiBool:
544 12 : if (jspGetBool(v))
545 6 : appendStringInfoString(buf, "true");
546 : else
547 6 : appendStringInfoString(buf, "false");
548 12 : break;
549 740 : case jpiAnd:
550 : case jpiOr:
551 : case jpiEqual:
552 : case jpiNotEqual:
553 : case jpiLess:
554 : case jpiGreater:
555 : case jpiLessOrEqual:
556 : case jpiGreaterOrEqual:
557 : case jpiAdd:
558 : case jpiSub:
559 : case jpiMul:
560 : case jpiDiv:
561 : case jpiMod:
562 : case jpiStartsWith:
563 740 : if (printBracketes)
564 114 : appendStringInfoChar(buf, '(');
565 740 : jspGetLeftArg(v, &elem);
566 740 : printJsonPathItem(buf, &elem, false,
567 740 : operationPriority(elem.type) <=
568 740 : operationPriority(v->type));
569 740 : appendStringInfoChar(buf, ' ');
570 740 : appendStringInfoString(buf, jspOperationName(v->type));
571 740 : appendStringInfoChar(buf, ' ');
572 740 : jspGetRightArg(v, &elem);
573 740 : printJsonPathItem(buf, &elem, false,
574 740 : operationPriority(elem.type) <=
575 740 : operationPriority(v->type));
576 740 : if (printBracketes)
577 114 : appendStringInfoChar(buf, ')');
578 740 : break;
579 48 : case jpiLikeRegex:
580 48 : if (printBracketes)
581 0 : appendStringInfoChar(buf, '(');
582 :
583 48 : jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
584 48 : printJsonPathItem(buf, &elem, false,
585 48 : operationPriority(elem.type) <=
586 48 : operationPriority(v->type));
587 :
588 48 : appendStringInfoString(buf, " like_regex ");
589 :
590 48 : escape_json(buf, v->content.like_regex.pattern);
591 :
592 48 : if (v->content.like_regex.flags)
593 : {
594 36 : appendStringInfoString(buf, " flag \"");
595 :
596 36 : if (v->content.like_regex.flags & JSP_REGEX_ICASE)
597 30 : appendStringInfoChar(buf, 'i');
598 36 : if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
599 18 : appendStringInfoChar(buf, 's');
600 36 : if (v->content.like_regex.flags & JSP_REGEX_MLINE)
601 12 : appendStringInfoChar(buf, 'm');
602 36 : if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
603 6 : appendStringInfoChar(buf, 'x');
604 36 : if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
605 18 : appendStringInfoChar(buf, 'q');
606 :
607 36 : appendStringInfoChar(buf, '"');
608 : }
609 :
610 48 : if (printBracketes)
611 0 : appendStringInfoChar(buf, ')');
612 48 : break;
613 48 : case jpiPlus:
614 : case jpiMinus:
615 48 : if (printBracketes)
616 18 : appendStringInfoChar(buf, '(');
617 48 : appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
618 48 : jspGetArg(v, &elem);
619 48 : printJsonPathItem(buf, &elem, false,
620 48 : operationPriority(elem.type) <=
621 48 : operationPriority(v->type));
622 48 : if (printBracketes)
623 18 : appendStringInfoChar(buf, ')');
624 48 : break;
625 506 : case jpiFilter:
626 506 : appendStringInfoString(buf, "?(");
627 506 : jspGetArg(v, &elem);
628 506 : printJsonPathItem(buf, &elem, false, false);
629 506 : appendStringInfoChar(buf, ')');
630 506 : break;
631 12 : case jpiNot:
632 12 : appendStringInfoString(buf, "!(");
633 12 : jspGetArg(v, &elem);
634 12 : printJsonPathItem(buf, &elem, false, false);
635 12 : appendStringInfoChar(buf, ')');
636 12 : break;
637 6 : case jpiIsUnknown:
638 6 : appendStringInfoChar(buf, '(');
639 6 : jspGetArg(v, &elem);
640 6 : printJsonPathItem(buf, &elem, false, false);
641 6 : appendStringInfoString(buf, ") is unknown");
642 6 : break;
643 24 : case jpiExists:
644 24 : appendStringInfoString(buf, "exists (");
645 24 : jspGetArg(v, &elem);
646 24 : printJsonPathItem(buf, &elem, false, false);
647 24 : appendStringInfoChar(buf, ')');
648 24 : break;
649 578 : case jpiCurrent:
650 : Assert(!inKey);
651 578 : appendStringInfoChar(buf, '@');
652 578 : break;
653 842 : case jpiRoot:
654 : Assert(!inKey);
655 842 : appendStringInfoChar(buf, '$');
656 842 : break;
657 12 : case jpiLast:
658 12 : appendStringInfoString(buf, "last");
659 12 : break;
660 86 : case jpiAnyArray:
661 86 : appendStringInfoString(buf, "[*]");
662 86 : break;
663 12 : case jpiAnyKey:
664 12 : if (inKey)
665 12 : appendStringInfoChar(buf, '.');
666 12 : appendStringInfoChar(buf, '*');
667 12 : break;
668 60 : case jpiIndexArray:
669 60 : appendStringInfoChar(buf, '[');
670 138 : for (i = 0; i < v->content.array.nelems; i++)
671 : {
672 : JsonPathItem from;
673 : JsonPathItem to;
674 78 : bool range = jspGetArraySubscript(v, &from, &to, i);
675 :
676 78 : if (i)
677 18 : appendStringInfoChar(buf, ',');
678 :
679 78 : printJsonPathItem(buf, &from, false, false);
680 :
681 78 : if (range)
682 : {
683 12 : appendStringInfoString(buf, " to ");
684 12 : printJsonPathItem(buf, &to, false, false);
685 : }
686 : }
687 60 : appendStringInfoChar(buf, ']');
688 60 : break;
689 48 : case jpiAny:
690 48 : if (inKey)
691 48 : appendStringInfoChar(buf, '.');
692 :
693 48 : if (v->content.anybounds.first == 0 &&
694 12 : v->content.anybounds.last == PG_UINT32_MAX)
695 6 : appendStringInfoString(buf, "**");
696 42 : else if (v->content.anybounds.first == v->content.anybounds.last)
697 : {
698 18 : if (v->content.anybounds.first == PG_UINT32_MAX)
699 6 : appendStringInfoString(buf, "**{last}");
700 : else
701 12 : appendStringInfo(buf, "**{%u}",
702 : v->content.anybounds.first);
703 : }
704 24 : else if (v->content.anybounds.first == PG_UINT32_MAX)
705 6 : appendStringInfo(buf, "**{last to %u}",
706 : v->content.anybounds.last);
707 18 : else if (v->content.anybounds.last == PG_UINT32_MAX)
708 6 : appendStringInfo(buf, "**{%u to last}",
709 : v->content.anybounds.first);
710 : else
711 12 : appendStringInfo(buf, "**{%u to %u}",
712 : v->content.anybounds.first,
713 : v->content.anybounds.last);
714 48 : break;
715 30 : case jpiType:
716 30 : appendStringInfoString(buf, ".type()");
717 30 : break;
718 6 : case jpiSize:
719 6 : appendStringInfoString(buf, ".size()");
720 6 : break;
721 6 : case jpiAbs:
722 6 : appendStringInfoString(buf, ".abs()");
723 6 : break;
724 6 : case jpiFloor:
725 6 : appendStringInfoString(buf, ".floor()");
726 6 : break;
727 6 : case jpiCeiling:
728 6 : appendStringInfoString(buf, ".ceiling()");
729 6 : break;
730 6 : case jpiDouble:
731 6 : appendStringInfoString(buf, ".double()");
732 6 : break;
733 12 : case jpiDatetime:
734 12 : appendStringInfoString(buf, ".datetime(");
735 12 : if (v->content.arg)
736 : {
737 6 : jspGetArg(v, &elem);
738 6 : printJsonPathItem(buf, &elem, false, false);
739 : }
740 12 : appendStringInfoChar(buf, ')');
741 12 : break;
742 6 : case jpiKeyValue:
743 6 : appendStringInfoString(buf, ".keyvalue()");
744 6 : break;
745 0 : default:
746 0 : elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
747 : }
748 :
749 5210 : if (jspGetNext(v, &elem))
750 1842 : printJsonPathItem(buf, &elem, true, true);
751 5210 : }
752 :
753 : const char *
754 902 : jspOperationName(JsonPathItemType type)
755 : {
756 902 : switch (type)
757 : {
758 30 : case jpiAnd:
759 30 : return "&&";
760 54 : case jpiOr:
761 54 : return "||";
762 162 : case jpiEqual:
763 162 : return "==";
764 6 : case jpiNotEqual:
765 6 : return "!=";
766 300 : case jpiLess:
767 300 : return "<";
768 38 : case jpiGreater:
769 38 : return ">";
770 6 : case jpiLessOrEqual:
771 6 : return "<=";
772 30 : case jpiGreaterOrEqual:
773 30 : return ">=";
774 72 : case jpiPlus:
775 : case jpiAdd:
776 72 : return "+";
777 24 : case jpiMinus:
778 : case jpiSub:
779 24 : return "-";
780 24 : case jpiMul:
781 24 : return "*";
782 6 : case jpiDiv:
783 6 : return "/";
784 6 : case jpiMod:
785 6 : return "%";
786 12 : case jpiStartsWith:
787 12 : return "starts with";
788 0 : case jpiLikeRegex:
789 0 : return "like_regex";
790 0 : case jpiType:
791 0 : return "type";
792 6 : case jpiSize:
793 6 : return "size";
794 18 : case jpiKeyValue:
795 18 : return "keyvalue";
796 60 : case jpiDouble:
797 60 : return "double";
798 6 : case jpiAbs:
799 6 : return "abs";
800 6 : case jpiFloor:
801 6 : return "floor";
802 6 : case jpiCeiling:
803 6 : return "ceiling";
804 30 : case jpiDatetime:
805 30 : return "datetime";
806 0 : default:
807 0 : elog(ERROR, "unrecognized jsonpath item type: %d", type);
808 : return NULL;
809 : }
810 : }
811 :
812 : static int
813 3152 : operationPriority(JsonPathItemType op)
814 : {
815 3152 : switch (op)
816 : {
817 114 : case jpiOr:
818 114 : return 0;
819 78 : case jpiAnd:
820 78 : return 1;
821 1234 : case jpiEqual:
822 : case jpiNotEqual:
823 : case jpiLess:
824 : case jpiGreater:
825 : case jpiLessOrEqual:
826 : case jpiGreaterOrEqual:
827 : case jpiStartsWith:
828 1234 : return 2;
829 180 : case jpiAdd:
830 : case jpiSub:
831 180 : return 3;
832 66 : case jpiMul:
833 : case jpiDiv:
834 : case jpiMod:
835 66 : return 4;
836 84 : case jpiPlus:
837 : case jpiMinus:
838 84 : return 5;
839 1396 : default:
840 1396 : return 6;
841 : }
842 : }
843 :
844 : /******************* Support functions for JsonPath *************************/
845 :
846 : /*
847 : * Support macros to read stored values
848 : */
849 :
850 : #define read_byte(v, b, p) do { \
851 : (v) = *(uint8*)((b) + (p)); \
852 : (p) += 1; \
853 : } while(0) \
854 :
855 : #define read_int32(v, b, p) do { \
856 : (v) = *(uint32*)((b) + (p)); \
857 : (p) += sizeof(int32); \
858 : } while(0) \
859 :
860 : #define read_int32_n(v, b, p, n) do { \
861 : (v) = (void *)((b) + (p)); \
862 : (p) += sizeof(int32) * (n); \
863 : } while(0) \
864 :
865 : /*
866 : * Read root node and fill root node representation
867 : */
868 : void
869 192026 : jspInit(JsonPathItem *v, JsonPath *js)
870 : {
871 : Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
872 192026 : jspInitByBuffer(v, js->data, 0);
873 192026 : }
874 :
875 : /*
876 : * Read node from buffer and fill its representation
877 : */
878 : void
879 648062 : jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
880 : {
881 648062 : v->base = base + pos;
882 :
883 648062 : read_byte(v->type, base, pos);
884 648062 : pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
885 648062 : read_int32(v->nextPos, base, pos);
886 :
887 648062 : switch (v->type)
888 : {
889 234312 : case jpiNull:
890 : case jpiRoot:
891 : case jpiCurrent:
892 : case jpiAnyArray:
893 : case jpiAnyKey:
894 : case jpiType:
895 : case jpiSize:
896 : case jpiAbs:
897 : case jpiFloor:
898 : case jpiCeiling:
899 : case jpiDouble:
900 : case jpiKeyValue:
901 : case jpiLast:
902 234312 : break;
903 195788 : case jpiKey:
904 : case jpiString:
905 : case jpiVariable:
906 195788 : read_int32(v->content.value.datalen, base, pos);
907 : /* FALLTHROUGH */
908 217636 : case jpiNumeric:
909 : case jpiBool:
910 217636 : v->content.value.data = base + pos;
911 217636 : break;
912 94742 : case jpiAnd:
913 : case jpiOr:
914 : case jpiAdd:
915 : case jpiSub:
916 : case jpiMul:
917 : case jpiDiv:
918 : case jpiMod:
919 : case jpiEqual:
920 : case jpiNotEqual:
921 : case jpiLess:
922 : case jpiGreater:
923 : case jpiLessOrEqual:
924 : case jpiGreaterOrEqual:
925 : case jpiStartsWith:
926 94742 : read_int32(v->content.args.left, base, pos);
927 94742 : read_int32(v->content.args.right, base, pos);
928 94742 : break;
929 444 : case jpiLikeRegex:
930 444 : read_int32(v->content.like_regex.flags, base, pos);
931 444 : read_int32(v->content.like_regex.expr, base, pos);
932 444 : read_int32(v->content.like_regex.patternlen, base, pos);
933 444 : v->content.like_regex.pattern = base + pos;
934 444 : break;
935 100226 : case jpiNot:
936 : case jpiExists:
937 : case jpiIsUnknown:
938 : case jpiPlus:
939 : case jpiMinus:
940 : case jpiFilter:
941 : case jpiDatetime:
942 100226 : read_int32(v->content.arg, base, pos);
943 100226 : break;
944 348 : case jpiIndexArray:
945 348 : read_int32(v->content.array.nelems, base, pos);
946 348 : read_int32_n(v->content.array.elems, base, pos,
947 : v->content.array.nelems * 2);
948 348 : break;
949 354 : case jpiAny:
950 354 : read_int32(v->content.anybounds.first, base, pos);
951 354 : read_int32(v->content.anybounds.last, base, pos);
952 354 : break;
953 0 : default:
954 0 : elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
955 : }
956 648062 : }
957 :
958 : void
959 100940 : jspGetArg(JsonPathItem *v, JsonPathItem *a)
960 : {
961 : Assert(v->type == jpiFilter ||
962 : v->type == jpiNot ||
963 : v->type == jpiIsUnknown ||
964 : v->type == jpiExists ||
965 : v->type == jpiPlus ||
966 : v->type == jpiMinus ||
967 : v->type == jpiDatetime);
968 :
969 100940 : jspInitByBuffer(a, v->base, v->content.arg);
970 100940 : }
971 :
972 : bool
973 421004 : jspGetNext(JsonPathItem *v, JsonPathItem *a)
974 : {
975 421004 : if (jspHasNext(v))
976 : {
977 : Assert(v->type == jpiString ||
978 : v->type == jpiNumeric ||
979 : v->type == jpiBool ||
980 : v->type == jpiNull ||
981 : v->type == jpiKey ||
982 : v->type == jpiAny ||
983 : v->type == jpiAnyArray ||
984 : v->type == jpiAnyKey ||
985 : v->type == jpiIndexArray ||
986 : v->type == jpiFilter ||
987 : v->type == jpiCurrent ||
988 : v->type == jpiExists ||
989 : v->type == jpiRoot ||
990 : v->type == jpiVariable ||
991 : v->type == jpiLast ||
992 : v->type == jpiAdd ||
993 : v->type == jpiSub ||
994 : v->type == jpiMul ||
995 : v->type == jpiDiv ||
996 : v->type == jpiMod ||
997 : v->type == jpiPlus ||
998 : v->type == jpiMinus ||
999 : v->type == jpiEqual ||
1000 : v->type == jpiNotEqual ||
1001 : v->type == jpiGreater ||
1002 : v->type == jpiGreaterOrEqual ||
1003 : v->type == jpiLess ||
1004 : v->type == jpiLessOrEqual ||
1005 : v->type == jpiAnd ||
1006 : v->type == jpiOr ||
1007 : v->type == jpiNot ||
1008 : v->type == jpiIsUnknown ||
1009 : v->type == jpiType ||
1010 : v->type == jpiSize ||
1011 : v->type == jpiAbs ||
1012 : v->type == jpiFloor ||
1013 : v->type == jpiCeiling ||
1014 : v->type == jpiDouble ||
1015 : v->type == jpiDatetime ||
1016 : v->type == jpiKeyValue ||
1017 : v->type == jpiStartsWith ||
1018 : v->type == jpiLikeRegex);
1019 :
1020 189726 : if (a)
1021 189726 : jspInitByBuffer(a, v->base, v->nextPos);
1022 189726 : return true;
1023 : }
1024 :
1025 231278 : return false;
1026 : }
1027 :
1028 : void
1029 94742 : jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
1030 : {
1031 : Assert(v->type == jpiAnd ||
1032 : v->type == jpiOr ||
1033 : v->type == jpiEqual ||
1034 : v->type == jpiNotEqual ||
1035 : v->type == jpiLess ||
1036 : v->type == jpiGreater ||
1037 : v->type == jpiLessOrEqual ||
1038 : v->type == jpiGreaterOrEqual ||
1039 : v->type == jpiAdd ||
1040 : v->type == jpiSub ||
1041 : v->type == jpiMul ||
1042 : v->type == jpiDiv ||
1043 : v->type == jpiMod ||
1044 : v->type == jpiStartsWith);
1045 :
1046 94742 : jspInitByBuffer(a, v->base, v->content.args.left);
1047 94742 : }
1048 :
1049 : void
1050 69776 : jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
1051 : {
1052 : Assert(v->type == jpiAnd ||
1053 : v->type == jpiOr ||
1054 : v->type == jpiEqual ||
1055 : v->type == jpiNotEqual ||
1056 : v->type == jpiLess ||
1057 : v->type == jpiGreater ||
1058 : v->type == jpiLessOrEqual ||
1059 : v->type == jpiGreaterOrEqual ||
1060 : v->type == jpiAdd ||
1061 : v->type == jpiSub ||
1062 : v->type == jpiMul ||
1063 : v->type == jpiDiv ||
1064 : v->type == jpiMod ||
1065 : v->type == jpiStartsWith);
1066 :
1067 69776 : jspInitByBuffer(a, v->base, v->content.args.right);
1068 69776 : }
1069 :
1070 : bool
1071 1434 : jspGetBool(JsonPathItem *v)
1072 : {
1073 : Assert(v->type == jpiBool);
1074 :
1075 1434 : return (bool) *v->content.value.data;
1076 : }
1077 :
1078 : Numeric
1079 20240 : jspGetNumeric(JsonPathItem *v)
1080 : {
1081 : Assert(v->type == jpiNumeric);
1082 :
1083 20240 : return (Numeric) v->content.value.data;
1084 : }
1085 :
1086 : char *
1087 195668 : jspGetString(JsonPathItem *v, int32 *len)
1088 : {
1089 : Assert(v->type == jpiKey ||
1090 : v->type == jpiString ||
1091 : v->type == jpiVariable);
1092 :
1093 195668 : if (len)
1094 194496 : *len = v->content.value.datalen;
1095 195668 : return v->content.value.data;
1096 : }
1097 :
1098 : bool
1099 366 : jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
1100 : int i)
1101 : {
1102 : Assert(v->type == jpiIndexArray);
1103 :
1104 366 : jspInitByBuffer(from, v->base, v->content.array.elems[i].from);
1105 :
1106 366 : if (!v->content.array.elems[i].to)
1107 324 : return false;
1108 :
1109 42 : jspInitByBuffer(to, v->base, v->content.array.elems[i].to);
1110 :
1111 42 : return true;
1112 : }
|