Skip to content

Commit f8f1a7d

Browse files
author
Nikita Glukhov
committed
Add jsonpath object constructors
1 parent 9d1d611 commit f8f1a7d

File tree

8 files changed

+233
-2
lines changed

8 files changed

+233
-2
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,38 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
444444
}
445445
}
446446
break;
447+
case jpiObject:
448+
{
449+
int32 nfields = list_length(item->value.object.fields);
450+
ListCell *lc;
451+
int offset;
452+
453+
appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
454+
455+
offset = buf->len;
456+
457+
appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
458+
459+
foreach(lc, item->value.object.fields)
460+
{
461+
JsonPathParseItem *field = lfirst(lc);
462+
int32 keypos =
463+
flattenJsonPathParseItem(buf, field->value.args.left,
464+
nestingLevel,
465+
insideArraySubscript);
466+
int32 valpos =
467+
flattenJsonPathParseItem(buf, field->value.args.right,
468+
nestingLevel,
469+
insideArraySubscript);
470+
int32 *ppos = (int32 *) &buf->data[offset];
471+
472+
ppos[0] = keypos - pos;
473+
ppos[1] = valpos - pos;
474+
475+
offset += 2 * sizeof(int32);
476+
}
477+
}
478+
break;
447479
default:
448480
elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
449481
}
@@ -767,6 +799,26 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
767799
}
768800
appendStringInfoChar(buf, ']');
769801
break;
802+
case jpiObject:
803+
appendStringInfoChar(buf, '{');
804+
805+
for (i = 0; i < v->content.object.nfields; i++)
806+
{
807+
JsonPathItem key;
808+
JsonPathItem val;
809+
810+
jspGetObjectField(v, i, &key, &val);
811+
812+
if (i)
813+
appendBinaryStringInfo(buf, ", ", 2);
814+
815+
printJsonPathItem(buf, &key, false, false);
816+
appendBinaryStringInfo(buf, ": ", 2);
817+
printJsonPathItem(buf, &val, false, val.type == jpiSequence);
818+
}
819+
820+
appendStringInfoChar(buf, '}');
821+
break;
770822
default:
771823
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
772824
}
@@ -983,6 +1035,11 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
9831035
read_int32_n(v->content.sequence.elems, base, pos,
9841036
v->content.sequence.nelems);
9851037
break;
1038+
case jpiObject:
1039+
read_int32(v->content.object.nfields, base, pos);
1040+
read_int32_n(v->content.object.fields, base, pos,
1041+
v->content.object.nfields * 2);
1042+
break;
9861043
default:
9871044
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
9881045
}
@@ -1049,7 +1106,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
10491106
v->type == jpiKeyValue ||
10501107
v->type == jpiStartsWith ||
10511108
v->type == jpiSequence ||
1052-
v->type == jpiArray);
1109+
v->type == jpiArray ||
1110+
v->type == jpiObject);
10531111

10541112
if (a)
10551113
jspInitByBuffer(a, v->base, v->nextPos);
@@ -1154,3 +1212,11 @@ jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
11541212

11551213
jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
11561214
}
1215+
1216+
void
1217+
jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
1218+
{
1219+
Assert(v->type == jpiObject);
1220+
jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
1221+
jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
1222+
}

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,6 +1660,65 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
16601660
break;
16611661
}
16621662

1663+
case jpiObject:
1664+
{
1665+
JsonbParseState *ps = NULL;
1666+
JsonbValue *obj;
1667+
JsonItem jsibuf;
1668+
int i;
1669+
JsonBuilderFunc push =
1670+
cxt->isJsonb ? pushJsonbValue : pushJsonValue;
1671+
1672+
push(&ps, WJB_BEGIN_OBJECT, NULL);
1673+
1674+
for (i = 0; i < jsp->content.object.nfields; i++)
1675+
{
1676+
JsonItem *jsi;
1677+
JsonItem jsitmp;
1678+
JsonPathItem key;
1679+
JsonPathItem val;
1680+
JsonbValue valjbv;
1681+
JsonValueList key_list = {0};
1682+
JsonValueList val_list = {0};
1683+
1684+
jspGetObjectField(jsp, i, &key, &val);
1685+
1686+
res = executeItem(cxt, &key, jb, &key_list);
1687+
if (jperIsError(res))
1688+
return res;
1689+
1690+
if (JsonValueListLength(&key_list) != 1 ||
1691+
!(jsi = getScalar(JsonValueListHead(&key_list), jbvString)))
1692+
RETURN_ERROR(ereport(ERROR,
1693+
(errcode(ERRCODE_JSON_SCALAR_REQUIRED),
1694+
errmsg(ERRMSG_JSON_SCALAR_REQUIRED),
1695+
errdetail("key in jsonpath object constructor must be a singleton string")))); /* XXX */
1696+
1697+
pushJsonbValue(&ps, WJB_KEY, JsonItemJbv(jsi));
1698+
1699+
res = executeItem(cxt, &val, jb, &val_list);
1700+
if (jperIsError(res))
1701+
return res;
1702+
1703+
if (JsonValueListLength(&val_list) != 1)
1704+
RETURN_ERROR(ereport(ERROR,
1705+
(errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
1706+
errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED),
1707+
errdetail("value in jsonpath object constructor must be a singleton"))));
1708+
1709+
jsi = JsonValueListHead(&val_list);
1710+
jsi = wrapJsonObjectOrArray(jsi, &jsitmp, cxt->isJsonb);
1711+
1712+
push(&ps, WJB_VALUE, JsonItemToJsonbValue(jsi, &valjbv));
1713+
}
1714+
1715+
obj = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
1716+
jb = JsonbValueToJsonItem(obj, &jsibuf);
1717+
1718+
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
1719+
break;
1720+
}
1721+
16631722
default:
16641723
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
16651724
}

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
5757
JsonPathString *pattern,
5858
JsonPathString *flags);
5959
static JsonPathParseItem *makeItemSequence(List *elems);
60+
static JsonPathParseItem *makeItemObject(List *fields);
6061

6162
/*
6263
* Bison doesn't allocate anything that needs to live across parser calls,
@@ -103,8 +104,9 @@ static JsonPathParseItem *makeItemSequence(List *elems);
103104
any_path accessor_op key predicate delimited_predicate
104105
index_elem starts_with_initial expr_or_predicate
105106
datetime_template opt_datetime_template expr_seq expr_or_seq
107+
object_field
106108

107-
%type <elems> accessor_expr expr_list
109+
%type <elems> accessor_expr expr_list object_field_list
108110

109111
%type <indexs> index_list
110112

@@ -213,6 +215,18 @@ path_primary:
213215
| '(' expr_seq ')' { $$ = $2; }
214216
| '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
215217
| '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
218+
| '{' object_field_list '}' { $$ = makeItemObject($2); }
219+
;
220+
221+
object_field_list:
222+
/* EMPTY */ { $$ = NIL; }
223+
| object_field { $$ = list_make1($1); }
224+
| object_field_list ',' object_field { $$ = lappend($1, $3); }
225+
;
226+
227+
object_field:
228+
key_name ':' expr_or_predicate
229+
{ $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
216230
;
217231

218232
accessor_expr:
@@ -580,6 +594,16 @@ makeItemSequence(List *elems)
580594
return v;
581595
}
582596

597+
static JsonPathParseItem *
598+
makeItemObject(List *fields)
599+
{
600+
JsonPathParseItem *v = makeItemType(jpiObject);
601+
602+
v->value.object.fields = fields;
603+
604+
return v;
605+
}
606+
583607
/*
584608
* jsonpath_scan.l is compiled as part of jsonpath_gram.y. Currently, this is
585609
* unavoidable because jsonpath_gram does not create a .h file to export its

src/include/utils/jsonpath.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ typedef enum JsonPathItemType
9191
jpiLikeRegex, /* LIKE_REGEX predicate */
9292
jpiSequence, /* sequence constructor: 'expr, ...' */
9393
jpiArray, /* array constructor: '[expr, ...]' */
94+
jpiObject, /* object constructor: '{ key : value, ... }' */
95+
jpiObjectField, /* element of object constructor: 'key : value' */
9496
} JsonPathItemType;
9597

9698
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -157,6 +159,16 @@ typedef struct JsonPathItem
157159
int32 *elems;
158160
} sequence;
159161

162+
struct
163+
{
164+
int32 nfields;
165+
struct
166+
{
167+
int32 key;
168+
int32 val;
169+
} *fields;
170+
} object;
171+
160172
struct
161173
{
162174
char *data; /* for bool, numeric and string/key */
@@ -187,6 +199,8 @@ extern char *jspGetString(JsonPathItem *v, int32 *len);
187199
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
188200
JsonPathItem *to, int i);
189201
extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
202+
extern void jspGetObjectField(JsonPathItem *v, int i,
203+
JsonPathItem *key, JsonPathItem *val);
190204

191205
extern const char *jspOperationName(JsonPathItemType type);
192206

@@ -244,6 +258,10 @@ struct JsonPathParseItem
244258
List *elems;
245259
} sequence;
246260

261+
struct {
262+
List *fields;
263+
} object;
264+
247265
/* scalars */
248266
Numeric numeric;
249267
bool boolean;

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,3 +2489,37 @@ select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]'
24892489
[4, 5, 6, 7]
24902490
(1 row)
24912491

2492+
-- extension: object constructors
2493+
select jsonb_path_query('[1, 2, 3]', '{}');
2494+
jsonb_path_query
2495+
------------------
2496+
{}
2497+
(1 row)
2498+
2499+
select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}');
2500+
jsonb_path_query
2501+
--------------------------------
2502+
{"a": 5, "b": [1, 2, 3, 4, 5]}
2503+
(1 row)
2504+
2505+
select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*');
2506+
jsonb_path_query
2507+
------------------
2508+
5
2509+
[1, 2, 3, 4, 5]
2510+
(2 rows)
2511+
2512+
select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]');
2513+
jsonb_path_query
2514+
--------------------------------
2515+
{"a": 5, "b": [1, 2, 3, 4, 5]}
2516+
(1 row)
2517+
2518+
select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}');
2519+
ERROR: value in jsonpath object constructor must be a singleton
2520+
select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}');
2521+
jsonb_path_query
2522+
---------------------------------------------------------
2523+
{"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
2524+
(1 row)
2525+

src/test/regress/expected/jsonpath.out

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

619+
select '{}'::jsonpath;
620+
jsonpath
621+
----------
622+
{}
623+
(1 row)
624+
625+
select '{a: 1 + 2}'::jsonpath;
626+
jsonpath
627+
--------------
628+
{"a": 1 + 2}
629+
(1 row)
630+
631+
select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
632+
jsonpath
633+
-----------------------------------------------------------------------
634+
{"a": 1 + 2, "b": (1, 2), "c": [$[*], 4, 5], "d": {"e e e": "f f f"}}
635+
(1 row)
636+
619637
select '$ ? (@.a < 1)'::jsonpath;
620638
jsonpath
621639
---------------

src/test/regress/sql/jsonb_jsonpath.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,3 +559,11 @@ select jsonb_path_query('[1, 2, 3]', '[(1, (2, $[*])), (4, 5)]');
559559
select jsonb_path_query('[1, 2, 3]', '[[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]');
560560
select jsonb_path_query('[1, 2, 3]', 'strict [1, 2, $[*].a, 4, 5]');
561561
select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', '[$[*][*] ? (@ > 3)]');
562+
563+
-- extension: object constructors
564+
select jsonb_path_query('[1, 2, 3]', '{}');
565+
select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}');
566+
select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}.*');
567+
select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": [$[*], 4, 5]}[*]');
568+
select jsonb_path_query('[1, 2, 3]', '{a: 2 + 3, "b": ($[*], 4, 5)}');
569+
select jsonb_path_query('[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
@@ -116,6 +116,10 @@ select '$[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
116116
select '[]'::jsonpath;
117117
select '[[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
118118
119+
select '{}'::jsonpath;
120+
select '{a: 1 + 2}'::jsonpath;
121+
select '{a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
122+
119123
select '$ ? (@.a < 1)'::jsonpath;
120124
select '$ ? (@.a < -1)'::jsonpath;
121125
select '$ ? (@.a < +1)'::jsonpath;

0 commit comments

Comments
 (0)