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

Commit a17ee17

Browse files
author
Nikita Glukhov
committed
Add boolean jsonpath expressions
1 parent cbf07b9 commit a17ee17

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
@@ -744,6 +744,16 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
744744
v->type == jpiMod ||
745745
v->type == jpiPlus ||
746746
v->type == jpiMinus ||
747+
v->type == jpiEqual ||
748+
v->type == jpiNotEqual ||
749+
v->type == jpiGreater ||
750+
v->type == jpiGreaterOrEqual ||
751+
v->type == jpiLess ||
752+
v->type == jpiLessOrEqual ||
753+
v->type == jpiAnd ||
754+
v->type == jpiOr ||
755+
v->type == jpiNot ||
756+
v->type == jpiIsUnknown ||
747757
v->type == jpiType ||
748758
v->type == jpiSize ||
749759
v->type == jpiAbs ||

src/backend/utils/adt/jsonpath_exec.c

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

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

@@ -1239,6 +1242,34 @@ tryToParseDatetime(const char *template, text *datetime,
12391242
return ok;
12401243
}
12411244

1245+
static inline JsonPathExecResult
1246+
appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
1247+
JsonValueList *found, JsonPathExecResult res, bool needBool)
1248+
{
1249+
JsonPathItem next;
1250+
JsonbValue jbv;
1251+
bool hasNext = jspGetNext(jsp, &next);
1252+
1253+
if (needBool)
1254+
{
1255+
Assert(!hasNext);
1256+
return res;
1257+
}
1258+
1259+
if (!found && !hasNext)
1260+
return jperOk; /* found singleton boolean value */
1261+
1262+
if (jperIsError(res))
1263+
jbv.type = jbvNull;
1264+
else
1265+
{
1266+
jbv.type = jbvBool;
1267+
jbv.val.boolean = res == jperOk;
1268+
}
1269+
1270+
return recursiveExecuteNext(cxt, jsp, &next, &jbv, found, true);
1271+
}
1272+
12421273
/*
12431274
* Main executor function: walks on jsonpath structure and tries to find
12441275
* correspoding parts of jsonb. Note, jsonb and jsonpath values should be
@@ -1251,7 +1282,7 @@ tryToParseDatetime(const char *template, text *datetime,
12511282
*/
12521283
static JsonPathExecResult
12531284
recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
1254-
JsonbValue *jb, JsonValueList *found)
1285+
JsonbValue *jb, JsonValueList *found, bool needBool)
12551286
{
12561287
JsonPathItem elem;
12571288
JsonPathExecResult res = jperNotFound;
@@ -1262,7 +1293,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
12621293
switch(jsp->type) {
12631294
case jpiAnd:
12641295
jspGetLeftArg(jsp, &elem);
1265-
res = recursiveExecute(cxt, &elem, jb, NULL);
1296+
res = recursiveExecuteBool(cxt, &elem, jb);
12661297
if (res != jperNotFound)
12671298
{
12681299
JsonPathExecResult res2;
@@ -1273,27 +1304,29 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
12731304
*/
12741305

12751306
jspGetRightArg(jsp, &elem);
1276-
res2 = recursiveExecute(cxt, &elem, jb, NULL);
1307+
res2 = recursiveExecuteBool(cxt, &elem, jb);
12771308

12781309
res = (res2 == jperOk) ? res : res2;
12791310
}
1311+
res = appendBoolResult(cxt, jsp, found, res, needBool);
12801312
break;
12811313
case jpiOr:
12821314
jspGetLeftArg(jsp, &elem);
1283-
res = recursiveExecute(cxt, &elem, jb, NULL);
1315+
res = recursiveExecuteBool(cxt, &elem, jb);
12841316
if (res != jperOk)
12851317
{
12861318
JsonPathExecResult res2;
12871319

12881320
jspGetRightArg(jsp, &elem);
1289-
res2 = recursiveExecute(cxt, &elem, jb, NULL);
1321+
res2 = recursiveExecuteBool(cxt, &elem, jb);
12901322

12911323
res = (res2 == jperNotFound) ? res : res2;
12921324
}
1325+
res = appendBoolResult(cxt, jsp, found, res, needBool);
12931326
break;
12941327
case jpiNot:
12951328
jspGetArg(jsp, &elem);
1296-
switch ((res = recursiveExecute(cxt, &elem, jb, NULL)))
1329+
switch ((res = recursiveExecuteBool(cxt, &elem, jb)))
12971330
{
12981331
case jperOk:
12991332
res = jperNotFound;
@@ -1304,11 +1337,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
13041337
default:
13051338
break;
13061339
}
1340+
res = appendBoolResult(cxt, jsp, found, res, needBool);
13071341
break;
13081342
case jpiIsUnknown:
13091343
jspGetArg(jsp, &elem);
1310-
res = recursiveExecute(cxt, &elem, jb, NULL);
1344+
res = recursiveExecuteBool(cxt, &elem, jb);
13111345
res = jperIsError(res) ? jperOk : jperNotFound;
1346+
res = appendBoolResult(cxt, jsp, found, res, needBool);
13121347
break;
13131348
case jpiKey:
13141349
if (JsonbType(jb) == jbvObject)
@@ -1575,6 +1610,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
15751610
case jpiLessOrEqual:
15761611
case jpiGreaterOrEqual:
15771612
res = executeExpr(cxt, jsp, jb);
1613+
res = appendBoolResult(cxt, jsp, found, res, needBool);
15781614
break;
15791615
case jpiAdd:
15801616
case jpiSub:
@@ -1589,7 +1625,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
15891625
break;
15901626
case jpiFilter:
15911627
jspGetArg(jsp, &elem);
1592-
res = recursiveExecute(cxt, &elem, jb, NULL);
1628+
res = recursiveExecuteBool(cxt, &elem, jb);
15931629
if (res != jperOk)
15941630
res = jperNotFound;
15951631
else
@@ -1638,6 +1674,8 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
16381674
if (!jperIsError(res))
16391675
res = JsonValueListIsEmpty(&vals) ? jperNotFound : jperOk;
16401676
}
1677+
1678+
res = appendBoolResult(cxt, jsp, found, res, needBool);
16411679
break;
16421680
case jpiNull:
16431681
case jpiBool:
@@ -1973,9 +2011,11 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
19732011
break;
19742012
case jpiStartsWith:
19752013
res = executeStartsWithPredicate(cxt, jsp, jb);
2014+
res = appendBoolResult(cxt, jsp, found, res, needBool);
19762015
break;
19772016
case jpiLikeRegex:
19782017
res = executeLikeRegexPredicate(cxt, jsp, jb);
2018+
res = appendBoolResult(cxt, jsp, found, res, needBool);
19792019
break;
19802020
default:
19812021
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
@@ -1997,7 +2037,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
19972037

19982038
for (; elem < last; elem++)
19992039
{
2000-
res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found);
2040+
res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found, false);
20012041

20022042
if (jperIsError(res))
20032043
break;
@@ -2017,7 +2057,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
20172057
{
20182058
if (tok == WJB_ELEM)
20192059
{
2020-
res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found);
2060+
res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found, false);
20212061
if (jperIsError(res))
20222062
break;
20232063
if (res == jperOk && !found)
@@ -2036,7 +2076,7 @@ recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
20362076
if (cxt->lax && JsonbType(jb) == jbvArray)
20372077
return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
20382078

2039-
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
2079+
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
20402080
}
20412081

20422082
/*
@@ -2112,7 +2152,39 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
21122152
}
21132153
}
21142154

2115-
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
2155+
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
2156+
}
2157+
2158+
static inline JsonPathExecResult
2159+
recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp,
2160+
JsonbValue *jb)
2161+
{
2162+
if (jspHasNext(jsp))
2163+
elog(ERROR, "boolean jsonpath item can not have next item");
2164+
2165+
switch (jsp->type)
2166+
{
2167+
case jpiAnd:
2168+
case jpiOr:
2169+
case jpiNot:
2170+
case jpiIsUnknown:
2171+
case jpiEqual:
2172+
case jpiNotEqual:
2173+
case jpiGreater:
2174+
case jpiGreaterOrEqual:
2175+
case jpiLess:
2176+
case jpiLessOrEqual:
2177+
case jpiExists:
2178+
case jpiStartsWith:
2179+
case jpiLikeRegex:
2180+
break;
2181+
2182+
default:
2183+
elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
2184+
break;
2185+
}
2186+
2187+
return recursiveExecuteNoUnwrap(cxt, jsp, jb, NULL, true);
21162188
}
21172189

21182190
/*

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
273273
%type <value> scalar_value path_primary expr pexpr array_accessor
274274
any_path accessor_op key predicate delimited_predicate
275275
index_elem starts_with_initial opt_datetime_template
276+
expr_or_predicate
276277

277278
%type <elems> accessor_expr
278279

@@ -297,14 +298,19 @@ makeItemLikeRegex(JsonPathParseItem *expr, string *pattern, string *flags)
297298
%%
298299

299300
result:
300-
mode expr {
301+
mode expr_or_predicate {
301302
*result = palloc(sizeof(JsonPathParseResult));
302303
(*result)->expr = $2;
303304
(*result)->lax = $1;
304305
}
305306
| /* EMPTY */ { *result = NULL; }
306307
;
307308

309+
expr_or_predicate:
310+
expr { $$ = $1; }
311+
| predicate { $$ = $1; }
312+
;
313+
308314
mode:
309315
STRICT_P { $$ = false; }
310316
| LAX_P { $$ = true; }
@@ -365,6 +371,7 @@ accessor_expr:
365371
path_primary { $$ = list_make1($1); }
366372
| '.' key { $$ = list_make2(makeItemType(jpiCurrent), $2); }
367373
| '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
374+
| '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
368375
| accessor_expr accessor_op { $$ = lappend($1, $2); }
369376
;
370377

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,25 @@ select _jsonpath_query(jsonb '{"a": [2, 3, 4]}', 'lax -$.a');
791791
-- should fail
792792
select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3');
793793
ERROR: Singleton SQL/JSON item required
794+
-- extension: boolean expressions
795+
select _jsonpath_query(jsonb '2', '$ > 1');
796+
_jsonpath_query
797+
-----------------
798+
true
799+
(1 row)
800+
801+
select _jsonpath_query(jsonb '2', '$ <= 1');
802+
_jsonpath_query
803+
-----------------
804+
false
805+
(1 row)
806+
807+
select _jsonpath_query(jsonb '2', '$ == "2"');
808+
_jsonpath_query
809+
-----------------
810+
null
811+
(1 row)
812+
794813
select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()');
795814
_jsonpath_query
796815
-----------------
@@ -856,6 +875,30 @@ select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10');
856875
4
857876
(1 row)
858877

878+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 2) ? (@ == true)');
879+
_jsonpath_query
880+
-----------------
881+
true
882+
(1 row)
883+
884+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 3).type()');
885+
_jsonpath_query
886+
-----------------
887+
"boolean"
888+
(1 row)
889+
890+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*].a > 3).type()');
891+
_jsonpath_query
892+
-----------------
893+
"boolean"
894+
(1 row)
895+
896+
select _jsonpath_query(jsonb '[1, 2, 3]', 'strict ($[*].a > 3).type()');
897+
_jsonpath_query
898+
-----------------
899+
"null"
900+
(1 row)
901+
859902
select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
860903
ERROR: SQL/JSON array not found
861904
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
@@ -454,6 +454,22 @@ ERROR: bad jsonpath representation
454454
LINE 1: select '$ ? (@ like_regex "pattern" flag "a")'::jsonpath;
455455
^
456456
DETAIL: unrecognized flag of LIKE_REGEX predicate at or near """
457+
select '$ < 1'::jsonpath;
458+
jsonpath
459+
----------
460+
($ < 1)
461+
(1 row)
462+
463+
select '($ < 1) || $.a.b <= $x'::jsonpath;
464+
jsonpath
465+
------------------------------
466+
($ < 1 || $."a"."b" <= $"x")
467+
(1 row)
468+
469+
select '@ + 1'::jsonpath;
470+
ERROR: @ is not allowed in root expressions
471+
LINE 1: select '@ + 1'::jsonpath;
472+
^
457473
select '($).a.b'::jsonpath;
458474
jsonpath
459475
-----------
@@ -484,6 +500,12 @@ select '1 + ($.a.b + 2).c.d'::jsonpath;
484500
(1 + ($."a"."b" + 2)."c"."d")
485501
(1 row)
486502

503+
select '1 + ($.a.b > 2).c.d'::jsonpath;
504+
jsonpath
505+
-------------------------------
506+
(1 + ($."a"."b" > 2)."c"."d")
507+
(1 row)
508+
487509
select '$ ? (a < 1)'::jsonpath;
488510
jsonpath
489511
-------------

src/test/regress/sql/jsonb_jsonpath.sql

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

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

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

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

0 commit comments

Comments
 (0)