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

Commit f092818

Browse files
author
Nikita Glukhov
committed
Add jsonpath last subscript
1 parent 61a1eef commit f092818

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
}
@@ -211,7 +223,7 @@ jsonpath_in(PG_FUNCTION_ARGS)
211223
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
212224
errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
213225

214-
flattenJsonPathParseItem(&buf, jsonpath->expr, false);
226+
flattenJsonPathParseItem(&buf, jsonpath->expr, false, false);
215227

216228
res = (JsonPath*)buf.data;
217229
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,
@@ -930,9 +931,12 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
930931
case jpiIndexArray:
931932
if (JsonbType(jb) == jbvArray)
932933
{
934+
int innermostArraySize = cxt->innermostArraySize;
933935
int i;
934936
int size = JsonbArraySize(jb);
935937

938+
cxt->innermostArraySize = size; /* for LAST evaluation */
939+
936940
hasNext = jspGetNext(jsp, &elem);
937941

938942
for (i = 0; i < jsp->content.array.nelems; i++)
@@ -1012,10 +1016,49 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
10121016
if (res == jperOk && !found)
10131017
break;
10141018
}
1019+
1020+
cxt->innermostArraySize = innermostArraySize;
10151021
}
10161022
else
10171023
res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
10181024
break;
1025+
1026+
case jpiLast:
1027+
{
1028+
JsonbValue tmpjbv;
1029+
JsonbValue *lastjbv;
1030+
int last;
1031+
bool hasNext;
1032+
1033+
if (cxt->innermostArraySize < 0)
1034+
elog(ERROR,
1035+
"evaluating jsonpath LAST outside of array subscript");
1036+
1037+
hasNext = jspGetNext(jsp, &elem);
1038+
1039+
if (!hasNext && !found)
1040+
{
1041+
res = jperOk;
1042+
break;
1043+
}
1044+
1045+
last = cxt->innermostArraySize - 1;
1046+
1047+
lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv));
1048+
1049+
lastjbv->type = jbvNumeric;
1050+
lastjbv->val.numeric = DatumGetNumeric(DirectFunctionCall1(
1051+
int4_numeric, Int32GetDatum(last)));
1052+
1053+
if (hasNext)
1054+
res = recursiveExecute(cxt, &elem, lastjbv, found);
1055+
else
1056+
{
1057+
res = jperOk;
1058+
*found = lappend(*found, lastjbv);
1059+
}
1060+
}
1061+
break;
10191062
case jpiAnyKey:
10201063
if (JsonbType(jb) == jbvObject)
10211064
{
@@ -1522,6 +1565,7 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson)
15221565

15231566
cxt.vars = vars;
15241567
cxt.lax = (path->header & JSONPATH_LAX) != 0;
1568+
cxt.innermostArraySize = -1;
15251569

15261570
if (!cxt.lax && !foundJson)
15271571
{

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ makeAny(int first, int last)
221221
%token <str> STRING_P NUMERIC_P INT_P VARIABLE_P
222222
%token <str> OR_P AND_P NOT_P
223223
%token <str> LESS_P LESSEQUAL_P EQUAL_P NOTEQUAL_P GREATEREQUAL_P GREATER_P
224-
%token <str> ANY_P STRICT_P LAX_P
224+
%token <str> ANY_P STRICT_P LAX_P LAST_P
225225
%token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P DATETIME_P
226226
%token <str> KEYVALUE_P
227227

@@ -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
@@ -273,6 +273,7 @@ static keyword keywords[] = {
273273
{ 2, false, TO_P, "to"},
274274
{ 3, false, ABS_P, "abs"},
275275
{ 3, false, LAX_P, "lax"},
276+
{ 4, false, LAST_P, "last"},
276277
{ 4, true, NULL_P, "null"},
277278
{ 4, false, SIZE_P, "size"},
278279
{ 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
@@ -315,6 +315,38 @@ select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath;
315315
$."a"[$"a" + 1,$"b"[*] to -(@[0] * 2)]
316316
(1 row)
317317

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

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
@@ -55,6 +55,12 @@ select '$.g ? (zip == $zip)'::jsonpath;
5555
select '$.a.[1,2, 3 to 16]'::jsonpath;
5656
select '$.a[1,2, 3 to 16]'::jsonpath;
5757
select '$.a[$a + 1, ($b[*]) to -(@[0] * 2)]'::jsonpath;
58+
select 'last'::jsonpath;
59+
select '"last"'::jsonpath;
60+
select '$.last'::jsonpath;
61+
select '$ ? (last > 0)'::jsonpath;
62+
select '$[last]'::jsonpath;
63+
select '$[@ ? (last > 0)]'::jsonpath;
5864

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

0 commit comments

Comments
 (0)