Skip to content

Commit d95b4a1

Browse files
author
Nikita Glukhov
committed
Add jsonpath object constructors
1 parent 8e6fc9d commit d95b4a1

File tree

8 files changed

+235
-3
lines changed

8 files changed

+235
-3
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,38 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
241241
}
242242
}
243243
break;
244+
case jpiObject:
245+
{
246+
int32 nfields = list_length(item->value.object.fields);
247+
ListCell *lc;
248+
int offset;
249+
250+
appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
251+
252+
offset = buf->len;
253+
254+
appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
255+
256+
foreach(lc, item->value.object.fields)
257+
{
258+
JsonPathParseItem *field = lfirst(lc);
259+
int32 keypos =
260+
flattenJsonPathParseItem(buf, field->value.args.left,
261+
allowCurrent,
262+
insideArraySubscript);
263+
int32 valpos =
264+
flattenJsonPathParseItem(buf, field->value.args.right,
265+
allowCurrent,
266+
insideArraySubscript);
267+
int32 *ppos = (int32 *) &buf->data[offset];
268+
269+
ppos[0] = keypos;
270+
ppos[1] = valpos;
271+
272+
offset += 2 * sizeof(int32);
273+
}
274+
}
275+
break;
244276
default:
245277
elog(ERROR, "Unknown jsonpath item type: %d", item->type);
246278
}
@@ -614,6 +646,26 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, bool printBracket
614646
}
615647
appendStringInfoChar(buf, ']');
616648
break;
649+
case jpiObject:
650+
appendStringInfoChar(buf, '{');
651+
652+
for (i = 0; i < v->content.object.nfields; i++)
653+
{
654+
JsonPathItem key;
655+
JsonPathItem val;
656+
657+
jspGetObjectField(v, i, &key, &val);
658+
659+
if (i)
660+
appendBinaryStringInfo(buf, ", ", 2);
661+
662+
printJsonPathItem(buf, &key, false, false);
663+
appendBinaryStringInfo(buf, ": ", 2);
664+
printJsonPathItem(buf, &val, false, val.type == jpiSequence);
665+
}
666+
667+
appendStringInfoChar(buf, '}');
668+
break;
617669
default:
618670
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
619671
}
@@ -764,6 +816,11 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
764816
read_int32_n(v->content.sequence.elems, base, pos,
765817
v->content.sequence.nelems);
766818
break;
819+
case jpiObject:
820+
read_int32(v->content.object.nfields, base, pos);
821+
read_int32_n(v->content.object.fields, base, pos,
822+
v->content.object.nfields * 2);
823+
break;
767824
default:
768825
elog(ERROR, "Unknown jsonpath item type: %d", v->type);
769826
}
@@ -833,7 +890,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
833890
v->type == jpiKeyValue ||
834891
v->type == jpiStartsWith ||
835892
v->type == jpiSequence ||
836-
v->type == jpiArray
893+
v->type == jpiArray ||
894+
v->type == jpiObject
837895
);
838896

839897
if (a)
@@ -945,3 +1003,11 @@ jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
9451003

9461004
jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
9471005
}
1006+
1007+
void
1008+
jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
1009+
{
1010+
Assert(v->type == jpiObject);
1011+
jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
1012+
jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
1013+
}

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2324,6 +2324,70 @@ recursiveExecuteNoUnwrap(JsonPathExecContext *cxt, JsonPathItem *jsp,
23242324
found, false);
23252325
}
23262326
break;
2327+
case jpiObject:
2328+
{
2329+
JsonbParseState *ps = NULL;
2330+
JsonbValue *obj;
2331+
int i;
2332+
2333+
pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
2334+
2335+
for (i = 0; i < jsp->content.object.nfields; i++)
2336+
{
2337+
JsonbValue *jbv;
2338+
JsonbValue jbvtmp;
2339+
JsonPathItem key;
2340+
JsonPathItem val;
2341+
JsonValueList key_list = { 0 };
2342+
JsonValueList val_list = { 0 };
2343+
2344+
jspGetObjectField(jsp, i, &key, &val);
2345+
2346+
recursiveExecute(cxt, &key, jb, &key_list);
2347+
2348+
if (JsonValueListLength(&key_list) != 1)
2349+
{
2350+
res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
2351+
break;
2352+
}
2353+
2354+
jbv = JsonValueListHead(&key_list);
2355+
2356+
if (JsonbType(jbv) == jbvScalar)
2357+
jbv = JsonbExtractScalar(jbv->val.binary.data, &jbvtmp);
2358+
2359+
if (jbv->type != jbvString)
2360+
{
2361+
res = jperMakeError(ERRCODE_JSON_SCALAR_REQUIRED); /* XXX */
2362+
break;
2363+
}
2364+
2365+
pushJsonbValue(&ps, WJB_KEY, jbv);
2366+
2367+
recursiveExecute(cxt, &val, jb, &val_list);
2368+
2369+
if (JsonValueListLength(&val_list) != 1)
2370+
{
2371+
res = jperMakeError(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED);
2372+
break;
2373+
}
2374+
2375+
jbv = JsonValueListHead(&val_list);
2376+
2377+
if (jbv->type == jbvObject || jbv->type == jbvArray)
2378+
jbv = JsonbWrapInBinary(jbv, &jbvtmp);
2379+
2380+
pushJsonbValue(&ps, WJB_VALUE, jbv);
2381+
}
2382+
2383+
if (jperIsError(res))
2384+
break;
2385+
2386+
obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
2387+
2388+
res = recursiveExecuteNext(cxt, jsp, NULL, obj, found, false);
2389+
}
2390+
break;
23272391
default:
23282392
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
23292393
}

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,16 @@ makeItemSequence(List *elems)
262262
return v;
263263
}
264264

265+
static JsonPathParseItem *
266+
makeItemObject(List *fields)
267+
{
268+
JsonPathParseItem *v = makeItemType(jpiObject);
269+
270+
v->value.object.fields = fields;
271+
272+
return v;
273+
}
274+
265275
%}
266276

267277
/* BISON Declarations */
@@ -295,9 +305,9 @@ makeItemSequence(List *elems)
295305
%type <value> scalar_value path_primary expr array_accessor
296306
any_path accessor_op key predicate delimited_predicate
297307
index_elem starts_with_initial datetime_template opt_datetime_template
298-
expr_or_predicate expr_or_seq expr_seq
308+
expr_or_predicate expr_or_seq expr_seq object_field
299309

300-
%type <elems> accessor_expr expr_list
310+
%type <elems> accessor_expr expr_list object_field_list
301311

302312
%type <indexs> index_list
303313

@@ -405,6 +415,18 @@ path_primary:
405415
| '(' expr_seq ')' { $$ = $2; }
406416
| '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
407417
| '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
418+
| '{' object_field_list '}' { $$ = makeItemObject($2); }
419+
;
420+
421+
object_field_list:
422+
/* EMPTY */ { $$ = NIL; }
423+
| object_field { $$ = list_make1($1); }
424+
| object_field_list ',' object_field { $$ = lappend($1, $3); }
425+
;
426+
427+
object_field:
428+
key_name ':' expr_or_predicate
429+
{ $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
408430
;
409431

410432
accessor_expr:

src/include/utils/jsonpath.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ typedef enum JsonPathItemType {
8888
jpiLikeRegex,
8989
jpiSequence,
9090
jpiArray,
91+
jpiObject,
92+
jpiObjectField,
9193
} JsonPathItemType;
9294

9395
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -146,6 +148,14 @@ typedef struct JsonPathItem {
146148
int32 *elems;
147149
} sequence;
148150

151+
struct {
152+
int32 nfields;
153+
struct {
154+
int32 key;
155+
int32 val;
156+
} *fields;
157+
} object;
158+
149159
struct {
150160
char *data; /* for bool, numeric and string/key */
151161
int32 datalen; /* filled only for string/key */
@@ -174,6 +184,8 @@ extern char * jspGetString(JsonPathItem *v, int32 *len);
174184
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
175185
JsonPathItem *to, int i);
176186
extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
187+
extern void jspGetObjectField(JsonPathItem *v, int i,
188+
JsonPathItem *key, JsonPathItem *val);
177189

178190
/*
179191
* Parsing
@@ -223,6 +235,10 @@ struct JsonPathParseItem {
223235
List *elems;
224236
} sequence;
225237

238+
struct {
239+
List *fields;
240+
} object;
241+
226242
/* scalars */
227243
Numeric numeric;
228244
bool boolean;

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,3 +1837,37 @@ select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
18371837
[4, 5, 6, 7]
18381838
(1 row)
18391839

1840+
-- extension: object constructors
1841+
select jsonb '[1, 2, 3]' @* '{}';
1842+
?column?
1843+
----------
1844+
{}
1845+
(1 row)
1846+
1847+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
1848+
?column?
1849+
--------------------------------
1850+
{"a": 5, "b": [1, 2, 3, 4, 5]}
1851+
(1 row)
1852+
1853+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
1854+
?column?
1855+
-----------------
1856+
5
1857+
[1, 2, 3, 4, 5]
1858+
(2 rows)
1859+
1860+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
1861+
?column?
1862+
--------------------------------
1863+
{"a": 5, "b": [1, 2, 3, 4, 5]}
1864+
(1 row)
1865+
1866+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
1867+
ERROR: Singleton SQL/JSON item required
1868+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';
1869+
?column?
1870+
---------------------------------------------------------
1871+
{"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
1872+
(1 row)
1873+

src/test/regress/expected/jsonpath.out

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,24 @@ select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
558558
[[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
559559
(1 row)
560560

561+
select '{}'::jsonpath;
562+
jsonpath
563+
----------
564+
{}
565+
(1 row)
566+
567+
select '{a: 1 + 2}'::jsonpath;
568+
jsonpath
569+
--------------
570+
{"a": 1 + 2}
571+
(1 row)
572+
573+
select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
574+
jsonpath
575+
-----------------------------------------------------------------------
576+
{"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
577+
(1 row)
578+
561579
select '$ ? (@.a < 1)'::jsonpath;
562580
jsonpath
563581
---------------

src/test/regress/sql/jsonb_jsonpath.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,11 @@ select jsonb '[1, 2, 3]' @* '[(1, (2, $[*])), (4, 5)]';
407407
select jsonb '[1, 2, 3]' @* '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]';
408408
select jsonb '[1, 2, 3]' @* 'strict [1, 2, $[*].a, 4, 5]';
409409
select jsonb '[[1, 2], [3, 4, 5], [], [6, 7]]' @* '[$[*][*] ? (@ > 3)]';
410+
411+
-- extension: object constructors
412+
select jsonb '[1, 2, 3]' @* '{}';
413+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}';
414+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}.*';
415+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": [$[*], 4, 5]}[*]';
416+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": ($[*], 4, 5)}';
417+
select jsonb '[1, 2, 3]' @* '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}';

src/test/regress/sql/jsonpath.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
106106
select '[]'::jsonpath;
107107
select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
108108
109+
select '{}'::jsonpath;
110+
select '{a: 1 + 2}'::jsonpath;
111+
select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
112+
109113
select '$ ? (@.a < 1)'::jsonpath;
110114
select '$ ? (@.a < -1)'::jsonpath;
111115
select '$ ? (@.a < +1)'::jsonpath;

0 commit comments

Comments
 (0)