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