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, 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
75static Datum jsonPathFromCstring(char *in, int len);
76static char *jsonPathToCstring(StringInfo out, JsonPath *in,
77 int estimated_len);
78static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
79 int nestingLevel, bool insideArraySubscript);
80static void alignStringInfoInt(StringInfo buf);
81static int32 reserveSpaceForItemPointer(StringInfo buf);
82static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
83 bool printBracketes);
84static int operationPriority(JsonPathItemType op);
85
86
87/**************************** INPUT/OUTPUT ********************************/
88
89/*
90 * jsonpath type input function
91 */
92Datum
93jsonpath_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 */
109Datum
110jsonpath_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 */
128Datum
129jsonpath_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 */
141Datum
142jsonpath_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 */
167static Datum
168jsonPathFromCstring(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 */
202static char *
203jsonPathToCstring(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 */
228static int
229flattenJsonPathParseItem(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 {
341 int32 arg = reserveSpaceForItemPointer(buf);
342
343 chld = flattenJsonPathParseItem(buf, item->value.arg,
344 nestingLevel + argNestingLevel,
345 insideArraySubscript);
346 *(int32 *) (buf->data + arg) = chld - pos;
347 }
348 break;
349 case jpiNull:
350 break;
351 case jpiRoot:
352 break;
353 case jpiAnyArray:
354 case jpiAnyKey:
355 break;
356 case jpiCurrent:
357 if (nestingLevel <= 0)
358 ereport(ERROR,
359 (errcode(ERRCODE_SYNTAX_ERROR),
360 errmsg("@ is not allowed in root expressions")));
361 break;
362 case jpiLast:
363 if (!insideArraySubscript)
364 ereport(ERROR,
365 (errcode(ERRCODE_SYNTAX_ERROR),
366 errmsg("LAST is allowed only in array subscripts")));
367 break;
368 case jpiIndexArray:
369 {
370 int32 nelems = item->value.array.nelems;
371 int offset;
372 int i;
373
374 appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
375
376 offset = buf->len;
377
378 appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems);
379
380 for (i = 0; i < nelems; i++)
381 {
382 int32 *ppos;
383 int32 topos;
384 int32 frompos =
385 flattenJsonPathParseItem(buf,
386 item->value.array.elems[i].from,
387 nestingLevel, true) - pos;
388
389 if (item->value.array.elems[i].to)
390 topos = flattenJsonPathParseItem(buf,
391 item->value.array.elems[i].to,
392 nestingLevel, true) - pos;
393 else
394 topos = 0;
395
396 ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
397
398 ppos[0] = frompos;
399 ppos[1] = topos;
400 }
401 }
402 break;
403 case jpiAny:
404 appendBinaryStringInfo(buf,
405 (char *) &item->value.anybounds.first,
406 sizeof(item->value.anybounds.first));
407 appendBinaryStringInfo(buf,
408 (char *) &item->value.anybounds.last,
409 sizeof(item->value.anybounds.last));
410 break;
411 case jpiType:
412 case jpiSize:
413 case jpiAbs:
414 case jpiFloor:
415 case jpiCeiling:
416 case jpiDouble:
417 case jpiKeyValue:
418 break;
419 default:
420 elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
421 }
422
423 if (item->next)
424 {
425 chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
426 insideArraySubscript) - pos;
427 *(int32 *) (buf->data + next) = chld;
428 }
429
430 return pos;
431}
432
433/*
434 * Align StringInfo to int by adding zero padding bytes
435 */
436static void
437alignStringInfoInt(StringInfo buf)
438{
439 switch (INTALIGN(buf->len) - buf->len)
440 {
441 case 3:
442 appendStringInfoCharMacro(buf, 0);
443 /* FALLTHROUGH */
444 case 2:
445 appendStringInfoCharMacro(buf, 0);
446 /* FALLTHROUGH */
447 case 1:
448 appendStringInfoCharMacro(buf, 0);
449 /* FALLTHROUGH */
450 default:
451 break;
452 }
453}
454
455/*
456 * Reserve space for int32 JsonPathItem pointer. Now zero pointer is written,
457 * actual value will be recorded at '(int32 *) &buf->data[pos]' later.
458 */
459static int32
460reserveSpaceForItemPointer(StringInfo buf)
461{
462 int32 pos = buf->len;
463 int32 ptr = 0;
464
465 appendBinaryStringInfo(buf, (char *) &ptr, sizeof(ptr));
466
467 return pos;
468}
469
470/*
471 * Prints text representation of given jsonpath item and all its children.
472 */
473static void
474printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
475 bool printBracketes)
476{
477 JsonPathItem elem;
478 int i;
479
480 check_stack_depth();
481 CHECK_FOR_INTERRUPTS();
482
483 switch (v->type)
484 {
485 case jpiNull:
486 appendStringInfoString(buf, "null");
487 break;
488 case jpiKey:
489 if (inKey)
490 appendStringInfoChar(buf, '.');
491 escape_json(buf, jspGetString(v, NULL));
492 break;
493 case jpiString:
494 escape_json(buf, jspGetString(v, NULL));
495 break;
496 case jpiVariable:
497 appendStringInfoChar(buf, '$');
498 escape_json(buf, jspGetString(v, NULL));
499 break;
500 case jpiNumeric:
501 appendStringInfoString(buf,
502 DatumGetCString(DirectFunctionCall1(numeric_out,
503 NumericGetDatum(jspGetNumeric(v)))));
504 break;
505 case jpiBool:
506 if (jspGetBool(v))
507 appendBinaryStringInfo(buf, "true", 4);
508 else
509 appendBinaryStringInfo(buf, "false", 5);
510 break;
511 case jpiAnd:
512 case jpiOr:
513 case jpiEqual:
514 case jpiNotEqual:
515 case jpiLess:
516 case jpiGreater:
517 case jpiLessOrEqual:
518 case jpiGreaterOrEqual:
519 case jpiAdd:
520 case jpiSub:
521 case jpiMul:
522 case jpiDiv:
523 case jpiMod:
524 case jpiStartsWith:
525 if (printBracketes)
526 appendStringInfoChar(buf, '(');
527 jspGetLeftArg(v, &elem);
528 printJsonPathItem(buf, &elem, false,
529 operationPriority(elem.type) <=
530 operationPriority(v->type));
531 appendStringInfoChar(buf, ' ');
532 appendStringInfoString(buf, jspOperationName(v->type));
533 appendStringInfoChar(buf, ' ');
534 jspGetRightArg(v, &elem);
535 printJsonPathItem(buf, &elem, false,
536 operationPriority(elem.type) <=
537 operationPriority(v->type));
538 if (printBracketes)
539 appendStringInfoChar(buf, ')');
540 break;
541 case jpiLikeRegex:
542 if (printBracketes)
543 appendStringInfoChar(buf, '(');
544
545 jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
546 printJsonPathItem(buf, &elem, false,
547 operationPriority(elem.type) <=
548 operationPriority(v->type));
549
550 appendBinaryStringInfo(buf, " like_regex ", 12);
551
552 escape_json(buf, v->content.like_regex.pattern);
553
554 if (v->content.like_regex.flags)
555 {
556 appendBinaryStringInfo(buf, " flag \"", 7);
557
558 if (v->content.like_regex.flags & JSP_REGEX_ICASE)
559 appendStringInfoChar(buf, 'i');
560 if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
561 appendStringInfoChar(buf, 's');
562 if (v->content.like_regex.flags & JSP_REGEX_MLINE)
563 appendStringInfoChar(buf, 'm');
564 if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
565 appendStringInfoChar(buf, 'x');
566 if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
567 appendStringInfoChar(buf, 'q');
568
569 appendStringInfoChar(buf, '"');
570 }
571
572 if (printBracketes)
573 appendStringInfoChar(buf, ')');
574 break;
575 case jpiPlus:
576 case jpiMinus:
577 if (printBracketes)
578 appendStringInfoChar(buf, '(');
579 appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
580 jspGetArg(v, &elem);
581 printJsonPathItem(buf, &elem, false,
582 operationPriority(elem.type) <=
583 operationPriority(v->type));
584 if (printBracketes)
585 appendStringInfoChar(buf, ')');
586 break;
587 case jpiFilter:
588 appendBinaryStringInfo(buf, "?(", 2);
589 jspGetArg(v, &elem);
590 printJsonPathItem(buf, &elem, false, false);
591 appendStringInfoChar(buf, ')');
592 break;
593 case jpiNot:
594 appendBinaryStringInfo(buf, "!(", 2);
595 jspGetArg(v, &elem);
596 printJsonPathItem(buf, &elem, false, false);
597 appendStringInfoChar(buf, ')');
598 break;
599 case jpiIsUnknown:
600 appendStringInfoChar(buf, '(');
601 jspGetArg(v, &elem);
602 printJsonPathItem(buf, &elem, false, false);
603 appendBinaryStringInfo(buf, ") is unknown", 12);
604 break;
605 case jpiExists:
606 appendBinaryStringInfo(buf, "exists (", 8);
607 jspGetArg(v, &elem);
608 printJsonPathItem(buf, &elem, false, false);
609 appendStringInfoChar(buf, ')');
610 break;
611 case jpiCurrent:
612 Assert(!inKey);
613 appendStringInfoChar(buf, '@');
614 break;
615 case jpiRoot:
616 Assert(!inKey);
617 appendStringInfoChar(buf, '$');
618 break;
619 case jpiLast:
620 appendBinaryStringInfo(buf, "last", 4);
621 break;
622 case jpiAnyArray:
623 appendBinaryStringInfo(buf, "[*]", 3);
624 break;
625 case jpiAnyKey:
626 if (inKey)
627 appendStringInfoChar(buf, '.');
628 appendStringInfoChar(buf, '*');
629 break;
630 case jpiIndexArray:
631 appendStringInfoChar(buf, '[');
632 for (i = 0; i < v->content.array.nelems; i++)
633 {
634 JsonPathItem from;
635 JsonPathItem to;
636 bool range = jspGetArraySubscript(v, &from, &to, i);
637
638 if (i)
639 appendStringInfoChar(buf, ',');
640
641 printJsonPathItem(buf, &from, false, false);
642
643 if (range)
644 {
645 appendBinaryStringInfo(buf, " to ", 4);
646 printJsonPathItem(buf, &to, false, false);
647 }
648 }
649 appendStringInfoChar(buf, ']');
650 break;
651 case jpiAny:
652 if (inKey)
653 appendStringInfoChar(buf, '.');
654
655 if (v->content.anybounds.first == 0 &&
656 v->content.anybounds.last == PG_UINT32_MAX)
657 appendBinaryStringInfo(buf, "**", 2);
658 else if (v->content.anybounds.first == v->content.anybounds.last)
659 {
660 if (v->content.anybounds.first == PG_UINT32_MAX)
661 appendStringInfo(buf, "**{last}");
662 else
663 appendStringInfo(buf, "**{%u}",
664 v->content.anybounds.first);
665 }
666 else if (v->content.anybounds.first == PG_UINT32_MAX)
667 appendStringInfo(buf, "**{last to %u}",
668 v->content.anybounds.last);
669 else if (v->content.anybounds.last == PG_UINT32_MAX)
670 appendStringInfo(buf, "**{%u to last}",
671 v->content.anybounds.first);
672 else
673 appendStringInfo(buf, "**{%u to %u}",
674 v->content.anybounds.first,
675 v->content.anybounds.last);
676 break;
677 case jpiType:
678 appendBinaryStringInfo(buf, ".type()", 7);
679 break;
680 case jpiSize:
681 appendBinaryStringInfo(buf, ".size()", 7);
682 break;
683 case jpiAbs:
684 appendBinaryStringInfo(buf, ".abs()", 6);
685 break;
686 case jpiFloor:
687 appendBinaryStringInfo(buf, ".floor()", 8);
688 break;
689 case jpiCeiling:
690 appendBinaryStringInfo(buf, ".ceiling()", 10);
691 break;
692 case jpiDouble:
693 appendBinaryStringInfo(buf, ".double()", 9);
694 break;
695 case jpiKeyValue:
696 appendBinaryStringInfo(buf, ".keyvalue()", 11);
697 break;
698 default:
699 elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
700 }
701
702 if (jspGetNext(v, &elem))
703 printJsonPathItem(buf, &elem, true, true);
704}
705
706const char *
707jspOperationName(JsonPathItemType type)
708{
709 switch (type)
710 {
711 case jpiAnd:
712 return "&&";
713 case jpiOr:
714 return "||";
715 case jpiEqual:
716 return "==";
717 case jpiNotEqual:
718 return "!=";
719 case jpiLess:
720 return "<";
721 case jpiGreater:
722 return ">";
723 case jpiLessOrEqual:
724 return "<=";
725 case jpiGreaterOrEqual:
726 return ">=";
727 case jpiPlus:
728 case jpiAdd:
729 return "+";
730 case jpiMinus:
731 case jpiSub:
732 return "-";
733 case jpiMul:
734 return "*";
735 case jpiDiv:
736 return "/";
737 case jpiMod:
738 return "%";
739 case jpiStartsWith:
740 return "starts with";
741 case jpiLikeRegex:
742 return "like_regex";
743 case jpiType:
744 return "type";
745 case jpiSize:
746 return "size";
747 case jpiKeyValue:
748 return "keyvalue";
749 case jpiDouble:
750 return "double";
751 case jpiAbs:
752 return "abs";
753 case jpiFloor:
754 return "floor";
755 case jpiCeiling:
756 return "ceiling";
757 default:
758 elog(ERROR, "unrecognized jsonpath item type: %d", type);
759 return NULL;
760 }
761}
762
763static int
764operationPriority(JsonPathItemType op)
765{
766 switch (op)
767 {
768 case jpiOr:
769 return 0;
770 case jpiAnd:
771 return 1;
772 case jpiEqual:
773 case jpiNotEqual:
774 case jpiLess:
775 case jpiGreater:
776 case jpiLessOrEqual:
777 case jpiGreaterOrEqual:
778 case jpiStartsWith:
779 return 2;
780 case jpiAdd:
781 case jpiSub:
782 return 3;
783 case jpiMul:
784 case jpiDiv:
785 case jpiMod:
786 return 4;
787 case jpiPlus:
788 case jpiMinus:
789 return 5;
790 default:
791 return 6;
792 }
793}
794
795/******************* Support functions for JsonPath *************************/
796
797/*
798 * Support macros to read stored values
799 */
800
801#define read_byte(v, b, p) do { \
802 (v) = *(uint8*)((b) + (p)); \
803 (p) += 1; \
804} while(0) \
805
806#define read_int32(v, b, p) do { \
807 (v) = *(uint32*)((b) + (p)); \
808 (p) += sizeof(int32); \
809} while(0) \
810
811#define read_int32_n(v, b, p, n) do { \
812 (v) = (void *)((b) + (p)); \
813 (p) += sizeof(int32) * (n); \
814} while(0) \
815
816/*
817 * Read root node and fill root node representation
818 */
819void
820jspInit(JsonPathItem *v, JsonPath *js)
821{
822 Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
823 jspInitByBuffer(v, js->data, 0);
824}
825
826/*
827 * Read node from buffer and fill its representation
828 */
829void
830jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
831{
832 v->base = base + pos;
833
834 read_byte(v->type, base, pos);
835 pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
836 read_int32(v->nextPos, base, pos);
837
838 switch (v->type)
839 {
840 case jpiNull:
841 case jpiRoot:
842 case jpiCurrent:
843 case jpiAnyArray:
844 case jpiAnyKey:
845 case jpiType:
846 case jpiSize:
847 case jpiAbs:
848 case jpiFloor:
849 case jpiCeiling:
850 case jpiDouble:
851 case jpiKeyValue:
852 case jpiLast:
853 break;
854 case jpiKey:
855 case jpiString:
856 case jpiVariable:
857 read_int32(v->content.value.datalen, base, pos);
858 /* FALLTHROUGH */
859 case jpiNumeric:
860 case jpiBool:
861 v->content.value.data = base + pos;
862 break;
863 case jpiAnd:
864 case jpiOr:
865 case jpiAdd:
866 case jpiSub:
867 case jpiMul:
868 case jpiDiv:
869 case jpiMod:
870 case jpiEqual:
871 case jpiNotEqual:
872 case jpiLess:
873 case jpiGreater:
874 case jpiLessOrEqual:
875 case jpiGreaterOrEqual:
876 case jpiStartsWith:
877 read_int32(v->content.args.left, base, pos);
878 read_int32(v->content.args.right, base, pos);
879 break;
880 case jpiLikeRegex:
881 read_int32(v->content.like_regex.flags, base, pos);
882 read_int32(v->content.like_regex.expr, base, pos);
883 read_int32(v->content.like_regex.patternlen, base, pos);
884 v->content.like_regex.pattern = base + pos;
885 break;
886 case jpiNot:
887 case jpiExists:
888 case jpiIsUnknown:
889 case jpiPlus:
890 case jpiMinus:
891 case jpiFilter:
892 read_int32(v->content.arg, base, pos);
893 break;
894 case jpiIndexArray:
895 read_int32(v->content.array.nelems, base, pos);
896 read_int32_n(v->content.array.elems, base, pos,
897 v->content.array.nelems * 2);
898 break;
899 case jpiAny:
900 read_int32(v->content.anybounds.first, base, pos);
901 read_int32(v->content.anybounds.last, base, pos);
902 break;
903 default:
904 elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
905 }
906}
907
908void
909jspGetArg(JsonPathItem *v, JsonPathItem *a)
910{
911 Assert(v->type == jpiFilter ||
912 v->type == jpiNot ||
913 v->type == jpiIsUnknown ||
914 v->type == jpiExists ||
915 v->type == jpiPlus ||
916 v->type == jpiMinus);
917
918 jspInitByBuffer(a, v->base, v->content.arg);
919}
920
921bool
922jspGetNext(JsonPathItem *v, JsonPathItem *a)
923{
924 if (jspHasNext(v))
925 {
926 Assert(v->type == jpiString ||
927 v->type == jpiNumeric ||
928 v->type == jpiBool ||
929 v->type == jpiNull ||
930 v->type == jpiKey ||
931 v->type == jpiAny ||
932 v->type == jpiAnyArray ||
933 v->type == jpiAnyKey ||
934 v->type == jpiIndexArray ||
935 v->type == jpiFilter ||
936 v->type == jpiCurrent ||
937 v->type == jpiExists ||
938 v->type == jpiRoot ||
939 v->type == jpiVariable ||
940 v->type == jpiLast ||
941 v->type == jpiAdd ||
942 v->type == jpiSub ||
943 v->type == jpiMul ||
944 v->type == jpiDiv ||
945 v->type == jpiMod ||
946 v->type == jpiPlus ||
947 v->type == jpiMinus ||
948 v->type == jpiEqual ||
949 v->type == jpiNotEqual ||
950 v->type == jpiGreater ||
951 v->type == jpiGreaterOrEqual ||
952 v->type == jpiLess ||
953 v->type == jpiLessOrEqual ||
954 v->type == jpiAnd ||
955 v->type == jpiOr ||
956 v->type == jpiNot ||
957 v->type == jpiIsUnknown ||
958 v->type == jpiType ||
959 v->type == jpiSize ||
960 v->type == jpiAbs ||
961 v->type == jpiFloor ||
962 v->type == jpiCeiling ||
963 v->type == jpiDouble ||
964 v->type == jpiKeyValue ||
965 v->type == jpiStartsWith);
966
967 if (a)
968 jspInitByBuffer(a, v->base, v->nextPos);
969 return true;
970 }
971
972 return false;
973}
974
975void
976jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
977{
978 Assert(v->type == jpiAnd ||
979 v->type == jpiOr ||
980 v->type == jpiEqual ||
981 v->type == jpiNotEqual ||
982 v->type == jpiLess ||
983 v->type == jpiGreater ||
984 v->type == jpiLessOrEqual ||
985 v->type == jpiGreaterOrEqual ||
986 v->type == jpiAdd ||
987 v->type == jpiSub ||
988 v->type == jpiMul ||
989 v->type == jpiDiv ||
990 v->type == jpiMod ||
991 v->type == jpiStartsWith);
992
993 jspInitByBuffer(a, v->base, v->content.args.left);
994}
995
996void
997jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
998{
999 Assert(v->type == jpiAnd ||
1000 v->type == jpiOr ||
1001 v->type == jpiEqual ||
1002 v->type == jpiNotEqual ||
1003 v->type == jpiLess ||
1004 v->type == jpiGreater ||
1005 v->type == jpiLessOrEqual ||
1006 v->type == jpiGreaterOrEqual ||
1007 v->type == jpiAdd ||
1008 v->type == jpiSub ||
1009 v->type == jpiMul ||
1010 v->type == jpiDiv ||
1011 v->type == jpiMod ||
1012 v->type == jpiStartsWith);
1013
1014 jspInitByBuffer(a, v->base, v->content.args.right);
1015}
1016
1017bool
1018jspGetBool(JsonPathItem *v)
1019{
1020 Assert(v->type == jpiBool);
1021
1022 return (bool) *v->content.value.data;
1023}
1024
1025Numeric
1026jspGetNumeric(JsonPathItem *v)
1027{
1028 Assert(v->type == jpiNumeric);
1029
1030 return (Numeric) v->content.value.data;
1031}
1032
1033char *
1034jspGetString(JsonPathItem *v, int32 *len)
1035{
1036 Assert(v->type == jpiKey ||
1037 v->type == jpiString ||
1038 v->type == jpiVariable);
1039
1040 if (len)
1041 *len = v->content.value.datalen;
1042 return v->content.value.data;
1043}
1044
1045bool
1046jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
1047 int i)
1048{
1049 Assert(v->type == jpiIndexArray);
1050
1051 jspInitByBuffer(from, v->base, v->content.array.elems[i].from);
1052
1053 if (!v->content.array.elems[i].to)
1054 return false;
1055
1056 jspInitByBuffer(to, v->base, v->content.array.elems[i].to);
1057
1058 return true;
1059}
1060