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

Commit 534b2a1

Browse files
author
Nikita Glukhov
committed
Add boolean jsonpath expressions
1 parent aef145d commit 534b2a1

File tree

7 files changed

+181
-13
lines changed

7 files changed

+181
-13
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,16 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
748748
v->type == jpiMod ||
749749
v->type == jpiPlus ||
750750
v->type == jpiMinus ||
751+
v->type == jpiEqual ||
752+
v->type == jpiNotEqual ||
753+
v->type == jpiGreater ||
754+
v->type == jpiGreaterOrEqual ||
755+
v->type == jpiLess ||
756+
v->type == jpiLessOrEqual ||
757+
v->type == jpiAnd ||
758+
v->type == jpiOr ||
759+
v->type == jpiNot ||
760+
v->type == jpiIsUnknown ||
751761
v->type == jpiType ||
752762
v->type == jpiSize ||
753763
v->type == jpiAbs ||

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ static inline JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
4343
JsonPathItem *jsp, JsonbValue *jb,
4444
JsonValueList *found);
4545

46+
static inline JsonPathExecResult recursiveExecuteBool(JsonPathExecContext *cxt,
47+
JsonPathItem *jsp, JsonbValue *jb);
48+
4649
static inline JsonPathExecResult recursiveExecuteUnwrap(JsonPathExecContext *cxt,
4750
JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
4851

@@ -1211,6 +1214,34 @@ tryToParseDatetime(const char *template, text *datetime,
12111214
return ok;
12121215
}
12131216

1217+
static inline JsonPathExecResult
1218+
appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
1219+
JsonValueList *found, JsonPathExecResult res, bool needBool)
1220+
{
1221+
JsonPathItem next;
1222+
JsonbValue jbv;
1223+
bool hasNext = jspGetNext(jsp, &next);
1224+
1225+
if (needBool)
1226+
{
1227+
Assert(!hasNext);
1228+
return res;
1229+
}
1230+
1231+
if (!found && !hasNext)
1232+
return jperOk; /* found singleton boolean value */
1233+
1234+
if (jperIsError(res))
1235+
jbv.type = jbvNull;
1236+
else
1237+
{
1238+
jbv.type = jbvBool;
1239+
jbv.val.boolean = res == jperOk;
1240+
}
1241+
1242+
return recursiveExecuteNext(cxt, jsp, &next, &jbv, found, true);
1243+
}
1244+
12141245
/*
12151246
* Main executor function: walks on jsonpath structure and tries to find
12161247
* correspoding parts of jsonb. Note, jsonb and jsonpath values should be
@@ -1223,7 +1254,7 @@ tryToParseDatetime(const char *template, text *datetime,
12231254
*/
12241255
static JsonPathExecResult
12251256
recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
1226-
JsonbValue *jb, JsonValueList *found)
1257+
JsonbValue *jb, JsonValueList *found, bool needBool)
12271258
{
12281259
JsonPathItem elem;
12291260
JsonPathExecResult res = jperNotFound;
@@ -1235,7 +1266,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
12351266
switch(jsp->type) {
12361267
case jpiAnd:
12371268
jspGetLeftArg(jsp, &elem);
1238-
res = recursiveExecute(cxt, &elem, jb, NULL);
1269+
res = recursiveExecuteBool(cxt, &elem, jb);
12391270
if (res != jperNotFound)
12401271
{
12411272
JsonPathExecResult res2;
@@ -1246,27 +1277,29 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
12461277
*/
12471278

12481279
jspGetRightArg(jsp, &elem);
1249-
res2 = recursiveExecute(cxt, &elem, jb, NULL);
1280+
res2 = recursiveExecuteBool(cxt, &elem, jb);
12501281

12511282
res = (res2 == jperOk) ? res : res2;
12521283
}
1284+
res = appendBoolResult(cxt, jsp, found, res, needBool);
12531285
break;
12541286
case jpiOr:
12551287
jspGetLeftArg(jsp, &elem);
1256-
res = recursiveExecute(cxt, &elem, jb, NULL);
1288+
res = recursiveExecuteBool(cxt, &elem, jb);
12571289
if (res != jperOk)
12581290
{
12591291
JsonPathExecResult res2;
12601292

12611293
jspGetRightArg(jsp, &elem);
1262-
res2 = recursiveExecute(cxt, &elem, jb, NULL);
1294+
res2 = recursiveExecuteBool(cxt, &elem, jb);
12631295

12641296
res = (res2 == jperNotFound) ? res : res2;
12651297
}
1298+
res = appendBoolResult(cxt, jsp, found, res, needBool);
12661299
break;
12671300
case jpiNot:
12681301
jspGetArg(jsp, &elem);
1269-
switch ((res = recursiveExecute(cxt, &elem, jb, NULL)))
1302+
switch ((res = recursiveExecuteBool(cxt, &elem, jb)))
12701303
{
12711304
case jperOk:
12721305
res = jperNotFound;
@@ -1277,11 +1310,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
12771310
default:
12781311
break;
12791312
}
1313+
res = appendBoolResult(cxt, jsp, found, res, needBool);
12801314
break;
12811315
case jpiIsUnknown:
12821316
jspGetArg(jsp, &elem);
1283-
res = recursiveExecute(cxt, &elem, jb, NULL);
1317+
res = recursiveExecuteBool(cxt, &elem, jb);
12841318
res = jperIsError(res) ? jperOk : jperNotFound;
1319+
res = appendBoolResult(cxt, jsp, found, res, needBool);
12851320
break;
12861321
case jpiKey:
12871322
if (JsonbType(jb) == jbvObject)
@@ -1548,6 +1583,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
15481583
case jpiLessOrEqual:
15491584
case jpiGreaterOrEqual:
15501585
res = executeExpr(cxt, jsp, jb);
1586+
res = appendBoolResult(cxt, jsp, found, res, needBool);
15511587
break;
15521588
case jpiAdd:
15531589
case jpiSub:
@@ -1562,7 +1598,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
15621598
break;
15631599
case jpiFilter:
15641600
jspGetArg(jsp, &elem);
1565-
res = recursiveExecute(cxt, &elem, jb, NULL);
1601+
res = recursiveExecuteBool(cxt, &elem, jb);
15661602
if (res != jperOk)
15671603
res = jperNotFound;
15681604
else
@@ -1611,6 +1647,8 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
16111647
if (!jperIsError(res))
16121648
res = JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
16131649
}
1650+
1651+
res = appendBoolResult(cxt, jsp, found, res, needBool);
16141652
break;
16151653
case jpiNull:
16161654
case jpiBool:
@@ -1946,9 +1984,11 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
19461984
break;
19471985
case jpiStartsWith:
19481986
res = executeStartsWithPredicate(cxt, jsp, jb);
1987+
res = appendBoolResult(cxt, jsp, found, res, needBool);
19491988
break;
19501989
case jpiLikeRegex:
19511990
res = executeLikeRegexPredicate(cxt, jsp, jb);
1991+
res = appendBoolResult(cxt, jsp, found, res, needBool);
19521992
break;
19531993
default:
19541994
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
@@ -1970,7 +2010,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
19702010

19712011
for (; elem < last; elem++)
19722012
{
1973-
res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found);
2013+
res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found, false);
19742014

19752015
if (jperIsError(res))
19762016
break;
@@ -1990,7 +2030,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
19902030
{
19912031
if (tok == WJB_ELEM)
19922032
{
1993-
res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found);
2033+
res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found, false);
19942034
if (jperIsError(res))
19952035
break;
19962036
if (res == jperOk && !found)
@@ -2009,7 +2049,7 @@ recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
20092049
if (cxt->lax && JsonbType(jb) == jbvArray)
20102050
return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
20112051

2012-
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
2052+
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
20132053
}
20142054

20152055
/*
@@ -2085,7 +2125,39 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
20852125
}
20862126
}
20872127

2088-
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
2128+
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
2129+
}
2130+
2131+
static inline JsonPathExecResult
2132+
recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp,
2133+
JsonbValue *jb)
2134+
{
2135+
if (jspHasNext(jsp))
2136+
elog(ERROR, "boolean jsonpath item can not have next item");
2137+
2138+
switch (jsp->type)
2139+
{
2140+
case jpiAnd:
2141+
case jpiOr:
2142+
case jpiNot:
2143+
case jpiIsUnknown:
2144+
case jpiEqual:
2145+
case jpiNotEqual:
2146+
case jpiGreater:
2147+
case jpiGreaterOrEqual:
2148+
case jpiLess:
2149+
case jpiLessOrEqual:
2150+
case jpiExists:
2151+
case jpiStartsWith:
2152+
case jpiLikeRegex:
2153+
break;
2154+
2155+
default:
2156+
elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
2157+
break;
2158+
}
2159+
2160+
return recursiveExecuteNoUnwrap(cxt, jsp, jb, NULL, true);
20892161
}
20902162

20912163
/*

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
287287
%type <value> scalar_value path_primary expr pexpr array_accessor
288288
any_path accessor_op key predicate delimited_predicate
289289
index_elem starts_with_initial opt_datetime_template
290+
expr_or_predicate
290291

291292
%type <elems> accessor_expr
292293

@@ -311,14 +312,19 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
311312
%%
312313

313314
result:
314-
mode expr {
315+
mode expr_or_predicate {
315316
*result = palloc(sizeof(JsonPathParseResult));
316317
(*result)->expr = $2;
317318
(*result)->lax = $1;
318319
}
319320
| /* EMPTY */ { *result = NULL; }
320321
;
321322

323+
expr_or_predicate:
324+
expr { $$ = $1; }
325+
| predicate { $$ = $1; }
326+
;
327+
322328
mode:
323329
STRICT_P { $$ = false; }
324330
| LAX_P { $$ = true; }
@@ -379,6 +385,7 @@ accessor_expr:
379385
path_primary { $$ = list_make1($1); }
380386
| '.' key { $$ = list_make2(makeItemType(jpiCurrent), $2); }
381387
| '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
388+
| '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
382389
| accessor_expr accessor_op { $$ = lappend($1, $2); }
383390
;
384391

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,25 @@ select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a');
801801
-- should fail
802802
select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3');
803803
ERROR: Singleton SQL/JSON item required
804+
-- extension: boolean expressions
805+
select _jsonpath_query(jsonb '2', '$ > 1');
806+
_jsonpath_query
807+
-----------------
808+
true
809+
(1 row)
810+
811+
select _jsonpath_query(jsonb '2', '$ <= 1');
812+
_jsonpath_query
813+
-----------------
814+
false
815+
(1 row)
816+
817+
select _jsonpath_query(jsonb '2', '$ == "2"');
818+
_jsonpath_query
819+
-----------------
820+
null
821+
(1 row)
822+
804823
select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()');
805824
_jsonpath_query
806825
-----------------
@@ -866,6 +885,30 @@ select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10');
866885
4
867886
(1 row)
868887

888+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 2) ? (@ == true)');
889+
_jsonpath_query
890+
-----------------
891+
true
892+
(1 row)
893+
894+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 3).type()');
895+
_jsonpath_query
896+
-----------------
897+
"boolean"
898+
(1 row)
899+
900+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*].a > 3).type()');
901+
_jsonpath_query
902+
-----------------
903+
"boolean"
904+
(1 row)
905+
906+
select _jsonpath_query(jsonb '[1, 2, 3]', 'strict ($[*].a > 3).type()');
907+
_jsonpath_query
908+
-----------------
909+
"null"
910+
(1 row)
911+
869912
select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
870913
ERROR: SQL/JSON array not found
871914
select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');

src/test/regress/expected/jsonpath.out

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,22 @@ ERROR: bad jsonpath representation
458458
LINE 1: select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath;
459459
^
460460
DETAIL: unrecognized flag of LIKE_REGEX predicate at or near """
461+
select '$ < 1'::jsonpath;
462+
jsonpath
463+
----------
464+
($ < 1)
465+
(1 row)
466+
467+
select '($ < 1) || $.a.b <= $x'::jsonpath;
468+
jsonpath
469+
------------------------------
470+
($ < 1 || $."a"."b" <= $"x")
471+
(1 row)
472+
473+
select '@ + 1'::jsonpath;
474+
ERROR: @ is not allowed in root expressions
475+
LINE 1: select '@ + 1'::jsonpath;
476+
^
461477
select '($).a.b'::jsonpath;
462478
jsonpath
463479
-----------
@@ -488,6 +504,12 @@ select '1 + ($.a.b + 2).c.d'::jsonpath;
488504
(1 + ($."a"."b" + 2)."c"."d")
489505
(1 row)
490506

507+
select '1 + ($.a.b > 2).c.d'::jsonpath;
508+
jsonpath
509+
-------------------------------
510+
(1 + ($."a"."b" > 2)."c"."d")
511+
(1 row)
512+
491513
select '$ ? (a < 1)'::jsonpath;
492514
jsonpath
493515
-------------

src/test/regress/sql/jsonb_jsonpath.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a');
163163
-- should fail
164164
select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3');
165165

166+
-- extension: boolean expressions
167+
select _jsonpath_query(jsonb '2', '$ > 1');
168+
select _jsonpath_query(jsonb '2', '$ <= 1');
169+
select _jsonpath_query(jsonb '2', '$ == "2"');
170+
166171
select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()');
167172
select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', 'lax $.type()');
168173
select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$[*].type()');
@@ -174,6 +179,10 @@ select _jsonpath_query(jsonb 'null', 'aaa.type()');
174179

175180
select _jsonpath_query(jsonb '{"a": 2}', '($.a - 5).abs() + 10');
176181
select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10');
182+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 2) ? (@ == true)');
183+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 3).type()');
184+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*].a > 3).type()');
185+
select _jsonpath_query(jsonb '[1, 2, 3]', 'strict ($[*].a > 3).type()');
177186

178187
select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
179188
select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'lax $[*].size()');

0 commit comments

Comments
 (0)