Age Owner Branch data TLA 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-2025, 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 : : %parse-param {yyscan_t yyscanner}
64 : : %lex-param {JsonPathParseResult **result}
65 : : %lex-param {struct Node *escontext}
66 : : %lex-param {yyscan_t yyscanner}
67 : :
68 : : %union
69 : : {
70 : : JsonPathString str;
71 : : List *elems; /* list of JsonPathParseItem */
72 : : List *indexs; /* list of integers */
73 : : JsonPathParseItem *value;
74 : : JsonPathParseResult *result;
75 : : JsonPathItemType optype;
76 : : bool boolean;
77 : : int integer;
78 : : }
79 : :
80 : : %token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
81 : : %token <str> IDENT_P STRING_P NUMERIC_P INT_P VARIABLE_P
82 : : %token <str> OR_P AND_P NOT_P
83 : : %token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
84 : : %token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P
85 : : %token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P
86 : : %token <str> DATETIME_P
87 : : %token <str> BIGINT_P BOOLEAN_P DATE_P DECIMAL_P INTEGER_P NUMBER_P
88 : : %token <str> STRINGFUNC_P TIME_P TIME_TZ_P TIMESTAMP_P TIMESTAMP_TZ_P
89 : :
90 : : %type <result> result
91 : :
92 : : %type <value> scalar_value path_primary expr array_accessor
93 : : any_path accessor_op key predicate delimited_predicate
94 : : index_elem starts_with_initial expr_or_predicate
95 : : datetime_template opt_datetime_template csv_elem
96 : : datetime_precision opt_datetime_precision
97 : :
98 : : %type <elems> accessor_expr csv_list opt_csv_list
99 : :
100 : : %type <indexs> index_list
101 : :
102 : : %type <optype> comp_op method
103 : :
104 : : %type <boolean> mode
105 : :
106 : : %type <str> key_name
107 : :
108 : : %type <integer> any_level
109 : :
110 : : %left OR_P
111 : : %left AND_P
112 : : %right NOT_P
113 : : %left '+' '-'
114 : : %left '*' '/' '%'
115 : : %left UMINUS
116 : : %nonassoc '(' ')'
117 : :
118 : : /* Grammar follows */
119 : : %%
120 : :
121 : : result:
122 : : mode expr_or_predicate {
2366 akorotkov@postgresql 123 :CBC 5144 : *result = palloc(sizeof(JsonPathParseResult));
124 : 5144 : (*result)->expr = $2;
125 : 5144 : (*result)->lax = $1;
126 : : (void) yynerrs;
127 : : }
128 : 15 : | /* EMPTY */ { *result = NULL; }
129 : : ;
130 : :
131 : : expr_or_predicate:
132 : 4928 : expr { $$ = $1; }
133 : 216 : | predicate { $$ = $1; }
134 : : ;
135 : :
136 : : mode:
137 : 324 : STRICT_P { $$ = false; }
138 : 345 : | LAX_P { $$ = true; }
139 : 4583 : | /* EMPTY */ { $$ = true; }
140 : : ;
141 : :
142 : : scalar_value:
143 : 417 : STRING_P { $$ = makeItemString(&$1); }
144 : 57 : | NULL_P { $$ = makeItemString(NULL); }
145 : 69 : | TRUE_P { $$ = makeItemBool(true); }
146 : 21 : | FALSE_P { $$ = makeItemBool(false); }
147 : 267 : | NUMERIC_P { $$ = makeItemNumeric(&$1); }
148 : 768 : | INT_P { $$ = makeItemNumeric(&$1); }
1760 peter@eisentraut.org 149 : 354 : | VARIABLE_P { $$ = makeItemVariable(&$1); }
150 : : ;
151 : :
152 : : comp_op:
2366 akorotkov@postgresql 153 : 501 : EQUAL_P { $$ = jpiEqual; }
154 : 6 : | NOTEQUAL_P { $$ = jpiNotEqual; }
155 : 378 : | LESS_P { $$ = jpiLess; }
156 : 222 : | GREATER_P { $$ = jpiGreater; }
157 : 21 : | LESSEQUAL_P { $$ = jpiLessOrEqual; }
158 : 168 : | GREATEREQUAL_P { $$ = jpiGreaterOrEqual; }
159 : : ;
160 : :
161 : : delimited_predicate:
2357 162 : 36 : '(' predicate ')' { $$ = $2; }
2366 163 : 132 : | EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
164 : : ;
165 : :
166 : : predicate:
167 : 156 : delimited_predicate { $$ = $1; }
168 : 1296 : | expr comp_op expr { $$ = makeItemBinary($2, $1, $3); }
169 : 93 : | predicate AND_P predicate { $$ = makeItemBinary(jpiAnd, $1, $3); }
170 : 54 : | predicate OR_P predicate { $$ = makeItemBinary(jpiOr, $1, $3); }
1760 peter@eisentraut.org 171 : 12 : | NOT_P delimited_predicate { $$ = makeItemUnary(jpiNot, $2); }
172 : : | '(' predicate ')' IS_P UNKNOWN_P
2357 akorotkov@postgresql 173 : 36 : { $$ = makeItemUnary(jpiIsUnknown, $2); }
174 : : | expr STARTS_P WITH_P starts_with_initial
175 : 30 : { $$ = makeItemBinary(jpiStartsWith, $1, $4); }
176 : : | expr LIKE_REGEX_P STRING_P
177 : : {
178 : : JsonPathParseItem *jppitem;
987 andrew@dunslane.net 179 [ - + ]: 9 : if (! makeItemLikeRegex($1, &$3, NULL, &jppitem, escontext))
987 andrew@dunslane.net 180 :UBC 0 : YYABORT;
987 andrew@dunslane.net 181 :CBC 6 : $$ = jppitem;
182 : : }
183 : : | expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
184 : : {
185 : : JsonPathParseItem *jppitem;
186 [ + + ]: 66 : if (! makeItemLikeRegex($1, &$3, &$5, &jppitem, escontext))
187 : 6 : YYABORT;
188 : 54 : $$ = jppitem;
189 : : }
190 : : ;
191 : :
192 : : starts_with_initial:
2366 akorotkov@postgresql 193 : 27 : STRING_P { $$ = makeItemString(&$1); }
194 : 3 : | VARIABLE_P { $$ = makeItemVariable(&$1); }
195 : : ;
196 : :
197 : : path_primary:
198 : 1953 : scalar_value { $$ = $1; }
199 : 5036 : | '$' { $$ = makeItemType(jpiRoot); }
200 : 1299 : | '@' { $$ = makeItemType(jpiCurrent); }
201 : 45 : | LAST_P { $$ = makeItemType(jpiLast); }
202 : : ;
203 : :
204 : : accessor_expr:
205 : 8333 : path_primary { $$ = list_make1($1); }
206 : 45 : | '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
207 : 15 : | '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
208 : 6693 : | accessor_expr accessor_op { $$ = lappend($1, $2); }
209 : : ;
210 : :
211 : : expr:
212 : 8324 : accessor_expr { $$ = makeItemList($1); }
213 : 81 : | '(' expr ')' { $$ = $2; }
612 peter@eisentraut.org 214 : 87 : | '+' expr %prec UMINUS { $$ = makeItemUnary(jpiPlus, $2); }
215 : 123 : | '-' expr %prec UMINUS { $$ = makeItemUnary(jpiMinus, $2); }
216 : 105 : | expr '+' expr { $$ = makeItemBinary(jpiAdd, $1, $3); }
217 : 51 : | expr '-' expr { $$ = makeItemBinary(jpiSub, $1, $3); }
2366 akorotkov@postgresql 218 : 36 : | expr '*' expr { $$ = makeItemBinary(jpiMul, $1, $3); }
219 : 18 : | expr '/' expr { $$ = makeItemBinary(jpiDiv, $1, $3); }
220 : 9 : | expr '%' expr { $$ = makeItemBinary(jpiMod, $1, $3); }
221 : : ;
222 : :
223 : : index_elem:
224 : 255 : expr { $$ = makeItemBinary(jpiSubscript, $1, NULL); }
225 : 24 : | expr TO_P expr { $$ = makeItemBinary(jpiSubscript, $1, $3); }
226 : : ;
227 : :
228 : : index_list:
229 : 255 : index_elem { $$ = list_make1($1); }
230 : 24 : | index_list ',' index_elem { $$ = lappend($1, $3); }
231 : : ;
232 : :
233 : : array_accessor:
234 : 1000 : '[' '*' ']' { $$ = makeItemType(jpiAnyArray); }
235 : 255 : | '[' index_list ']' { $$ = makeIndexArray($2); }
236 : : ;
237 : :
238 : : any_level:
1300 peter@eisentraut.org 239 : 141 : INT_P { $$ = pg_strtoint32($1.val); }
2366 akorotkov@postgresql 240 : 48 : | LAST_P { $$ = -1; }
241 : : ;
242 : :
243 : : any_path:
244 : 57 : ANY_P { $$ = makeAny(0, -1); }
245 : 51 : | ANY_P '{' any_level '}' { $$ = makeAny($3, $3); }
246 : : | ANY_P '{' any_level TO_P any_level '}'
2357 247 : 69 : { $$ = makeAny($3, $5); }
248 : : ;
249 : :
250 : : accessor_op:
2366 251 : 1916 : '.' key { $$ = $2; }
252 : 87 : | '.' '*' { $$ = makeItemType(jpiAnyKey); }
253 : 1255 : | array_accessor { $$ = $1; }
254 : 177 : | '.' any_path { $$ = $2; }
255 : 876 : | '.' method '(' ')' { $$ = makeItemType($2); }
256 : 1140 : | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); }
257 : : | '.' DECIMAL_P '(' opt_csv_list ')'
258 : : {
590 andrew@dunslane.net 259 [ + + ]: 138 : if (list_length($4) == 0)
260 : 84 : $$ = makeItemBinary(jpiDecimal, NULL, NULL);
261 [ - + ]: 54 : else if (list_length($4) == 1)
590 andrew@dunslane.net 262 :UBC 0 : $$ = makeItemBinary(jpiDecimal, linitial($4), NULL);
590 andrew@dunslane.net 263 [ + - ]:CBC 54 : else if (list_length($4) == 2)
264 : 54 : $$ = makeItemBinary(jpiDecimal, linitial($4), lsecond($4));
265 : : else
590 andrew@dunslane.net 266 [ # # ]:UBC 0 : ereturn(escontext, false,
267 : : (errcode(ERRCODE_SYNTAX_ERROR),
268 : : errmsg("invalid input syntax for type %s", "jsonpath"),
269 : : errdetail(".decimal() can only have an optional precision[,scale].")));
270 : : }
271 : : | '.' DATETIME_P '(' opt_datetime_template ')'
590 andrew@dunslane.net 272 :CBC 516 : { $$ = makeItemUnary(jpiDatetime, $4); }
273 : : | '.' TIME_P '(' opt_datetime_precision ')'
274 : 162 : { $$ = makeItemUnary(jpiTime, $4); }
275 : : | '.' TIME_TZ_P '(' opt_datetime_precision ')'
276 : 147 : { $$ = makeItemUnary(jpiTimeTz, $4); }
277 : : | '.' TIMESTAMP_P '(' opt_datetime_precision ')'
278 : 168 : { $$ = makeItemUnary(jpiTimestamp, $4); }
279 : : | '.' TIMESTAMP_TZ_P '(' opt_datetime_precision ')'
280 : 171 : { $$ = makeItemUnary(jpiTimestampTz, $4); }
281 : : ;
282 : :
283 : : csv_elem:
284 : : INT_P
285 : 78 : { $$ = makeItemNumeric(&$1); }
286 : : | '+' INT_P %prec UMINUS
287 : 15 : { $$ = makeItemUnary(jpiPlus, makeItemNumeric(&$2)); }
288 : : | '-' INT_P %prec UMINUS
289 : 15 : { $$ = makeItemUnary(jpiMinus, makeItemNumeric(&$2)); }
290 : : ;
291 : :
292 : : csv_list:
293 : 54 : csv_elem { $$ = list_make1($1); }
294 : 54 : | csv_list ',' csv_elem { $$ = lappend($1, $3); }
295 : : ;
296 : :
297 : : opt_csv_list:
298 : 54 : csv_list { $$ = $1; }
299 : 84 : | /* EMPTY */ { $$ = NULL; }
300 : : ;
301 : :
302 : : datetime_precision:
303 : 138 : INT_P { $$ = makeItemNumeric(&$1); }
304 : : ;
305 : :
306 : : opt_datetime_precision:
307 : 138 : datetime_precision { $$ = $1; }
308 : 534 : | /* EMPTY */ { $$ = NULL; }
309 : : ;
310 : :
311 : : datetime_template:
2173 akorotkov@postgresql 312 : 258 : STRING_P { $$ = makeItemString(&$1); }
313 : : ;
314 : :
315 : : opt_datetime_template:
316 : 258 : datetime_template { $$ = $1; }
317 : 258 : | /* EMPTY */ { $$ = NULL; }
318 : : ;
319 : :
320 : : key:
2366 321 : 1916 : key_name { $$ = makeItemKey(&$1); }
322 : : ;
323 : :
324 : : key_name:
325 : : IDENT_P
326 : : | STRING_P
327 : : | TO_P
328 : : | NULL_P
329 : : | TRUE_P
330 : : | FALSE_P
331 : : | IS_P
332 : : | UNKNOWN_P
333 : : | EXISTS_P
334 : : | STRICT_P
335 : : | LAX_P
336 : : | ABS_P
337 : : | SIZE_P
338 : : | TYPE_P
339 : : | FLOOR_P
340 : : | DOUBLE_P
341 : : | CEILING_P
342 : : | DATETIME_P
343 : : | KEYVALUE_P
344 : : | LAST_P
345 : : | STARTS_P
346 : : | WITH_P
347 : : | LIKE_REGEX_P
348 : : | FLAG_P
349 : : | BIGINT_P
350 : : | BOOLEAN_P
351 : : | DATE_P
352 : : | DECIMAL_P
353 : : | INTEGER_P
354 : : | NUMBER_P
355 : : | STRINGFUNC_P
356 : : | TIME_P
357 : : | TIME_TZ_P
358 : : | TIMESTAMP_P
359 : : | TIMESTAMP_TZ_P
360 : : ;
361 : :
362 : : method:
612 peter@eisentraut.org 363 : 21 : ABS_P { $$ = jpiAbs; }
2366 akorotkov@postgresql 364 : 15 : | SIZE_P { $$ = jpiSize; }
612 peter@eisentraut.org 365 : 126 : | TYPE_P { $$ = jpiType; }
366 : 15 : | FLOOR_P { $$ = jpiFloor; }
2366 akorotkov@postgresql 367 : 60 : | DOUBLE_P { $$ = jpiDouble; }
368 : 18 : | CEILING_P { $$ = jpiCeiling; }
369 : 33 : | KEYVALUE_P { $$ = jpiKeyValue; }
590 andrew@dunslane.net 370 : 93 : | BIGINT_P { $$ = jpiBigint; }
371 : 123 : | BOOLEAN_P { $$ = jpiBoolean; }
372 : 117 : | DATE_P { $$ = jpiDate; }
373 : 87 : | INTEGER_P { $$ = jpiInteger; }
374 : 84 : | NUMBER_P { $$ = jpiNumber; }
375 : 87 : | STRINGFUNC_P { $$ = jpiStringFunc; }
376 : : ;
377 : : %%
378 : :
379 : : /*
380 : : * The helper functions below allocate and fill JsonPathParseItem's of various
381 : : * types.
382 : : */
383 : :
384 : : static JsonPathParseItem *
2357 akorotkov@postgresql 385 : 18011 : makeItemType(JsonPathItemType type)
386 : : {
1212 peter@eisentraut.org 387 : 18011 : JsonPathParseItem *v = palloc(sizeof(*v));
388 : :
2362 akorotkov@postgresql 389 [ - + ]: 18011 : CHECK_FOR_INTERRUPTS();
390 : :
391 : 18011 : v->type = type;
392 : 18011 : v->next = NULL;
393 : :
394 : 18011 : return v;
395 : : }
396 : :
397 : : static JsonPathParseItem *
398 : 2675 : makeItemString(JsonPathString *s)
399 : : {
400 : : JsonPathParseItem *v;
401 : :
402 [ + + ]: 2675 : if (s == NULL)
403 : : {
404 : 57 : v = makeItemType(jpiNull);
405 : : }
406 : : else
407 : : {
408 : 2618 : v = makeItemType(jpiString);
409 : 2618 : v->value.string.val = s->val;
410 : 2618 : v->value.string.len = s->len;
411 : : }
412 : :
413 : 2675 : return v;
414 : : }
415 : :
416 : : static JsonPathParseItem *
417 : 357 : makeItemVariable(JsonPathString *s)
418 : : {
419 : : JsonPathParseItem *v;
420 : :
421 : 357 : v = makeItemType(jpiVariable);
422 : 357 : v->value.string.val = s->val;
423 : 357 : v->value.string.len = s->len;
424 : :
425 : 357 : return v;
426 : : }
427 : :
428 : : static JsonPathParseItem *
429 : 1916 : makeItemKey(JsonPathString *s)
430 : : {
431 : : JsonPathParseItem *v;
432 : :
433 : 1916 : v = makeItemString(s);
434 : 1916 : v->type = jpiKey;
435 : :
436 : 1916 : return v;
437 : : }
438 : :
439 : : static JsonPathParseItem *
440 : 1281 : makeItemNumeric(JsonPathString *s)
441 : : {
442 : : JsonPathParseItem *v;
443 : :
444 : 1281 : v = makeItemType(jpiNumeric);
445 : 1281 : v->value.numeric =
446 : 1281 : DatumGetNumeric(DirectFunctionCall3(numeric_in,
447 : : CStringGetDatum(s->val),
448 : : ObjectIdGetDatum(InvalidOid),
449 : : Int32GetDatum(-1)));
450 : :
451 : 1281 : return v;
452 : : }
453 : :
454 : : static JsonPathParseItem *
455 : 90 : makeItemBool(bool val)
456 : : {
1212 peter@eisentraut.org 457 : 90 : JsonPathParseItem *v = makeItemType(jpiBool);
458 : :
2362 akorotkov@postgresql 459 : 90 : v->value.boolean = val;
460 : :
461 : 90 : return v;
462 : : }
463 : :
464 : : static JsonPathParseItem *
2357 465 : 2109 : makeItemBinary(JsonPathItemType type, JsonPathParseItem *la, JsonPathParseItem *ra)
466 : : {
1212 peter@eisentraut.org 467 : 2109 : JsonPathParseItem *v = makeItemType(type);
468 : :
2362 akorotkov@postgresql 469 : 2109 : v->value.args.left = la;
470 : 2109 : v->value.args.right = ra;
471 : :
472 : 2109 : return v;
473 : : }
474 : :
475 : : static JsonPathParseItem *
2357 476 : 2724 : makeItemUnary(JsonPathItemType type, JsonPathParseItem *a)
477 : : {
478 : : JsonPathParseItem *v;
479 : :
2362 480 [ + + + + : 2724 : if (type == jpiPlus && a->type == jpiNumeric && !a->next)
+ - ]
481 : 75 : return a;
482 : :
483 [ + + + + : 2649 : if (type == jpiMinus && a->type == jpiNumeric && !a->next)
+ - ]
484 : : {
485 : 84 : v = makeItemType(jpiNumeric);
486 : 84 : v->value.numeric =
487 : 84 : DatumGetNumeric(DirectFunctionCall1(numeric_uminus,
488 : : NumericGetDatum(a->value.numeric)));
489 : 84 : return v;
490 : : }
491 : :
492 : 2565 : v = makeItemType(type);
493 : :
494 : 2565 : v->value.arg = a;
495 : :
496 : 2565 : return v;
497 : : }
498 : :
499 : : static JsonPathParseItem *
500 : 8324 : makeItemList(List *list)
501 : : {
502 : : JsonPathParseItem *head,
503 : : *end;
504 : : ListCell *cell;
505 : :
2245 tgl@sss.pgh.pa.us 506 : 8324 : head = end = (JsonPathParseItem *) linitial(list);
507 : :
508 [ + + ]: 8324 : if (list_length(list) == 1)
2362 akorotkov@postgresql 509 : 3425 : return head;
510 : :
511 : : /* append items to the end of already existing list */
512 [ + + ]: 4905 : while (end->next)
513 : 6 : end = end->next;
514 : :
1804 tgl@sss.pgh.pa.us 515 [ + - + + : 11652 : for_each_from(cell, list, 1)
+ + ]
516 : : {
2362 akorotkov@postgresql 517 : 6753 : JsonPathParseItem *c = (JsonPathParseItem *) lfirst(cell);
518 : :
519 : 6753 : end->next = c;
520 : 6753 : end = c;
521 : : }
522 : :
523 : 4899 : return head;
524 : : }
525 : :
526 : : static JsonPathParseItem *
527 : 255 : makeIndexArray(List *list)
528 : : {
1212 peter@eisentraut.org 529 : 255 : JsonPathParseItem *v = makeItemType(jpiIndexArray);
530 : : ListCell *cell;
531 : 255 : int i = 0;
532 : :
1116 tgl@sss.pgh.pa.us 533 [ - + ]: 255 : Assert(list != NIL);
2362 akorotkov@postgresql 534 : 255 : v->value.array.nelems = list_length(list);
535 : :
536 : 510 : v->value.array.elems = palloc(sizeof(v->value.array.elems[0]) *
537 : 255 : v->value.array.nelems);
538 : :
539 [ + - + + : 534 : foreach(cell, list)
+ + ]
540 : : {
1212 peter@eisentraut.org 541 : 279 : JsonPathParseItem *jpi = lfirst(cell);
542 : :
2362 akorotkov@postgresql 543 [ - + ]: 279 : Assert(jpi->type == jpiSubscript);
544 : :
545 : 279 : v->value.array.elems[i].from = jpi->value.args.left;
546 : 279 : v->value.array.elems[i++].to = jpi->value.args.right;
547 : : }
548 : :
549 : 255 : return v;
550 : : }
551 : :
552 : : static JsonPathParseItem *
553 : 177 : makeAny(int first, int last)
554 : : {
1212 peter@eisentraut.org 555 : 177 : JsonPathParseItem *v = makeItemType(jpiAny);
556 : :
2362 akorotkov@postgresql 557 [ + + ]: 177 : v->value.anybounds.first = (first >= 0) ? first : PG_UINT32_MAX;
558 [ + + ]: 177 : v->value.anybounds.last = (last >= 0) ? last : PG_UINT32_MAX;
559 : :
560 : 177 : return v;
561 : : }
562 : :
563 : : static bool
564 : 75 : makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
565 : : JsonPathString *flags, JsonPathParseItem **result,
566 : : struct Node *escontext)
567 : : {
1212 peter@eisentraut.org 568 : 75 : JsonPathParseItem *v = makeItemType(jpiLikeRegex);
569 : : int i;
570 : : int cflags;
571 : :
2362 akorotkov@postgresql 572 : 75 : v->value.like_regex.expr = expr;
573 : 75 : v->value.like_regex.pattern = pattern->val;
574 : 75 : v->value.like_regex.patternlen = pattern->len;
575 : :
576 : : /* Parse the flags string, convert to bitmask. Duplicate flags are OK. */
2181 tgl@sss.pgh.pa.us 577 : 75 : v->value.like_regex.flags = 0;
2362 akorotkov@postgresql 578 [ + + + + ]: 186 : for (i = 0; flags && i < flags->len; i++)
579 : : {
580 [ + + + + : 120 : switch (flags->val[i])
+ + ]
581 : : {
582 : 30 : case 'i':
583 : 30 : v->value.like_regex.flags |= JSP_REGEX_ICASE;
584 : 30 : break;
585 : 24 : case 's':
2181 tgl@sss.pgh.pa.us 586 : 24 : v->value.like_regex.flags |= JSP_REGEX_DOTALL;
2362 akorotkov@postgresql 587 : 24 : break;
588 : 18 : case 'm':
589 : 18 : v->value.like_regex.flags |= JSP_REGEX_MLINE;
590 : 18 : break;
591 : 12 : case 'x':
592 : 12 : v->value.like_regex.flags |= JSP_REGEX_WSPACE;
593 : 12 : break;
2271 594 : 27 : case 'q':
595 : 27 : v->value.like_regex.flags |= JSP_REGEX_QUOTE;
596 : 27 : break;
2362 597 : 9 : default:
987 andrew@dunslane.net 598 [ + + ]: 9 : ereturn(escontext, false,
599 : : (errcode(ERRCODE_SYNTAX_ERROR),
600 : : errmsg("invalid input syntax for type %s", "jsonpath"),
601 : : errdetail("Unrecognized flag character \"%.*s\" in LIKE_REGEX predicate.",
602 : : pg_mblen(flags->val + i), flags->val + i)));
603 : : break;
604 : : }
605 : : }
606 : :
607 : : /* Convert flags to what pg_regcomp needs */
255 peter@eisentraut.org 608 [ - + ]: 66 : if (!jspConvertRegexFlags(v->value.like_regex.flags, &cflags, escontext))
255 peter@eisentraut.org 609 :UBC 0 : return false;
610 : :
611 : : /* check regex validity */
612 : : {
613 : : regex_t re_tmp;
614 : : pg_wchar *wpattern;
615 : : int wpattern_len;
616 : : int re_result;
617 : :
987 andrew@dunslane.net 618 :CBC 63 : wpattern = (pg_wchar *) palloc((pattern->len + 1) * sizeof(pg_wchar));
619 : 63 : wpattern_len = pg_mb2wchar_with_len(pattern->val,
620 : : wpattern,
621 : : pattern->len);
622 : :
623 [ + + ]: 63 : if ((re_result = pg_regcomp(&re_tmp, wpattern, wpattern_len, cflags,
624 : : DEFAULT_COLLATION_OID)) != REG_OKAY)
625 : : {
626 : : char errMsg[100];
627 : :
628 : 3 : pg_regerror(re_result, &re_tmp, errMsg, sizeof(errMsg));
629 [ + - ]: 3 : ereturn(escontext, false,
630 : : (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
631 : : errmsg("invalid regular expression: %s", errMsg)));
632 : : }
633 : :
634 : 60 : pg_regfree(&re_tmp);
635 : : }
636 : :
637 : 60 : *result = v;
638 : :
639 : 60 : return true;
640 : : }
641 : :
642 : : /*
643 : : * Convert from XQuery regex flags to those recognized by our regex library.
644 : : */
645 : : bool
646 : 204 : jspConvertRegexFlags(uint32 xflags, int *result, struct Node *escontext)
647 : : {
648 : : /* By default, XQuery is very nearly the same as Spencer's AREs */
2181 tgl@sss.pgh.pa.us 649 : 204 : int cflags = REG_ADVANCED;
650 : :
651 : : /* Ignore-case means the same thing, too, modulo locale issues */
652 [ + + ]: 204 : if (xflags & JSP_REGEX_ICASE)
653 : 57 : cflags |= REG_ICASE;
654 : :
655 : : /* Per XQuery spec, if 'q' is specified then 'm', 's', 'x' are ignored */
656 [ + + ]: 204 : if (xflags & JSP_REGEX_QUOTE)
657 : : {
658 : 63 : cflags &= ~REG_ADVANCED;
659 : 63 : cflags |= REG_QUOTE;
660 : : }
661 : : else
662 : : {
663 : : /* Note that dotall mode is the default in POSIX */
664 [ + + ]: 141 : if (!(xflags & JSP_REGEX_DOTALL))
665 : 108 : cflags |= REG_NLSTOP;
666 [ + + ]: 141 : if (xflags & JSP_REGEX_MLINE)
667 : 30 : cflags |= REG_NLANCH;
668 : :
669 : : /*
670 : : * XQuery's 'x' mode is related to Spencer's expanded mode, but it's
671 : : * not really enough alike to justify treating JSP_REGEX_WSPACE as
672 : : * REG_EXPANDED. For now we treat 'x' as unimplemented; perhaps in
673 : : * future we'll modify the regex library to have an option for
674 : : * XQuery-style ignore-whitespace mode.
675 : : */
676 [ + + ]: 141 : if (xflags & JSP_REGEX_WSPACE)
987 andrew@dunslane.net 677 [ + - ]: 3 : ereturn(escontext, false,
678 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
679 : : errmsg("XQuery \"x\" flag (expanded regular expressions) is not implemented")));
680 : : }
681 : :
682 : 201 : *result = cflags;
683 : :
684 : 201 : return true;
685 : : }
|