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

Commit b86833e

Browse files
author
Nikita Glukhov
committed
Add jsonpath STARTS WITH predicate
1 parent f092818 commit b86833e

File tree

9 files changed

+174
-11
lines changed

9 files changed

+174
-11
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
7575
case jpiMul:
7676
case jpiDiv:
7777
case jpiMod:
78+
case jpiStartsWith:
7879
{
7980
int32 left, right;
8081

@@ -265,6 +266,8 @@ printOperation(StringInfo buf, JsonPathItemType type)
265266
appendBinaryStringInfo(buf, " / ", 3); break;
266267
case jpiMod:
267268
appendBinaryStringInfo(buf, " % ", 3); break;
269+
case jpiStartsWith:
270+
appendBinaryStringInfo(buf, " starts with ", 13); break;
268271
default:
269272
elog(ERROR, "Unknown jsonpath item type: %d", type);
270273
}
@@ -285,6 +288,7 @@ operationPriority(JsonPathItemType op)
285288
case jpiGreater:
286289
case jpiLessOrEqual:
287290
case jpiGreaterOrEqual:
291+
case jpiStartsWith:
288292
return 2;
289293
case jpiAdd:
290294
case jpiSub:
@@ -350,6 +354,7 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
350354
case jpiMul:
351355
case jpiDiv:
352356
case jpiMod:
357+
case jpiStartsWith:
353358
if (printBracketes)
354359
appendStringInfoChar(buf, '(');
355360
jspGetLeftArg(v, &elem);
@@ -598,6 +603,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
598603
case jpiGreater:
599604
case jpiLessOrEqual:
600605
case jpiGreaterOrEqual:
606+
case jpiStartsWith:
601607
read_int32(v->content.args.left, base, pos);
602608
read_int32(v->content.args.right, base, pos);
603609
break;
@@ -666,7 +672,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
666672
v->type == jpiCeiling ||
667673
v->type == jpiDouble ||
668674
v->type == jpiDatetime ||
669-
v->type == jpiKeyValue
675+
v->type == jpiKeyValue ||
676+
v->type == jpiStartsWith
670677
);
671678

672679
if (a)
@@ -693,7 +700,8 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
693700
v->type == jpiSub ||
694701
v->type == jpiMul ||
695702
v->type == jpiDiv ||
696-
v->type == jpiMod
703+
v->type == jpiMod ||
704+
v->type == jpiStartsWith
697705
);
698706

699707
jspInitByBuffer(a, v->base, v->content.args.left);
@@ -715,7 +723,8 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
715723
v->type == jpiSub ||
716724
v->type == jpiMul ||
717725
v->type == jpiDiv ||
718-
v->type == jpiMod
726+
v->type == jpiMod ||
727+
v->type == jpiStartsWith
719728
);
720729

721730
jspInitByBuffer(a, v->base, v->content.args.right);

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,77 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
748748
return jperOk;
749749
}
750750

751+
static JsonPathExecResult
752+
executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
753+
JsonbValue *jb)
754+
{
755+
JsonPathExecResult res;
756+
JsonPathItem elem;
757+
List *lseq = NIL;
758+
List *rseq = NIL;
759+
ListCell *lc;
760+
JsonbValue *initial;
761+
JsonbValue initialbuf;
762+
bool error = false;
763+
bool found = false;
764+
765+
jspGetRightArg(jsp, &elem);
766+
res = recursiveExecute(cxt, &elem, jb, &rseq);
767+
if (jperIsError(res))
768+
return jperError;
769+
770+
if (list_length(rseq) != 1)
771+
return jperError;
772+
773+
initial = linitial(rseq);
774+
775+
if (JsonbType(initial) == jbvScalar)
776+
initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf);
777+
778+
if (initial->type != jbvString)
779+
return jperError;
780+
781+
jspGetLeftArg(jsp, &elem);
782+
res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
783+
if (jperIsError(res))
784+
return jperError;
785+
786+
foreach(lc, lseq)
787+
{
788+
JsonbValue *whole = lfirst(lc);
789+
JsonbValue wholebuf;
790+
791+
if (JsonbType(whole) == jbvScalar)
792+
whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf);
793+
794+
if (whole->type != jbvString)
795+
{
796+
if (!cxt->lax)
797+
return jperError;
798+
799+
error = true;
800+
}
801+
else if (whole->val.string.len >= initial->val.string.len &&
802+
!memcmp(whole->val.string.val,
803+
initial->val.string.val,
804+
initial->val.string.len))
805+
{
806+
if (cxt->lax)
807+
return jperOk;
808+
809+
found = true;
810+
}
811+
}
812+
813+
if (found) /* possible only in strict mode */
814+
return jperOk;
815+
816+
if (error) /* possible only in lax mode */
817+
return jperError;
818+
819+
return jperNotFound;
820+
}
821+
751822
/*
752823
* Main executor function: walks on jsonpath structure and tries to find
753824
* correspoding parts of jsonb. Note, jsonb and jsonpath values should be
@@ -1445,6 +1516,9 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
14451516
}
14461517
}
14471518
break;
1519+
case jpiStartsWith:
1520+
res = executeStartsWithPredicate(cxt, jsp, jb);
1521+
break;
14481522
default:
14491523
elog(ERROR,"2Wrong state: %d", jsp->type);
14501524
}

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,15 @@ makeAny(int first, int last)
221221
%token <str> STRING_P NUMERIC_P INT_P VARIABLE_P
222222
%token <str> OR_P AND_P NOT_P
223223
%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
224-
%token <str> ANY_P STRICT_P LAX_P LAST_P
224+
%token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P
225225
%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
226226
%token <str> KEYVALUE_P
227227

228228
%type <result> result
229229

230230
%type <value> scalar_value path_primary expr pexpr array_accessor
231231
any_path accessor_op key predicate delimited_predicate
232-
index_elem
232+
index_elem starts_with_initial
233233

234234
%type <elems> accessor_expr
235235

@@ -299,15 +299,18 @@ predicate:
299299
| predicate OR_P predicate { $$ = makeItemBinary(jpiOr, $1, $3); }
300300
| NOT_P delimited_predicate { $$ = makeItemUnary(jpiNot, $2); }
301301
| '(' predicate ')' IS_P UNKNOWN_P { $$ = makeItemUnary(jpiIsUnknown, $2); }
302-
/*
303-
Left for the future
304-
| pexpr LIKE_REGEX pattern { $$ = ...; }
305-
| pexpr STARTS WITH STRING_P { $$ = ...; }
306-
| pexpr STARTS WITH '$' STRING_P { $$ = ...; }
307-
| pexpr STARTS WITH '$' STRING_P { $$ = ...; }
302+
| pexpr STARTS_P WITH_P starts_with_initial
303+
{ $$ = makeItemBinary(jpiStartsWith, $1, $4); }
304+
/* Left for the future (needs XQuery support)
305+
| pexpr LIKE_REGEX pattern [FLAG_P flags] { $$ = ...; };
308306
*/
309307
;
310308

309+
starts_with_initial:
310+
STRING_P { $$ = makeItemString(&$1); }
311+
| VARIABLE_P { $$ = makeItemVariable(&$1); }
312+
;
313+
311314
path_primary:
312315
scalar_value { $$ = $1; }
313316
| '$' { $$ = makeItemType(jpiRoot); }
@@ -397,6 +400,8 @@ key_name:
397400
| DATETIME_P
398401
| KEYVALUE_P
399402
| LAST_P
403+
| STARTS_P
404+
| WITH_P
400405
;
401406

402407
method:

src/backend/utils/adt/jsonpath_scan.l

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,12 @@ static keyword keywords[] = {
278278
{ 4, false, SIZE_P, "size"},
279279
{ 4, true, TRUE_P, "true"},
280280
{ 4, false, TYPE_P, "type"},
281+
{ 4, false, WITH_P, "with"},
281282
{ 5, true, FALSE_P, "false"},
282283
{ 5, false, FLOOR_P, "floor"},
283284
{ 6, false, DOUBLE_P, "double"},
284285
{ 6, false, EXISTS_P, "exists"},
286+
{ 6, false, STARTS_P, "starts"},
285287
{ 6, false, STRICT_P, "strict"},
286288
{ 7, false, CEILING_P, "ceiling"},
287289
{ 7, false, UNKNOWN_P, "unknown"},

src/include/utils/jsonpath.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ typedef enum JsonPathItemType {
8080
jpiKeyValue,
8181
jpiSubscript,
8282
jpiLast,
83+
jpiStartsWith,
8384
} JsonPathItemType;
8485

8586

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,3 +958,51 @@ select _jsonpath_query(jsonb '"1.23"', '$.double()');
958958

959959
select _jsonpath_query(jsonb '"1.23aaa"', '$.double()');
960960
ERROR: Non-numeric SQL/JSON item
961+
select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
962+
_jsonpath_query
963+
-----------------
964+
"abc"
965+
"abcabc"
966+
(2 rows)
967+
968+
select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
969+
_jsonpath_query
970+
----------------------------
971+
["", "a", "abc", "abcabc"]
972+
(1 row)
973+
974+
select _jsonpath_query(jsonb '["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
975+
_jsonpath_query
976+
-----------------
977+
(0 rows)
978+
979+
select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
980+
_jsonpath_query
981+
-----------------
982+
(0 rows)
983+
984+
select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
985+
_jsonpath_query
986+
----------------------------
987+
["abc", "abcabc", null, 1]
988+
(1 row)
989+
990+
select _jsonpath_query(jsonb '[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
991+
_jsonpath_query
992+
----------------------------
993+
[null, 1, "abc", "abcabc"]
994+
(1 row)
995+
996+
select _jsonpath_query(jsonb '[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
997+
_jsonpath_query
998+
----------------------------
999+
[null, 1, "abd", "abdabc"]
1000+
(1 row)
1001+
1002+
select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
1003+
_jsonpath_query
1004+
-----------------
1005+
null
1006+
1
1007+
(2 rows)
1008+

src/test/regress/expected/jsonpath.out

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,18 @@ select 'true.type()'::jsonpath;
377377
true.type()
378378
(1 row)
379379

380+
select '$ ? (@ starts with "abc")'::jsonpath;
381+
jsonpath
382+
-------------------------
383+
$?(@ starts with "abc")
384+
(1 row)
385+
386+
select '$ ? (@ starts with $var)'::jsonpath;
387+
jsonpath
388+
--------------------------
389+
$?(@ starts with $"var")
390+
(1 row)
391+
380392
select '$ ? (a < 1)'::jsonpath;
381393
jsonpath
382394
-------------

src/test/regress/sql/jsonb_jsonpath.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,12 @@ select _jsonpath_query(jsonb '{}', '$.double()');
193193
select _jsonpath_query(jsonb '1.23', '$.double()');
194194
select _jsonpath_query(jsonb '"1.23"', '$.double()');
195195
select _jsonpath_query(jsonb '"1.23aaa"', '$.double()');
196+
197+
select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
198+
select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
199+
select _jsonpath_query(jsonb '["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
200+
select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
201+
select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
202+
select _jsonpath_query(jsonb '[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
203+
select _jsonpath_query(jsonb '[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
204+
select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');

src/test/regress/sql/jsonpath.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ select '"aaa".type()'::jsonpath;
6868
select 'aaa.type()'::jsonpath;
6969
select 'true.type()'::jsonpath;
7070

71+
select '$ ? (@ starts with "abc")'::jsonpath;
72+
select '$ ? (@ starts with $var)'::jsonpath;
73+
7174
select '$ ? (a < 1)'::jsonpath;
7275
select '$ ? (a < -1)'::jsonpath;
7376
select '$ ? (a < +1)'::jsonpath;

0 commit comments

Comments
 (0)