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

Commit 6aaf389

Browse files
author
Nikita Glukhov
committed
Add jsonpath last subscript
1 parent 29fa653 commit 6aaf389

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;
@@ -90,9 +90,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
9090
right = buf->len;
9191
appendBinaryStringInfo(buf, (char*)&right /* fake value */, sizeof(right));
9292

93-
chld = flattenJsonPathParseItem(buf, item->value.args.left, forbiddenRoot);
93+
chld = flattenJsonPathParseItem(buf, item->value.args.left,
94+
forbiddenRoot,
95+
insideArraySubscript);
9496
*(int32*)(buf->data + left) = chld;
95-
chld = flattenJsonPathParseItem(buf, item->value.args.right, forbiddenRoot);
97+
chld = flattenJsonPathParseItem(buf, item->value.args.right,
98+
forbiddenRoot,
99+
insideArraySubscript);
96100
*(int32*)(buf->data + right) = chld;
97101
}
98102
break;
@@ -110,7 +114,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
110114

111115
chld = flattenJsonPathParseItem(buf, item->value.arg,
112116
item->type == jpiFilter ||
113-
forbiddenRoot);
117+
forbiddenRoot,
118+
insideArraySubscript);
114119
*(int32*)(buf->data + arg) = chld;
115120
}
116121
break;
@@ -131,6 +136,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
131136
(errcode(ERRCODE_SYNTAX_ERROR),
132137
errmsg("@ is not allowed in root expressions")));
133138
break;
139+
case jpiLast:
140+
if (!insideArraySubscript)
141+
ereport(ERROR,
142+
(errcode(ERRCODE_SYNTAX_ERROR),
143+
errmsg("LAST is allowed only in array subscripts")));
144+
break;
134145
case jpiIndexArray:
135146
{
136147
int32 nelems = item->value.array.nelems;
@@ -150,12 +161,12 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
150161
int32 frompos =
151162
flattenJsonPathParseItem(buf,
152163
item->value.array.elems[i].from,
153-
true);
164+
true, true);
154165

155166
if (item->value.array.elems[i].to)
156167
topos = flattenJsonPathParseItem(buf,
157168
item->value.array.elems[i].to,
158-
true);
169+
true, true);
159170
else
160171
topos = 0;
161172

@@ -189,7 +200,8 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
189200

190201
if (item->next)
191202
{
192-
chld = flattenJsonPathParseItem(buf, item->next, forbiddenRoot);
203+
chld = flattenJsonPathParseItem(buf, item->next, forbiddenRoot,
204+
insideArraySubscript);
193205
*(int32 *)(buf->data + next) = chld;
194206
}
195207

@@ -215,7 +227,7 @@ jsonpath_in(PG_FUNCTION_ARGS)
215227
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
216228
errmsg("invalid input syntax for jsonpath: \"%s\"", in)));
217229

218-
flattenJsonPathParseItem(&buf, jsonpath->expr, false);
230+
flattenJsonPathParseItem(&buf, jsonpath->expr, false, false);
219231

220232
res = (JsonPath*)buf.data;
221233
SET_VARSIZE(res, buf.len);
@@ -400,6 +412,9 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
400412
Assert(!inKey);
401413
appendStringInfoChar(buf, '$');
402414
break;
415+
case jpiLast:
416+
appendBinaryStringInfo(buf, "last", 4);
417+
break;
403418
case jpiAnyArray:
404419
appendBinaryStringInfo(buf, "[*]", 3);
405420
break;
@@ -563,6 +578,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
563578
case jpiDouble:
564579
case jpiDatetime:
565580
case jpiKeyValue:
581+
case jpiLast:
566582
break;
567583
case jpiKey:
568584
case jpiString:
@@ -646,6 +662,7 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
646662
v->type == jpiExists ||
647663
v->type == jpiRoot ||
648664
v->type == jpiVariable ||
665+
v->type == jpiLast ||
649666
v->type == jpiType ||
650667
v->type == jpiSize ||
651668
v->type == jpiAbs ||

src/backend/utils/adt/jsonpath_exec.c

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

3132
static JsonPathExecResult recursiveExecute(JsonPathExecContext *cxt,
@@ -952,9 +953,12 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
952953
case jpiIndexArray:
953954
if (JsonbType(jb) == jbvArray)
954955
{
956+
int innermostArraySize = cxt->innermostArraySize;
955957
int i;
956958
int size = JsonbArraySize(jb);
957959

960+
cxt->innermostArraySize = size; /* for LAST evaluation */
961+
958962
hasNext = jspGetNext(jsp, &elem);
959963

960964
for (i = 0; i < jsp->content.array.nelems; i++)
@@ -1034,10 +1038,49 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
10341038
if (res == jperOk && !found)
10351039
break;
10361040
}
1041+
1042+
cxt->innermostArraySize = innermostArraySize;
10371043
}
10381044
else
10391045
res = jperMakeError(ERRCODE_JSON_ARRAY_NOT_FOUND);
10401046
break;
1047+
1048+
case jpiLast:
1049+
{
1050+
JsonbValue tmpjbv;
1051+
JsonbValue *lastjbv;
1052+
int last;
1053+
bool hasNext;
1054+
1055+
if (cxt->innermostArraySize < 0)
1056+
elog(ERROR,
1057+
"evaluating jsonpath LAST outside of array subscript");
1058+
1059+
hasNext = jspGetNext(jsp, &elem);
1060+
1061+
if (!hasNext && !found)
1062+
{
1063+
res = jperOk;
1064+
break;
1065+
}
1066+
1067+
last = cxt->innermostArraySize - 1;
1068+
1069+
lastjbv = hasNext ? &tmpjbv : palloc(sizeof(*lastjbv));
1070+
1071+
lastjbv->type = jbvNumeric;
1072+
lastjbv->val.numeric = DatumGetNumeric(DirectFunctionCall1(
1073+
int4_numeric, Int32GetDatum(last)));
1074+
1075+
if (hasNext)
1076+
res = recursiveExecute(cxt, &elem, lastjbv, found);
1077+
else
1078+
{
1079+
res = jperOk;
1080+
*found = lappend(*found, lastjbv);
1081+
}
1082+
}
1083+
break;
10411084
case jpiAnyKey:
10421085
if (JsonbType(jb) == jbvObject)
10431086
{
@@ -1555,6 +1598,7 @@ executeJsonPath(JsonPath *path, List *vars, Jsonb *json, List **foundJson)
15551598

15561599
cxt.vars = vars;
15571600
cxt.lax = (path->header & JSONPATH_LAX) != 0;
1601+
cxt.innermostArraySize = -1;
15581602

15591603
if (!cxt.lax && !foundJson)
15601604
{

src/backend/utils/adt/jsonpath_gram.y

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

@@ -315,6 +315,7 @@ path_primary:
315315
scalar_value { $$ = $1; }
316316
| '$' { $$ = makeItemType(jpiRoot); }
317317
| '@' { $$ = makeItemType(jpiCurrent); }
318+
| LAST_P { $$ = makeItemType(jpiLast); }
318319
;
319320

320321
accessor_expr:
@@ -397,6 +398,7 @@ key_name:
397398
| CEILING_P
398399
| DATETIME_P
399400
| KEYVALUE_P
401+
| LAST_P
400402
;
401403

402404
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
@@ -274,6 +274,39 @@ select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]');
274274
3
275275
(3 rows)
276276

277+
select * from _jsonpath_query(jsonb '[]', '$[last]');
278+
_jsonpath_query
279+
-----------------
280+
(0 rows)
281+
282+
select * from _jsonpath_query(jsonb '[]', 'strict $[last]');
283+
ERROR: Invalid SQL/JSON subscript
284+
select * from _jsonpath_query(jsonb '[1]', '$[last]');
285+
_jsonpath_query
286+
-----------------
287+
1
288+
(1 row)
289+
290+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last]');
291+
_jsonpath_query
292+
-----------------
293+
3
294+
(1 row)
295+
296+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last - 1]');
297+
_jsonpath_query
298+
-----------------
299+
2
300+
(1 row)
301+
302+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]');
303+
_jsonpath_query
304+
-----------------
305+
3
306+
(1 row)
307+
308+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "string")]');
309+
ERROR: Invalid SQL/JSON subscript
277310
select * from _jsonpath_query(jsonb '{"a": 10}', '$');
278311
_jsonpath_query
279312
-----------------

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
@@ -45,7 +45,13 @@ select * from _jsonpath_query(jsonb '1', 'lax $[*]');
4545
select * from _jsonpath_query(jsonb '[1]', 'lax $[0]');
4646
select * from _jsonpath_query(jsonb '[1]', 'lax $[*]');
4747
select * from _jsonpath_query(jsonb '[1,2,3]', 'lax $[*]');
48-
48+
select * from _jsonpath_query(jsonb '[]', '$[last]');
49+
select * from _jsonpath_query(jsonb '[]', 'strict $[last]');
50+
select * from _jsonpath_query(jsonb '[1]', '$[last]');
51+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last]');
52+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last - 1]');
53+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "number")]');
54+
select * from _jsonpath_query(jsonb '[1,2,3]', '$[last ? (@.type() == "string")]');
4955

5056
select * from _jsonpath_query(jsonb '{"a": 10}', '$');
5157
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)