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

Commit 086bca1

Browse files
author
Nikita Glukhov
committed
Add jsonpath last subscript
1 parent 805b1dc commit 086bca1

File tree

9 files changed

+152
-10
lines changed

9 files changed

+152
-10
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*/
2626
static int
2727
flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
28-
bool forbiddenRoot)
28+
bool forbiddenRoot, bool insideArraySubscript)
2929
{
3030
/* position from begining of jsonpath data */
3131
int32 pos = buf->len - JSONPATH_HDRSZ;
@@ -88,9 +88,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
8888
right = buf->len;
8989
appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right));
9090

91-
chld = flattenJsonPathParseItem(buf, item->value.args.left, forbiddenRoot);
91+
chld = flattenJsonPathParseItem(buf, item->value.args.left,
92+
forbiddenRoot,
93+
insideArraySubscript);
9294
*(int32*)(buf->data + left) = chld;
93-
chld = flattenJsonPathParseItem(buf, item->value.args.right, forbiddenRoot);
95+
chld = flattenJsonPathParseItem(buf, item->value.args.right,
96+
forbiddenRoot,
97+
insideArraySubscript);
9498
*(int32*)(buf->data + right) = chld;
9599
}
96100
break;
@@ -108,7 +112,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
108112

109113
chld = flattenJsonPathParseItem(buf, item->value.arg,
110114
item->type == jpiFilter ||
111-
forbiddenRoot);
115+
forbiddenRoot,
116+
insideArraySubscript);
112117
*(int32*)(buf->data + arg) = chld;
113118
}
114119
break;
@@ -129,6 +134,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
129134
(errcode(ERRCODE_SYNTAX_ERROR),
130135
errmsg("@ is not allowed in root expressions")));
131136
break;
137+
case jpiLast:
138+
if (!insideArraySubscript)
139+
ereport(ERROR,
140+
(errcode(ERRCODE_SYNTAX_ERROR),
141+
errmsg("LAST is allowed only in array subscripts")));
142+
break;
132143
case jpiIndexArray:
133144
{
134145
int32 nelems = item->value.array.nelems;
@@ -148,12 +159,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
148159
int32 frompos =
149160
flattenJsonPathParseItem(buf,
150161
item->value.array.elems[i].from,
151-
true);
162+
true, true);
152163

153164
if (item->value.array.elems[i].to)
154165
topos = flattenJsonPathParseItem(buf,
155166
item->value.array.elems[i].to,
156-
true);
167+
true, true);
157168
else
158169
topos = 0;
159170

@@ -187,7 +198,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
187198

188199
if (item->next)
189200
*(int32*)(buf->data + next) =
190-
flattenJsonPathParseItem(buf, item->next, forbiddenRoot);
201+
flattenJsonPathParseItem(buf, item->next, forbiddenRoot,
202+
insideArraySubscript);
191203

192204
return pos;
193205
}
@@ -208,7 +220,7 @@ jsonpath_in(PG_FUNCTION_ARGS)
208220

209221
if (jsonpath != NULL)
210222
{
211-
flattenJsonPathParseItem(&buf, jsonpath->expr, false);
223+
flattenJsonPathParseItem(&buf, jsonpath->expr, false, false);
212224

213225
res = (JsonPath*)buf.data;
214226
SET_VARSIZE(res, buf.len);
@@ -396,6 +408,9 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
396408
Assert(!inKey);
397409
appendStringInfoChar(buf, '$');
398410
break;
411+
case jpiLast:
412+
appendBinaryStringInfo(buf, "last", 4);
413+
break;
399414
case jpiAnyArray:
400415
appendBinaryStringInfo(buf, "[*]", 3);
401416
break;
@@ -559,6 +574,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
559574
case jpiDouble:
560575
case jpiDatetime:
561576
case jpiKeyValue:
577+
case jpiLast:
562578
break;
563579
case jpiKey:
564580
case jpiString:
@@ -642,6 +658,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
642658
v->type == jpiExists ||
643659
v->type == jpiRoot ||
644660
v->type == jpiVariable ||
661+
v->type == jpiLast ||
645662
v->type == jpiType ||
646663
v->type == jpiSize ||
647664
v->type == jpiAbs ||

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ typedef struct JsonPathExecContext
2525
{
2626
List *vars;
2727
bool lax;
28+
int innermostArraySize; /* for LAST array index evaluation */
2829
} JsonPathExecContext;
2930

3031
static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
@@ -920,9 +921,12 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
920921
case jpiIndexArray:
921922
if (JsonbType(jb) == jbvArray)
922923
{
924+
int innermostArraySize = cxt->innermostArraySize;
923925
int i;
924926
int size = JsonbArraySize(jb);
925927

928+
cxt->innermostArraySize = size; /* for LAST evaluation */
929+
926930
hasNext = jspGetNext(jsp, &elem);
927931

928932
for (i = 0; i < jsp->content.array.nelems; i++)
@@ -1002,10 +1006,49 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
10021006
if (res == jperOk && !found)
10031007
break;
10041008
}
1009+
1010+
cxt->innermostArraySize = innermostArraySize;
10051011
}
10061012
else
10071013
res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
10081014
break;
1015+
1016+
case jpiLast:
1017+
{
1018+
JsonbValue tmpjbv;
1019+
JsonbValue *lastjbv;
1020+
int last;
1021+
bool hasNext;
1022+
1023+
if (cxt->innermostArraySize < 0)
1024+
elog(ERROR,
1025+
"evaluating jsonpath LAST outside of array subscript");
1026+
1027+
hasNext = jspGetNext(jsp, &elem);
1028+
1029+
if (!hasNext && !found)
1030+
{
1031+
res = jperOk;
1032+
break;
1033+
}
1034+
1035+
last = cxt->innermostArraySize - 1;
1036+
1037+
lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv));
1038+
1039+
lastjbv->type = jbvNumeric;
1040+
lastjbv->val.numeric = DatumGetNumeric(DirectFunctionCall1(
1041+
int4_numeric, Int32GetDatum(last)));
1042+
1043+
if (hasNext)
1044+
res = recursiveExecute(cxt, &elem, lastjbv, found);
1045+
else
1046+
{
1047+
res = jperOk;
1048+
*found = lappend(*found, lastjbv);
1049+
}
1050+
}
1051+
break;
10091052
case jpiAnyKey:
10101053
if (JsonbType(jb) == jbvObject)
10111054
{
@@ -1494,6 +1537,7 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson)
14941537

14951538
cxt.vars = vars;
14961539
cxt.lax = (path->header & JSONPATH_LAX) != 0;
1540+
cxt.innermostArraySize = -1;
14971541

14981542
return recursiveExecute(&cxt, &jsp, &jbv, foundJson);
14991543
}

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ makeAny(int first, int last)
218218
}
219219

220220
%token <str> TO_P NULL_P TRUE_P FALSE_P IS_P UNKNOWN_P
221-
%token <str> STRING_P NUMERIC_P INT_P EXISTS_P STRICT_P LAX_P
221+
%token <str> STRING_P NUMERIC_P INT_P EXISTS_P STRICT_P LAX_P LAST_P
222222
%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P KEYVALUE_P
223223

224224
%token <str> OR_P AND_P NOT_P
@@ -312,6 +312,7 @@ path_primary:
312312
scalar_value { $$ = $1; }
313313
| '$' { $$ = makeItemType(jpiRoot); }
314314
| '@' { $$ = makeItemType(jpiCurrent); }
315+
| LAST_P { $$ = makeItemType(jpiLast); }
315316
;
316317

317318
accessor_expr:
@@ -395,6 +396,7 @@ key_name:
395396
| CEILING_P
396397
| DATETIME_P
397398
| KEYVALUE_P
399+
| LAST_P
398400
;
399401

400402
method:

src/backend/utils/adt/jsonpath_scan.l

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ static keyword keywords[] = {
255255
{ 2, false, TO_P, "to"},
256256
{ 3, false, ABS_P, "abs"},
257257
{ 3, false, LAX_P, "lax"},
258+
{ 4, false, LAST_P, "last"},
258259
{ 4, true, NULL_P, "null"},
259260
{ 4, false, SIZE_P, "size"},
260261
{ 4, true, TRUE_P, "true"},

src/include/utils/jsonpath.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ typedef enum JsonPathItemType {
7979
jpiDatetime,
8080
jpiKeyValue,
8181
jpiSubscript,
82+
jpiLast,
8283
} JsonPathItemType;
8384

8485

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,39 @@ select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]');
264264
3
265265
(3 rows)
266266

267+
select * from _jsonpath_query(jsonb '[]', '$[last]');
268+
_jsonpath_query
269+
-----------------
270+
(0 rows)
271+
272+
select * from _jsonpath_query(jsonb '[]', 'strict $[last]');
273+
ERROR: Invalid SQL/JSON subscript
274+
select * from _jsonpath_query(jsonb '[1]', '$[last]');
275+
_jsonpath_query
276+
-----------------
277+
1
278+
(1 row)
279+
280+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last]');
281+
_jsonpath_query
282+
-----------------
283+
3
284+
(1 row)
285+
286+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last - 1]');
287+
_jsonpath_query
288+
-----------------
289+
2
290+
(1 row)
291+
292+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]');
293+
_jsonpath_query
294+
-----------------
295+
3
296+
(1 row)
297+
298+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "string")]');
299+
ERROR: Invalid SQL/JSON subscript
267300
select * from _jsonpath_query(jsonb '{"a": 10}', '$');
268301
_jsonpath_query
269302
-----------------

src/test/regress/expected/jsonpath.out

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,38 @@ select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath;
311311
$."a"[$"a" + 1,$"b"[*] to -(@[0] * 2)]
312312
(1 row)
313313

314+
select 'last'::jsonpath;
315+
ERROR: LAST is allowed only in array subscripts
316+
LINE 1: select 'last'::jsonpath;
317+
^
318+
select '"last"'::jsonpath;
319+
jsonpath
320+
----------
321+
"last"
322+
(1 row)
323+
324+
select '$.last'::jsonpath;
325+
jsonpath
326+
----------
327+
$."last"
328+
(1 row)
329+
330+
select '$ ? (last > 0)'::jsonpath;
331+
ERROR: LAST is allowed only in array subscripts
332+
LINE 1: select '$ ? (last > 0)'::jsonpath;
333+
^
334+
select '$[last]'::jsonpath;
335+
jsonpath
336+
----------
337+
$[last]
338+
(1 row)
339+
340+
select '$[@ ? (last > 0)]'::jsonpath;
341+
jsonpath
342+
-----------------
343+
$[@?(last > 0)]
344+
(1 row)
345+
314346
select 'null.type()'::jsonpath;
315347
jsonpath
316348
-------------

src/test/regress/sql/jsonb_jsonpath.sql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ select * from _jsonpath_query(jsonb '1', 'lax $[*]');
4444
select * from _jsonpath_query(jsonb '[1]', 'lax $[0]');
4545
select * from _jsonpath_query(jsonb '[1]', 'lax $[*]');
4646
select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]');
47-
47+
select * from _jsonpath_query(jsonb '[]', '$[last]');
48+
select * from _jsonpath_query(jsonb '[]', 'strict $[last]');
49+
select * from _jsonpath_query(jsonb '[1]', '$[last]');
50+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last]');
51+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last - 1]');
52+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]');
53+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "string")]');
4854

4955
select * from _jsonpath_query(jsonb '{"a": 10}', '$');
5056
select * from _jsonpath_query(jsonb '{"a": 10}', '$ ? (.a < $value)');

src/test/regress/sql/jsonpath.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ select '$.g ? (zip == $zip)'::jsonpath;
5454
select '$.a.[1,2, 3 to 16]'::jsonpath;
5555
select '$.a[1,2, 3 to 16]'::jsonpath;
5656
select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath;
57+
select 'last'::jsonpath;
58+
select '"last"'::jsonpath;
59+
select '$.last'::jsonpath;
60+
select '$ ? (last > 0)'::jsonpath;
61+
select '$[last]'::jsonpath;
62+
select '$[@ ? (last > 0)]'::jsonpath;
5763

5864
select 'null.type()'::jsonpath;
5965
select '1.type()'::jsonpath;

0 commit comments

Comments
 (0)