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