Age Owner Branch data 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-2025, 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 "catalog/pg_type.h"
67 : : #include "lib/stringinfo.h"
68 : : #include "libpq/pqformat.h"
69 : : #include "miscadmin.h"
70 : : #include "nodes/miscnodes.h"
71 : : #include "nodes/nodeFuncs.h"
72 : : #include "utils/fmgrprotos.h"
73 : : #include "utils/formatting.h"
74 : : #include "utils/json.h"
75 : : #include "utils/jsonpath.h"
76 : :
77 : :
78 : : static Datum jsonPathFromCstring(char *in, int len, struct Node *escontext);
79 : : static char *jsonPathToCstring(StringInfo out, JsonPath *in,
80 : : int estimated_len);
81 : : static bool flattenJsonPathParseItem(StringInfo buf, int *result,
82 : : struct Node *escontext,
83 : : JsonPathParseItem *item,
84 : : int nestingLevel, bool insideArraySubscript);
85 : : static void alignStringInfoInt(StringInfo buf);
86 : : static int32 reserveSpaceForItemPointer(StringInfo buf);
87 : : static void printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
88 : : bool printBracketes);
89 : : static int operationPriority(JsonPathItemType op);
90 : :
91 : :
92 : : /**************************** INPUT/OUTPUT ********************************/
93 : :
94 : : /*
95 : : * jsonpath type input function
96 : : */
97 : : Datum
2366 akorotkov@postgresql 98 :CBC 5351 : jsonpath_in(PG_FUNCTION_ARGS)
99 : : {
100 : 5351 : char *in = PG_GETARG_CSTRING(0);
101 : 5351 : int len = strlen(in);
102 : :
987 andrew@dunslane.net 103 : 5351 : return jsonPathFromCstring(in, len, fcinfo->context);
104 : : }
105 : :
106 : : /*
107 : : * jsonpath type recv function
108 : : *
109 : : * The type is sent as text in binary mode, so this is almost the same
110 : : * as the input function, but it's prefixed with a version number so we
111 : : * can change the binary format sent in future if necessary. For now,
112 : : * only version 1 is supported.
113 : : */
114 : : Datum
2366 akorotkov@postgresql 115 :UBC 0 : jsonpath_recv(PG_FUNCTION_ARGS)
116 : : {
117 : 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
118 : 0 : int version = pq_getmsgint(buf, 1);
119 : : char *str;
120 : : int nbytes;
121 : :
122 [ # # ]: 0 : if (version == JSONPATH_VERSION)
123 : 0 : str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes);
124 : : else
125 [ # # ]: 0 : elog(ERROR, "unsupported jsonpath version number: %d", version);
126 : :
987 andrew@dunslane.net 127 : 0 : return jsonPathFromCstring(str, nbytes, NULL);
128 : : }
129 : :
130 : : /*
131 : : * jsonpath type output function
132 : : */
133 : : Datum
2366 akorotkov@postgresql 134 :CBC 943 : jsonpath_out(PG_FUNCTION_ARGS)
135 : : {
136 : 943 : JsonPath *in = PG_GETARG_JSONPATH_P(0);
137 : :
138 : 943 : PG_RETURN_CSTRING(jsonPathToCstring(NULL, in, VARSIZE(in)));
139 : : }
140 : :
141 : : /*
142 : : * jsonpath type send function
143 : : *
144 : : * Just send jsonpath as a version number, then a string of text
145 : : */
146 : : Datum
2366 akorotkov@postgresql 147 :UBC 0 : jsonpath_send(PG_FUNCTION_ARGS)
148 : : {
149 : 0 : JsonPath *in = PG_GETARG_JSONPATH_P(0);
150 : : StringInfoData buf;
151 : : StringInfoData jtext;
152 : 0 : int version = JSONPATH_VERSION;
153 : :
154 : 0 : initStringInfo(&jtext);
155 : 0 : (void) jsonPathToCstring(&jtext, in, VARSIZE(in));
156 : :
157 : 0 : pq_begintypsend(&buf);
158 : 0 : pq_sendint8(&buf, version);
159 : 0 : pq_sendtext(&buf, jtext.data, jtext.len);
160 : 0 : pfree(jtext.data);
161 : :
162 : 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
163 : : }
164 : :
165 : : /*
166 : : * Converts C-string to a jsonpath value.
167 : : *
168 : : * Uses jsonpath parser to turn string into an AST, then
169 : : * flattenJsonPathParseItem() does second pass turning AST into binary
170 : : * representation of jsonpath.
171 : : */
172 : : static Datum
987 andrew@dunslane.net 173 :CBC 5351 : jsonPathFromCstring(char *in, int len, struct Node *escontext)
174 : : {
175 : 5351 : JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
176 : : JsonPath *res;
177 : : StringInfoData buf;
178 : :
179 [ + + + - : 5168 : if (SOFT_ERROR_OCCURRED(escontext))
+ + ]
180 : 21 : return (Datum) 0;
181 : :
2366 akorotkov@postgresql 182 [ + + ]: 5147 : if (!jsonpath)
987 andrew@dunslane.net 183 [ + - ]: 3 : ereturn(escontext, (Datum) 0,
184 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
185 : : errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
186 : : in)));
187 : :
188 : 5144 : initStringInfo(&buf);
189 : 5144 : enlargeStringInfo(&buf, 4 * len /* estimation */ );
190 : :
191 : 5144 : appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
192 : :
193 [ + + ]: 5144 : if (!flattenJsonPathParseItem(&buf, NULL, escontext,
194 : : jsonpath->expr, 0, false))
195 : 6 : return (Datum) 0;
196 : :
2366 akorotkov@postgresql 197 : 5129 : res = (JsonPath *) buf.data;
198 : 5129 : SET_VARSIZE(res, buf.len);
199 : 5129 : res->header = JSONPATH_VERSION;
200 [ + + ]: 5129 : if (jsonpath->lax)
201 : 4805 : res->header |= JSONPATH_LAX;
202 : :
203 : 5129 : PG_RETURN_JSONPATH_P(res);
204 : : }
205 : :
206 : : /*
207 : : * Converts jsonpath value to a C-string.
208 : : *
209 : : * If 'out' argument is non-null, the resulting C-string is stored inside the
210 : : * StringBuffer. The resulting string is always returned.
211 : : */
212 : : static char *
213 : 943 : jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
214 : : {
215 : : StringInfoData buf;
216 : : JsonPathItem v;
217 : :
218 [ + - ]: 943 : if (!out)
219 : : {
220 : 943 : out = &buf;
221 : 943 : initStringInfo(out);
222 : : }
223 : 943 : enlargeStringInfo(out, estimated_len);
224 : :
225 [ + + ]: 943 : if (!(in->header & JSONPATH_LAX))
981 peter@eisentraut.org 226 : 9 : appendStringInfoString(out, "strict ");
227 : :
2366 akorotkov@postgresql 228 : 943 : jspInit(&v, in);
229 : 943 : printJsonPathItem(out, &v, false, true);
230 : :
231 : 943 : return out->data;
232 : : }
233 : :
234 : : /*
235 : : * Recursive function converting given jsonpath parse item and all its
236 : : * children into a binary representation.
237 : : */
238 : : static bool
841 tgl@sss.pgh.pa.us 239 : 17537 : flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
240 : : JsonPathParseItem *item, int nestingLevel,
241 : : bool insideArraySubscript)
242 : : {
243 : : /* position from beginning of jsonpath data */
2366 akorotkov@postgresql 244 : 17537 : int32 pos = buf->len - JSONPATH_HDRSZ;
245 : : int32 chld;
246 : : int32 next;
247 : 17537 : int argNestingLevel = 0;
248 : :
249 : 17537 : check_stack_depth();
250 [ - + ]: 17537 : CHECK_FOR_INTERRUPTS();
251 : :
252 : 17537 : appendStringInfoChar(buf, (char) (item->type));
253 : :
254 : : /*
255 : : * We align buffer to int32 because a series of int32 values often goes
256 : : * after the header, and we want to read them directly by dereferencing
257 : : * int32 pointer (see jspInitByBuffer()).
258 : : */
259 : 17537 : alignStringInfoInt(buf);
260 : :
261 : : /*
262 : : * Reserve space for next item pointer. Actual value will be recorded
263 : : * later, after next and children items processing.
264 : : */
265 : 17537 : next = reserveSpaceForItemPointer(buf);
266 : :
267 [ + + + + : 17537 : switch (item->type)
+ + + + +
+ + + + +
+ - ]
268 : : {
269 : 2975 : case jpiString:
270 : : case jpiVariable:
271 : : case jpiKey:
981 peter@eisentraut.org 272 : 2975 : appendBinaryStringInfo(buf, &item->value.string.len,
273 : : sizeof(item->value.string.len));
2366 akorotkov@postgresql 274 : 2975 : appendBinaryStringInfo(buf, item->value.string.val,
275 : 2975 : item->value.string.len);
276 : 2975 : appendStringInfoChar(buf, '\0');
277 : 2975 : break;
278 : 1269 : case jpiNumeric:
981 peter@eisentraut.org 279 : 1269 : appendBinaryStringInfo(buf, item->value.numeric,
2366 akorotkov@postgresql 280 : 1269 : VARSIZE(item->value.numeric));
281 : 1269 : break;
282 : 90 : case jpiBool:
981 peter@eisentraut.org 283 : 90 : appendBinaryStringInfo(buf, &item->value.boolean,
284 : : sizeof(item->value.boolean));
2366 akorotkov@postgresql 285 : 90 : break;
286 : 1830 : case jpiAnd:
287 : : case jpiOr:
288 : : case jpiEqual:
289 : : case jpiNotEqual:
290 : : case jpiLess:
291 : : case jpiGreater:
292 : : case jpiLessOrEqual:
293 : : case jpiGreaterOrEqual:
294 : : case jpiAdd:
295 : : case jpiSub:
296 : : case jpiMul:
297 : : case jpiDiv:
298 : : case jpiMod:
299 : : case jpiStartsWith:
300 : : case jpiDecimal:
301 : : {
302 : : /*
303 : : * First, reserve place for left/right arg's positions, then
304 : : * record both args and sets actual position in reserved
305 : : * places.
306 : : */
307 : 1830 : int32 left = reserveSpaceForItemPointer(buf);
308 : 1830 : int32 right = reserveSpaceForItemPointer(buf);
309 : :
987 andrew@dunslane.net 310 [ + + ]: 1830 : if (!item->value.args.left)
311 : 84 : chld = pos;
841 tgl@sss.pgh.pa.us 312 [ + + ]: 1746 : else if (!flattenJsonPathParseItem(buf, &chld, escontext,
313 : : item->value.args.left,
314 : : nestingLevel + argNestingLevel,
315 : : insideArraySubscript))
987 andrew@dunslane.net 316 : 6 : return false;
2366 akorotkov@postgresql 317 : 1818 : *(int32 *) (buf->data + left) = chld - pos;
318 : :
987 andrew@dunslane.net 319 [ + + ]: 1818 : if (!item->value.args.right)
320 : 84 : chld = pos;
841 tgl@sss.pgh.pa.us 321 [ - + ]: 1734 : else if (!flattenJsonPathParseItem(buf, &chld, escontext,
322 : : item->value.args.right,
323 : : nestingLevel + argNestingLevel,
324 : : insideArraySubscript))
987 andrew@dunslane.net 325 :UBC 0 : return false;
2366 akorotkov@postgresql 326 :CBC 1818 : *(int32 *) (buf->data + right) = chld - pos;
327 : : }
328 : 1818 : break;
329 : 60 : case jpiLikeRegex:
330 : : {
331 : : int32 offs;
332 : :
333 : 60 : appendBinaryStringInfo(buf,
981 peter@eisentraut.org 334 : 60 : &item->value.like_regex.flags,
335 : : sizeof(item->value.like_regex.flags));
2366 akorotkov@postgresql 336 : 60 : offs = reserveSpaceForItemPointer(buf);
337 : 60 : appendBinaryStringInfo(buf,
981 peter@eisentraut.org 338 : 60 : &item->value.like_regex.patternlen,
339 : : sizeof(item->value.like_regex.patternlen));
2366 akorotkov@postgresql 340 : 60 : appendBinaryStringInfo(buf, item->value.like_regex.pattern,
341 : 60 : item->value.like_regex.patternlen);
342 : 60 : appendStringInfoChar(buf, '\0');
343 : :
841 tgl@sss.pgh.pa.us 344 [ - + ]: 60 : if (!flattenJsonPathParseItem(buf, &chld, escontext,
345 : : item->value.like_regex.expr,
346 : : nestingLevel,
347 : : insideArraySubscript))
987 andrew@dunslane.net 348 :UBC 0 : return false;
2366 akorotkov@postgresql 349 :CBC 60 : *(int32 *) (buf->data + offs) = chld - pos;
350 : : }
351 : 60 : break;
352 : 1140 : case jpiFilter:
353 : 1140 : argNestingLevel++;
354 : : /* FALLTHROUGH */
355 : 2565 : case jpiIsUnknown:
356 : : case jpiNot:
357 : : case jpiPlus:
358 : : case jpiMinus:
359 : : case jpiExists:
360 : : case jpiDatetime:
361 : : case jpiTime:
362 : : case jpiTimeTz:
363 : : case jpiTimestamp:
364 : : case jpiTimestampTz:
365 : : {
366 : 2565 : int32 arg = reserveSpaceForItemPointer(buf);
367 : :
987 andrew@dunslane.net 368 [ + + ]: 2565 : if (!item->value.arg)
369 : 768 : chld = pos;
841 tgl@sss.pgh.pa.us 370 [ - + ]: 1797 : else if (!flattenJsonPathParseItem(buf, &chld, escontext,
371 : : item->value.arg,
372 : : nestingLevel + argNestingLevel,
373 : : insideArraySubscript))
987 andrew@dunslane.net 374 :UBC 0 : return false;
2366 akorotkov@postgresql 375 :CBC 2562 : *(int32 *) (buf->data + arg) = chld - pos;
376 : : }
377 : 2562 : break;
378 : 57 : case jpiNull:
379 : 57 : break;
380 : 4967 : case jpiRoot:
381 : 4967 : break;
382 : 1087 : case jpiAnyArray:
383 : : case jpiAnyKey:
384 : 1087 : break;
385 : 1284 : case jpiCurrent:
386 [ + + ]: 1284 : if (nestingLevel <= 0)
987 andrew@dunslane.net 387 [ + + ]: 9 : ereturn(escontext, false,
388 : : (errcode(ERRCODE_SYNTAX_ERROR),
389 : : errmsg("@ is not allowed in root expressions")));
2366 akorotkov@postgresql 390 : 1275 : break;
391 : 45 : case jpiLast:
392 [ + + ]: 45 : if (!insideArraySubscript)
987 andrew@dunslane.net 393 [ + - ]: 6 : ereturn(escontext, false,
394 : : (errcode(ERRCODE_SYNTAX_ERROR),
395 : : errmsg("LAST is allowed only in array subscripts")));
2366 akorotkov@postgresql 396 : 39 : break;
397 : 255 : case jpiIndexArray:
398 : : {
399 : 255 : int32 nelems = item->value.array.nelems;
400 : : int offset;
401 : : int i;
402 : :
981 peter@eisentraut.org 403 : 255 : appendBinaryStringInfo(buf, &nelems, sizeof(nelems));
404 : :
2366 akorotkov@postgresql 405 : 255 : offset = buf->len;
406 : :
407 : 255 : appendStringInfoSpaces(buf, sizeof(int32) * 2 * nelems);
408 : :
409 [ + + ]: 534 : for (i = 0; i < nelems; i++)
410 : : {
411 : : int32 *ppos;
412 : : int32 topos;
413 : : int32 frompos;
414 : :
841 tgl@sss.pgh.pa.us 415 [ - + ]: 279 : if (!flattenJsonPathParseItem(buf, &frompos, escontext,
416 : 279 : item->value.array.elems[i].from,
417 : : nestingLevel, true))
987 andrew@dunslane.net 418 :UBC 0 : return false;
987 andrew@dunslane.net 419 :CBC 279 : frompos -= pos;
420 : :
2366 akorotkov@postgresql 421 [ + + ]: 279 : if (item->value.array.elems[i].to)
422 : : {
841 tgl@sss.pgh.pa.us 423 [ - + ]: 24 : if (!flattenJsonPathParseItem(buf, &topos, escontext,
424 : 24 : item->value.array.elems[i].to,
425 : : nestingLevel, true))
987 andrew@dunslane.net 426 :UBC 0 : return false;
987 andrew@dunslane.net 427 :CBC 24 : topos -= pos;
428 : : }
429 : : else
2366 akorotkov@postgresql 430 : 255 : topos = 0;
431 : :
432 : 279 : ppos = (int32 *) &buf->data[offset + i * 2 * sizeof(int32)];
433 : :
434 : 279 : ppos[0] = frompos;
435 : 279 : ppos[1] = topos;
436 : : }
437 : : }
438 : 255 : break;
439 : 177 : case jpiAny:
440 : 177 : appendBinaryStringInfo(buf,
981 peter@eisentraut.org 441 : 177 : &item->value.anybounds.first,
442 : : sizeof(item->value.anybounds.first));
2366 akorotkov@postgresql 443 : 177 : appendBinaryStringInfo(buf,
981 peter@eisentraut.org 444 : 177 : &item->value.anybounds.last,
445 : : sizeof(item->value.anybounds.last));
2366 akorotkov@postgresql 446 : 177 : break;
447 : 876 : case jpiType:
448 : : case jpiSize:
449 : : case jpiAbs:
450 : : case jpiFloor:
451 : : case jpiCeiling:
452 : : case jpiDouble:
453 : : case jpiKeyValue:
454 : : case jpiBigint:
455 : : case jpiBoolean:
456 : : case jpiDate:
457 : : case jpiInteger:
458 : : case jpiNumber:
459 : : case jpiStringFunc:
460 : 876 : break;
2366 akorotkov@postgresql 461 :UBC 0 : default:
462 [ # # ]: 0 : elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
463 : : }
464 : :
2366 akorotkov@postgresql 465 [ + + ]:CBC 17507 : if (item->next)
466 : : {
841 tgl@sss.pgh.pa.us 467 [ - + ]: 6753 : if (!flattenJsonPathParseItem(buf, &chld, escontext,
468 : : item->next, nestingLevel,
469 : : insideArraySubscript))
987 andrew@dunslane.net 470 :UBC 0 : return false;
987 andrew@dunslane.net 471 :CBC 6750 : chld -= pos;
2366 akorotkov@postgresql 472 : 6750 : *(int32 *) (buf->data + next) = chld;
473 : : }
474 : :
987 andrew@dunslane.net 475 [ + + ]: 17504 : if (result)
476 : 12375 : *result = pos;
477 : 17504 : return true;
478 : : }
479 : :
480 : : /*
481 : : * Align StringInfo to int by adding zero padding bytes
482 : : */
483 : : static void
2366 akorotkov@postgresql 484 : 17537 : alignStringInfoInt(StringInfo buf)
485 : : {
486 [ + + + + ]: 17537 : switch (INTALIGN(buf->len) - buf->len)
487 : : {
488 : 15953 : case 3:
489 [ - + ]: 15953 : appendStringInfoCharMacro(buf, 0);
490 : : /* FALLTHROUGH */
491 : : case 2:
492 [ - + ]: 16157 : appendStringInfoCharMacro(buf, 0);
493 : : /* FALLTHROUGH */
494 : : case 1:
495 [ - + ]: 17312 : appendStringInfoCharMacro(buf, 0);
496 : : /* FALLTHROUGH */
497 : : default:
498 : 17537 : break;
499 : : }
500 : 17537 : }
501 : :
502 : : /*
503 : : * Reserve space for int32 JsonPathItem pointer. Now zero pointer is written,
504 : : * actual value will be recorded at '(int32 *) &buf->data[pos]' later.
505 : : */
506 : : static int32
507 : 23822 : reserveSpaceForItemPointer(StringInfo buf)
508 : : {
509 : 23822 : int32 pos = buf->len;
510 : 23822 : int32 ptr = 0;
511 : :
981 peter@eisentraut.org 512 : 23822 : appendBinaryStringInfo(buf, &ptr, sizeof(ptr));
513 : :
2366 akorotkov@postgresql 514 : 23822 : return pos;
515 : : }
516 : :
517 : : /*
518 : : * Prints text representation of given jsonpath item and all its children.
519 : : */
520 : : static void
521 : 3328 : printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
522 : : bool printBracketes)
523 : : {
524 : : JsonPathItem elem;
525 : : int i;
526 : : int32 len;
527 : : char *str;
528 : :
529 : 3328 : check_stack_depth();
530 [ - + ]: 3328 : CHECK_FOR_INTERRUPTS();
531 : :
532 [ + + + + : 3328 : switch (v->type)
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
- ]
533 : : {
534 : 21 : case jpiNull:
535 : 21 : appendStringInfoString(buf, "null");
536 : 21 : break;
537 : 42 : case jpiString:
406 drowley@postgresql.o 538 : 42 : str = jspGetString(v, &len);
539 : 42 : escape_json_with_len(buf, str, len);
2366 akorotkov@postgresql 540 : 42 : break;
541 : 490 : case jpiNumeric:
1258 peter@eisentraut.org 542 [ + + ]: 490 : if (jspHasNext(v))
543 : 42 : appendStringInfoChar(buf, '(');
2366 akorotkov@postgresql 544 : 490 : appendStringInfoString(buf,
545 : 490 : DatumGetCString(DirectFunctionCall1(numeric_out,
546 : : NumericGetDatum(jspGetNumeric(v)))));
1258 peter@eisentraut.org 547 [ + + ]: 490 : if (jspHasNext(v))
548 : 42 : appendStringInfoChar(buf, ')');
2366 akorotkov@postgresql 549 : 490 : break;
550 : 6 : case jpiBool:
551 [ + + ]: 6 : if (jspGetBool(v))
981 peter@eisentraut.org 552 : 3 : appendStringInfoString(buf, "true");
553 : : else
554 : 3 : appendStringInfoString(buf, "false");
2366 akorotkov@postgresql 555 : 6 : break;
556 : 394 : case jpiAnd:
557 : : case jpiOr:
558 : : case jpiEqual:
559 : : case jpiNotEqual:
560 : : case jpiLess:
561 : : case jpiGreater:
562 : : case jpiLessOrEqual:
563 : : case jpiGreaterOrEqual:
564 : : case jpiAdd:
565 : : case jpiSub:
566 : : case jpiMul:
567 : : case jpiDiv:
568 : : case jpiMod:
569 : : case jpiStartsWith:
570 [ + + ]: 394 : if (printBracketes)
571 : 57 : appendStringInfoChar(buf, '(');
572 : 394 : jspGetLeftArg(v, &elem);
573 : 394 : printJsonPathItem(buf, &elem, false,
574 : 394 : operationPriority(elem.type) <=
575 : 394 : operationPriority(v->type));
576 : 394 : appendStringInfoChar(buf, ' ');
577 : 394 : appendStringInfoString(buf, jspOperationName(v->type));
578 : 394 : appendStringInfoChar(buf, ' ');
579 : 394 : jspGetRightArg(v, &elem);
580 : 394 : printJsonPathItem(buf, &elem, false,
581 : 394 : operationPriority(elem.type) <=
582 : 394 : operationPriority(v->type));
583 [ + + ]: 394 : if (printBracketes)
584 : 57 : appendStringInfoChar(buf, ')');
585 : 394 : break;
586 : 6 : case jpiNot:
981 peter@eisentraut.org 587 : 6 : appendStringInfoString(buf, "!(");
2366 akorotkov@postgresql 588 : 6 : jspGetArg(v, &elem);
589 : 6 : printJsonPathItem(buf, &elem, false, false);
590 : 6 : appendStringInfoChar(buf, ')');
591 : 6 : break;
592 : 3 : case jpiIsUnknown:
593 : 3 : appendStringInfoChar(buf, '(');
594 : 3 : jspGetArg(v, &elem);
595 : 3 : printJsonPathItem(buf, &elem, false, false);
981 peter@eisentraut.org 596 : 3 : appendStringInfoString(buf, ") is unknown");
2366 akorotkov@postgresql 597 : 3 : break;
612 peter@eisentraut.org 598 : 24 : case jpiPlus:
599 : : case jpiMinus:
600 [ + + ]: 24 : if (printBracketes)
601 : 9 : appendStringInfoChar(buf, '(');
602 [ + + ]: 24 : appendStringInfoChar(buf, v->type == jpiPlus ? '+' : '-');
2366 akorotkov@postgresql 603 : 24 : jspGetArg(v, &elem);
612 peter@eisentraut.org 604 : 24 : printJsonPathItem(buf, &elem, false,
605 : 24 : operationPriority(elem.type) <=
606 : 24 : operationPriority(v->type));
607 [ + + ]: 24 : if (printBracketes)
608 : 9 : appendStringInfoChar(buf, ')');
2366 akorotkov@postgresql 609 : 24 : break;
610 : 109 : case jpiAnyArray:
981 peter@eisentraut.org 611 : 109 : appendStringInfoString(buf, "[*]");
2366 akorotkov@postgresql 612 : 109 : break;
613 : 6 : case jpiAnyKey:
614 [ + - ]: 6 : if (inKey)
615 : 6 : appendStringInfoChar(buf, '.');
616 : 6 : appendStringInfoChar(buf, '*');
617 : 6 : break;
618 : 48 : case jpiIndexArray:
619 : 48 : appendStringInfoChar(buf, '[');
620 [ + + ]: 105 : for (i = 0; i < v->content.array.nelems; i++)
621 : : {
622 : : JsonPathItem from;
623 : : JsonPathItem to;
624 : 57 : bool range = jspGetArraySubscript(v, &from, &to, i);
625 : :
626 [ + + ]: 57 : if (i)
627 : 9 : appendStringInfoChar(buf, ',');
628 : :
629 : 57 : printJsonPathItem(buf, &from, false, false);
630 : :
631 [ + + ]: 57 : if (range)
632 : : {
981 peter@eisentraut.org 633 : 6 : appendStringInfoString(buf, " to ");
2366 akorotkov@postgresql 634 : 6 : printJsonPathItem(buf, &to, false, false);
635 : : }
636 : : }
637 : 48 : appendStringInfoChar(buf, ']');
638 : 48 : break;
639 : 24 : case jpiAny:
640 [ + - ]: 24 : if (inKey)
641 : 24 : appendStringInfoChar(buf, '.');
642 : :
643 [ + + ]: 24 : if (v->content.anybounds.first == 0 &&
644 [ + + ]: 6 : v->content.anybounds.last == PG_UINT32_MAX)
981 peter@eisentraut.org 645 : 3 : appendStringInfoString(buf, "**");
2366 akorotkov@postgresql 646 [ + + ]: 21 : else if (v->content.anybounds.first == v->content.anybounds.last)
647 : : {
648 [ + + ]: 9 : if (v->content.anybounds.first == PG_UINT32_MAX)
1787 drowley@postgresql.o 649 : 3 : appendStringInfoString(buf, "**{last}");
650 : : else
2366 akorotkov@postgresql 651 : 6 : appendStringInfo(buf, "**{%u}",
652 : : v->content.anybounds.first);
653 : : }
654 [ + + ]: 12 : else if (v->content.anybounds.first == PG_UINT32_MAX)
655 : 3 : appendStringInfo(buf, "**{last to %u}",
656 : : v->content.anybounds.last);
657 [ + + ]: 9 : else if (v->content.anybounds.last == PG_UINT32_MAX)
658 : 3 : appendStringInfo(buf, "**{%u to last}",
659 : : v->content.anybounds.first);
660 : : else
661 : 6 : appendStringInfo(buf, "**{%u to %u}",
662 : : v->content.anybounds.first,
663 : : v->content.anybounds.last);
664 : 24 : break;
612 peter@eisentraut.org 665 : 640 : case jpiKey:
666 [ + - ]: 640 : if (inKey)
667 : 640 : appendStringInfoChar(buf, '.');
406 drowley@postgresql.o 668 : 640 : str = jspGetString(v, &len);
669 : 640 : escape_json_with_len(buf, str, len);
612 peter@eisentraut.org 670 : 640 : break;
671 : 301 : case jpiCurrent:
672 [ - + ]: 301 : Assert(!inKey);
673 : 301 : appendStringInfoChar(buf, '@');
674 : 301 : break;
675 : 784 : case jpiRoot:
676 [ - + ]: 784 : Assert(!inKey);
677 : 784 : appendStringInfoChar(buf, '$');
678 : 784 : break;
679 : 36 : case jpiVariable:
680 : 36 : appendStringInfoChar(buf, '$');
406 drowley@postgresql.o 681 : 36 : str = jspGetString(v, &len);
682 : 36 : escape_json_with_len(buf, str, len);
612 peter@eisentraut.org 683 : 36 : break;
684 : 265 : case jpiFilter:
685 : 265 : appendStringInfoString(buf, "?(");
686 : 265 : jspGetArg(v, &elem);
687 : 265 : printJsonPathItem(buf, &elem, false, false);
688 : 265 : appendStringInfoChar(buf, ')');
689 : 265 : break;
690 : 12 : case jpiExists:
691 : 12 : appendStringInfoString(buf, "exists (");
692 : 12 : jspGetArg(v, &elem);
693 : 12 : printJsonPathItem(buf, &elem, false, false);
694 : 12 : appendStringInfoChar(buf, ')');
695 : 12 : break;
2366 akorotkov@postgresql 696 : 15 : case jpiType:
981 peter@eisentraut.org 697 : 15 : appendStringInfoString(buf, ".type()");
2366 akorotkov@postgresql 698 : 15 : break;
699 : 3 : case jpiSize:
981 peter@eisentraut.org 700 : 3 : appendStringInfoString(buf, ".size()");
2366 akorotkov@postgresql 701 : 3 : break;
702 : 3 : case jpiAbs:
981 peter@eisentraut.org 703 : 3 : appendStringInfoString(buf, ".abs()");
2366 akorotkov@postgresql 704 : 3 : break;
612 peter@eisentraut.org 705 : 3 : case jpiFloor:
706 : 3 : appendStringInfoString(buf, ".floor()");
707 : 3 : break;
2366 akorotkov@postgresql 708 : 3 : case jpiCeiling:
981 peter@eisentraut.org 709 : 3 : appendStringInfoString(buf, ".ceiling()");
2366 akorotkov@postgresql 710 : 3 : break;
612 peter@eisentraut.org 711 : 3 : case jpiDouble:
712 : 3 : appendStringInfoString(buf, ".double()");
2366 akorotkov@postgresql 713 : 3 : break;
2173 714 : 6 : case jpiDatetime:
981 peter@eisentraut.org 715 : 6 : appendStringInfoString(buf, ".datetime(");
2173 akorotkov@postgresql 716 [ + + ]: 6 : if (v->content.arg)
717 : : {
718 : 3 : jspGetArg(v, &elem);
719 : 3 : printJsonPathItem(buf, &elem, false, false);
720 : : }
721 : 6 : appendStringInfoChar(buf, ')');
722 : 6 : break;
2366 723 : 3 : case jpiKeyValue:
981 peter@eisentraut.org 724 : 3 : appendStringInfoString(buf, ".keyvalue()");
2366 akorotkov@postgresql 725 : 3 : break;
612 peter@eisentraut.org 726 : 6 : case jpiLast:
727 : 6 : appendStringInfoString(buf, "last");
728 : 6 : break;
729 : 24 : case jpiLikeRegex:
730 [ - + ]: 24 : if (printBracketes)
612 peter@eisentraut.org 731 :UBC 0 : appendStringInfoChar(buf, '(');
732 : :
612 peter@eisentraut.org 733 :CBC 24 : jspInitByBuffer(&elem, v->base, v->content.like_regex.expr);
734 : 24 : printJsonPathItem(buf, &elem, false,
735 : 24 : operationPriority(elem.type) <=
736 : 24 : operationPriority(v->type));
737 : :
738 : 24 : appendStringInfoString(buf, " like_regex ");
739 : :
406 drowley@postgresql.o 740 : 24 : escape_json_with_len(buf,
741 : 24 : v->content.like_regex.pattern,
742 : : v->content.like_regex.patternlen);
743 : :
612 peter@eisentraut.org 744 [ + + ]: 24 : if (v->content.like_regex.flags)
745 : : {
746 : 18 : appendStringInfoString(buf, " flag \"");
747 : :
748 [ + + ]: 18 : if (v->content.like_regex.flags & JSP_REGEX_ICASE)
749 : 15 : appendStringInfoChar(buf, 'i');
750 [ + + ]: 18 : if (v->content.like_regex.flags & JSP_REGEX_DOTALL)
751 : 9 : appendStringInfoChar(buf, 's');
752 [ + + ]: 18 : if (v->content.like_regex.flags & JSP_REGEX_MLINE)
753 : 6 : appendStringInfoChar(buf, 'm');
754 [ + + ]: 18 : if (v->content.like_regex.flags & JSP_REGEX_WSPACE)
755 : 3 : appendStringInfoChar(buf, 'x');
756 [ + + ]: 18 : if (v->content.like_regex.flags & JSP_REGEX_QUOTE)
757 : 9 : appendStringInfoChar(buf, 'q');
758 : :
759 : 18 : appendStringInfoChar(buf, '"');
760 : : }
761 : :
762 [ - + ]: 24 : if (printBracketes)
612 peter@eisentraut.org 763 :UBC 0 : appendStringInfoChar(buf, ')');
612 peter@eisentraut.org 764 :CBC 24 : break;
590 andrew@dunslane.net 765 : 3 : case jpiBigint:
766 : 3 : appendStringInfoString(buf, ".bigint()");
767 : 3 : break;
768 : 3 : case jpiBoolean:
769 : 3 : appendStringInfoString(buf, ".boolean()");
770 : 3 : break;
771 : 3 : case jpiDate:
772 : 3 : appendStringInfoString(buf, ".date()");
773 : 3 : break;
774 : 6 : case jpiDecimal:
775 : 6 : appendStringInfoString(buf, ".decimal(");
776 [ + + ]: 6 : if (v->content.args.left)
777 : : {
778 : 3 : jspGetLeftArg(v, &elem);
779 : 3 : printJsonPathItem(buf, &elem, false, false);
780 : : }
781 [ + + ]: 6 : if (v->content.args.right)
782 : : {
783 : 3 : appendStringInfoChar(buf, ',');
784 : 3 : jspGetRightArg(v, &elem);
785 : 3 : printJsonPathItem(buf, &elem, false, false);
786 : : }
787 : 6 : appendStringInfoChar(buf, ')');
788 : 6 : break;
789 : 3 : case jpiInteger:
790 : 3 : appendStringInfoString(buf, ".integer()");
791 : 3 : break;
792 : 3 : case jpiNumber:
793 : 3 : appendStringInfoString(buf, ".number()");
794 : 3 : break;
795 : 3 : case jpiStringFunc:
796 : 3 : appendStringInfoString(buf, ".string()");
797 : 3 : break;
798 : 6 : case jpiTime:
799 : 6 : appendStringInfoString(buf, ".time(");
800 [ + + ]: 6 : if (v->content.arg)
801 : : {
802 : 3 : jspGetArg(v, &elem);
803 : 3 : printJsonPathItem(buf, &elem, false, false);
804 : : }
805 : 6 : appendStringInfoChar(buf, ')');
806 : 6 : break;
807 : 6 : case jpiTimeTz:
808 : 6 : appendStringInfoString(buf, ".time_tz(");
809 [ + + ]: 6 : if (v->content.arg)
810 : : {
811 : 3 : jspGetArg(v, &elem);
812 : 3 : printJsonPathItem(buf, &elem, false, false);
813 : : }
814 : 6 : appendStringInfoChar(buf, ')');
815 : 6 : break;
816 : 6 : case jpiTimestamp:
817 : 6 : appendStringInfoString(buf, ".timestamp(");
818 [ + + ]: 6 : if (v->content.arg)
819 : : {
820 : 3 : jspGetArg(v, &elem);
821 : 3 : printJsonPathItem(buf, &elem, false, false);
822 : : }
823 : 6 : appendStringInfoChar(buf, ')');
824 : 6 : break;
825 : 6 : case jpiTimestampTz:
826 : 6 : appendStringInfoString(buf, ".timestamp_tz(");
827 [ + + ]: 6 : if (v->content.arg)
828 : : {
829 : 3 : jspGetArg(v, &elem);
830 : 3 : printJsonPathItem(buf, &elem, false, false);
831 : : }
832 : 6 : appendStringInfoChar(buf, ')');
833 : 6 : break;
2366 akorotkov@postgresql 834 :UBC 0 : default:
835 [ # # ]: 0 : elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
836 : : }
837 : :
2366 akorotkov@postgresql 838 [ + + ]:CBC 3328 : if (jspGetNext(v, &elem))
839 : 1179 : printJsonPathItem(buf, &elem, true, true);
840 : 3328 : }
841 : :
842 : : const char *
843 : 766 : jspOperationName(JsonPathItemType type)
844 : : {
845 [ + + + + : 766 : switch (type)
+ + + + +
+ + + + -
+ + + + +
+ + + - +
+ + + + +
+ + + + +
- ]
846 : : {
847 : 15 : case jpiAnd:
848 : 15 : return "&&";
849 : 27 : case jpiOr:
850 : 27 : return "||";
851 : 84 : case jpiEqual:
852 : 84 : return "==";
853 : 3 : case jpiNotEqual:
854 : 3 : return "!=";
855 : 150 : case jpiLess:
856 : 150 : return "<";
857 : 22 : case jpiGreater:
858 : 22 : return ">";
859 : 3 : case jpiLessOrEqual:
860 : 3 : return "<=";
861 : 21 : case jpiGreaterOrEqual:
862 : 21 : return ">=";
612 peter@eisentraut.org 863 : 42 : case jpiAdd:
864 : : case jpiPlus:
2366 akorotkov@postgresql 865 : 42 : return "+";
612 peter@eisentraut.org 866 : 18 : case jpiSub:
867 : : case jpiMinus:
2366 akorotkov@postgresql 868 : 18 : return "-";
869 : 12 : case jpiMul:
870 : 12 : return "*";
871 : 3 : case jpiDiv:
872 : 3 : return "/";
873 : 3 : case jpiMod:
874 : 3 : return "%";
2366 akorotkov@postgresql 875 :UBC 0 : case jpiType:
876 : 0 : return "type";
2366 akorotkov@postgresql 877 :CBC 3 : case jpiSize:
878 : 3 : return "size";
879 : 3 : case jpiAbs:
880 : 3 : return "abs";
612 peter@eisentraut.org 881 : 3 : case jpiFloor:
882 : 3 : return "floor";
883 : 3 : case jpiCeiling:
884 : 3 : return "ceiling";
885 : 30 : case jpiDouble:
886 : 30 : return "double";
2173 akorotkov@postgresql 887 : 15 : case jpiDatetime:
888 : 15 : return "datetime";
612 peter@eisentraut.org 889 : 9 : case jpiKeyValue:
890 : 9 : return "keyvalue";
891 : 6 : case jpiStartsWith:
892 : 6 : return "starts with";
612 peter@eisentraut.org 893 :UBC 0 : case jpiLikeRegex:
894 : 0 : return "like_regex";
590 andrew@dunslane.net 895 :CBC 39 : case jpiBigint:
896 : 39 : return "bigint";
897 : 36 : case jpiBoolean:
898 : 36 : return "boolean";
899 : 18 : case jpiDate:
900 : 18 : return "date";
901 : 39 : case jpiDecimal:
902 : 39 : return "decimal";
903 : 39 : case jpiInteger:
904 : 39 : return "integer";
905 : 27 : case jpiNumber:
906 : 27 : return "number";
907 : 9 : case jpiStringFunc:
908 : 9 : return "string";
909 : 21 : case jpiTime:
910 : 21 : return "time";
911 : 21 : case jpiTimeTz:
912 : 21 : return "time_tz";
913 : 21 : case jpiTimestamp:
914 : 21 : return "timestamp";
915 : 21 : case jpiTimestampTz:
916 : 21 : return "timestamp_tz";
2366 akorotkov@postgresql 917 :UBC 0 : default:
918 [ # # ]: 0 : elog(ERROR, "unrecognized jsonpath item type: %d", type);
919 : : return NULL;
920 : : }
921 : : }
922 : :
923 : : static int
2366 akorotkov@postgresql 924 :CBC 1672 : operationPriority(JsonPathItemType op)
925 : : {
926 [ + + + + : 1672 : switch (op)
+ + + ]
927 : : {
928 : 57 : case jpiOr:
929 : 57 : return 0;
930 : 39 : case jpiAnd:
931 : 39 : return 1;
932 : 641 : case jpiEqual:
933 : : case jpiNotEqual:
934 : : case jpiLess:
935 : : case jpiGreater:
936 : : case jpiLessOrEqual:
937 : : case jpiGreaterOrEqual:
938 : : case jpiStartsWith:
939 : 641 : return 2;
940 : 126 : case jpiAdd:
941 : : case jpiSub:
942 : 126 : return 3;
943 : 33 : case jpiMul:
944 : : case jpiDiv:
945 : : case jpiMod:
946 : 33 : return 4;
947 : 42 : case jpiPlus:
948 : : case jpiMinus:
949 : 42 : return 5;
950 : 734 : default:
951 : 734 : return 6;
952 : : }
953 : : }
954 : :
955 : : /******************* Support functions for JsonPath *************************/
956 : :
957 : : /*
958 : : * Support macros to read stored values
959 : : */
960 : :
961 : : #define read_byte(v, b, p) do { \
962 : : (v) = *(uint8*)((b) + (p)); \
963 : : (p) += 1; \
964 : : } while(0) \
965 : :
966 : : #define read_int32(v, b, p) do { \
967 : : (v) = *(uint32*)((b) + (p)); \
968 : : (p) += sizeof(int32); \
969 : : } while(0) \
970 : :
971 : : #define read_int32_n(v, b, p, n) do { \
972 : : (v) = (void *)((b) + (p)); \
973 : : (p) += sizeof(int32) * (n); \
974 : : } while(0) \
975 : :
976 : : /*
977 : : * Read root node and fill root node representation
978 : : */
979 : : void
980 : 100762 : jspInit(JsonPathItem *v, JsonPath *js)
981 : : {
982 [ - + ]: 100762 : Assert((js->header & ~JSONPATH_LAX) == JSONPATH_VERSION);
983 : 100762 : jspInitByBuffer(v, js->data, 0);
984 : 100762 : }
985 : :
986 : : /*
987 : : * Read node from buffer and fill its representation
988 : : */
989 : : void
990 : 343793 : jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
991 : : {
992 : 343793 : v->base = base + pos;
993 : :
994 : 343793 : read_byte(v->type, base, pos);
995 : 343793 : pos = INTALIGN((uintptr_t) (base + pos)) - (uintptr_t) base;
996 : 343793 : read_int32(v->nextPos, base, pos);
997 : :
998 [ + + + + : 343793 : switch (v->type)
+ + + +
- ]
999 : : {
1000 : 126029 : case jpiNull:
1001 : : case jpiRoot:
1002 : : case jpiCurrent:
1003 : : case jpiAnyArray:
1004 : : case jpiAnyKey:
1005 : : case jpiType:
1006 : : case jpiSize:
1007 : : case jpiAbs:
1008 : : case jpiFloor:
1009 : : case jpiCeiling:
1010 : : case jpiDouble:
1011 : : case jpiKeyValue:
1012 : : case jpiLast:
1013 : : case jpiBigint:
1014 : : case jpiBoolean:
1015 : : case jpiDate:
1016 : : case jpiInteger:
1017 : : case jpiNumber:
1018 : : case jpiStringFunc:
1019 : 126029 : break;
1020 : 101604 : case jpiString:
1021 : : case jpiKey:
1022 : : case jpiVariable:
1023 : 101604 : read_int32(v->content.value.datalen, base, pos);
1024 : : /* FALLTHROUGH */
1025 : 113341 : case jpiNumeric:
1026 : : case jpiBool:
1027 : 113341 : v->content.value.data = base + pos;
1028 : 113341 : break;
1029 : 50584 : case jpiAnd:
1030 : : case jpiOr:
1031 : : case jpiEqual:
1032 : : case jpiNotEqual:
1033 : : case jpiLess:
1034 : : case jpiGreater:
1035 : : case jpiLessOrEqual:
1036 : : case jpiGreaterOrEqual:
1037 : : case jpiAdd:
1038 : : case jpiSub:
1039 : : case jpiMul:
1040 : : case jpiDiv:
1041 : : case jpiMod:
1042 : : case jpiStartsWith:
1043 : : case jpiDecimal:
1044 : 50584 : read_int32(v->content.args.left, base, pos);
1045 : 50584 : read_int32(v->content.args.right, base, pos);
1046 : 50584 : break;
1047 : 53137 : case jpiNot:
1048 : : case jpiIsUnknown:
1049 : : case jpiExists:
1050 : : case jpiPlus:
1051 : : case jpiMinus:
1052 : : case jpiFilter:
1053 : : case jpiDatetime:
1054 : : case jpiTime:
1055 : : case jpiTimeTz:
1056 : : case jpiTimestamp:
1057 : : case jpiTimestampTz:
1058 : 53137 : read_int32(v->content.arg, base, pos);
1059 : 53137 : break;
1060 : 303 : case jpiIndexArray:
1061 : 303 : read_int32(v->content.array.nelems, base, pos);
1062 : 303 : read_int32_n(v->content.array.elems, base, pos,
1063 : : v->content.array.nelems * 2);
1064 : 303 : break;
1065 : 177 : case jpiAny:
1066 : 177 : read_int32(v->content.anybounds.first, base, pos);
1067 : 177 : read_int32(v->content.anybounds.last, base, pos);
1068 : 177 : break;
612 peter@eisentraut.org 1069 : 222 : case jpiLikeRegex:
1070 : 222 : read_int32(v->content.like_regex.flags, base, pos);
1071 : 222 : read_int32(v->content.like_regex.expr, base, pos);
1072 : 222 : read_int32(v->content.like_regex.patternlen, base, pos);
1073 : 222 : v->content.like_regex.pattern = base + pos;
1074 : 222 : break;
2366 akorotkov@postgresql 1075 :UBC 0 : default:
1076 [ # # ]: 0 : elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
1077 : : }
2366 akorotkov@postgresql 1078 :CBC 343793 : }
1079 : :
1080 : : void
1081 : 53035 : jspGetArg(JsonPathItem *v, JsonPathItem *a)
1082 : : {
612 peter@eisentraut.org 1083 [ + + + + : 53035 : Assert(v->type == jpiNot ||
+ + + + +
+ + + + +
+ + + + +
+ - + ]
1084 : : v->type == jpiIsUnknown ||
1085 : : v->type == jpiPlus ||
1086 : : v->type == jpiMinus ||
1087 : : v->type == jpiFilter ||
1088 : : v->type == jpiExists ||
1089 : : v->type == jpiDatetime ||
1090 : : v->type == jpiTime ||
1091 : : v->type == jpiTimeTz ||
1092 : : v->type == jpiTimestamp ||
1093 : : v->type == jpiTimestampTz);
1094 : :
2366 akorotkov@postgresql 1095 : 53035 : jspInitByBuffer(a, v->base, v->content.arg);
1096 : 53035 : }
1097 : :
1098 : : bool
1099 : 226337 : jspGetNext(JsonPathItem *v, JsonPathItem *a)
1100 : : {
1101 [ + + ]: 226337 : if (jspHasNext(v))
1102 : : {
612 peter@eisentraut.org 1103 [ + + + + : 101080 : Assert(v->type == jpiNull ||
+ + + + +
- + - + -
+ - + - +
- + - + +
+ - + - +
+ + + + +
+ - + - +
- + + + +
+ + + + +
+ + + + +
+ + + + +
+ + - + -
+ - + + +
+ + + + +
+ + + + +
+ + - + -
+ + + + +
+ + - + +
+ + + + +
+ + + + +
- + ]
1104 : : v->type == jpiString ||
1105 : : v->type == jpiNumeric ||
1106 : : v->type == jpiBool ||
1107 : : v->type == jpiAnd ||
1108 : : v->type == jpiOr ||
1109 : : v->type == jpiNot ||
1110 : : v->type == jpiIsUnknown ||
1111 : : v->type == jpiEqual ||
1112 : : v->type == jpiNotEqual ||
1113 : : v->type == jpiLess ||
1114 : : v->type == jpiGreater ||
1115 : : v->type == jpiLessOrEqual ||
1116 : : v->type == jpiGreaterOrEqual ||
1117 : : v->type == jpiAdd ||
1118 : : v->type == jpiSub ||
1119 : : v->type == jpiMul ||
1120 : : v->type == jpiDiv ||
1121 : : v->type == jpiMod ||
1122 : : v->type == jpiPlus ||
1123 : : v->type == jpiMinus ||
1124 : : v->type == jpiAnyArray ||
1125 : : v->type == jpiAnyKey ||
1126 : : v->type == jpiIndexArray ||
1127 : : v->type == jpiAny ||
1128 : : v->type == jpiKey ||
1129 : : v->type == jpiCurrent ||
1130 : : v->type == jpiRoot ||
1131 : : v->type == jpiVariable ||
1132 : : v->type == jpiFilter ||
1133 : : v->type == jpiExists ||
1134 : : v->type == jpiType ||
1135 : : v->type == jpiSize ||
1136 : : v->type == jpiAbs ||
1137 : : v->type == jpiFloor ||
1138 : : v->type == jpiCeiling ||
1139 : : v->type == jpiDouble ||
1140 : : v->type == jpiDatetime ||
1141 : : v->type == jpiKeyValue ||
1142 : : v->type == jpiLast ||
1143 : : v->type == jpiStartsWith ||
1144 : : v->type == jpiLikeRegex ||
1145 : : v->type == jpiBigint ||
1146 : : v->type == jpiBoolean ||
1147 : : v->type == jpiDate ||
1148 : : v->type == jpiDecimal ||
1149 : : v->type == jpiInteger ||
1150 : : v->type == jpiNumber ||
1151 : : v->type == jpiStringFunc ||
1152 : : v->type == jpiTime ||
1153 : : v->type == jpiTimeTz ||
1154 : : v->type == jpiTimestamp ||
1155 : : v->type == jpiTimestampTz);
1156 : :
2366 akorotkov@postgresql 1157 [ + - ]: 101080 : if (a)
1158 : 101080 : jspInitByBuffer(a, v->base, v->nextPos);
1159 : 101080 : return true;
1160 : : }
1161 : :
1162 : 125257 : return false;
1163 : : }
1164 : :
1165 : : void
1166 : 50500 : jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
1167 : : {
1168 [ + + + + : 50500 : Assert(v->type == jpiAnd ||
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + -
+ ]
1169 : : v->type == jpiOr ||
1170 : : v->type == jpiEqual ||
1171 : : v->type == jpiNotEqual ||
1172 : : v->type == jpiLess ||
1173 : : v->type == jpiGreater ||
1174 : : v->type == jpiLessOrEqual ||
1175 : : v->type == jpiGreaterOrEqual ||
1176 : : v->type == jpiAdd ||
1177 : : v->type == jpiSub ||
1178 : : v->type == jpiMul ||
1179 : : v->type == jpiDiv ||
1180 : : v->type == jpiMod ||
1181 : : v->type == jpiStartsWith ||
1182 : : v->type == jpiDecimal);
1183 : :
1184 : 50500 : jspInitByBuffer(a, v->base, v->content.args.left);
1185 : 50500 : }
1186 : :
1187 : : void
1188 : 37849 : jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
1189 : : {
1190 [ + + + + : 37849 : Assert(v->type == jpiAnd ||
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + -
+ ]
1191 : : v->type == jpiOr ||
1192 : : v->type == jpiEqual ||
1193 : : v->type == jpiNotEqual ||
1194 : : v->type == jpiLess ||
1195 : : v->type == jpiGreater ||
1196 : : v->type == jpiLessOrEqual ||
1197 : : v->type == jpiGreaterOrEqual ||
1198 : : v->type == jpiAdd ||
1199 : : v->type == jpiSub ||
1200 : : v->type == jpiMul ||
1201 : : v->type == jpiDiv ||
1202 : : v->type == jpiMod ||
1203 : : v->type == jpiStartsWith ||
1204 : : v->type == jpiDecimal);
1205 : :
1206 : 37849 : jspInitByBuffer(a, v->base, v->content.args.right);
1207 : 37849 : }
1208 : :
1209 : : bool
1210 : 717 : jspGetBool(JsonPathItem *v)
1211 : : {
1212 [ - + ]: 717 : Assert(v->type == jpiBool);
1213 : :
1214 : 717 : return (bool) *v->content.value.data;
1215 : : }
1216 : :
1217 : : Numeric
1218 : 10918 : jspGetNumeric(JsonPathItem *v)
1219 : : {
1220 [ - + ]: 10918 : Assert(v->type == jpiNumeric);
1221 : :
1222 : 10918 : return (Numeric) v->content.value.data;
1223 : : }
1224 : :
1225 : : char *
1226 : 101136 : jspGetString(JsonPathItem *v, int32 *len)
1227 : : {
1228 [ + + + + : 101136 : Assert(v->type == jpiKey ||
- + ]
1229 : : v->type == jpiString ||
1230 : : v->type == jpiVariable);
1231 : :
1232 [ + + ]: 101136 : if (len)
1233 : 101103 : *len = v->content.value.datalen;
1234 : 101136 : return v->content.value.data;
1235 : : }
1236 : :
1237 : : bool
1238 : 321 : jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
1239 : : int i)
1240 : : {
1241 [ - + ]: 321 : Assert(v->type == jpiIndexArray);
1242 : :
1243 : 321 : jspInitByBuffer(from, v->base, v->content.array.elems[i].from);
1244 : :
1245 [ + + ]: 321 : if (!v->content.array.elems[i].to)
1246 : 297 : return false;
1247 : :
1248 : 24 : jspInitByBuffer(to, v->base, v->content.array.elems[i].to);
1249 : :
1250 : 24 : return true;
1251 : : }
1252 : :
1253 : : /* SQL/JSON datatype status: */
1254 : : enum JsonPathDatatypeStatus
1255 : : {
1256 : : jpdsNonDateTime, /* null, bool, numeric, string, array, object */
1257 : : jpdsUnknownDateTime, /* unknown datetime type */
1258 : : jpdsDateTimeZoned, /* timetz, timestamptz */
1259 : : jpdsDateTimeNonZoned, /* time, timestamp, date */
1260 : : };
1261 : :
1262 : : /* Context for jspIsMutableWalker() */
1263 : : struct JsonPathMutableContext
1264 : : {
1265 : : List *varnames; /* list of variable names */
1266 : : List *varexprs; /* list of variable expressions */
1267 : : enum JsonPathDatatypeStatus current; /* status of @ item */
1268 : : bool lax; /* jsonpath is lax or strict */
1269 : : bool mutable; /* resulting mutability status */
1270 : : };
1271 : :
1272 : : static enum JsonPathDatatypeStatus jspIsMutableWalker(JsonPathItem *jpi,
1273 : : struct JsonPathMutableContext *cxt);
1274 : :
1275 : : /*
1276 : : * Function to check whether jsonpath expression is mutable to be used in the
1277 : : * planner function contain_mutable_functions().
1278 : : */
1279 : : bool
534 amitlan@postgresql.o 1280 : 117 : jspIsMutable(JsonPath *path, List *varnames, List *varexprs)
1281 : : {
1282 : : struct JsonPathMutableContext cxt;
1283 : : JsonPathItem jpi;
1284 : :
1285 : 117 : cxt.varnames = varnames;
1286 : 117 : cxt.varexprs = varexprs;
1287 : 117 : cxt.current = jpdsNonDateTime;
1288 : 117 : cxt.lax = (path->header & JSONPATH_LAX) != 0;
1289 : 117 : cxt.mutable = false;
1290 : :
1291 : 117 : jspInit(&jpi, path);
1292 : 117 : (void) jspIsMutableWalker(&jpi, &cxt);
1293 : :
1294 : 117 : return cxt.mutable;
1295 : : }
1296 : :
1297 : : /*
1298 : : * Recursive walker for jspIsMutable()
1299 : : */
1300 : : static enum JsonPathDatatypeStatus
1301 : 393 : jspIsMutableWalker(JsonPathItem *jpi, struct JsonPathMutableContext *cxt)
1302 : : {
1303 : : JsonPathItem next;
1304 : 393 : enum JsonPathDatatypeStatus status = jpdsNonDateTime;
1305 : :
1306 [ + + ]: 669 : while (!cxt->mutable)
1307 : : {
1308 : : JsonPathItem arg;
1309 : : enum JsonPathDatatypeStatus leftStatus;
1310 : : enum JsonPathDatatypeStatus rightStatus;
1311 : :
1312 [ + + + + : 621 : switch (jpi->type)
+ - - + -
- + - + +
+ + - ]
1313 : : {
1314 : 144 : case jpiRoot:
1315 [ - + ]: 144 : Assert(status == jpdsNonDateTime);
1316 : 144 : break;
1317 : :
1318 : 72 : case jpiCurrent:
1319 [ - + ]: 72 : Assert(status == jpdsNonDateTime);
1320 : 72 : status = cxt->current;
1321 : 72 : break;
1322 : :
1323 : 72 : case jpiFilter:
1324 : : {
1325 : 72 : enum JsonPathDatatypeStatus prevStatus = cxt->current;
1326 : :
1327 : 72 : cxt->current = status;
1328 : 72 : jspGetArg(jpi, &arg);
1329 : 72 : jspIsMutableWalker(&arg, cxt);
1330 : :
1331 : 72 : cxt->current = prevStatus;
1332 : 72 : break;
1333 : : }
1334 : :
1335 : 27 : case jpiVariable:
1336 : : {
1337 : : int32 len;
1338 : 27 : const char *name = jspGetString(jpi, &len);
1339 : : ListCell *lc1;
1340 : : ListCell *lc2;
1341 : :
1342 [ - + ]: 27 : Assert(status == jpdsNonDateTime);
1343 : :
1344 [ + - + + : 30 : forboth(lc1, cxt->varnames, lc2, cxt->varexprs)
+ - + + +
+ + - +
+ ]
1345 : : {
1346 : 27 : String *varname = lfirst_node(String, lc1);
1347 : 27 : Node *varexpr = lfirst(lc2);
1348 : :
1349 [ + + ]: 27 : if (strncmp(varname->sval, name, len))
1350 : 3 : continue;
1351 : :
1352 [ + + + ]: 24 : switch (exprType(varexpr))
1353 : : {
1354 : 15 : case DATEOID:
1355 : : case TIMEOID:
1356 : : case TIMESTAMPOID:
1357 : 15 : status = jpdsDateTimeNonZoned;
1358 : 15 : break;
1359 : :
1360 : 6 : case TIMETZOID:
1361 : : case TIMESTAMPTZOID:
1362 : 6 : status = jpdsDateTimeZoned;
1363 : 6 : break;
1364 : :
1365 : 3 : default:
1366 : 3 : status = jpdsNonDateTime;
1367 : 3 : break;
1368 : : }
1369 : :
1370 : 24 : break;
1371 : : }
1372 : 27 : break;
1373 : : }
1374 : :
1375 : 90 : case jpiEqual:
1376 : : case jpiNotEqual:
1377 : : case jpiLess:
1378 : : case jpiGreater:
1379 : : case jpiLessOrEqual:
1380 : : case jpiGreaterOrEqual:
1381 [ - + ]: 90 : Assert(status == jpdsNonDateTime);
1382 : 90 : jspGetLeftArg(jpi, &arg);
1383 : 90 : leftStatus = jspIsMutableWalker(&arg, cxt);
1384 : :
1385 : 90 : jspGetRightArg(jpi, &arg);
1386 : 90 : rightStatus = jspIsMutableWalker(&arg, cxt);
1387 : :
1388 : : /*
1389 : : * Comparison of datetime type with different timezone status
1390 : : * is mutable.
1391 : : */
1392 [ + + + + ]: 90 : if (leftStatus != jpdsNonDateTime &&
1393 [ + + ]: 36 : rightStatus != jpdsNonDateTime &&
1394 [ + - ]: 18 : (leftStatus == jpdsUnknownDateTime ||
1395 [ + + ]: 18 : rightStatus == jpdsUnknownDateTime ||
1396 : : leftStatus != rightStatus))
1397 : 21 : cxt->mutable = true;
1398 : 90 : break;
1399 : :
534 amitlan@postgresql.o 1400 :UBC 0 : case jpiNot:
1401 : : case jpiIsUnknown:
1402 : : case jpiExists:
1403 : : case jpiPlus:
1404 : : case jpiMinus:
1405 [ # # ]: 0 : Assert(status == jpdsNonDateTime);
1406 : 0 : jspGetArg(jpi, &arg);
1407 : 0 : jspIsMutableWalker(&arg, cxt);
1408 : 0 : break;
1409 : :
1410 : 0 : case jpiAnd:
1411 : : case jpiOr:
1412 : : case jpiAdd:
1413 : : case jpiSub:
1414 : : case jpiMul:
1415 : : case jpiDiv:
1416 : : case jpiMod:
1417 : : case jpiStartsWith:
1418 [ # # ]: 0 : Assert(status == jpdsNonDateTime);
1419 : 0 : jspGetLeftArg(jpi, &arg);
1420 : 0 : jspIsMutableWalker(&arg, cxt);
1421 : 0 : jspGetRightArg(jpi, &arg);
1422 : 0 : jspIsMutableWalker(&arg, cxt);
1423 : 0 : break;
1424 : :
534 amitlan@postgresql.o 1425 :CBC 12 : case jpiIndexArray:
1426 [ + + ]: 33 : for (int i = 0; i < jpi->content.array.nelems; i++)
1427 : : {
1428 : : JsonPathItem from;
1429 : : JsonPathItem to;
1430 : :
1431 [ + + ]: 21 : if (jspGetArraySubscript(jpi, &from, &to, i))
1432 : 3 : jspIsMutableWalker(&to, cxt);
1433 : :
1434 : 21 : jspIsMutableWalker(&from, cxt);
1435 : : }
1436 : : /* FALLTHROUGH */
1437 : :
1438 : : case jpiAnyArray:
1439 [ - + ]: 12 : if (!cxt->lax)
534 amitlan@postgresql.o 1440 :UBC 0 : status = jpdsNonDateTime;
534 amitlan@postgresql.o 1441 :CBC 12 : break;
1442 : :
534 amitlan@postgresql.o 1443 :UBC 0 : case jpiAny:
1444 [ # # ]: 0 : if (jpi->content.anybounds.first > 0)
1445 : 0 : status = jpdsNonDateTime;
1446 : 0 : break;
1447 : :
534 amitlan@postgresql.o 1448 :CBC 63 : case jpiDatetime:
1449 [ + + ]: 63 : if (jpi->content.arg)
1450 : : {
1451 : : char *template;
1452 : :
1453 : 33 : jspGetArg(jpi, &arg);
1454 [ - + ]: 33 : if (arg.type != jpiString)
1455 : : {
534 amitlan@postgresql.o 1456 :UBC 0 : status = jpdsNonDateTime;
1457 : 0 : break; /* there will be runtime error */
1458 : : }
1459 : :
534 amitlan@postgresql.o 1460 :CBC 33 : template = jspGetString(&arg, NULL);
1461 [ + + ]: 33 : if (datetime_format_has_tz(template))
1462 : 18 : status = jpdsDateTimeZoned;
1463 : : else
1464 : 15 : status = jpdsDateTimeNonZoned;
1465 : : }
1466 : : else
1467 : : {
1468 : 30 : status = jpdsUnknownDateTime;
1469 : : }
1470 : 63 : break;
1471 : :
534 amitlan@postgresql.o 1472 :UBC 0 : case jpiLikeRegex:
1473 [ # # ]: 0 : Assert(status == jpdsNonDateTime);
1474 : 0 : jspInitByBuffer(&arg, jpi->base, jpi->content.like_regex.expr);
1475 : 0 : jspIsMutableWalker(&arg, cxt);
1476 : 0 : break;
1477 : :
1478 : : /* literals */
534 amitlan@postgresql.o 1479 :CBC 12 : case jpiNull:
1480 : : case jpiString:
1481 : : case jpiNumeric:
1482 : : case jpiBool:
1483 : 12 : break;
1484 : : /* accessors */
1485 : 69 : case jpiKey:
1486 : : case jpiAnyKey:
1487 : : /* special items */
1488 : : case jpiSubscript:
1489 : : case jpiLast:
1490 : : /* item methods */
1491 : : case jpiType:
1492 : : case jpiSize:
1493 : : case jpiAbs:
1494 : : case jpiFloor:
1495 : : case jpiCeiling:
1496 : : case jpiDouble:
1497 : : case jpiKeyValue:
1498 : : case jpiBigint:
1499 : : case jpiBoolean:
1500 : : case jpiDecimal:
1501 : : case jpiInteger:
1502 : : case jpiNumber:
1503 : : case jpiStringFunc:
1504 : 69 : status = jpdsNonDateTime;
1505 : 69 : break;
1506 : :
1507 : 45 : case jpiTime:
1508 : : case jpiDate:
1509 : : case jpiTimestamp:
1510 : 45 : status = jpdsDateTimeNonZoned;
1511 : 45 : cxt->mutable = true;
1512 : 45 : break;
1513 : :
1514 : 15 : case jpiTimeTz:
1515 : : case jpiTimestampTz:
1516 : 15 : status = jpdsDateTimeNonZoned;
1517 : 15 : cxt->mutable = true;
1518 : 15 : break;
1519 : :
1520 : : }
1521 : :
1522 [ + + ]: 621 : if (!jspGetNext(jpi, &next))
1523 : 345 : break;
1524 : :
1525 : 276 : jpi = &next;
1526 : : }
1527 : :
1528 : 393 : return status;
1529 : : }
|