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

Commit 9dbe6d1

Browse files
author
Nikita Glukhov
committed
Add jsonpath sequences
1 parent eafa131 commit 9dbe6d1

File tree

8 files changed

+257
-7
lines changed

8 files changed

+257
-7
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
216216
appendBinaryStringInfo(out, "strict ", 7);
217217

218218
jspInit(&v, in);
219-
printJsonPathItem(out, &v, false, true);
219+
printJsonPathItem(out, &v, false, v.type != jpiSequence);
220220

221221
return out->data;
222222
}
@@ -417,6 +417,29 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
417417
case jpiDouble:
418418
case jpiKeyValue:
419419
break;
420+
case jpiSequence:
421+
{
422+
int32 nelems = list_length(item->value.sequence.elems);
423+
ListCell *lc;
424+
int offset;
425+
426+
appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
427+
428+
offset = buf->len;
429+
430+
appendStringInfoSpaces(buf, sizeof(int32) * nelems);
431+
432+
foreach(lc, item->value.sequence.elems)
433+
{
434+
int32 elempos =
435+
flattenJsonPathParseItem(buf, lfirst(lc), nestingLevel,
436+
insideArraySubscript);
437+
438+
*(int32 *) &buf->data[offset] = elempos - pos;
439+
offset += sizeof(int32);
440+
}
441+
}
442+
break;
420443
default:
421444
elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
422445
}
@@ -639,12 +662,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
639662
if (i)
640663
appendStringInfoChar(buf, ',');
641664

642-
printJsonPathItem(buf, &from, false, false);
665+
printJsonPathItem(buf, &from, false, from.type == jpiSequence);
643666

644667
if (range)
645668
{
646669
appendBinaryStringInfo(buf, " to ", 4);
647-
printJsonPathItem(buf, &to, false, false);
670+
printJsonPathItem(buf, &to, false, to.type == jpiSequence);
648671
}
649672
}
650673
appendStringInfoChar(buf, ']');
@@ -712,6 +735,25 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
712735
case jpiKeyValue:
713736
appendBinaryStringInfo(buf, ".keyvalue()", 11);
714737
break;
738+
case jpiSequence:
739+
if (printBracketes || jspHasNext(v))
740+
appendStringInfoChar(buf, '(');
741+
742+
for (i = 0; i < v->content.sequence.nelems; i++)
743+
{
744+
JsonPathItem elem;
745+
746+
if (i)
747+
appendBinaryStringInfo(buf, ", ", 2);
748+
749+
jspGetSequenceElement(v, i, &elem);
750+
751+
printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
752+
}
753+
754+
if (printBracketes || jspHasNext(v))
755+
appendStringInfoChar(buf, ')');
756+
break;
715757
default:
716758
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
717759
}
@@ -784,6 +826,8 @@ operationPriority(JsonPathItemType op)
784826
{
785827
switch (op)
786828
{
829+
case jpiSequence:
830+
return -1;
787831
case jpiOr:
788832
return 0;
789833
case jpiAnd:
@@ -920,6 +964,11 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
920964
read_int32(v->content.anybounds.first, base, pos);
921965
read_int32(v->content.anybounds.last, base, pos);
922966
break;
967+
case jpiSequence:
968+
read_int32(v->content.sequence.nelems, base, pos);
969+
read_int32_n(v->content.sequence.elems, base, pos,
970+
v->content.sequence.nelems);
971+
break;
923972
default:
924973
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
925974
}
@@ -983,7 +1032,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
9831032
v->type == jpiDouble ||
9841033
v->type == jpiDatetime ||
9851034
v->type == jpiKeyValue ||
986-
v->type == jpiStartsWith);
1035+
v->type == jpiStartsWith ||
1036+
v->type == jpiSequence);
9871037

9881038
if (a)
9891039
jspInitByBuffer(a, v->base, v->nextPos);
@@ -1080,3 +1130,11 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
10801130

10811131
return true;
10821132
}
1133+
1134+
void
1135+
jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
1136+
{
1137+
Assert(v->type == jpiSequence);
1138+
1139+
jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
1140+
}

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,52 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
15911591

15921592
return executeKeyValueMethod(cxt, jsp, jb, found);
15931593

1594+
case jpiSequence:
1595+
{
1596+
JsonPathItem next;
1597+
bool hasNext = jspGetNext(jsp, &next);
1598+
JsonValueList list;
1599+
JsonValueList *plist = hasNext ? &list : found;
1600+
JsonValueListIterator it;
1601+
int i;
1602+
1603+
for (i = 0; i < jsp->content.sequence.nelems; i++)
1604+
{
1605+
JsonItem *v;
1606+
1607+
if (hasNext)
1608+
memset(&list, 0, sizeof(list));
1609+
1610+
jspGetSequenceElement(jsp, i, &elem);
1611+
res = executeItem(cxt, &elem, jb, plist);
1612+
1613+
if (jperIsError(res))
1614+
break;
1615+
1616+
if (!hasNext)
1617+
{
1618+
if (!found && res == jperOk)
1619+
break;
1620+
continue;
1621+
}
1622+
1623+
JsonValueListInitIterator(&list, &it);
1624+
1625+
while ((v = JsonValueListNext(&list, &it)))
1626+
{
1627+
res = executeItem(cxt, &next, v, found);
1628+
1629+
if (jperIsError(res) || (!found && res == jperOk))
1630+
{
1631+
i = jsp->content.sequence.nelems;
1632+
break;
1633+
}
1634+
}
1635+
}
1636+
1637+
break;
1638+
}
1639+
15941640
default:
15951641
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
15961642
}

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ static JsonPathParseItem *makeAny(int first, int last);
5656
static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
5757
JsonPathString *pattern,
5858
JsonPathString *flags);
59+
static JsonPathParseItem *makeItemSequence(List *elems);
5960

6061
/*
6162
* Bison doesn't allocate anything that needs to live across parser calls,
@@ -101,9 +102,9 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
101102
%type <value> scalar_value path_primary expr array_accessor
102103
any_path accessor_op key predicate delimited_predicate
103104
index_elem starts_with_initial expr_or_predicate
104-
datetime_template opt_datetime_template
105+
datetime_template opt_datetime_template expr_seq expr_or_seq
105106

106-
%type <elems> accessor_expr
107+
%type <elems> accessor_expr expr_list
107108

108109
%type <indexs> index_list
109110

@@ -127,7 +128,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
127128
%%
128129

129130
result:
130-
mode expr_or_predicate {
131+
mode expr_or_seq {
131132
*result = palloc(sizeof(JsonPathParseResult));
132133
(*result)->expr = $2;
133134
(*result)->lax = $1;
@@ -140,6 +141,20 @@ expr_or_predicate:
140141
| predicate { $$ = $1; }
141142
;
142143

144+
expr_or_seq:
145+
expr_or_predicate { $$ = $1; }
146+
| expr_seq { $$ = $1; }
147+
;
148+
149+
expr_seq:
150+
expr_list { $$ = makeItemSequence($1); }
151+
;
152+
153+
expr_list:
154+
expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
155+
| expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
156+
;
157+
143158
mode:
144159
STRICT_P { $$ = false; }
145160
| LAX_P { $$ = true; }
@@ -195,6 +210,7 @@ path_primary:
195210
| '$' { $$ = makeItemType(jpiRoot); }
196211
| '@' { $$ = makeItemType(jpiCurrent); }
197212
| LAST_P { $$ = makeItemType(jpiLast); }
213+
| '(' expr_seq ')' { $$ = $2; }
198214
;
199215

200216
accessor_expr:
@@ -552,6 +568,16 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
552568
return v;
553569
}
554570

571+
static JsonPathParseItem *
572+
makeItemSequence(List *elems)
573+
{
574+
JsonPathParseItem *v = makeItemType(jpiSequence);
575+
576+
v->value.sequence.elems = elems;
577+
578+
return v;
579+
}
580+
555581
/*
556582
* jsonpath_scan.l is compiled as part of jsonpath_gram.y. Currently, this is
557583
* unavoidable because jsonpath_gram does not create a .h file to export its

src/include/utils/jsonpath.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ typedef enum JsonPathItemType
8989
jpiLast, /* LAST array subscript */
9090
jpiStartsWith, /* STARTS WITH predicate */
9191
jpiLikeRegex, /* LIKE_REGEX predicate */
92+
jpiSequence, /* sequence constructor: 'expr, ...' */
9293
} JsonPathItemType;
9394

9495
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -149,6 +150,12 @@ typedef struct JsonPathItem
149150
uint32 last;
150151
} anybounds;
151152

153+
struct
154+
{
155+
int32 nelems;
156+
int32 *elems;
157+
} sequence;
158+
152159
struct
153160
{
154161
char *data; /* for bool, numeric and string/key */
@@ -178,6 +185,7 @@ extern bool jspGetBool(JsonPathItem *v);
178185
extern char *jspGetString(JsonPathItem *v, int32 *len);
179186
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
180187
JsonPathItem *to, int i);
188+
extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
181189

182190
extern const char *jspOperationName(JsonPathItemType type);
183191

@@ -231,6 +239,10 @@ struct JsonPathParseItem
231239
uint32 flags;
232240
} like_regex;
233241

242+
struct {
243+
List *elems;
244+
} sequence;
245+
234246
/* scalars */
235247
Numeric numeric;
236248
bool boolean;

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2375,3 +2375,59 @@ SELECT jsonb_path_match('[{"a": 1}, {"a": 2}]', '$[*].a > 1');
23752375
t
23762376
(1 row)
23772377

2378+
-- extension: path sequences
2379+
select jsonb_path_query('[1,2,3,4,5]', '10, 20, $[*], 30');
2380+
jsonb_path_query
2381+
------------------
2382+
10
2383+
20
2384+
1
2385+
2
2386+
3
2387+
4
2388+
5
2389+
30
2390+
(8 rows)
2391+
2392+
select jsonb_path_query('[1,2,3,4,5]', 'lax 10, 20, $[*].a, 30');
2393+
jsonb_path_query
2394+
------------------
2395+
10
2396+
20
2397+
30
2398+
(3 rows)
2399+
2400+
select jsonb_path_query('[1,2,3,4,5]', 'strict 10, 20, $[*].a, 30');
2401+
ERROR: SQL/JSON member not found
2402+
DETAIL: jsonpath member accessor can only be applied to an object
2403+
select jsonb_path_query('[1,2,3,4,5]', '-(10, 20, $[1 to 3], 30)');
2404+
jsonb_path_query
2405+
------------------
2406+
-10
2407+
-20
2408+
-2
2409+
-3
2410+
-4
2411+
-30
2412+
(6 rows)
2413+
2414+
select jsonb_path_query('[1,2,3,4,5]', 'lax (10, 20.5, $[1 to 3], "30").double()');
2415+
jsonb_path_query
2416+
------------------
2417+
10
2418+
20.5
2419+
2
2420+
3
2421+
4
2422+
30
2423+
(6 rows)
2424+
2425+
select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 5) ? (@ == 3)]');
2426+
jsonb_path_query
2427+
------------------
2428+
4
2429+
(1 row)
2430+
2431+
select jsonb_path_query('[1,2,3,4,5]', '$[(0, $[*], 3) ? (@ == 3)]');
2432+
ERROR: invalid SQL/JSON subscript
2433+
DETAIL: jsonpath array subscript is not a singleton numeric value

src/test/regress/expected/jsonpath.out

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,42 @@ select '((($ + 1)).a + ((2)).b ? ((((@ > 1)) || (exists(@.c)))))'::jsonpath;
568568
(($ + 1)."a" + 2."b"?(@ > 1 || exists (@."c")))
569569
(1 row)
570570

571+
select '1, 2 + 3, $.a[*] + 5'::jsonpath;
572+
jsonpath
573+
------------------------
574+
1, 2 + 3, $."a"[*] + 5
575+
(1 row)
576+
577+
select '(1, 2, $.a)'::jsonpath;
578+
jsonpath
579+
-------------
580+
1, 2, $."a"
581+
(1 row)
582+
583+
select '(1, 2, $.a).a[*]'::jsonpath;
584+
jsonpath
585+
----------------------
586+
(1, 2, $."a")."a"[*]
587+
(1 row)
588+
589+
select '(1, 2, $.a) == 5'::jsonpath;
590+
jsonpath
591+
----------------------
592+
((1, 2, $."a") == 5)
593+
(1 row)
594+
595+
select '$[(1, 2, $.a) to (3, 4)]'::jsonpath;
596+
jsonpath
597+
----------------------------
598+
$[(1, 2, $."a") to (3, 4)]
599+
(1 row)
600+
601+
select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
602+
jsonpath
603+
-----------------------------
604+
$[(1, (2, $."a")),3,(4, 5)]
605+
(1 row)
606+
571607
select '$ ? (@.a < 1)'::jsonpath;
572608
jsonpath
573609
---------------

0 commit comments

Comments
 (0)