Skip to content

Commit bbb1f1e

Browse files
author
Nikita Glukhov
committed
Add jsonpath sequence constructors
1 parent 0abe1a1 commit bbb1f1e

File tree

9 files changed

+264
-7
lines changed

9 files changed

+264
-7
lines changed

doc/src/sgml/func.sgml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13687,6 +13687,13 @@ table2-mapping
1368713687
</row>
1368813688
</thead>
1368913689
<tbody>
13690+
<row>
13691+
<entry>Sequence constructor</entry>
13692+
<entry>Construct a JSON sequence from a list of comma-separated expressions</entry>
13693+
<entry><literal>[1, 2, 3]</literal></entry>
13694+
<entry><literal>pg $[*], 4, 5</literal></entry>
13695+
<entry><literal>1, 2, 3, 4, 5</literal></entry>
13696+
</row>
1369013697
</tbody>
1369113698
</tgroup>
1369213699
</table>

src/backend/utils/adt/jsonpath.c

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
233233
appendBinaryStringInfo(out, "strict ", 7);
234234

235235
jspInit(&v, in);
236-
printJsonPathItem(out, &v, false, true);
236+
printJsonPathItem(out, &v, false, v.type != jpiSequence);
237237

238238
return out->data;
239239
}
@@ -448,6 +448,31 @@ flattenJsonPathParseItem(JsonPathEncodingContext *cxt, JsonPathParseItem *item,
448448
case jpiDouble:
449449
case jpiKeyValue:
450450
break;
451+
case jpiSequence:
452+
{
453+
int32 nelems = list_length(item->value.sequence.elems);
454+
ListCell *lc;
455+
int offset;
456+
457+
checkJsonPathExtensionsEnabled(cxt, item->type);
458+
459+
appendBinaryStringInfo(buf, (char *) &nelems, sizeof(nelems));
460+
461+
offset = buf->len;
462+
463+
appendStringInfoSpaces(buf, sizeof(int32) * nelems);
464+
465+
foreach(lc, item->value.sequence.elems)
466+
{
467+
int32 elempos =
468+
flattenJsonPathParseItem(cxt, lfirst(lc), nestingLevel,
469+
insideArraySubscript);
470+
471+
*(int32 *) &buf->data[offset] = elempos - pos;
472+
offset += sizeof(int32);
473+
}
474+
}
475+
break;
451476
default:
452477
elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
453478
}
@@ -670,12 +695,12 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
670695
if (i)
671696
appendStringInfoChar(buf, ',');
672697

673-
printJsonPathItem(buf, &from, false, false);
698+
printJsonPathItem(buf, &from, false, from.type == jpiSequence);
674699

675700
if (range)
676701
{
677702
appendBinaryStringInfo(buf, " to ", 4);
678-
printJsonPathItem(buf, &to, false, false);
703+
printJsonPathItem(buf, &to, false, to.type == jpiSequence);
679704
}
680705
}
681706
appendStringInfoChar(buf, ']');
@@ -736,6 +761,25 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
736761
case jpiKeyValue:
737762
appendBinaryStringInfo(buf, ".keyvalue()", 11);
738763
break;
764+
case jpiSequence:
765+
if (printBracketes || jspHasNext(v))
766+
appendStringInfoChar(buf, '(');
767+
768+
for (i = 0; i < v->content.sequence.nelems; i++)
769+
{
770+
JsonPathItem elem;
771+
772+
if (i)
773+
appendBinaryStringInfo(buf, ", ", 2);
774+
775+
jspGetSequenceElement(v, i, &elem);
776+
777+
printJsonPathItem(buf, &elem, false, elem.type == jpiSequence);
778+
}
779+
780+
if (printBracketes || jspHasNext(v))
781+
appendStringInfoChar(buf, ')');
782+
break;
739783
default:
740784
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
741785
}
@@ -808,6 +852,8 @@ operationPriority(JsonPathItemType op)
808852
{
809853
switch (op)
810854
{
855+
case jpiSequence:
856+
return -1;
811857
case jpiOr:
812858
return 0;
813859
case jpiAnd:
@@ -944,6 +990,11 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
944990
read_int32(v->content.anybounds.first, base, pos);
945991
read_int32(v->content.anybounds.last, base, pos);
946992
break;
993+
case jpiSequence:
994+
read_int32(v->content.sequence.nelems, base, pos);
995+
read_int32_n(v->content.sequence.elems, base, pos,
996+
v->content.sequence.nelems);
997+
break;
947998
default:
948999
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
9491000
}
@@ -1008,7 +1059,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
10081059
v->type == jpiDouble ||
10091060
v->type == jpiDatetime ||
10101061
v->type == jpiKeyValue ||
1011-
v->type == jpiStartsWith);
1062+
v->type == jpiStartsWith ||
1063+
v->type == jpiSequence);
10121064

10131065
if (a)
10141066
jspInitByBuffer(a, v->base, v->nextPos);
@@ -1103,3 +1155,11 @@ jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from, JsonPathItem *to,
11031155

11041156
return true;
11051157
}
1158+
1159+
void
1160+
jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
1161+
{
1162+
Assert(v->type == jpiSequence);
1163+
1164+
jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
1165+
}

src/backend/utils/adt/jsonpath_exec.c

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

11301130
return executeKeyValueMethod(cxt, jsp, jb, found);
11311131

1132+
case jpiSequence:
1133+
{
1134+
JsonPathItem next;
1135+
bool hasNext = jspGetNext(jsp, &next);
1136+
JsonValueList list;
1137+
JsonValueList *plist = hasNext ? &list : found;
1138+
JsonValueListIterator it;
1139+
int i;
1140+
1141+
for (i = 0; i < jsp->content.sequence.nelems; i++)
1142+
{
1143+
JsonbValue *v;
1144+
1145+
if (hasNext)
1146+
memset(&list, 0, sizeof(list));
1147+
1148+
jspGetSequenceElement(jsp, i, &elem);
1149+
res = executeItem(cxt, &elem, jb, plist);
1150+
1151+
if (jperIsError(res))
1152+
break;
1153+
1154+
if (!hasNext)
1155+
{
1156+
if (!found && res == jperOk)
1157+
break;
1158+
continue;
1159+
}
1160+
1161+
JsonValueListInitIterator(&list, &it);
1162+
1163+
while ((v = JsonValueListNext(&list, &it)))
1164+
{
1165+
res = executeItem(cxt, &next, v, found);
1166+
1167+
if (jperIsError(res) || (!found && res == jperOk))
1168+
{
1169+
i = jsp->content.sequence.nelems;
1170+
break;
1171+
}
1172+
}
1173+
}
1174+
1175+
break;
1176+
}
1177+
11321178
default:
11331179
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
11341180
}

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-
pg_opt mode expr_or_predicate {
131+
pg_opt mode expr_or_seq {
131132
*result = palloc(sizeof(JsonPathParseResult));
132133
(*result)->expr = $3;
133134
(*result)->lax = $2;
@@ -146,6 +147,20 @@ pg_opt:
146147
| /* EMPTY */ { $$ = false; }
147148
;
148149

150+
expr_or_seq:
151+
expr_or_predicate { $$ = $1; }
152+
| expr_seq { $$ = $1; }
153+
;
154+
155+
expr_seq:
156+
expr_list { $$ = makeItemSequence($1); }
157+
;
158+
159+
expr_list:
160+
expr_or_predicate ',' expr_or_predicate { $$ = list_make2($1, $3); }
161+
| expr_list ',' expr_or_predicate { $$ = lappend($1, $3); }
162+
;
163+
149164
mode:
150165
STRICT_P { $$ = false; }
151166
| LAX_P { $$ = true; }
@@ -201,6 +216,7 @@ path_primary:
201216
| '$' { $$ = makeItemType(jpiRoot); }
202217
| '@' { $$ = makeItemType(jpiCurrent); }
203218
| LAST_P { $$ = makeItemType(jpiLast); }
219+
| '(' expr_seq ')' { $$ = $2; }
204220
;
205221

206222
accessor_expr:
@@ -550,6 +566,16 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
550566
return v;
551567
}
552568

569+
static JsonPathParseItem *
570+
makeItemSequence(List *elems)
571+
{
572+
JsonPathParseItem *v = makeItemType(jpiSequence);
573+
574+
v->value.sequence.elems = elems;
575+
576+
return v;
577+
}
578+
553579
/*
554580
* Convert from XQuery regex flags to those recognized by our regex library.
555581
*/

src/include/utils/jsonpath.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ typedef enum JsonPathItemType
8787
jpiLast, /* LAST array subscript */
8888
jpiStartsWith, /* STARTS WITH predicate */
8989
jpiLikeRegex, /* LIKE_REGEX predicate */
90+
jpiSequence, /* sequence constructor: 'expr, ...' */
9091
} JsonPathItemType;
9192

9293
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -147,6 +148,12 @@ typedef struct JsonPathItem
147148
uint32 last;
148149
} anybounds;
149150

151+
struct
152+
{
153+
int32 nelems;
154+
int32 *elems;
155+
} sequence;
156+
150157
struct
151158
{
152159
char *data; /* for bool, numeric and string/key */
@@ -176,6 +183,7 @@ extern bool jspGetBool(JsonPathItem *v);
176183
extern char *jspGetString(JsonPathItem *v, int32 *len);
177184
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
178185
JsonPathItem *to, int i);
186+
extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
179187

180188
extern const char *jspOperationName(JsonPathItemType type);
181189

@@ -229,6 +237,10 @@ struct JsonPathParseItem
229237
uint32 flags;
230238
} like_regex;
231239

240+
struct {
241+
List *elems;
242+
} sequence;
243+
232244
/* scalars */
233245
Numeric numeric;
234246
bool boolean;

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2524,3 +2524,57 @@ ORDER BY s1.num, s2.num;
25242524
{"s": "B"} | {"s": "B"} | false | true | true | true | false
25252525
(144 rows)
25262526

2527+
-- extension: path sequences
2528+
select jsonb_path_query('[1,2,3,4,5]', 'pg 10, 20, $[*], 30');
2529+
jsonb_path_query
2530+
------------------
2531+
10
2532+
20
2533+
1
2534+
2
2535+
3
2536+
4
2537+
5
2538+
30
2539+
(8 rows)
2540+
2541+
select jsonb_path_query('[1,2,3,4,5]', 'pg lax 10, 20, $[*].a, 30');
2542+
jsonb_path_query
2543+
------------------
2544+
10
2545+
20
2546+
30
2547+
(3 rows)
2548+
2549+
select jsonb_path_query('[1,2,3,4,5]', 'pg strict 10, 20, $[*].a, 30');
2550+
ERROR: jsonpath member accessor can only be applied to an object
2551+
select jsonb_path_query('[1,2,3,4,5]', 'pg -(10, 20, $[1 to 3], 30)');
2552+
jsonb_path_query
2553+
------------------
2554+
-10
2555+
-20
2556+
-2
2557+
-3
2558+
-4
2559+
-30
2560+
(6 rows)
2561+
2562+
select jsonb_path_query('[1,2,3,4,5]', 'pg lax (10, 20.5, $[1 to 3], "30").double()');
2563+
jsonb_path_query
2564+
------------------
2565+
10
2566+
20.5
2567+
2
2568+
3
2569+
4
2570+
30
2571+
(6 rows)
2572+
2573+
select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 5) ? (@ == 3)]');
2574+
jsonb_path_query
2575+
------------------
2576+
4
2577+
(1 row)
2578+
2579+
select jsonb_path_query('[1,2,3,4,5]', 'pg $[(0, $[*], 3) ? (@ == 3)]');
2580+
ERROR: jsonpath array subscript is not a single numeric value

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy