Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8050541

Browse files
feodorNikita Glukhov
authored and
Nikita Glukhov
committed
implement jsonpath EXISTS clause
1 parent 58c3265 commit 8050541

File tree

9 files changed

+117
-34
lines changed

9 files changed

+117
-34
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020

2121
/*****************************INPUT/OUTPUT************************************/
2222
static int
23-
flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item)
23+
flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
24+
bool forbiddenRoot)
2425
{
2526
int32 pos = buf->len - VARHDRSZ; /* position from begining of jsonpath data */
2627
int32 chld, next;
@@ -35,17 +36,30 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item)
3536

3637
switch(item->type)
3738
{
38-
case jpiKey:
3939
case jpiString:
4040
case jpiVariable:
41+
/* scalars aren't checked during grammar parse */
42+
if (item->next != NULL)
43+
ereport(ERROR,
44+
(errcode(ERRCODE_SYNTAX_ERROR),
45+
errmsg("scalar could not be a part of path")));
46+
case jpiKey:
4147
appendBinaryStringInfo(buf, (char*)&item->string.len, sizeof(item->string.len));
4248
appendBinaryStringInfo(buf, item->string.val, item->string.len);
4349
appendStringInfoChar(buf, '\0');
4450
break;
4551
case jpiNumeric:
52+
if (item->next != NULL)
53+
ereport(ERROR,
54+
(errcode(ERRCODE_SYNTAX_ERROR),
55+
errmsg("scalar could not be a part of path")));
4656
appendBinaryStringInfo(buf, (char*)item->numeric, VARSIZE(item->numeric));
4757
break;
4858
case jpiBool:
59+
if (item->next != NULL)
60+
ereport(ERROR,
61+
(errcode(ERRCODE_SYNTAX_ERROR),
62+
errmsg("scalar could not be a part of path")));
4963
appendBinaryStringInfo(buf, (char*)&item->boolean, sizeof(item->boolean));
5064
break;
5165
case jpiAnd:
@@ -69,32 +83,50 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item)
6983
right = buf->len;
7084
appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right));
7185

72-
chld = flattenJsonPathParseItem(buf, item->args.left);
86+
chld = flattenJsonPathParseItem(buf, item->args.left, forbiddenRoot);
7387
*(int32*)(buf->data + left) = chld;
74-
chld = flattenJsonPathParseItem(buf, item->args.right);
88+
chld = flattenJsonPathParseItem(buf, item->args.right, forbiddenRoot);
7589
*(int32*)(buf->data + right) = chld;
7690
}
7791
break;
78-
case jpiNot:
92+
case jpiFilter:
7993
case jpiIsUnknown:
94+
case jpiNot:
8095
case jpiPlus:
8196
case jpiMinus:
82-
case jpiFilter:
97+
case jpiExists:
8398
{
8499
int32 arg;
85100

86101
arg = buf->len;
87102
appendBinaryStringInfo(buf, (char*)&arg /* fake value */, sizeof(arg));
88103

89-
chld = flattenJsonPathParseItem(buf, item->arg);
104+
chld = flattenJsonPathParseItem(buf, item->arg,
105+
item->type == jpiFilter ||
106+
forbiddenRoot);
90107
*(int32*)(buf->data + arg) = chld;
91108
}
92109
break;
110+
case jpiNull:
111+
if (item->next != NULL)
112+
ereport(ERROR,
113+
(errcode(ERRCODE_SYNTAX_ERROR),
114+
errmsg("scalar could not be a part of path")));
115+
break;
116+
case jpiRoot:
117+
if (forbiddenRoot)
118+
ereport(ERROR,
119+
(errcode(ERRCODE_SYNTAX_ERROR),
120+
errmsg("root is not allowed in expression")));
121+
break;
93122
case jpiAnyArray:
94123
case jpiAnyKey:
124+
break;
95125
case jpiCurrent:
96-
case jpiRoot:
97-
case jpiNull:
126+
if (!forbiddenRoot)
127+
ereport(ERROR,
128+
(errcode(ERRCODE_SYNTAX_ERROR),
129+
errmsg("@ is not allowed in root expressions")));
98130
break;
99131
case jpiIndexArray:
100132
appendBinaryStringInfo(buf,
@@ -117,7 +149,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item)
117149
}
118150

119151
if (item->next)
120-
*(int32*)(buf->data + next) = flattenJsonPathParseItem(buf, item->next);
152+
*(int32*)(buf->data + next) =
153+
flattenJsonPathParseItem(buf, item->next, forbiddenRoot);
121154

122155
return pos;
123156
}
@@ -141,7 +174,7 @@ jsonpath_in(PG_FUNCTION_ARGS)
141174
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
142175
errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
143176

144-
flattenJsonPathParseItem(&buf, jsonpath);
177+
flattenJsonPathParseItem(&buf, jsonpath, false);
145178

146179
res = (JsonPath*)buf.data;
147180
SET_VARSIZE(res, buf.len);
@@ -294,6 +327,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
294327
printJsonPathItem(buf, &elem, false, false);
295328
appendBinaryStringInfo(buf, ") is unknown", 12);
296329
break;
330+
case jpiExists:
331+
appendBinaryStringInfo(buf,"exists (", 8);
332+
jspGetArg(v, &elem);
333+
printJsonPathItem(buf, &elem, false, false);
334+
appendStringInfoChar(buf, ')');
335+
break;
297336
case jpiCurrent:
298337
Assert(!inKey);
299338
appendStringInfoChar(buf, '@');
@@ -435,6 +474,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
435474
read_int32(v->args.right, base, pos);
436475
break;
437476
case jpiNot:
477+
case jpiExists:
438478
case jpiIsUnknown:
439479
case jpiMinus:
440480
case jpiPlus:
@@ -461,6 +501,7 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a)
461501
v->type == jpiFilter ||
462502
v->type == jpiNot ||
463503
v->type == jpiIsUnknown ||
504+
v->type == jpiExists ||
464505
v->type == jpiPlus ||
465506
v->type == jpiMinus
466507
);
@@ -481,6 +522,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
481522
v->type == jpiIndexArray ||
482523
v->type == jpiFilter ||
483524
v->type == jpiCurrent ||
525+
v->type == jpiExists ||
484526
v->type == jpiRoot
485527
);
486528

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,10 @@ recursiveExecute(JsonPathItem *jsp, List *vars, JsonbValue *jb, List **found)
735735
jsp->anybounds.last);
736736
break;
737737
}
738+
case jpiExists:
739+
jspGetArg(jsp, &elem);
740+
res = recursiveExecute(&elem, vars, jb, NULL);
741+
break;
738742
case jpiNull:
739743
case jpiBool:
740744
case jpiNumeric:

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -207,15 +207,15 @@ makeAny(int first, int last)
207207
int optype;
208208
}
209209

210-
%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P
210+
%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P EXISTS_P
211211
%token <str> STRING_P NUMERIC_P INT_P VARIABLE_P
212212
%token <str> OR_P AND_P NOT_P
213213
%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
214214
%token <str> ANY_P
215215

216216
%type <value> result jsonpath scalar_value path_primary expr
217217
array_accessor any_path accessor_op key unary_expr
218-
predicate delimited_predicate // numeric
218+
predicate delimited_predicate
219219

220220
%type <elems> accessor_expr /* path absolute_path relative_path */
221221

@@ -240,9 +240,6 @@ result:
240240

241241
scalar_value:
242242
STRING_P { $$ = makeItemString(&$1); }
243-
| TO_P { $$ = makeItemString(&$1); }
244-
| IS_P { $$ = makeItemString(&$1); }
245-
| UNKNOWN_P { $$ = makeItemString(&$1); }
246243
| NULL_P { $$ = makeItemString(NULL); }
247244
| TRUE_P { $$ = makeItemBool(true); }
248245
| FALSE_P { $$ = makeItemBool(false); }
@@ -251,14 +248,6 @@ scalar_value:
251248
| VARIABLE_P { $$ = makeItemVariable(&$1); }
252249
;
253250

254-
/*
255-
numeric:
256-
NUMERIC_P { $$ = makeItemNumeric(&$1); }
257-
| INT_P { $$ = makeItemNumeric(&$1); }
258-
| VARIABLE_P { $$ = makeItemVariable(&$1); }
259-
;
260-
*/
261-
262251
jsonpath:
263252
expr
264253
;
@@ -273,8 +262,8 @@ comp_op:
273262
;
274263

275264
delimited_predicate:
276-
'(' predicate ')' { $$ = $2; }
277-
// | EXISTS '(' relative_path ')' { $$ = makeItemUnary(jpiExists, $2); }
265+
'(' predicate ')' { $$ = $2; }
266+
| EXISTS_P '(' expr ')' { $$ = makeItemUnary(jpiExists, $3); }
278267
;
279268

280269
predicate:
@@ -309,8 +298,6 @@ unary_expr:
309298
| '-' unary_expr { $$ = makeItemUnary(jpiMinus, $2); }
310299
;
311300

312-
// | '(' expr ')' { $$ = $2; }
313-
314301
expr:
315302
unary_expr { $$ = $1; }
316303
| expr '+' expr { $$ = makeItemBinary(jpiAdd, $1, $3); }
@@ -370,6 +357,9 @@ key:
370357
| TRUE_P { $$ = makeItemKey(&$1); }
371358
| FALSE_P { $$ = makeItemKey(&$1); }
372359
| INT_P { $$ = makeItemKey(&$1); }
360+
| IS_P { $$ = makeItemKey(&$1); }
361+
| UNKNOWN_P { $$ = makeItemKey(&$1); }
362+
| EXISTS_P { $$ = makeItemKey(&$1); }
373363
;
374364
/*
375365
absolute_path:

src/backend/utils/adt/jsonpath_scan.l

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ unicode \\u[0-9A-Fa-f]{4}
172172
<xNONQUOTED>\/\* {
173173
yylval->str = scanstring;
174174
BEGIN xCOMMENT;
175-
return checkSpecialVal();
176175
}
177176
178177
<xNONQUOTED>({special}|\") {
@@ -270,12 +269,13 @@ typedef struct keyword
270269
*/
271270

272271
static keyword keywords[] = {
273-
{ 2, true, IS_P, "is"},
272+
{ 2, false, IS_P, "is"},
274273
{ 2, false, TO_P, "to"},
275274
{ 4, true, NULL_P, "null"},
276275
{ 4, true, TRUE_P, "true"},
277276
{ 5, true, FALSE_P, "false"},
278-
{ 7, true, UNKNOWN_P, "unknown"},
277+
{ 6, false, EXISTS_P, "exists"},
278+
{ 7, false, UNKNOWN_P, "unknown"}
279279
};
280280

281281
static int

src/include/utils/jsonpath.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,12 @@ typedef enum JsonPathItemType {
5555
jpiAnyKey,
5656
jpiIndexArray,
5757
jpiAny,
58-
//jpiAll,
59-
//jpiAllArray,
60-
//jpiAllKey,
6158
jpiKey,
6259
jpiCurrent,
6360
jpiRoot,
6461
jpiVariable,
6562
jpiFilter,
63+
jpiExists
6664
} JsonPathItemType;
6765

6866

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,3 +448,20 @@ select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@
448448
t
449449
(1 row)
450450

451+
select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x))');
452+
_jsonpath_query
453+
-----------------
454+
{"x": 2}
455+
(1 row)
456+
457+
select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.y))');
458+
_jsonpath_query
459+
-----------------
460+
(0 rows)
461+
462+
select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');
463+
_jsonpath_query
464+
-----------------
465+
{"x": 2}
466+
(1 row)
467+

src/test/regress/expected/jsonpath.out

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,30 @@ select '$.g ? ((x >= 123 || a == 4) is unknown)'::jsonpath;
231231
$."g"?(("x" >= 123 || "a" == 4) is unknown)
232232
(1 row)
233233

234+
select '$.g ? (exists (.x))'::jsonpath;
235+
jsonpath
236+
------------------------
237+
$."g"?(exists (@."x"))
238+
(1 row)
239+
240+
select '$.g ? (exists (@.x ? (@ == 14)))'::jsonpath;
241+
jsonpath
242+
----------------------------------
243+
$."g"?(exists (@."x"?(@ == 14)))
244+
(1 row)
245+
246+
select '$.g ? (exists (.x ? (@ == 14)))'::jsonpath;
247+
jsonpath
248+
----------------------------------
249+
$."g"?(exists (@."x"?(@ == 14)))
250+
(1 row)
251+
252+
select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath;
253+
jsonpath
254+
--------------------------------------------------------------
255+
$."g"?(("x" >= 123 || "a" == 4) && exists (@."x"?(@ == 14)))
256+
(1 row)
257+
234258
select '$.g ? (zip == $zip)'::jsonpath;
235259
jsonpath
236260
-------------------------

src/test/regress/sql/jsonb_jsonpath.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,7 @@ select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{0,}.b ? (@
7878
select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,}.b ? (@ > 0)');
7979
select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{1,2}.b ? (@ > 0)');
8080
select * from _jsonpath_exists(jsonb '{"a": {"c": {"b": 1}}}', '$.**{2,3}.b ? (@ > 0)');
81+
82+
select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x))');
83+
select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.y))');
84+
select _jsonpath_query(jsonb '{"g": {"x": 2}}', '$.g ? (exists (@.x ? (@ >= 2) ))');

src/test/regress/sql/jsonpath.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ select '$.g ? (@.a == 1 || !(a == 4) && b == 7)'::jsonpath;
4040
select '$.g ? (@.a == 1 || !(x >= 123 || a == 4) && b == 7)'::jsonpath;
4141
select '$.g ? (.x >= @[*]?(@.a > "abc"))'::jsonpath;
4242
select '$.g ? ((x >= 123 || a == 4) is unknown)'::jsonpath;
43+
select '$.g ? (exists (.x))'::jsonpath;
44+
select '$.g ? (exists (@.x ? (@ == 14)))'::jsonpath;
45+
select '$.g ? (exists (.x ? (@ == 14)))'::jsonpath;
46+
select '$.g ? ((x >= 123 || a == 4) && exists (.x ? (@ == 14)))'::jsonpath;
4347

4448
select '$.g ? (zip == $zip)'::jsonpath;
4549
select '$.a.[1,2, 3 to 16]'::jsonpath;

0 commit comments

Comments
 (0)