Line data Source code
1 : %{
2 : /*-------------------------------------------------------------------------
3 : *
4 : * jsonpath_gram.y
5 : * Grammar definitions for jsonpath datatype
6 : *
7 : * Transforms tokenized jsonpath into tree of JsonPathParseItem structs.
8 : *
9 : * Copyright (c) 2019-2023, PostgreSQL Global Development Group
10 : *
11 : * IDENTIFICATION
12 : * src/backend/utils/adt/jsonpath_gram.y
13 : *
14 : *-------------------------------------------------------------------------
15 : */
16 :
17 : #include "postgres.h"
18 :
19 : #include "catalog/pg_collation.h"
20 : #include "fmgr.h"
21 : #include "jsonpath_internal.h"
22 : #include "miscadmin.h"
23 : #include "nodes/pg_list.h"
24 : #include "regex/regex.h"
25 : #include "utils/builtins.h"
26 :
27 : static JsonPathParseItem *makeItemType(JsonPathItemType type);
28 : static JsonPathParseItem *makeItemString(JsonPathString *s);
29 : static JsonPathParseItem *makeItemVariable(JsonPathString *s);
30 : static JsonPathParseItem *makeItemKey(JsonPathString *s);
31 : static JsonPathParseItem *makeItemNumeric(JsonPathString *s);
32 : static JsonPathParseItem *makeItemBool(bool val);
33 : static JsonPathParseItem *makeItemBinary(JsonPathItemType type,
34 : JsonPathParseItem *la,
35 : JsonPathParseItem *ra);
36 : static JsonPathParseItem *makeItemUnary(JsonPathItemType type,
37 : JsonPathParseItem *a);
38 : static JsonPathParseItem *makeItemList(List *list);
39 : static JsonPathParseItem *makeIndexArray(List *list);
40 : static JsonPathParseItem *makeAny(int first, int last);
41 : static bool makeItemLikeRegex(JsonPathParseItem *expr,
42 : JsonPathString *pattern,
43 : JsonPathString *flags,
44 : JsonPathParseItem ** result,
45 : struct Node *escontext);
46 :
47 : /*
48 : * Bison doesn't allocate anything that needs to live across parser calls,
49 : * so we can easily have it use palloc instead of malloc. This prevents
50 : * memory leaks if we error out during parsing.
51 : */
52 : #define YYMALLOC palloc
53 : #define YYFREE pfree
54 :
55 : %}
56 :
57 : /* BISON Declarations */
58 : %pure-parser
59 : %expect 0
60 : %name-prefix="jsonpath_yy"
61 : %parse-param {JsonPathParseResult **result}
62 : %parse-param {struct Node *escontext}
63 : %lex-param {JsonPathParseResult **result}
64 : %lex-param {struct Node *escontext}
65 :
66 : %union
67 : {
68 : JsonPathString str;
69 : List *elems; /* list of JsonPathParseItem */
70 : List *indexs; /* list of integers */
71 : JsonPathParseItem *value;
72 : JsonPathParseResult *result;
73 : JsonPathItemType optype;
74 : bool boolean;
75 : int integer;
76 : }
77 :
78 : %token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
79 : %token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
80 : %token <str> OR_P AND_P NOT_P
81 : %token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
82 : %token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
83 : %token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
84 : %token <str> DATETIME_P
85 :
86 : %type <result> result
87 :
88 : %type <value> scalar_value path_primary expr array_accessor
89 : any_path accessor_op key predicate delimited_predicate
90 : index_elem starts_with_initial expr_or_predicate
91 : datetime_template opt_datetime_template
92 :
93 : %type <elems> accessor_expr
94 :
95 : %type <indexs> index_list
96 :
97 : %type <optype> comp_op method
98 :
99 : %type <boolean> mode
100 :
101 : %type <str> key_name
102 :
103 : %type <integer> any_level
104 :
105 : %left OR_P
106 : %left AND_P
107 : %right NOT_P
108 : %left '+' '-'
109 : %left '*' '/' '%'
110 : %left UMINUS
111 : %nonassoc '(' ')'
112 :
113 : /* Grammar follows */
114 : %%
115 :
116 : result:
117 : mode expr_or_predicate {
118 4218 : *result = palloc(sizeof(JsonPathParseResult));
119 4218 : (*result)->expr = $2;
120 4218 : (*result)->lax = $1;
121 : (void) yynerrs;
122 : }
123 30 : | /* EMPTY */ { *result = NULL; }
124 : ;
125 :
126 : expr_or_predicate:
127 3834 : expr { $$ = $1; }
128 384 : | predicate { $$ = $1; }
129 : ;
130 :
131 : mode:
132 354 : STRICT_P { $$ = false; }
133 456 : | LAX_P { $$ = true; }
134 3552 : | /* EMPTY */ { $$ = true; }
135 : ;
136 :
137 : scalar_value:
138 516 : STRING_P { $$ = makeItemString(&$1); }
139 114 : | NULL_P { $$ = makeItemString(NULL); }
140 138 : | TRUE_P { $$ = makeItemBool(true); }
141 42 : | FALSE_P { $$ = makeItemBool(false); }
142 534 : | NUMERIC_P { $$ = makeItemNumeric(&$1); }
143 1230 : | INT_P { $$ = makeItemNumeric(&$1); }
144 318 : | VARIABLE_P { $$ = makeItemVariable(&$1); }
145 : ;
146 :
147 : comp_op:
148 864 : EQUAL_P { $$ = jpiEqual; }
149 12 : | NOTEQUAL_P { $$ = jpiNotEqual; }
150 474 : | LESS_P { $$ = jpiLess; }
151 384 : | GREATER_P { $$ = jpiGreater; }
152 24 : | LESSEQUAL_P { $$ = jpiLessOrEqual; }
153 126 : | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
154 : ;
155 :
156 : delimited_predicate:
157 72 : '(' predicate ')' { $$ = $2; }
158 264 : | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
159 : ;
160 :
161 : predicate:
162 312 : delimited_predicate { $$ = $1; }
163 1884 : | expr comp_op expr { $$ = makeItemBinary($2, $1, $3); }
164 168 : | predicate AND_P predicate { $$ = makeItemBinary(jpiAnd, $1, $3); }
165 108 : | predicate OR_P predicate { $$ = makeItemBinary(jpiOr, $1, $3); }
166 24 : | NOT_P delimited_predicate { $$ = makeItemUnary(jpiNot, $2); }
167 : | '(' predicate ')' IS_P UNKNOWN_P
168 72 : { $$ = makeItemUnary(jpiIsUnknown, $2); }
169 : | expr STARTS_P WITH_P starts_with_initial
170 60 : { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
171 : | expr LIKE_REGEX_P STRING_P
172 : {
173 : JsonPathParseItem *jppitem;
174 18 : if (! makeItemLikeRegex($1, &$3, NULL, &jppitem, escontext))
175 0 : YYABORT;
176 12 : $$ = jppitem;
177 : }
178 : | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
179 : {
180 : JsonPathParseItem *jppitem;
181 132 : if (! makeItemLikeRegex($1, &$3, &$5, &jppitem, escontext))
182 12 : YYABORT;
183 108 : $$ = jppitem;
184 : }
185 : ;
186 :
187 : starts_with_initial:
188 54 : STRING_P { $$ = makeItemString(&$1); }
189 6 : | VARIABLE_P { $$ = makeItemVariable(&$1); }
190 : ;
191 :
192 : path_primary:
193 2892 : scalar_value { $$ = $1; }
194 3942 : | '$' { $$ = makeItemType(jpiRoot); }
195 1938 : | '@' { $$ = makeItemType(jpiCurrent); }
196 90 : | LAST_P { $$ = makeItemType(jpiLast); }
197 : ;
198 :
199 : accessor_expr:
200 8862 : path_primary { $$ = list_make1($1); }
201 90 : | '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
202 30 : | '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
203 7182 : | accessor_expr accessor_op { $$ = lappend($1, $2); }
204 : ;
205 :
206 : expr:
207 8898 : accessor_expr { $$ = makeItemList($1); }
208 72 : | '(' expr ')' { $$ = $2; }
209 174 : | '+' expr %prec UMINUS { $$ = makeItemUnary(jpiPlus, $2); }
210 246 : | '-' expr %prec UMINUS { $$ = makeItemUnary(jpiMinus, $2); }
211 162 : | expr '+' expr { $$ = makeItemBinary(jpiAdd, $1, $3); }
212 60 : | expr '-' expr { $$ = makeItemBinary(jpiSub, $1, $3); }
213 48 : | expr '*' expr { $$ = makeItemBinary(jpiMul, $1, $3); }
214 36 : | expr '/' expr { $$ = makeItemBinary(jpiDiv, $1, $3); }
215 18 : | expr '%' expr { $$ = makeItemBinary(jpiMod, $1, $3); }
216 : ;
217 :
218 : index_elem:
219 324 : expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
220 42 : | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
221 : ;
222 :
223 : index_list:
224 336 : index_elem { $$ = list_make1($1); }
225 30 : | index_list ',' index_elem { $$ = lappend($1, $3); }
226 : ;
227 :
228 : array_accessor:
229 1038 : '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
230 336 : | '[' index_list ']' { $$ = makeIndexArray($2); }
231 : ;
232 :
233 : any_level:
234 282 : INT_P { $$ = pg_strtoint32($1.val); }
235 96 : | LAST_P { $$ = -1; }
236 : ;
237 :
238 : any_path:
239 114 : ANY_P { $$ = makeAny(0, -1); }
240 102 : | ANY_P '{' any_level '}' { $$ = makeAny($3, $3); }
241 : | ANY_P '{' any_level TO_P any_level '}'
242 138 : { $$ = makeAny($3, $5); }
243 : ;
244 :
245 : accessor_op:
246 2586 : '.' key { $$ = $2; }
247 84 : | '.' '*' { $$ = makeItemType(jpiAnyKey); }
248 1374 : | array_accessor { $$ = $1; }
249 354 : | '.' any_path { $$ = $2; }
250 516 : | '.' method '(' ')' { $$ = makeItemType($2); }
251 : | '.' DATETIME_P '(' opt_datetime_template ')'
252 750 : { $$ = makeItemUnary(jpiDatetime, $4); }
253 1638 : | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
254 : ;
255 :
256 : datetime_template:
257 414 : STRING_P { $$ = makeItemString(&$1); }
258 : ;
259 :
260 : opt_datetime_template:
261 414 : datetime_template { $$ = $1; }
262 336 : | /* EMPTY */ { $$ = NULL; }
263 : ;
264 :
265 : key:
266 2586 : key_name { $$ = makeItemKey(&$1); }
267 : ;
268 :
269 : key_name:
270 : IDENT_P
271 : | STRING_P
272 : | TO_P
273 : | NULL_P
274 : | TRUE_P
275 : | FALSE_P
276 : | IS_P
277 : | UNKNOWN_P
278 : | EXISTS_P
279 : | STRICT_P
280 : | LAX_P
281 : | ABS_P
282 : | SIZE_P
283 : | TYPE_P
284 : | FLOOR_P
285 : | DOUBLE_P
286 : | CEILING_P
287 : | DATETIME_P
288 : | KEYVALUE_P
289 : | LAST_P
290 : | STARTS_P
291 : | WITH_P
292 : | LIKE_REGEX_P
293 : | FLAG_P
294 : ;
295 :
296 : method:
297 42 : ABS_P { $$ = jpiAbs; }
298 30 : | SIZE_P { $$ = jpiSize; }
299 192 : | TYPE_P { $$ = jpiType; }
300 30 : | FLOOR_P { $$ = jpiFloor; }
301 120 : | DOUBLE_P { $$ = jpiDouble; }
302 36 : | CEILING_P { $$ = jpiCeiling; }
303 66 : | KEYVALUE_P { $$ = jpiKeyValue; }
304 : ;
305 : %%
306 :
307 : /*
308 : * The helper functions below allocate and fill JsonPathParseItem's of various
309 : * types.
310 : */
311 :
312 : static JsonPathParseItem *
313 20358 : makeItemType(JsonPathItemType type)
314 : {
315 20358 : JsonPathParseItem *v = palloc(sizeof(*v));
316 :
317 20358 : CHECK_FOR_INTERRUPTS();
318 :
319 20358 : v->type = type;
320 20358 : v->next = NULL;
321 :
322 20358 : return v;
323 : }
324 :
325 : static JsonPathParseItem *
326 3684 : makeItemString(JsonPathString *s)
327 : {
328 : JsonPathParseItem *v;
329 :
330 3684 : if (s == NULL)
331 : {
332 114 : v = makeItemType(jpiNull);
333 : }
334 : else
335 : {
336 3570 : v = makeItemType(jpiString);
337 3570 : v->value.string.val = s->val;
338 3570 : v->value.string.len = s->len;
339 : }
340 :
341 3684 : return v;
342 : }
343 :
344 : static JsonPathParseItem *
345 324 : makeItemVariable(JsonPathString *s)
346 : {
347 : JsonPathParseItem *v;
348 :
349 324 : v = makeItemType(jpiVariable);
350 324 : v->value.string.val = s->val;
351 324 : v->value.string.len = s->len;
352 :
353 324 : return v;
354 : }
355 :
356 : static JsonPathParseItem *
357 2586 : makeItemKey(JsonPathString *s)
358 : {
359 : JsonPathParseItem *v;
360 :
361 2586 : v = makeItemString(s);
362 2586 : v->type = jpiKey;
363 :
364 2586 : return v;
365 : }
366 :
367 : static JsonPathParseItem *
368 1764 : makeItemNumeric(JsonPathString *s)
369 : {
370 : JsonPathParseItem *v;
371 :
372 1764 : v = makeItemType(jpiNumeric);
373 1764 : v->value.numeric =
374 1764 : DatumGetNumeric(DirectFunctionCall3(numeric_in,
375 : CStringGetDatum(s->val),
376 : ObjectIdGetDatum(InvalidOid),
377 : Int32GetDatum(-1)));
378 :
379 1764 : return v;
380 : }
381 :
382 : static JsonPathParseItem *
383 180 : makeItemBool(bool val)
384 : {
385 180 : JsonPathParseItem *v = makeItemType(jpiBool);
386 :
387 180 : v->value.boolean = val;
388 :
389 180 : return v;
390 : }
391 :
392 : static JsonPathParseItem *
393 2910 : makeItemBinary(JsonPathItemType type, JsonPathParseItem *la, JsonPathParseItem *ra)
394 : {
395 2910 : JsonPathParseItem *v = makeItemType(type);
396 :
397 2910 : v->value.args.left = la;
398 2910 : v->value.args.right = ra;
399 :
400 2910 : return v;
401 : }
402 :
403 : static JsonPathParseItem *
404 3168 : makeItemUnary(JsonPathItemType type, JsonPathParseItem *a)
405 : {
406 : JsonPathParseItem *v;
407 :
408 3168 : if (type == jpiPlus && a->type == jpiNumeric && !a->next)
409 120 : return a;
410 :
411 3048 : if (type == jpiMinus && a->type == jpiNumeric && !a->next)
412 : {
413 138 : v = makeItemType(jpiNumeric);
414 138 : v->value.numeric =
415 138 : DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
416 : NumericGetDatum(a->value.numeric)));
417 138 : return v;
418 : }
419 :
420 2910 : v = makeItemType(type);
421 :
422 2910 : v->value.arg = a;
423 :
424 2910 : return v;
425 : }
426 :
427 : static JsonPathParseItem *
428 8898 : makeItemList(List *list)
429 : {
430 : JsonPathParseItem *head,
431 : *end;
432 : ListCell *cell;
433 :
434 8898 : head = end = (JsonPathParseItem *) linitial(list);
435 :
436 8898 : if (list_length(list) == 1)
437 3852 : return head;
438 :
439 : /* append items to the end of already existing list */
440 5058 : while (end->next)
441 12 : end = end->next;
442 :
443 12348 : for_each_from(cell, list, 1)
444 : {
445 7302 : JsonPathParseItem *c = (JsonPathParseItem *) lfirst(cell);
446 :
447 7302 : end->next = c;
448 7302 : end = c;
449 : }
450 :
451 5046 : return head;
452 : }
453 :
454 : static JsonPathParseItem *
455 336 : makeIndexArray(List *list)
456 : {
457 336 : JsonPathParseItem *v = makeItemType(jpiIndexArray);
458 : ListCell *cell;
459 336 : int i = 0;
460 :
461 : Assert(list != NIL);
462 336 : v->value.array.nelems = list_length(list);
463 :
464 672 : v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) *
465 336 : v->value.array.nelems);
466 :
467 702 : foreach(cell, list)
468 : {
469 366 : JsonPathParseItem *jpi = lfirst(cell);
470 :
471 : Assert(jpi->type == jpiSubscript);
472 :
473 366 : v->value.array.elems[i].from = jpi->value.args.left;
474 366 : v->value.array.elems[i++].to = jpi->value.args.right;
475 : }
476 :
477 336 : return v;
478 : }
479 :
480 : static JsonPathParseItem *
481 354 : makeAny(int first, int last)
482 : {
483 354 : JsonPathParseItem *v = makeItemType(jpiAny);
484 :
485 354 : v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
486 354 : v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
487 :
488 354 : return v;
489 : }
490 :
491 : static bool
492 150 : makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
493 : JsonPathString *flags, JsonPathParseItem ** result,
494 : struct Node *escontext)
495 : {
496 150 : JsonPathParseItem *v = makeItemType(jpiLikeRegex);
497 : int i;
498 : int cflags;
499 :
500 150 : v->value.like_regex.expr = expr;
501 150 : v->value.like_regex.pattern = pattern->val;
502 150 : v->value.like_regex.patternlen = pattern->len;
503 :
504 : /* Parse the flags string, convert to bitmask. Duplicate flags are OK. */
505 150 : v->value.like_regex.flags = 0;
506 372 : for (i = 0; flags && i < flags->len; i++)
507 : {
508 240 : switch (flags->val[i])
509 : {
510 60 : case 'i':
511 60 : v->value.like_regex.flags |= JSP_REGEX_ICASE;
512 60 : break;
513 48 : case 's':
514 48 : v->value.like_regex.flags |= JSP_REGEX_DOTALL;
515 48 : break;
516 36 : case 'm':
517 36 : v->value.like_regex.flags |= JSP_REGEX_MLINE;
518 36 : break;
519 24 : case 'x':
520 24 : v->value.like_regex.flags |= JSP_REGEX_WSPACE;
521 24 : break;
522 54 : case 'q':
523 54 : v->value.like_regex.flags |= JSP_REGEX_QUOTE;
524 54 : break;
525 18 : default:
526 18 : ereturn(escontext, false,
527 : (errcode(ERRCODE_SYNTAX_ERROR),
528 : errmsg("invalid input syntax for type %s", "jsonpath"),
529 : errdetail("Unrecognized flag character \"%.*s\" in LIKE_REGEX predicate.",
530 : pg_mblen(flags->val + i), flags->val + i)));
531 : break;
532 : }
533 : }
534 :
535 : /* Convert flags to what pg_regcomp needs */
536 132 : if ( !jspConvertRegexFlags(v->value.like_regex.flags, &cflags, escontext))
537 0 : return false;
538 :
539 : /* check regex validity */
540 : {
541 : regex_t re_tmp;
542 : pg_wchar *wpattern;
543 : int wpattern_len;
544 : int re_result;
545 :
546 126 : wpattern = (pg_wchar *) palloc((pattern->len + 1) * sizeof(pg_wchar));
547 126 : wpattern_len = pg_mb2wchar_with_len(pattern->val,
548 : wpattern,
549 : pattern->len);
550 :
551 126 : if ((re_result = pg_regcomp(&re_tmp, wpattern, wpattern_len, cflags,
552 : DEFAULT_COLLATION_OID)) != REG_OKAY)
553 : {
554 : char errMsg[100];
555 :
556 6 : pg_regerror(re_result, &re_tmp, errMsg, sizeof(errMsg));
557 6 : ereturn(escontext, false,
558 : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
559 : errmsg("invalid regular expression: %s", errMsg)));
560 : }
561 :
562 120 : pg_regfree(&re_tmp);
563 : }
564 :
565 120 : *result = v;
566 :
567 120 : return true;
568 : }
569 :
570 : /*
571 : * Convert from XQuery regex flags to those recognized by our regex library.
572 : */
573 : bool
574 408 : jspConvertRegexFlags(uint32 xflags, int *result, struct Node *escontext)
575 : {
576 : /* By default, XQuery is very nearly the same as Spencer's AREs */
577 408 : int cflags = REG_ADVANCED;
578 :
579 : /* Ignore-case means the same thing, too, modulo locale issues */
580 408 : if (xflags & JSP_REGEX_ICASE)
581 114 : cflags |= REG_ICASE;
582 :
583 : /* Per XQuery spec, if 'q' is specified then 'm', 's', 'x' are ignored */
584 408 : if (xflags & JSP_REGEX_QUOTE)
585 : {
586 126 : cflags &= ~REG_ADVANCED;
587 126 : cflags |= REG_QUOTE;
588 : }
589 : else
590 : {
591 : /* Note that dotall mode is the default in POSIX */
592 282 : if (!(xflags & JSP_REGEX_DOTALL))
593 216 : cflags |= REG_NLSTOP;
594 282 : if (xflags & JSP_REGEX_MLINE)
595 60 : cflags |= REG_NLANCH;
596 :
597 : /*
598 : * XQuery's 'x' mode is related to Spencer's expanded mode, but it's
599 : * not really enough alike to justify treating JSP_REGEX_WSPACE as
600 : * REG_EXPANDED. For now we treat 'x' as unimplemented; perhaps in
601 : * future we'll modify the regex library to have an option for
602 : * XQuery-style ignore-whitespace mode.
603 : */
604 282 : if (xflags & JSP_REGEX_WSPACE)
605 6 : ereturn(escontext, false,
606 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
607 : errmsg("XQuery \"x\" flag (expanded regular expressions) is not implemented")));
608 : }
609 :
610 402 : *result = cflags;
611 :
612 402 : return true;
613 : }
|