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)