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