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
jsonpath_in(PG_FUNCTION_ARGS)93 jsonpath_in(PG_FUNCTION_ARGS)
94 {
95 char *in = PG_GETARG_CSTRING(0);
96 int len = strlen(in);
97
98 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
jsonpath_recv(PG_FUNCTION_ARGS)110 jsonpath_recv(PG_FUNCTION_ARGS)
111 {
112 StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
113 int version = pq_getmsgint(buf, 1);
114 char *str;
115 int nbytes;
116
117 if (version == JSONPATH_VERSION)
118 str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
119 else
120 elog(ERROR, "unsupported jsonpath version number: %d", version);
121
122 return jsonPathFromCstring(str, nbytes);
123 }
124
125 /*
126 * jsonpath type output function
127 */
128 Datum
jsonpath_out(PG_FUNCTION_ARGS)129 jsonpath_out(PG_FUNCTION_ARGS)
130 {
131 JsonPath *in = PG_GETARG_JSONPATH_P(0);
132
133 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
jsonpath_send(PG_FUNCTION_ARGS)142 jsonpath_send(PG_FUNCTION_ARGS)
143 {
144 JsonPath *in = PG_GETARG_JSONPATH_P(0);
145 StringInfoData buf;
146 StringInfoData jtext;
147 int version = JSONPATH_VERSION;
148
149 initStringInfo(&jtext);
150 (void) jsonPathToCstring(&jtext, in, VARSIZE(in));
151
152 pq_begintypsend(&buf);
153 pq_sendint8(&buf, version);
154 pq_sendtext(&buf, jtext.data, jtext.len);
155 pfree(jtext.data);
156
157 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
jsonPathFromCstring(char * in,int len)168 jsonPathFromCstring(char *in, int len)
169 {
170 JsonPathParseResult *jsonpath = parsejsonpath(in, len);
171 JsonPath *res;
172 StringInfoData buf;
173
174 initStringInfo(&buf);
175 enlargeStringInfo(&buf, 4 * len /* estimation */ );
176
177 appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
178
179 if (!jsonpath)
180 ereport(ERROR,
181 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
182 errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
183 in)));
184
185 flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false);
186
187 res = (JsonPath *) buf.data;
188 SET_VARSIZE(res, buf.len);
189 res->header = JSONPATH_VERSION;
190 if (jsonpath->lax)
191 res->header |= JSONPATH_LAX;
192
193 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 *
jsonPathToCstring(StringInfo out,JsonPath * in,int estimated_len)203 jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
204 {
205 StringInfoData buf;
206 JsonPathItem v;
207
208 if (!out)
209 {
210 out = &buf;
211 initStringInfo(out);
212 }
213 enlargeStringInfo(out, estimated_len);
214
215 if (!(in->header & JSONPATH_LAX))
216 appendBinaryStringInfo(out, "strict ", 7);
217
218 jspInit(&v, in);
219 printJsonPathItem(out, &v, false, true);
220
221 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
flattenJsonPathParseItem(StringInfo buf,JsonPathParseItem * item,int nestingLevel,bool insideArraySubscript)229 flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
230 int nestingLevel, bool insideArraySubscript)
231 {
232 /* position from beginning of jsonpath data */
233 int32 pos = buf->len - JSONPATH_HDRSZ;
234 int32 chld;
235 int32 next;
236 int argNestingLevel = 0;
237
238 check_stack_depth();
239 CHECK_FOR_INTERRUPTS();
240
241 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 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 next = reserveSpaceForItemPointer(buf);
255
256 switch (item->type)
257 {
258 case jpiString:
259 case jpiVariable:
260 case jpiKey:
261 appendBinaryStringInfo(buf, (char *) &item->value.string.len,
262 sizeof(item->value.string.len));
263 appendBinaryStringInfo(buf, item->value.string.val,
264 item->value.string.len);
265 appendStringInfoChar(buf, '\0');
266 break;
267 case jpiNumeric:
268 appendBinaryStringInfo(buf, (char *) item->value.numeric,
269 VARSIZE(item->value.numeric));
270 break;
271 case jpiBool:
272 appendBinaryStringInfo(buf, (char *) &item->value.boolean,
273 sizeof(item->value.boolean));
274 break;
275 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 int32 left = reserveSpaceForItemPointer(buf);
296 int32 right = reserveSpaceForItemPointer(buf);
297
298 chld = !item->value.args.left ? pos :
299 flattenJsonPathParseItem(buf, item->value.args.left,
300 nestingLevel + argNestingLevel,
301 insideArraySubscript);
302 *(int32 *) (buf->data + left) = chld - pos;
303
304 chld = !item->value.args.right ? pos :
305 flattenJsonPathParseItem(buf, item->value.args.right,
306 nestingLevel + argNestingLevel,
307 insideArraySubscript);
308 *(int32 *) (buf->data + right) = chld - pos;
309 }
310 break;
311 case jpiLikeRegex:
312 {
313 int32 offs;
314
315 appendBinaryStringInfo(buf,
316 (char *) &item->value.like_regex.flags,
317 sizeof(item->value.like_regex.flags));
318 offs = reserveSpaceForItemPointer(buf);
319 appendBinaryStringInfo(buf,
320 (char *) &item->value.like_regex.patternlen,
321 sizeof(item->value.like_regex.patternlen));
322 appendBinaryStringInfo(buf, item->value.like_regex.pattern,
323 item->value.like_regex.patternlen);
324 appendStringInfoChar(buf, '\0');
325
326 chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr,
327 nestingLevel,
328 insideArraySubscript);
329 *(int32 *) (buf->data + offs) = chld - pos;
330 }
331 break;
332 case jpiFilter:
333 argNestingLevel++;
334 /* FALLTHROUGH */
335 case jpiIsUnknown:
336 case jpiNot:
337 case jpiPlus:
338 case jpiMinus:
339 case jpiExists:
340 case jpiDatetime:
341 {
342 int32 arg = reserveSpaceForItemPointer(buf);
343
344 chld = !item->value.arg ? pos :
345 flattenJsonPathParseItem(buf, item->value.arg,
346 nestingLevel + argNestingLevel,
347 insideArraySubscript);
348 *(int32 *) (buf->data + arg) = chld - pos;
349 }
350 break;
351 case jpiNull:
352 break;
353 case jpiRoot:
354 break;
355 case jpiAnyArray:
356 case jpiAnyKey:
357 break;
358 case jpiCurrent:
359 if (nestingLevel <= 0)
360 ereport(ERROR,
361 (errcode(ERRCODE_SYNTAX_ERROR),
362 errmsg("@ is not allowed in root expressions")));
363 break;
364 case jpiLast:
365 if (!insideArraySubscript)
366 ereport(ERROR,
367 (errcode(ERRCODE_SYNTAX_ERROR),
368 errmsg("LAST is allowed only in array subscripts")));
369 break;
370 case jpiIndexArray:
371 {
372 int32 nelems = item->value.array.nelems;
373 int offset;
374 int i;
375
376 appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
377
378 offset = buf->len;
379
380 appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems);
381
382 for (i = 0; i < nelems; i++)
383 {
384 int32 *ppos;
385 int32 topos;
386 int32 frompos =
387 flattenJsonPathParseItem(buf,
388 item->value.array.elems[i].from,
389 nestingLevel, true) - pos;
390
391 if (item->value.array.elems[i].to)
392 topos = flattenJsonPathParseItem(buf,
393 item->value.array.elems[i].to,
394 nestingLevel, true) - pos;
395 else
396 topos = 0;
397
398 ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
399
400 ppos[0] = frompos;
401 ppos[1] = topos;
402 }
403 }
404 break;
405 case jpiAny:
406 appendBinaryStringInfo(buf,
407 (char *) &item->value.anybounds.first,
408 sizeof(item->value.anybounds.first));
409 appendBinaryStringInfo(buf,
410 (char *) &item->value.anybounds.last,
411 sizeof(item->value.anybounds.last));
412 break;
413 case jpiType:
414 case jpiSize:
415 case jpiAbs:
416 case jpiFloor:
417 case jpiCeiling:
418 case jpiDouble:
419 case jpiKeyValue:
420 break;
421 default:
422 elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
423 }
424
425 if (item->next)
426 {
427 chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
428 insideArraySubscript) - pos;
429 *(int32 *) (buf->data + next) = chld;
430 }
431
432 return pos;
433 }
434
435 /*
436 * Align StringInfo to int by adding zero padding bytes
437 */
438 static void
alignStringInfoInt(StringInfo buf)439 alignStringInfoInt(StringInfo buf)
440 {
441 switch (INTALIGN(buf->len) - buf->len)
442 {
443 case 3:
444 appendStringInfoCharMacro(buf, 0);
445 /* FALLTHROUGH */
446 case 2:
447 appendStringInfoCharMacro(buf, 0);
448 /* FALLTHROUGH */
449 case 1:
450 appendStringInfoCharMacro(buf, 0);
451 /* FALLTHROUGH */
452 default:
453 break;
454 }
455 }
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
reserveSpaceForItemPointer(StringInfo buf)462 reserveSpaceForItemPointer(StringInfo buf)
463 {
464 int32 pos = buf->len;
465 int32 ptr = 0;
466
467 appendBinaryStringInfo(buf, (char *) &ptr, sizeof(ptr));
468
469 return pos;
470 }
471
472 /*
473 * Prints text representation of given jsonpath item and all its children.
474 */
475 static void
printJsonPathItem(StringInfo buf,JsonPathItem * v,bool inKey,bool printBracketes)476 printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
477 bool printBracketes)
478 {
479 JsonPathItem elem;
480 int i;
481
482 check_stack_depth();
483 CHECK_FOR_INTERRUPTS();
484
485 switch (v->type)
486 {
487 case jpiNull:
488 appendStringInfoString(buf, "null");
489 break;
490 case jpiKey:
491 if (inKey)
492 appendStringInfoChar(buf, '.');
493 escape_json(buf, jspGetString(v, NULL));
494 break;
495 case jpiString:
496 escape_json(buf, jspGetString(v, NULL));
497 break;
498 case jpiVariable:
499 appendStringInfoChar(buf, '$');
500 escape_json(buf, jspGetString(v, NULL));
501 break;
502 case jpiNumeric:
503 appendStringInfoString(buf,
504 DatumGetCString(DirectFunctionCall1(numeric_out,
505 NumericGetDatum(jspGetNumeric(v)))));
506 break;
507 case jpiBool:
508 if (jspGetBool(v))
509 appendBinaryStringInfo(buf, "true", 4);
510 else
511 appendBinaryStringInfo(buf, "false", 5);
512 break;
513 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 if (printBracketes)
528 appendStringInfoChar(buf, '(');
529 jspGetLeftArg(v, &elem);
530 printJsonPathItem(buf, &elem, false,
531 operationPriority(elem.type) <=
532 operationPriority(v->type));
533 appendStringInfoChar(buf, ' ');
534 appendStringInfoString(buf, jspOperationName(v->type));
535 appendStringInfoChar(buf, ' ');
536 jspGetRightArg(v, &elem);
537 printJsonPathItem(buf, &elem, false,
538 operationPriority(elem.type) <=
539 operationPriority(v->type));
540 if (printBracketes)
541 appendStringInfoChar(buf, ')');
542 break;
543 case jpiLikeRegex:
544 if (printBracketes)
545 appendStringInfoChar(buf, '(');
546
547 jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
548 printJsonPathItem(buf, &elem, false,
549 operationPriority(elem.type) <=
550 operationPriority(v->type));
551
552 appendBinaryStringInfo(buf, " like_regex ", 12);
553
554 escape_json(buf, v->content.like_regex.pattern);
555
556 if (v->content.like_regex.flags)
557 {
558 appendBinaryStringInfo(buf, " flag \"", 7);
559
560 if (v->content.like_regex.flags & JSP_REGEX_ICASE)
561 appendStringInfoChar(buf, 'i');
562 if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
563 appendStringInfoChar(buf, 's');
564 if (v->content.like_regex.flags & JSP_REGEX_MLINE)
565 appendStringInfoChar(buf, 'm');
566 if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
567 appendStringInfoChar(buf, 'x');
568 if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
569 appendStringInfoChar(buf, 'q');
570
571 appendStringInfoChar(buf, '"');
572 }
573
574 if (printBracketes)
575 appendStringInfoChar(buf, ')');
576 break;
577 case jpiPlus:
578 case jpiMinus:
579 if (printBracketes)
580 appendStringInfoChar(buf, '(');
581 appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
582 jspGetArg(v, &elem);
583 printJsonPathItem(buf, &elem, false,
584 operationPriority(elem.type) <=
585 operationPriority(v->type));
586 if (printBracketes)
587 appendStringInfoChar(buf, ')');
588 break;
589 case jpiFilter:
590 appendBinaryStringInfo(buf, "?(", 2);
591 jspGetArg(v, &elem);
592 printJsonPathItem(buf, &elem, false, false);
593 appendStringInfoChar(buf, ')');
594 break;
595 case jpiNot:
596 appendBinaryStringInfo(buf, "!(", 2);
597 jspGetArg(v, &elem);
598 printJsonPathItem(buf, &elem, false, false);
599 appendStringInfoChar(buf, ')');
600 break;
601 case jpiIsUnknown:
602 appendStringInfoChar(buf, '(');
603 jspGetArg(v, &elem);
604 printJsonPathItem(buf, &elem, false, false);
605 appendBinaryStringInfo(buf, ") is unknown", 12);
606 break;
607 case jpiExists:
608 appendBinaryStringInfo(buf, "exists (", 8);
609 jspGetArg(v, &elem);
610 printJsonPathItem(buf, &elem, false, false);
611 appendStringInfoChar(buf, ')');
612 break;
613 case jpiCurrent:
614 Assert(!inKey);
615 appendStringInfoChar(buf, '@');
616 break;
617 case jpiRoot:
618 Assert(!inKey);
619 appendStringInfoChar(buf, '$');
620 break;
621 case jpiLast:
622 appendBinaryStringInfo(buf, "last", 4);
623 break;
624 case jpiAnyArray:
625 appendBinaryStringInfo(buf, "[*]", 3);
626 break;
627 case jpiAnyKey:
628 if (inKey)
629 appendStringInfoChar(buf, '.');
630 appendStringInfoChar(buf, '*');
631 break;
632 case jpiIndexArray:
633 appendStringInfoChar(buf, '[');
634 for (i = 0; i < v->content.array.nelems; i++)
635 {
636 JsonPathItem from;
637 JsonPathItem to;
638 bool range = jspGetArraySubscript(v, &from, &to, i);
639
640 if (i)
641 appendStringInfoChar(buf, ',');
642
643 printJsonPathItem(buf, &from, false, false);
644
645 if (range)
646 {
647 appendBinaryStringInfo(buf, " to ", 4);
648 printJsonPathItem(buf, &to, false, false);
649 }
650 }
651 appendStringInfoChar(buf, ']');
652 break;
653 case jpiAny:
654 if (inKey)
655 appendStringInfoChar(buf, '.');
656
657 if (v->content.anybounds.first == 0 &&
658 v->content.anybounds.last == PG_UINT32_MAX)
659 appendBinaryStringInfo(buf, "**", 2);
660 else if (v->content.anybounds.first == v->content.anybounds.last)
661 {
662 if (v->content.anybounds.first == PG_UINT32_MAX)
663 appendStringInfoString(buf, "**{last}");
664 else
665 appendStringInfo(buf, "**{%u}",
666 v->content.anybounds.first);
667 }
668 else if (v->content.anybounds.first == PG_UINT32_MAX)
669 appendStringInfo(buf, "**{last to %u}",
670 v->content.anybounds.last);
671 else if (v->content.anybounds.last == PG_UINT32_MAX)
672 appendStringInfo(buf, "**{%u to last}",
673 v->content.anybounds.first);
674 else
675 appendStringInfo(buf, "**{%u to %u}",
676 v->content.anybounds.first,
677 v->content.anybounds.last);
678 break;
679 case jpiType:
680 appendBinaryStringInfo(buf, ".type()", 7);
681 break;
682 case jpiSize:
683 appendBinaryStringInfo(buf, ".size()", 7);
684 break;
685 case jpiAbs:
686 appendBinaryStringInfo(buf, ".abs()", 6);
687 break;
688 case jpiFloor:
689 appendBinaryStringInfo(buf, ".floor()", 8);
690 break;
691 case jpiCeiling:
692 appendBinaryStringInfo(buf, ".ceiling()", 10);
693 break;
694 case jpiDouble:
695 appendBinaryStringInfo(buf, ".double()", 9);
696 break;
697 case jpiDatetime:
698 appendBinaryStringInfo(buf, ".datetime(", 10);
699 if (v->content.arg)
700 {
701 jspGetArg(v, &elem);
702 printJsonPathItem(buf, &elem, false, false);
703 }
704 appendStringInfoChar(buf, ')');
705 break;
706 case jpiKeyValue:
707 appendBinaryStringInfo(buf, ".keyvalue()", 11);
708 break;
709 default:
710 elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
711 }
712
713 if (jspGetNext(v, &elem))
714 printJsonPathItem(buf, &elem, true, true);
715 }
716
717 const char *
jspOperationName(JsonPathItemType type)718 jspOperationName(JsonPathItemType type)
719 {
720 switch (type)
721 {
722 case jpiAnd:
723 return "&&";
724 case jpiOr:
725 return "||";
726 case jpiEqual:
727 return "==";
728 case jpiNotEqual:
729 return "!=";
730 case jpiLess:
731 return "<";
732 case jpiGreater:
733 return ">";
734 case jpiLessOrEqual:
735 return "<=";
736 case jpiGreaterOrEqual:
737 return ">=";
738 case jpiPlus:
739 case jpiAdd:
740 return "+";
741 case jpiMinus:
742 case jpiSub:
743 return "-";
744 case jpiMul:
745 return "*";
746 case jpiDiv:
747 return "/";
748 case jpiMod:
749 return "%";
750 case jpiStartsWith:
751 return "starts with";
752 case jpiLikeRegex:
753 return "like_regex";
754 case jpiType:
755 return "type";
756 case jpiSize:
757 return "size";
758 case jpiKeyValue:
759 return "keyvalue";
760 case jpiDouble:
761 return "double";
762 case jpiAbs:
763 return "abs";
764 case jpiFloor:
765 return "floor";
766 case jpiCeiling:
767 return "ceiling";
768 case jpiDatetime:
769 return "datetime";
770 default:
771 elog(ERROR, "unrecognized jsonpath item type: %d", type);
772 return NULL;
773 }
774 }
775
776 static int
operationPriority(JsonPathItemType op)777 operationPriority(JsonPathItemType op)
778 {
779 switch (op)
780 {
781 case jpiOr:
782 return 0;
783 case jpiAnd:
784 return 1;
785 case jpiEqual:
786 case jpiNotEqual:
787 case jpiLess:
788 case jpiGreater:
789 case jpiLessOrEqual:
790 case jpiGreaterOrEqual:
791 case jpiStartsWith:
792 return 2;
793 case jpiAdd:
794 case jpiSub:
795 return 3;
796 case jpiMul:
797 case jpiDiv:
798 case jpiMod:
799 return 4;
800 case jpiPlus:
801 case jpiMinus:
802 return 5;
803 default:
804 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
jspInit(JsonPathItem * v,JsonPath * js)833 jspInit(JsonPathItem *v, JsonPath *js)
834 {
835 Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
836 jspInitByBuffer(v, js->data, 0);
837 }
838
839 /*
840 * Read node from buffer and fill its representation
841 */
842 void
jspInitByBuffer(JsonPathItem * v,char * base,int32 pos)843 jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
844 {
845 v->base = base + pos;
846
847 read_byte(v->type, base, pos);
848 pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
849 read_int32(v->nextPos, base, pos);
850
851 switch (v->type)
852 {
853 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 break;
867 case jpiKey:
868 case jpiString:
869 case jpiVariable:
870 read_int32(v->content.value.datalen, base, pos);
871 /* FALLTHROUGH */
872 case jpiNumeric:
873 case jpiBool:
874 v->content.value.data = base + pos;
875 break;
876 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 read_int32(v->content.args.left, base, pos);
891 read_int32(v->content.args.right, base, pos);
892 break;
893 case jpiLikeRegex:
894 read_int32(v->content.like_regex.flags, base, pos);
895 read_int32(v->content.like_regex.expr, base, pos);
896 read_int32(v->content.like_regex.patternlen, base, pos);
897 v->content.like_regex.pattern = base + pos;
898 break;
899 case jpiNot:
900 case jpiExists:
901 case jpiIsUnknown:
902 case jpiPlus:
903 case jpiMinus:
904 case jpiFilter:
905 case jpiDatetime:
906 read_int32(v->content.arg, base, pos);
907 break;
908 case jpiIndexArray:
909 read_int32(v->content.array.nelems, base, pos);
910 read_int32_n(v->content.array.elems, base, pos,
911 v->content.array.nelems * 2);
912 break;
913 case jpiAny:
914 read_int32(v->content.anybounds.first, base, pos);
915 read_int32(v->content.anybounds.last, base, pos);
916 break;
917 default:
918 elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
919 }
920 }
921
922 void
jspGetArg(JsonPathItem * v,JsonPathItem * a)923 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 jspInitByBuffer(a, v->base, v->content.arg);
934 }
935
936 bool
jspGetNext(JsonPathItem * v,JsonPathItem * a)937 jspGetNext(JsonPathItem *v, JsonPathItem *a)
938 {
939 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 if (a)
984 jspInitByBuffer(a, v->base, v->nextPos);
985 return true;
986 }
987
988 return false;
989 }
990
991 void
jspGetLeftArg(JsonPathItem * v,JsonPathItem * a)992 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 jspInitByBuffer(a, v->base, v->content.args.left);
1010 }
1011
1012 void
jspGetRightArg(JsonPathItem * v,JsonPathItem * a)1013 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 jspInitByBuffer(a, v->base, v->content.args.right);
1031 }
1032
1033 bool
jspGetBool(JsonPathItem * v)1034 jspGetBool(JsonPathItem *v)
1035 {
1036 Assert(v->type == jpiBool);
1037
1038 return (bool) *v->content.value.data;
1039 }
1040
1041 Numeric
jspGetNumeric(JsonPathItem * v)1042 jspGetNumeric(JsonPathItem *v)
1043 {
1044 Assert(v->type == jpiNumeric);
1045
1046 return (Numeric) v->content.value.data;
1047 }
1048
1049 char *
jspGetString(JsonPathItem * v,int32 * len)1050 jspGetString(JsonPathItem *v, int32 *len)
1051 {
1052 Assert(v->type == jpiKey ||
1053 v->type == jpiString ||
1054 v->type == jpiVariable);
1055
1056 if (len)
1057 *len = v->content.value.datalen;
1058 return v->content.value.data;
1059 }
1060
1061 bool
jspGetArraySubscript(JsonPathItem * v,JsonPathItem * from,JsonPathItem * to,int i)1062 jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
1063 int i)
1064 {
1065 Assert(v->type == jpiIndexArray);
1066
1067 jspInitByBuffer(from, v->base, v->content.array.elems[i].from);
1068
1069 if (!v->content.array.elems[i].to)
1070 return false;
1071
1072 jspInitByBuffer(to, v->base, v->content.array.elems[i].to);
1073
1074 return true;
1075 }
1076