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

Commit 48d02a4

Browse files
author
Nikita Glukhov
committed
Add JSON_QUERY support for row, array and domain types
1 parent 5767be1 commit 48d02a4

File tree

9 files changed

+129
-6
lines changed

9 files changed

+129
-6
lines changed

src/backend/executor/execExpr.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,6 +2101,8 @@ ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
21012101
lappend(scratch.d.jsonexpr.args, var);
21022102
}
21032103

2104+
scratch.d.jsonexpr.cache = NULL;
2105+
21042106
memset(&scratch.d.jsonexpr.scalar, 0,
21052107
sizeof(scratch.d.jsonexpr.scalar));
21062108

src/backend/executor/execExprInterp.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
#include "pgstat.h"
7171
#include "utils/builtins.h"
7272
#include "utils/date.h"
73+
#include "utils/jsonapi.h"
7374
#include "utils/jsonb.h"
7475
#include "utils/jsonpath.h"
7576
#include "utils/lsyscache.h"
@@ -3739,6 +3740,13 @@ ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
37393740
else if (op->d.jsonexpr.result_expr)
37403741
res = ExecEvalExprPassingCaseValue(op->d.jsonexpr.result_expr, econtext,
37413742
isNull, res, *isNull);
3743+
else if (jexpr->coerce_via_populate)
3744+
res = json_populate_type(res, JSONBOID,
3745+
jexpr->returning.typid,
3746+
jexpr->returning.typmod,
3747+
&op->d.jsonexpr.cache,
3748+
econtext->ecxt_per_query_memory,
3749+
isNull);
37423750
/* else no coercion, simply return item */
37433751

37443752
return res;

src/backend/parser/parse_expr.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4483,11 +4483,10 @@ coerceJsonExpr(ParseState *pstate, Node *expr, JsonReturning *returning,
44834483

44844484
typtype = get_typtype(returning->typid);
44854485

4486-
if (coerce_via_populate &&
4487-
(returning->typid == RECORDOID ||
4488-
typtype == TYPTYPE_COMPOSITE ||
4489-
typtype == TYPTYPE_DOMAIN ||
4490-
type_is_array(returning->typid)))
4486+
if (returning->typid == RECORDOID ||
4487+
typtype == TYPTYPE_COMPOSITE ||
4488+
typtype == TYPTYPE_DOMAIN ||
4489+
type_is_array(returning->typid))
44914490
*coerce_via_populate = true;
44924491
else
44934492
*coerce_via_io = true;
@@ -4542,7 +4541,7 @@ transformJsonFuncExprOutput(ParseState *pstate, JsonFuncExpr *func,
45424541
jsexpr->result_expr = coerceJsonExpr(pstate, placeholder,
45434542
&jsexpr->returning,
45444543
&jsexpr->coerce_via_io,
4545-
NULL);
4544+
&jsexpr->coerce_via_populate);
45464545
}
45474546
}
45484547
else

src/backend/utils/adt/jsonfuncs.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3047,6 +3047,50 @@ populate_record_field(ColumnIOData *col,
30473047
}
30483048
}
30493049

3050+
/* recursively populate specified type from a json/jsonb value */
3051+
Datum
3052+
json_populate_type(Datum json_val, Oid json_type, Oid typid, int32 typmod,
3053+
void **cache, MemoryContext mcxt, bool *isnull)
3054+
{
3055+
JsValue jsv = { 0 };
3056+
JsonbValue jbv;
3057+
3058+
jsv.is_json = json_type == JSONOID;
3059+
3060+
if (*isnull)
3061+
{
3062+
if (jsv.is_json)
3063+
jsv.val.json.str = NULL;
3064+
else
3065+
jsv.val.jsonb = NULL;
3066+
}
3067+
else if (jsv.is_json)
3068+
{
3069+
text *json = DatumGetTextPP(json_val);
3070+
3071+
jsv.val.json.str = VARDATA_ANY(json);
3072+
jsv.val.json.len = VARSIZE_ANY_EXHDR(json);
3073+
jsv.val.json.type = JSON_TOKEN_INVALID; /* not used in populate_composite() */
3074+
}
3075+
else
3076+
{
3077+
Jsonb *jsonb = DatumGetJsonbP(json_val);
3078+
3079+
jsv.val.jsonb = &jbv;
3080+
3081+
/* fill binary jsonb value pointing to jb */
3082+
jbv.type = jbvBinary;
3083+
jbv.val.binary.data = &jsonb->root;
3084+
jbv.val.binary.len = VARSIZE(jsonb) - VARHDRSZ;
3085+
}
3086+
3087+
if (!*cache)
3088+
*cache = MemoryContextAllocZero(mcxt, sizeof(ColumnIOData));
3089+
3090+
return populate_record_field(*cache , typid, typmod, NULL, mcxt,
3091+
PointerGetDatum(NULL), &jsv, isnull);
3092+
}
3093+
30503094
static RecordIOData *
30513095
allocate_record_info(MemoryContext mcxt, int ncolumns)
30523096
{

src/include/executor/execExpr.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ typedef struct ExprEvalStep
584584
ExprState *default_on_error; /* ON ERROR DEFAULT expression */
585585
List *args; /* passing arguments */
586586

587+
void *cache; /* cache for json_populate_type() */
588+
587589
struct JsonScalarCoercions
588590
{
589591
struct JsonScalarCoercionExprState

src/include/nodes/primnodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,7 @@ typedef struct JsonExpr
12911291
Node *raw_expr; /* raw context item expression */
12921292
Node *formatted_expr; /* formatted context item expression */
12931293
Node *result_expr; /* resulting expression (coerced to RETURNING type) */
1294+
bool coerce_via_populate; /* coerce result using json_populate_type() */
12941295
bool coerce_via_io; /* coerce result using type input function */
12951296
Oid coerce_via_io_collation; /* collation for conversion through I/O */
12961297
JsonFormat format; /* context item format (JSON/JSONB) */

src/include/utils/jsonapi.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ extern Jsonb *transform_jsonb_string_values(Jsonb *jsonb, void *action_state,
190190
extern text *transform_json_string_values(text *json, void *action_state,
191191
JsonTransformStringValuesAction transform_action);
192192

193+
extern Datum json_populate_type(Datum json_val, Oid json_type,
194+
Oid typid, int32 typmod,
195+
void **cache, MemoryContext mcxt, bool *isnull);
196+
193197
extern Json *JsonCreate(text *json);
194198
extern JsonbIteratorToken JsonIteratorNext(JsonIterator **pit, JsonbValue *val,
195199
bool skipNested);

src/test/regress/expected/jsonb_sqljson.out

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,53 @@ FROM
751751
4 | 4 | [4]
752752
(25 rows)
753753

754+
-- Conversion to record types
755+
CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
756+
CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]);
757+
SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec);
758+
?column?
759+
-----------------------------------------------------
760+
(1,aaa,"[1, ""2"", {}]","{""x"": [1, ""2"", {}]}",)
761+
(1 row)
762+
763+
SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa);
764+
unnest
765+
------------------------
766+
{"a": 1, "b": ["foo"]}
767+
{"a": 2, "c": {}}
768+
123
769+
(3 rows)
770+
771+
SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca);
772+
a | t | js | jb | jsa
773+
---+-------------+----+------------+-----
774+
1 | ["foo", []] | | |
775+
2 | | | [{}, true] |
776+
(2 rows)
777+
778+
-- Conversion to array types
779+
SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
780+
?column?
781+
--------------
782+
{1,2,NULL,3}
783+
(1 row)
784+
785+
SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[]));
786+
a | t | js | jb | jsa
787+
---+-------------+----+------------+-----
788+
1 | ["foo", []] | | |
789+
2 | | | [{}, true] |
790+
(2 rows)
791+
792+
-- Conversion to domain types
793+
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null);
794+
?column?
795+
----------
796+
1
797+
(1 row)
798+
799+
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null);
800+
ERROR: domain sqljsonb_int_not_null does not allow null values
754801
-- Test constraints
755802
CREATE TABLE test_jsonb_constraints (
756803
js text,

src/test/regress/sql/jsonb_sqljson.sql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,22 @@ FROM
213213
generate_series(0, 4) x,
214214
generate_series(0, 4) y;
215215

216+
-- Conversion to record types
217+
CREATE TYPE sqljsonb_rec AS (a int, t text, js json, jb jsonb, jsa json[]);
218+
CREATE TYPE sqljsonb_reca AS (reca sqljsonb_rec[]);
219+
220+
SELECT JSON_QUERY(jsonb '[{"a": 1, "b": "foo", "t": "aaa", "js": [1, "2", {}], "jb": {"x": [1, "2", {}]}}, {"a": 2}]', '$[0]' RETURNING sqljsonb_rec);
221+
SELECT * FROM unnest((JSON_QUERY(jsonb '{"jsa": [{"a": 1, "b": ["foo"]}, {"a": 2, "c": {}}, 123]}', '$' RETURNING sqljsonb_rec)).jsa);
222+
SELECT * FROM unnest((JSON_QUERY(jsonb '{"reca": [{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]}', '$' RETURNING sqljsonb_reca)).reca);
223+
224+
-- Conversion to array types
225+
SELECT JSON_QUERY(jsonb '[1,2,null,"3"]', '$[*]' RETURNING int[] WITH WRAPPER);
226+
SELECT * FROM unnest(JSON_QUERY(jsonb '[{"a": 1, "t": ["foo", []]}, {"a": 2, "jb": [{}, true]}]', '$' RETURNING sqljsonb_rec[]));
227+
228+
-- Conversion to domain types
229+
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null);
230+
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null);
231+
216232
-- Test constraints
217233

218234
CREATE TABLE test_jsonb_constraints (

0 commit comments

Comments
 (0)