Skip to content

Commit b163914

Browse files
author
Nikita Glukhov
committed
Add jsonpath object constructors
1 parent 5336bd8 commit b163914

File tree

9 files changed

+251
-2
lines changed

9 files changed

+251
-2
lines changed

doc/src/sgml/func.sgml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13701,6 +13701,13 @@ table2-mapping
1370113701
<entry><literal>pg [$[*], 4, 5]</literal></entry>
1370213702
<entry><literal>[1, 2, 3, 4, 5]</literal></entry>
1370313703
</row>
13704+
<row>
13705+
<entry>Object constructor</entry>
13706+
<entry>Construct a JSON object by enumeration of its fields enclosed in braces</entry>
13707+
<entry><literal>{"x": "y"}</literal></entry>
13708+
<entry><literal>pg {a: 1, "b c": $.x}</literal></entry>
13709+
<entry><literal>{"a": 1, "b c": "y"}</literal></entry>
13710+
</row>
1370413711
</tbody>
1370513712
</tgroup>
1370613713
</table>

src/backend/utils/adt/jsonpath.c

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,40 @@ flattenJsonPathParseItem(JsonPathEncodingContext *cxt, JsonPathParseItem *item,
479479
}
480480
}
481481
break;
482+
case jpiObject:
483+
{
484+
int32 nfields = list_length(item->value.object.fields);
485+
ListCell *lc;
486+
int offset;
487+
488+
checkJsonPathExtensionsEnabled(cxt, item->type);
489+
490+
appendBinaryStringInfo(buf, (char *) &nfields, sizeof(nfields));
491+
492+
offset = buf->len;
493+
494+
appendStringInfoSpaces(buf, sizeof(int32) * 2 * nfields);
495+
496+
foreach(lc, item->value.object.fields)
497+
{
498+
JsonPathParseItem *field = lfirst(lc);
499+
int32 keypos =
500+
flattenJsonPathParseItem(cxt, field->value.args.left,
501+
nestingLevel,
502+
insideArraySubscript);
503+
int32 valpos =
504+
flattenJsonPathParseItem(cxt, field->value.args.right,
505+
nestingLevel,
506+
insideArraySubscript);
507+
int32 *ppos = (int32 *) &buf->data[offset];
508+
509+
ppos[0] = keypos - pos;
510+
ppos[1] = valpos - pos;
511+
512+
offset += 2 * sizeof(int32);
513+
}
514+
}
515+
break;
482516
default:
483517
elog(ERROR, "unrecognized jsonpath item type: %d", item->type);
484518
}
@@ -795,6 +829,26 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey,
795829
}
796830
appendStringInfoChar(buf, ']');
797831
break;
832+
case jpiObject:
833+
appendStringInfoChar(buf, '{');
834+
835+
for (i = 0; i < v->content.object.nfields; i++)
836+
{
837+
JsonPathItem key;
838+
JsonPathItem val;
839+
840+
jspGetObjectField(v, i, &key, &val);
841+
842+
if (i)
843+
appendBinaryStringInfo(buf, ", ", 2);
844+
845+
printJsonPathItem(buf, &key, false, false);
846+
appendBinaryStringInfo(buf, ": ", 2);
847+
printJsonPathItem(buf, &val, false, val.type == jpiSequence);
848+
}
849+
850+
appendStringInfoChar(buf, '}');
851+
break;
798852
default:
799853
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
800854
}
@@ -1011,6 +1065,11 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos)
10111065
read_int32_n(v->content.sequence.elems, base, pos,
10121066
v->content.sequence.nelems);
10131067
break;
1068+
case jpiObject:
1069+
read_int32(v->content.object.nfields, base, pos);
1070+
read_int32_n(v->content.object.fields, base, pos,
1071+
v->content.object.nfields * 2);
1072+
break;
10141073
default:
10151074
elog(ERROR, "unrecognized jsonpath item type: %d", v->type);
10161075
}
@@ -1078,7 +1137,8 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a)
10781137
v->type == jpiKeyValue ||
10791138
v->type == jpiStartsWith ||
10801139
v->type == jpiSequence ||
1081-
v->type == jpiArray);
1140+
v->type == jpiArray ||
1141+
v->type == jpiObject);
10821142

10831143
if (a)
10841144
jspInitByBuffer(a, v->base, v->nextPos);
@@ -1181,3 +1241,11 @@ jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem)
11811241

11821242
jspInitByBuffer(elem, v->base, v->content.sequence.elems[i]);
11831243
}
1244+
1245+
void
1246+
jspGetObjectField(JsonPathItem *v, int i, JsonPathItem *key, JsonPathItem *val)
1247+
{
1248+
Assert(v->type == jpiObject);
1249+
jspInitByBuffer(key, v->base, v->content.object.fields[i].key);
1250+
jspInitByBuffer(val, v->base, v->content.object.fields[i].val);
1251+
}

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,62 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp,
11941194
break;
11951195
}
11961196

1197+
case jpiObject:
1198+
{
1199+
JsonbParseState *ps = NULL;
1200+
int i;
1201+
1202+
pushJsonbValue(&ps, WJB_BEGIN_OBJECT, NULL);
1203+
1204+
for (i = 0; i < jsp->content.object.nfields; i++)
1205+
{
1206+
JsonbValue *jbv;
1207+
JsonbValue jbvtmp;
1208+
JsonPathItem key;
1209+
JsonPathItem val;
1210+
JsonValueList key_list = {0};
1211+
JsonValueList val_list = {0};
1212+
1213+
jspGetObjectField(jsp, i, &key, &val);
1214+
1215+
res = executeItem(cxt, &key, jb, &key_list);
1216+
if (jperIsError(res))
1217+
return res;
1218+
1219+
if (JsonValueListLength(&key_list) != 1 ||
1220+
!(jbv = getScalar(JsonValueListHead(&key_list), jbvString)))
1221+
RETURN_ERROR(ereport(ERROR,
1222+
(errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED),
1223+
errmsg("key in jsonpath object constructor is not a single string"))));
1224+
1225+
res = executeItem(cxt, &val, jb, &val_list);
1226+
if (jperIsError(res))
1227+
return res;
1228+
1229+
if (jspIgnoreStructuralErrors(cxt) &&
1230+
JsonValueListIsEmpty(&val_list))
1231+
continue; /* skip empty fields in lax mode */
1232+
1233+
if (JsonValueListLength(&val_list) != 1)
1234+
RETURN_ERROR(ereport(ERROR,
1235+
(errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED),
1236+
errmsg("value in jsonpath object constructor is not single"),
1237+
errhint("Use jsonpath array syntax to wrap multi-item sequences into arrays"))));
1238+
1239+
pushJsonbValue(&ps, WJB_KEY, jbv);
1240+
1241+
jbv = JsonValueListHead(&val_list);
1242+
jbv = wrapJsonObjectOrArray(jbv, &jbvtmp);
1243+
1244+
pushJsonbValue(&ps, WJB_VALUE, jbv);
1245+
}
1246+
1247+
jb = pushJsonbValue(&ps, WJB_END_OBJECT, NULL);
1248+
1249+
res = executeNextItem(cxt, jsp, NULL, jb, found, false);
1250+
break;
1251+
}
1252+
11971253
default:
11981254
elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type);
11991255
}

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

@@ -219,6 +221,18 @@ path_primary:
219221
| '(' expr_seq ')' { $$ = $2; }
220222
| '[' ']' { $$ = makeItemUnary(jpiArray, NULL); }
221223
| '[' expr_or_seq ']' { $$ = makeItemUnary(jpiArray, $2); }
224+
| '{' object_field_list '}' { $$ = makeItemObject($2); }
225+
;
226+
227+
object_field_list:
228+
/* EMPTY */ { $$ = NIL; }
229+
| object_field { $$ = list_make1($1); }
230+
| object_field_list ',' object_field { $$ = lappend($1, $3); }
231+
;
232+
233+
object_field:
234+
key_name ':' expr_or_predicate
235+
{ $$ = makeItemBinary(jpiObjectField, makeItemString(&$1), $3); }
222236
;
223237

224238
accessor_expr:
@@ -578,6 +592,16 @@ makeItemSequence(List *elems)
578592
return v;
579593
}
580594

595+
static JsonPathParseItem *
596+
makeItemObject(List *fields)
597+
{
598+
JsonPathParseItem *v = makeItemType(jpiObject);
599+
600+
v->value.object.fields = fields;
601+
602+
return v;
603+
}
604+
581605
/*
582606
* Convert from XQuery regex flags to those recognized by our regex library.
583607
*/

src/include/utils/jsonpath.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ typedef enum JsonPathItemType
8989
jpiLikeRegex, /* LIKE_REGEX predicate */
9090
jpiSequence, /* sequence constructor: 'expr, ...' */
9191
jpiArray, /* array constructor: '[expr, ...]' */
92+
jpiObject, /* object constructor: '{ key : value, ... }' */
93+
jpiObjectField, /* element of object constructor: 'key : value' */
9294
} JsonPathItemType;
9395

9496
/* XQuery regex mode flags for LIKE_REGEX predicate */
@@ -155,6 +157,16 @@ typedef struct JsonPathItem
155157
int32 *elems;
156158
} sequence;
157159

160+
struct
161+
{
162+
int32 nfields;
163+
struct
164+
{
165+
int32 key;
166+
int32 val;
167+
} *fields;
168+
} object;
169+
158170
struct
159171
{
160172
char *data; /* for bool, numeric and string/key */
@@ -185,6 +197,8 @@ extern char *jspGetString(JsonPathItem *v, int32 *len);
185197
extern bool jspGetArraySubscript(JsonPathItem *v, JsonPathItem *from,
186198
JsonPathItem *to, int i);
187199
extern void jspGetSequenceElement(JsonPathItem *v, int i, JsonPathItem *elem);
200+
extern void jspGetObjectField(JsonPathItem *v, int i,
201+
JsonPathItem *key, JsonPathItem *val);
188202

189203
extern const char *jspOperationName(JsonPathItemType type);
190204

@@ -242,6 +256,10 @@ struct JsonPathParseItem
242256
List *elems;
243257
} sequence;
244258

259+
struct {
260+
List *fields;
261+
} object;
262+
245263
/* scalars */
246264
Numeric numeric;
247265
bool boolean;

src/test/regress/expected/jsonb_jsonpath.out

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2635,3 +2635,47 @@ select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'pg [$[*][*] ? (@ > 3
26352635
[4, 5, 6, 7]
26362636
(1 row)
26372637

2638+
-- extension: object constructors
2639+
select jsonb_path_query('[1, 2, 3]', 'pg {}');
2640+
jsonb_path_query
2641+
------------------
2642+
{}
2643+
(1 row)
2644+
2645+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": [$[*], 4, 5]}');
2646+
jsonb_path_query
2647+
--------------------------------
2648+
{"a": 5, "b": [1, 2, 3, 4, 5]}
2649+
(1 row)
2650+
2651+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": [$[*], 4, 5]}.*');
2652+
jsonb_path_query
2653+
------------------
2654+
5
2655+
[1, 2, 3, 4, 5]
2656+
(2 rows)
2657+
2658+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": [$[*], 4, 5]}[*]');
2659+
jsonb_path_query
2660+
--------------------------------
2661+
{"a": 5, "b": [1, 2, 3, 4, 5]}
2662+
(1 row)
2663+
2664+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": ($[*], 4, 5)}');
2665+
ERROR: value in jsonpath object constructor is not single
2666+
HINT: Use jsonpath array syntax to wrap multi-item sequences into arrays
2667+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}');
2668+
jsonb_path_query
2669+
---------------------------------------------------------
2670+
{"a": 5, "b": {"x": [1, 2, 3], "y": false, "z": "foo"}}
2671+
(1 row)
2672+
2673+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": $[*] ? (@ > 3), c: "foo"}');
2674+
jsonb_path_query
2675+
----------------------
2676+
{"a": 5, "c": "foo"}
2677+
(1 row)
2678+
2679+
select jsonb_path_query('[1, 2, 3]', 'pg strict {a: 2 + 3, "b": $[*] ? (@ > 3), c: "foo"}');
2680+
ERROR: value in jsonpath object constructor is not single
2681+
HINT: Use jsonpath array syntax to wrap multi-item sequences into arrays

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 'pg [[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
616616
pg [[1, 2], ([(3, 4, 5), 6], []), $."a"[*]]
617617
(1 row)
618618

619+
select 'pg {}'::jsonpath;
620+
jsonpath
621+
----------
622+
pg {}
623+
(1 row)
624+
625+
select 'pg {a: 1 + 2}'::jsonpath;
626+
jsonpath
627+
-----------------
628+
pg {"a": 1 + 2}
629+
(1 row)
630+
631+
select 'pg {a: 1 + 2, b : (1,2), c: [$[*],4,5], d: { "e e e": "f f f" }}'::jsonpath;
632+
jsonpath
633+
--------------------------------------------------------------------------
634+
pg {"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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,3 +598,13 @@ select jsonb_path_query('[1, 2, 3]', 'pg [(1, (2, $[*])), (4, 5)]');
598598
select jsonb_path_query('[1, 2, 3]', 'pg [[1, 2], [$[*], 4], 5, [(1,2)?(@ > 5)]]');
599599
select jsonb_path_query('[1, 2, 3]', 'pg strict [1, 2, $[*].a, 4, 5]');
600600
select jsonb_path_query('[[1, 2], [3, 4, 5], [], [6, 7]]', 'pg [$[*][*] ? (@ > 3)]');
601+
602+
-- extension: object constructors
603+
select jsonb_path_query('[1, 2, 3]', 'pg {}');
604+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": [$[*], 4, 5]}');
605+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": [$[*], 4, 5]}.*');
606+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": [$[*], 4, 5]}[*]');
607+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": ($[*], 4, 5)}');
608+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": {x: $, y: $[1] > 2, z: "foo"}}');
609+
select jsonb_path_query('[1, 2, 3]', 'pg {a: 2 + 3, "b": $[*] ? (@ > 3), c: "foo"}');
610+
select jsonb_path_query('[1, 2, 3]', 'pg strict {a: 2 + 3, "b": $[*] ? (@ > 3), c: "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 'pg $[(1, (2, $.a)), 3, (4, 5)]'::jsonpath;
116116
select 'pg []'::jsonpath;
117117
select 'pg [[1, 2], ([(3, 4, 5), 6], []), $.a[*]]'::jsonpath;
118118
119+
select 'pg {}'::jsonpath;
120+
select 'pg {a: 1 + 2}'::jsonpath;
121+
select 'pg {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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy