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-2020, 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 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
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
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
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
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 *
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
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
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
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
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 					appendStringInfo(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 *
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
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
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
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
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
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
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
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
1034 jspGetBool(JsonPathItem *v)
1035 {
1036 	Assert(v->type == jpiBool);
1037 
1038 	return (bool) *v->content.value.data;
1039 }
1040 
1041 Numeric
1042 jspGetNumeric(JsonPathItem *v)
1043 {
1044 	Assert(v->type == jpiNumeric);
1045 
1046 	return (Numeric) v->content.value.data;
1047 }
1048 
1049 char *
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
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