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