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

Commit 34c7b12

Browse files
author
Nikita Glukhov
committed
Add jsonpath STARTS WITH predicate
1 parent 6aaf389 commit 34c7b12

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
@@ -77,6 +77,7 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
7777
case jpiMul:
7878
case jpiDiv:
7979
case jpiMod:
80+
case jpiStartsWith:
8081
{
8182
int32 left, right;
8283

@@ -269,6 +270,8 @@ printOperation(StringInfo buf, JsonPathItemType type)
269270
appendBinaryStringInfo(buf, " / ", 3); break;
270271
case jpiMod:
271272
appendBinaryStringInfo(buf, " % ", 3); break;
273+
case jpiStartsWith:
274+
appendBinaryStringInfo(buf, " starts with ", 13); break;
272275
default:
273276
elog(ERROR, "Unknown jsonpath item type: %d", type);
274277
}
@@ -289,6 +292,7 @@ operationPriority(JsonPathItemType op)
289292
case jpiGreater:
290293
case jpiLessOrEqual:
291294
case jpiGreaterOrEqual:
295+
case jpiStartsWith:
292296
return 2;
293297
case jpiAdd:
294298
case jpiSub:
@@ -354,6 +358,7 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
354358
case jpiMul:
355359
case jpiDiv:
356360
case jpiMod:
361+
case jpiStartsWith:
357362
if (printBracketes)
358363
appendStringInfoChar(buf, '(');
359364
jspGetLeftArg(v, &elem);
@@ -602,6 +607,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
602607
case jpiGreater:
603608
case jpiLessOrEqual:
604609
case jpiGreaterOrEqual:
610+
case jpiStartsWith:
605611
read_int32(v->content.args.left, base, pos);
606612
read_int32(v->content.args.right, base, pos);
607613
break;
@@ -670,7 +676,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
670676
v->type == jpiCeiling ||
671677
v->type == jpiDouble ||
672678
v->type == jpiDatetime ||
673-
v->type == jpiKeyValue
679+
v->type == jpiKeyValue ||
680+
v->type == jpiStartsWith
674681
);
675682

676683
if (a)
@@ -697,7 +704,8 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a)
697704
v->type == jpiSub ||
698705
v->type == jpiMul ||
699706
v->type == jpiDiv ||
700-
v->type == jpiMod
707+
v->type == jpiMod ||
708+
v->type == jpiStartsWith
701709
);
702710

703711
jspInitByBuffer(a, v->base, v->content.args.left);
@@ -719,7 +727,8 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a)
719727
v->type == jpiSub ||
720728
v->type == jpiMul ||
721729
v->type == jpiDiv ||
722-
v->type == jpiMod
730+
v->type == jpiMod ||
731+
v->type == jpiStartsWith
723732
);
724733

725734
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
@@ -769,6 +769,77 @@ getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
769769
return jperOk;
770770
}
771771

772+
static JsonPathExecResult
773+
executeStartsWithPredicate(JsonPathExecContext *cxt, JsonPathItem *jsp,
774+
JsonbValue *jb)
775+
{
776+
JsonPathExecResult res;
777+
JsonPathItem elem;
778+
List *lseq = NIL;
779+
List *rseq = NIL;
780+
ListCell *lc;
781+
JsonbValue *initial;
782+
JsonbValue initialbuf;
783+
bool error = false;
784+
bool found = false;
785+
786+
jspGetRightArg(jsp, &elem);
787+
res = recursiveExecute(cxt, &elem, jb, &rseq);
788+
if (jperIsError(res))
789+
return jperError;
790+
791+
if (list_length(rseq) != 1)
792+
return jperError;
793+
794+
initial = linitial(rseq);
795+
796+
if (JsonbType(initial) == jbvScalar)
797+
initial = JsonbExtractScalar(initial->val.binary.data, &initialbuf);
798+
799+
if (initial->type != jbvString)
800+
return jperError;
801+
802+
jspGetLeftArg(jsp, &elem);
803+
res = recursiveExecuteAndUnwrap(cxt, &elem, jb, &lseq);
804+
if (jperIsError(res))
805+
return jperError;
806+
807+
foreach(lc, lseq)
808+
{
809+
JsonbValue *whole = lfirst(lc);
810+
JsonbValue wholebuf;
811+
812+
if (JsonbType(whole) == jbvScalar)
813+
whole = JsonbExtractScalar(whole->val.binary.data, &wholebuf);
814+
815+
if (whole->type != jbvString)
816+
{
817+
if (!cxt->lax)
818+
return jperError;
819+
820+
error = true;
821+
}
822+
else if (whole->val.string.len >= initial->val.string.len &&
823+
!memcmp(whole->val.string.val,
824+
initial->val.string.val,
825+
initial->val.string.len))
826+
{
827+
if (cxt->lax)
828+
return jperOk;
829+
830+
found = true;
831+
}
832+
}
833+
834+
if (found) /* possible only in strict mode */
835+
return jperOk;
836+
837+
if (error) /* possible only in lax mode */
838+
return jperError;
839+
840+
return jperNotFound;
841+
}
842+
772843
/*
773844
* Main executor function: walks on jsonpath structure and tries to find
774845
* correspoding parts of jsonb. Note, jsonb and jsonpath values should be
@@ -1466,6 +1537,9 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
14661537
}
14671538
}
14681539
break;
1540+
case jpiStartsWith:
1541+
res = executeStartsWithPredicate(cxt, jsp, jb);
1542+
break;
14691543
default:
14701544
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
14711545
}

src/backend/utils/adt/jsonpath_gram.y

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

231231
%type <result> result
232232

233233
%type <value> scalar_value path_primary expr pexpr array_accessor
234234
any_path accessor_op key predicate delimited_predicate
235-
index_elem
235+
index_elem starts_with_initial
236236

237237
%type <elems> accessor_expr
238238

@@ -302,15 +302,18 @@ predicate:
302302
| predicate OR_P predicate { $$ = makeItemBinary(jpiOr, $1, $3); }
303303
| NOT_P delimited_predicate { $$ = makeItemUnary(jpiNot, $2); }
304304
| '(' predicate ')' IS_P UNKNOWN_P { $$ = makeItemUnary(jpiIsUnknown, $2); }
305-
/*
306-
Left for the future
307-
| pexpr LIKE_REGEX pattern { $$ = ...; }
308-
| pexpr STARTS WITH STRING_P { $$ = ...; }
309-
| pexpr STARTS WITH '$' STRING_P { $$ = ...; }
310-
| pexpr STARTS WITH '$' STRING_P { $$ = ...; }
305+
| pexpr STARTS_P WITH_P starts_with_initial
306+
{ $$ = makeItemBinary(jpiStartsWith, $1, $4); }
307+
/* Left for the future (needs XQuery support)
308+
| pexpr LIKE_REGEX pattern [FLAG_P flags] { $$ = ...; };
311309
*/
312310
;
313311

312+
starts_with_initial:
313+
STRING_P { $$ = makeItemString(&$1); }
314+
| VARIABLE_P { $$ = makeItemVariable(&$1); }
315+
;
316+
314317
path_primary:
315318
scalar_value { $$ = $1; }
316319
| '$' { $$ = makeItemType(jpiRoot); }
@@ -399,6 +402,8 @@ key_name:
399402
| DATETIME_P
400403
| KEYVALUE_P
401404
| LAST_P
405+
| STARTS_P
406+
| WITH_P
402407
;
403408

404409
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
@@ -968,3 +968,51 @@ select _jsonpath_query(jsonb '"1.23"', '$.double()');
968968

969969
select _jsonpath_query(jsonb '"1.23aaa"', '$.double()');
970970
ERROR: Non-numeric SQL/JSON item
971+
select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
972+
_jsonpath_query
973+
-----------------
974+
"abc"
975+
"abcabc"
976+
(2 rows)
977+
978+
select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
979+
_jsonpath_query
980+
----------------------------
981+
["", "a", "abc", "abcabc"]
982+
(1 row)
983+
984+
select _jsonpath_query(jsonb '["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
985+
_jsonpath_query
986+
-----------------
987+
(0 rows)
988+
989+
select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
990+
_jsonpath_query
991+
-----------------
992+
(0 rows)
993+
994+
select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
995+
_jsonpath_query
996+
----------------------------
997+
["abc", "abcabc", null, 1]
998+
(1 row)
999+
1000+
select _jsonpath_query(jsonb '[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
1001+
_jsonpath_query
1002+
----------------------------
1003+
[null, 1, "abc", "abcabc"]
1004+
(1 row)
1005+
1006+
select _jsonpath_query(jsonb '[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
1007+
_jsonpath_query
1008+
----------------------------
1009+
[null, 1, "abd", "abdabc"]
1010+
(1 row)
1011+
1012+
select _jsonpath_query(jsonb '[null, 1, "abd", "abdabc"]', 'lax $[*] ? ((@ starts with "abc") is unknown)');
1013+
_jsonpath_query
1014+
-----------------
1015+
null
1016+
1
1017+
(2 rows)
1018+

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
@@ -194,3 +194,12 @@ select _jsonpath_query(jsonb '{}', '$.double()');
194194
select _jsonpath_query(jsonb '1.23', '$.double()');
195195
select _jsonpath_query(jsonb '"1.23"', '$.double()');
196196
select _jsonpath_query(jsonb '"1.23aaa"', '$.double()');
197+
198+
select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', '$[*] ? (@ starts with "abc")');
199+
select _jsonpath_query(jsonb '["", "a", "abc", "abcabc"]', 'strict $ ? (@[*] starts with "abc")');
200+
select _jsonpath_query(jsonb '["", "a", "abd", "abdabc"]', 'strict $ ? (@[*] starts with "abc")');
201+
select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? (@[*] starts with "abc")');
202+
select _jsonpath_query(jsonb '["abc", "abcabc", null, 1]', 'strict $ ? ((@[*] starts with "abc") is unknown)');
203+
select _jsonpath_query(jsonb '[[null, 1, "abc", "abcabc"]]', 'lax $ ? (@[*] starts with "abc")');
204+
select _jsonpath_query(jsonb '[[null, 1, "abd", "abdabc"]]', 'lax $ ? ((@[*] starts with "abc") is unknown)');
205+
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)