Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

+7
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

+69-1
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

+56
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

+25-1
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

+18
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

+44
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

+18
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

+10
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

+4
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)