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

Commit 50d0693

Browse files
author
Nikita Glukhov
committed
Add boolean jsonpath expressions
1 parent dd70c15 commit 50d0693

File tree

7 files changed

+178
-13
lines changed

7 files changed

+178
-13
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,16 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
683683
v->type == jpiMod ||
684684
v->type == jpiPlus ||
685685
v->type == jpiMinus ||
686+
v->type == jpiEqual ||
687+
v->type == jpiNotEqual ||
688+
v->type == jpiGreater ||
689+
v->type == jpiGreaterOrEqual ||
690+
v->type == jpiLess ||
691+
v->type == jpiLessOrEqual ||
692+
v->type == jpiAnd ||
693+
v->type == jpiOr ||
694+
v->type == jpiNot ||
695+
v->type == jpiIsUnknown ||
686696
v->type == jpiType ||
687697
v->type == jpiSize ||
688698
v->type == jpiAbs ||

src/backend/utils/adt/jsonpath_exec.c

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

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

@@ -1095,6 +1098,34 @@ tryToParseDatetime(const char *template, text *datetime,
10951098
return ok;
10961099
}
10971100

1101+
static inline JsonPathExecResult
1102+
appendBoolResult(JsonPathExecContext *cxt, JsonPathItem *jsp,
1103+
JsonValueList *found, JsonPathExecResult res, bool needBool)
1104+
{
1105+
JsonPathItem next;
1106+
JsonbValue jbv;
1107+
bool hasNext = jspGetNext(jsp, &next);
1108+
1109+
if (needBool)
1110+
{
1111+
Assert(!hasNext);
1112+
return res;
1113+
}
1114+
1115+
if (!found && !hasNext)
1116+
return jperOk; /* found singleton boolean value */
1117+
1118+
if (jperIsError(res))
1119+
jbv.type = jbvNull;
1120+
else
1121+
{
1122+
jbv.type = jbvBool;
1123+
jbv.val.boolean = res == jperOk;
1124+
}
1125+
1126+
return recursiveExecuteNext(cxt, jsp, &next, &jbv, found, true);
1127+
}
1128+
10981129
/*
10991130
* Main executor function: walks on jsonpath structure and tries to find
11001131
* correspoding parts of jsonb. Note, jsonb and jsonpath values should be
@@ -1107,7 +1138,7 @@ tryToParseDatetime(const char *template, text *datetime,
11071138
*/
11081139
static JsonPathExecResult
11091140
recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
1110-
JsonbValue *jb, JsonValueList *found)
1141+
JsonbValue *jb, JsonValueList *found, bool needBool)
11111142
{
11121143
JsonPathItem elem;
11131144
JsonPathExecResult res = jperNotFound;
@@ -1118,7 +1149,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
11181149
switch(jsp->type) {
11191150
case jpiAnd:
11201151
jspGetLeftArg(jsp, &elem);
1121-
res = recursiveExecute(cxt, &elem, jb, NULL);
1152+
res = recursiveExecuteBool(cxt, &elem, jb);
11221153
if (res != jperNotFound)
11231154
{
11241155
JsonPathExecResult res2;
@@ -1129,27 +1160,29 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
11291160
*/
11301161

11311162
jspGetRightArg(jsp, &elem);
1132-
res2 = recursiveExecute(cxt, &elem, jb, NULL);
1163+
res2 = recursiveExecuteBool(cxt, &elem, jb);
11331164

11341165
res = (res2 == jperOk) ? res : res2;
11351166
}
1167+
res = appendBoolResult(cxt, jsp, found, res, needBool);
11361168
break;
11371169
case jpiOr:
11381170
jspGetLeftArg(jsp, &elem);
1139-
res = recursiveExecute(cxt, &elem, jb, NULL);
1171+
res = recursiveExecuteBool(cxt, &elem, jb);
11401172
if (res != jperOk)
11411173
{
11421174
JsonPathExecResult res2;
11431175

11441176
jspGetRightArg(jsp, &elem);
1145-
res2 = recursiveExecute(cxt, &elem, jb, NULL);
1177+
res2 = recursiveExecuteBool(cxt, &elem, jb);
11461178

11471179
res = (res2 == jperNotFound) ? res : res2;
11481180
}
1181+
res = appendBoolResult(cxt, jsp, found, res, needBool);
11491182
break;
11501183
case jpiNot:
11511184
jspGetArg(jsp, &elem);
1152-
switch ((res = recursiveExecute(cxt, &elem, jb, NULL)))
1185+
switch ((res = recursiveExecuteBool(cxt, &elem, jb)))
11531186
{
11541187
case jperOk:
11551188
res = jperNotFound;
@@ -1160,11 +1193,13 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
11601193
default:
11611194
break;
11621195
}
1196+
res = appendBoolResult(cxt, jsp, found, res, needBool);
11631197
break;
11641198
case jpiIsUnknown:
11651199
jspGetArg(jsp, &elem);
1166-
res = recursiveExecute(cxt, &elem, jb, NULL);
1200+
res = recursiveExecuteBool(cxt, &elem, jb);
11671201
res = jperIsError(res) ? jperOk : jperNotFound;
1202+
res = appendBoolResult(cxt, jsp, found, res, needBool);
11681203
break;
11691204
case jpiKey:
11701205
if (JsonbType(jb) == jbvObject)
@@ -1422,6 +1457,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
14221457
case jpiLessOrEqual:
14231458
case jpiGreaterOrEqual:
14241459
res = executeExpr(cxt, jsp, jb);
1460+
res = appendBoolResult(cxt, jsp, found, res, needBool);
14251461
break;
14261462
case jpiAdd:
14271463
case jpiSub:
@@ -1436,7 +1472,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
14361472
break;
14371473
case jpiFilter:
14381474
jspGetArg(jsp, &elem);
1439-
res = recursiveExecute(cxt, &elem, jb, NULL);
1475+
res = recursiveExecuteBool(cxt, &elem, jb);
14401476
if (res != jperOk)
14411477
res = jperNotFound;
14421478
else
@@ -1470,6 +1506,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
14701506
case jpiExists:
14711507
jspGetArg(jsp, &elem);
14721508
res = recursiveExecute(cxt, &elem, jb, NULL);
1509+
res = appendBoolResult(cxt, jsp, found, res, needBool);
14731510
break;
14741511
case jpiNull:
14751512
case jpiBool:
@@ -1805,6 +1842,7 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
18051842
break;
18061843
case jpiStartsWith:
18071844
res = executeStartsWithPredicate(cxt, jsp, jb);
1845+
res = appendBoolResult(cxt, jsp, found, res, needBool);
18081846
break;
18091847
default:
18101848
elog(ERROR,"2Wrong state: %d", jsp->type);
@@ -1826,7 +1864,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
18261864

18271865
for (; elem < last; elem++)
18281866
{
1829-
res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found);
1867+
res = recursiveExecuteNoUnwrap(cxt, jsp, elem, found, false);
18301868

18311869
if (jperIsError(res))
18321870
break;
@@ -1846,7 +1884,7 @@ recursiveExecuteUnwrapArray(JsonPathExecContext *cxt, JsonPathItem *jsp,
18461884
{
18471885
if (tok == WJB_ELEM)
18481886
{
1849-
res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found);
1887+
res = recursiveExecuteNoUnwrap(cxt, jsp, &v, found, false);
18501888
if (jperIsError(res))
18511889
break;
18521890
if (res == jperOk && !found)
@@ -1865,7 +1903,7 @@ recursiveExecuteUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
18651903
if (cxt->lax && JsonbType(jb) == jbvArray)
18661904
return recursiveExecuteUnwrapArray(cxt, jsp, jb, found);
18671905

1868-
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
1906+
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
18691907
}
18701908

18711909
static inline JsonbValue *
@@ -1919,7 +1957,38 @@ recursiveExecute(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
19191957
}
19201958
}
19211959

1922-
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found);
1960+
return recursiveExecuteNoUnwrap(cxt, jsp, jb, found, false);
1961+
}
1962+
1963+
static inline JsonPathExecResult
1964+
recursiveExecuteBool(JsonPathExecContext *cxt, JsonPathItem *jsp,
1965+
JsonbValue *jb)
1966+
{
1967+
if (jspHasNext(jsp))
1968+
elog(ERROR, "boolean jsonpath item can not have next item");
1969+
1970+
switch (jsp->type)
1971+
{
1972+
case jpiAnd:
1973+
case jpiOr:
1974+
case jpiNot:
1975+
case jpiIsUnknown:
1976+
case jpiEqual:
1977+
case jpiNotEqual:
1978+
case jpiGreater:
1979+
case jpiGreaterOrEqual:
1980+
case jpiLess:
1981+
case jpiLessOrEqual:
1982+
case jpiExists:
1983+
case jpiStartsWith:
1984+
break;
1985+
1986+
default:
1987+
elog(ERROR, "invalid boolean jsonpath item type: %d", jsp->type);
1988+
break;
1989+
}
1990+
1991+
return recursiveExecuteNoUnwrap(cxt, jsp, jb, NULL, true);
19231992
}
19241993

19251994
/*

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ makeAny(int first, int last)
235235
%type <value> scalar_value path_primary expr pexpr array_accessor
236236
any_path accessor_op key predicate delimited_predicate
237237
index_elem starts_with_initial opt_datetime_template
238+
expr_or_predicate
238239

239240
%type <elems> accessor_expr
240241

@@ -259,14 +260,19 @@ makeAny(int first, int last)
259260
%%
260261

261262
result:
262-
mode expr {
263+
mode expr_or_predicate {
263264
*result = palloc(sizeof(JsonPathParseResult));
264265
(*result)->expr = $2;
265266
(*result)->lax = $1;
266267
}
267268
| /* EMPTY */ { *result = NULL; }
268269
;
269270

271+
expr_or_predicate:
272+
expr { $$ = $1; }
273+
| predicate { $$ = $1; }
274+
;
275+
270276
mode:
271277
STRICT_P { $$ = false; }
272278
| LAX_P { $$ = true; }
@@ -327,6 +333,7 @@ accessor_expr:
327333
path_primary { $$ = list_make1($1); }
328334
| '.' key { $$ = list_make2(makeItemType(jpiCurrent), $2); }
329335
| '(' expr ')' accessor_op { $$ = list_make2($2, $4); }
336+
| '(' predicate ')' accessor_op { $$ = list_make2($2, $4); }
330337
| accessor_expr accessor_op { $$ = lappend($1, $2); }
331338
;
332339

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,25 @@ ERROR: Singleton SQL/JSON item required
775775
-- should fail (by standard unwrapped only arguments of multiplicative expressions)
776776
select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a + 3');
777777
ERROR: Singleton SQL/JSON item required
778+
-- extension: boolean expressions
779+
select _jsonpath_query(jsonb '2', '$ > 1');
780+
_jsonpath_query
781+
-----------------
782+
true
783+
(1 row)
784+
785+
select _jsonpath_query(jsonb '2', '$ <= 1');
786+
_jsonpath_query
787+
-----------------
788+
false
789+
(1 row)
790+
791+
select _jsonpath_query(jsonb '2', '$ == "2"');
792+
_jsonpath_query
793+
-----------------
794+
null
795+
(1 row)
796+
778797
select _jsonpath_query(jsonb '[null,1,true,"a",[],{}]', '$.type()');
779798
_jsonpath_query
780799
-----------------
@@ -840,6 +859,30 @@ select _jsonpath_query(jsonb '{"a": 2.5}', '-($.a * $.a).floor() + 10');
840859
4
841860
(1 row)
842861

862+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 2) ? (@ == true)');
863+
_jsonpath_query
864+
-----------------
865+
true
866+
(1 row)
867+
868+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*] > 3).type()');
869+
_jsonpath_query
870+
-----------------
871+
"boolean"
872+
(1 row)
873+
874+
select _jsonpath_query(jsonb '[1, 2, 3]', '($[*].a > 3).type()');
875+
_jsonpath_query
876+
-----------------
877+
"boolean"
878+
(1 row)
879+
880+
select _jsonpath_query(jsonb '[1, 2, 3]', 'strict ($[*].a > 3).type()');
881+
_jsonpath_query
882+
-----------------
883+
"null"
884+
(1 row)
885+
843886
select _jsonpath_query(jsonb '[1,null,true,"11",[],[1],[1,2,3],{},{"a":1,"b":2}]', 'strict $[*].size()');
844887
ERROR: SQL/JSON array not found
845888
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
@@ -409,6 +409,22 @@ select '$ ? (@ starts with $var)'::jsonpath;
409409
$?(@ starts with $"var")
410410
(1 row)
411411

412+
select '$ < 1'::jsonpath;
413+
jsonpath
414+
----------
415+
($ < 1)
416+
(1 row)
417+
418+
select '($ < 1) || $.a.b <= $x'::jsonpath;
419+
jsonpath
420+
------------------------------
421+
($ < 1 || $."a"."b" <= $"x")
422+
(1 row)
423+
424+
select '@ + 1'::jsonpath;
425+
ERROR: @ is not allowed in root expressions
426+
LINE 1: select '@ + 1'::jsonpath;
427+
^
412428
select '($).a.b'::jsonpath;
413429
jsonpath
414430
-----------
@@ -439,6 +455,12 @@ select '1 + ($.a.b + 2).c.d'::jsonpath;
439455
(1 + ($."a"."b" + 2)."c"."d")
440456
(1 row)
441457

458+
select '1 + ($.a.b > 2).c.d'::jsonpath;
459+
jsonpath
460+
-------------------------------
461+
(1 + ($."a"."b" > 2)."c"."d")
462+
(1 row)
463+
442464
select '$ ? (a < 1)'::jsonpath;
443465
jsonpath
444466
-------------

src/test/regress/sql/jsonb_jsonpath.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ select _jsonpath_query(jsonb '{"a": [1, 2]}', 'lax $.a * 3');
161161
-- should fail (by standard unwrapped only arguments of multiplicative expressions)
162162
select _jsonpath_query(jsonb '{"a": [2]}', 'lax $.a + 3');
163163

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

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

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

src/test/regress/sql/jsonpath.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,16 @@ select '$.datetime("datetime template")'::jsonpath;
7474
select '$ ? (@ starts with "abc")'::jsonpath;
7575
select '$ ? (@ starts with $var)'::jsonpath;
7676

77+
select '$ < 1'::jsonpath;
78+
select '($ < 1) || $.a.b <= $x'::jsonpath;
79+
select '@ + 1'::jsonpath;
80+
7781
select '($).a.b'::jsonpath;
7882
select '($.a.b).c.d'::jsonpath;
7983
select '($.a.b + -$.x.y).c.d'::jsonpath;
8084
select '(-+$.a.b).c.d'::jsonpath;
8185
select '1 + ($.a.b + 2).c.d'::jsonpath;
86+
select '1 + ($.a.b > 2).c.d'::jsonpath;
8287

8388
select '$ ? (a < 1)'::jsonpath;
8489
select '$ ? (a < -1)'::jsonpath;

0 commit comments

Comments
 (0)