From cf35346e813e5a1373f308d397bb0a8f3f21d530 Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Thu, 6 Apr 2017 22:11:21 -0400 Subject: [PATCH] Make json_populate_record and friends operate recursively With this change array fields are populated from json(b) arrays, and composite fields are populated from json(b) objects. Along the way, some significant code refactoring is done to remove redundancy in the way to populate_record[_set] and to_record[_set] functions operate, and some significant efficiency gains are made by caching tuple descriptors. Nikita Glukhov, edited some by me. Reviewed by Aleksander Alekseev and Tom Lane. --- doc/src/sgml/func.sgml | 16 +- src/backend/utils/adt/jsonfuncs.c | 1700 ++++++++++++++++++--------- src/test/regress/expected/json.out | 466 +++++++- src/test/regress/expected/jsonb.out | 478 +++++++- src/test/regress/sql/json.sql | 156 ++- src/test/regress/sql/jsonb.sql | 156 ++- 6 files changed, 2412 insertions(+), 560 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e3d852cdc7..cb0a36a170 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -11546,12 +11546,12 @@ table2-mapping whose columns match the record type defined by base (see note below). - select * from json_populate_record(null::myrowtype, '{"a":1,"b":2}') + select * from json_populate_record(null::myrowtype, '{"a": 1, "b": ["2", "a b"], "c": {"d": 4, "e": "a b c"}}') - a | b ----+--- - 1 | 2 + a | b | c +---+-----------+------------- + 1 | {2,"a b"} | (4,"a b c") @@ -11640,12 +11640,12 @@ table2-mapping explicitly define the structure of the record with an AS clause. - select * from json_to_record('{"a":1,"b":[1,2,3],"c":"bar"}') as x(a int, b text, d text) + select * from json_to_record('{"a":1,"b":[1,2,3],"c":[1,2,3],"e":"bar","r": {"a": 123, "b": "a b c"}}') as x(a int, b text, c int[], d text, r myrowtype) - a | b | d ----+---------+--- - 1 | [1,2,3] | + a | b | c | d | r +---+---------+---------+---+--------------- + 1 | [1,2,3] | {1,2,3} | | (123,"a b c") diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 0db37234fc..70edd42c8d 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -31,6 +31,7 @@ #include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/syscache.h" #include "utils/typcache.h" /* Operations available for setPath */ @@ -128,6 +129,7 @@ typedef struct JhashState HTAB *hash; char *saved_scalar; char *save_json_start; + JsonTokenType saved_token_type; } JHashState; /* hashtable element */ @@ -135,26 +137,83 @@ typedef struct JsonHashEntry { char fname[NAMEDATALEN]; /* hash key (MUST BE FIRST) */ char *val; - char *json; - bool isnull; + JsonTokenType type; } JsonHashEntry; -/* these two are stolen from hstore / record_out, used in populate_record* */ -typedef struct ColumnIOData +/* structure to cache type I/O metadata needed for populate_scalar() */ +typedef struct ScalarIOData { - Oid column_type; - Oid typiofunc; Oid typioparam; - FmgrInfo proc; -} ColumnIOData; + FmgrInfo typiofunc; +} ScalarIOData; + +/* these two structures are used recursively */ +typedef struct ColumnIOData ColumnIOData; +typedef struct RecordIOData RecordIOData; + +/* structure to cache metadata needed for populate_array() */ +typedef struct ArrayIOData +{ + ColumnIOData *element_info; /* metadata cache */ + Oid element_type; /* array element type id */ + int32 element_typmod; /* array element type modifier */ +} ArrayIOData; + +/* structure to cache metadata needed for populate_composite() */ +typedef struct CompositeIOData +{ + /* + * We use pointer to a RecordIOData here because variable-length + * struct RecordIOData can't be used directly in ColumnIOData.io union + */ + RecordIOData *record_io; /* metadata cache for populate_record() */ + TupleDesc tupdesc; /* cached tuple descriptor */ +} CompositeIOData; -typedef struct RecordIOData +/* structure to cache metadata needed for populate_domain() */ +typedef struct DomainIOData +{ + ColumnIOData *base_io; /* metadata cache */ + Oid base_typid; /* base type id */ + int32 base_typmod; /* base type modifier */ + void *domain_info; /* opaque cache for domain checks */ +} DomainIOData; + +/* enumeration type categories */ +typedef enum TypeCat +{ + TYPECAT_SCALAR = 's', + TYPECAT_ARRAY = 'a', + TYPECAT_COMPOSITE = 'c', + TYPECAT_DOMAIN = 'd', +} TypeCat; + +/* these two are stolen from hstore / record_out, used in populate_record* */ + +/* structure to cache record metadata needed for populate_record_field() */ +struct ColumnIOData +{ + Oid typid; /* column type id */ + int32 typmod; /* column type modifier */ + TypeCat typcat; /* column type category */ + ScalarIOData scalar_io; /* metadata cache for directi conversion + * through input function */ + union + { + ArrayIOData array; + CompositeIOData composite; + DomainIOData domain; + } io; /* metadata cache for various column type categories */ +}; + +/* structure to cache record metadata needed for populate_record() */ +struct RecordIOData { Oid record_type; int32 record_typmod; int ncolumns; ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER]; -} RecordIOData; +}; /* state for populate_recordset */ typedef struct PopulateRecordsetState @@ -164,13 +223,44 @@ typedef struct PopulateRecordsetState HTAB *json_hash; char *saved_scalar; char *save_json_start; + JsonTokenType saved_token_type; Tuplestorestate *tuple_store; TupleDesc ret_tdesc; HeapTupleHeader rec; - RecordIOData *my_extra; + RecordIOData **my_extra; MemoryContext fn_mcxt; /* used to stash IO funcs */ } PopulateRecordsetState; +/* structure to cache metadata needed for populate_record_worker() */ +typedef struct PopulateRecordCache +{ + Oid argtype; /* verified row type of the first argument */ + CompositeIOData io; /* metadata cache for populate_composite() */ +} PopulateRecordCache; + +/* common data for populate_array_json() and populate_array_dim_jsonb() */ +typedef struct PopulateArrayContext +{ + ArrayBuildState *astate; /* array build state */ + ArrayIOData *aio; /* metadata cache */ + MemoryContext acxt; /* array build memory context */ + MemoryContext mcxt; /* cache memory context */ + const char *colname; /* for diagnostics only */ + int *dims; /* dimensions */ + int *sizes; /* current dimension counters */ + int ndims; /* number of dimensions */ +} PopulateArrayContext; + +/* state for populate_array_json() */ +typedef struct PopulateArrayState +{ + JsonLexContext *lex; /* json lexer */ + PopulateArrayContext *ctx; /* context */ + char *element_start; /* start of the current array element */ + char *element_scalar; /* current array element token if it is a scalar */ + JsonTokenType element_type; /* current array element type */ +} PopulateArrayState; + /* state for json_strip_nulls */ typedef struct StripnullState { @@ -179,6 +269,55 @@ typedef struct StripnullState bool skip_next_null; } StripnullState; +/* structure for generalized json/jsonb value passing */ +typedef struct JsValue +{ + bool is_json; /* json/jsonb */ + union + { + struct + { + char *str; /* json string */ + int len; /* json string length or -1 if null-terminated */ + JsonTokenType type; /* json type */ + } json; /* json value */ + + JsonbValue *jsonb; /* jsonb value */ + } val; +} JsValue; + +typedef struct JsObject +{ + bool is_json; /* json/jsonb */ + union + { + HTAB *json_hash; + JsonbContainer *jsonb_cont; + } val; +} JsObject; + +/* useful macros for testing JsValue properties */ +#define JsValueIsNull(jsv) \ + ((jsv)->is_json ? \ + (!(jsv)->val.json.str || (jsv)->val.json.type == JSON_TOKEN_NULL) : \ + (!(jsv)->val.jsonb || (jsv)->val.jsonb->type == jbvNull)) + +#define JsValueIsString(jsv) \ + ((jsv)->is_json ? (jsv)->val.json.type == JSON_TOKEN_STRING \ + : ((jsv)->val.jsonb && (jsv)->val.jsonb->type == jbvString)) + +#define JsObjectSize(jso) \ + ((jso)->is_json \ + ? hash_get_num_entries((jso)->val.json_hash) \ + : !(jso)->val.jsonb_cont || JsonContainerSize((jso)->val.jsonb_cont)) + +#define JsObjectIsEmpty(jso) (JsObjectSize(jso) == 0) + +#define JsObjectFree(jso) do { \ + if ((jso)->is_json) \ + hash_destroy((jso)->val.json_hash); \ + } while (0) + /* semantic action functions for json_object_keys */ static void okeys_object_field_start(void *state, char *fname, bool isnull); static void okeys_array_start(void *state); @@ -230,11 +369,14 @@ static void elements_array_element_end(void *state, bool isnull); static void elements_scalar(void *state, char *token, JsonTokenType tokentype); /* turn a json object into a hash table */ -static HTAB *get_json_object_as_hash(text *json, const char *funcname); +static HTAB *get_json_object_as_hash(char *json, int len, const char *funcname); -/* common worker for populate_record and to_record */ -static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, - bool have_record_arg); +/* semantic actions for populate_array_json */ +static void populate_array_object_start(void *_state); +static void populate_array_array_end(void *_state); +static void populate_array_element_start(void *_state, bool isnull); +static void populate_array_element_end(void *_state, bool isnull); +static void populate_array_scalar(void *_state, char *token, JsonTokenType tokentype); /* semantic action functions for get_json_object_as_hash */ static void hash_object_field_start(void *state, char *fname, bool isnull); @@ -260,13 +402,43 @@ static void sn_object_field_start(void *state, char *fname, bool isnull); static void sn_array_element_start(void *state, bool isnull); static void sn_scalar(void *state, char *token, JsonTokenType tokentype); -/* Turn a jsonb object into a record */ -static void make_row_from_rec_and_jsonb(Jsonb *element, - PopulateRecordsetState *state); - -/* worker function for populate_recordset and to_recordset */ +/* worker functions for populate_record, to_record, populate_recordset and to_recordset */ static Datum populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, bool have_record_arg); +static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, + bool have_record_arg); + +/* helper functions for populate_record[set] */ +static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_info, + HeapTupleHeader template, MemoryContext mcxt, + JsObject *obj); +static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod, + const char *colname, MemoryContext mcxt, + Datum defaultval, JsValue *jsv, bool *isnull); +static void JsValueToJsObject(JsValue *jsv, JsObject *jso); +static Datum populate_composite(CompositeIOData *io, Oid typid, int32 typmod, + const char *colname, MemoryContext mcxt, + HeapTupleHeader defaultval, JsValue *jsv); +static Datum populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv); +static void prepare_column_cache(ColumnIOData *column, Oid typid, int32 typmod, + MemoryContext mcxt, bool json); +static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod, + const char *colname, MemoryContext mcxt, Datum defaultval, + JsValue *jsv, bool *isnull); +static RecordIOData * allocate_record_info(MemoryContext mcxt, int ncolumns); +static bool JsObjectGetField(JsObject *obj, char *field, JsValue *jsv); +static void populate_recordset_record(PopulateRecordsetState *state, JsObject *obj); +static void populate_array_json(PopulateArrayContext *ctx, char *json, int len); +static void populate_array_dim_jsonb(PopulateArrayContext *ctx, JsonbValue *jbv, + int ndim); +static void populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim); +static void populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims); +static void populate_array_check_dimension(PopulateArrayContext *ctx, int ndim); +static void populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv); +static Datum populate_array(ArrayIOData *aio, const char *colname, + MemoryContext mcxt, JsValue *jsv); +static Datum populate_domain(DomainIOData *io, Oid typid, const char *colname, + MemoryContext mcxt, JsValue *jsv, bool isnull); /* Worker that takes care of common setup for us */ static JsonbValue *findJsonbValueFromContainerLen(JsonbContainer *container, @@ -302,7 +474,6 @@ static void transform_string_values_object_field_start(void *state, char *fname, static void transform_string_values_array_element_start(void *state, bool isnull); static void transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype); - /* * SQL function json_object_keys * @@ -2130,324 +2301,993 @@ json_to_record(PG_FUNCTION_ARGS) return populate_record_worker(fcinfo, "json_to_record", false); } -static Datum -populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, - bool have_record_arg) +/* helper function for diagnostics */ +static void +populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim) { - int json_arg_num = have_record_arg ? 1 : 0; - Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num); - text *json; - Jsonb *jb = NULL; - HTAB *json_hash = NULL; - HeapTupleHeader rec = NULL; - Oid tupType = InvalidOid; - int32 tupTypmod = -1; - TupleDesc tupdesc; - HeapTupleData tuple; - HeapTuple rettuple; - RecordIOData *my_extra; - int ncolumns; - int i; - Datum *values; - bool *nulls; + if (ndim <= 0) + { + if (ctx->colname) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected json array"), + errhint("see the value of key \"%s\"", ctx->colname))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected json array"))); + } + else + { + StringInfoData indices; + int i; - Assert(jtype == JSONOID || jtype == JSONBOID); + initStringInfo(&indices); - if (have_record_arg) - { - Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Assert(ctx->ndims > 0 && ndim < ctx->ndims); - if (!type_is_rowtype(argtype)) + for (i = 0; i < ndim; i++) + appendStringInfo(&indices, "[%d]", ctx->sizes[i]); + + if (ctx->colname) ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("first argument of %s must be a row type", - funcname))); + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected json array"), + errhint("see the array element %s of key \"%s\"", + indices.data, ctx->colname))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("expected json array"), + errhint("see the array element %s", + indices.data))); + } +} - if (PG_ARGISNULL(0)) - { - if (PG_ARGISNULL(1)) - PG_RETURN_NULL(); +/* set the number of dimensions of the populated array when it becomes known */ +static void +populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims) +{ + int i; - /* - * have no tuple to look at, so the only source of type info is - * the argtype. The lookup_rowtype_tupdesc call below will error - * out if we don't have a known composite type oid here. - */ - tupType = argtype; - tupTypmod = -1; - } - else - { - rec = PG_GETARG_HEAPTUPLEHEADER(0); + Assert(ctx->ndims <= 0); - if (PG_ARGISNULL(1)) - PG_RETURN_POINTER(rec); + if (ndims <= 0) + populate_array_report_expected_array(ctx, ndims); - /* Extract type info from the tuple itself */ - tupType = HeapTupleHeaderGetTypeId(rec); - tupTypmod = HeapTupleHeaderGetTypMod(rec); - } + ctx->ndims = ndims; + ctx->dims = palloc(sizeof(int) * ndims); + ctx->sizes = palloc0(sizeof(int) * ndims); - tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - } - else - { - /* json{b}_to_record case */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); + for (i = 0; i < ndims; i++) + ctx->dims[i] = -1; /* dimensions are unknown yet */ +} - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning record called in context " - "that cannot accept type record"), - errhint("Try calling the function in the FROM clause " - "using a column definition list."))); - } +/* check the populated subarray dimension */ +static void +populate_array_check_dimension(PopulateArrayContext *ctx, int ndim) +{ + int dim = ctx->sizes[ndim]; /* current dimension counter */ - if (jtype == JSONOID) - { - /* just get the text */ - json = PG_GETARG_TEXT_PP(json_arg_num); + if (ctx->dims[ndim] == -1) + ctx->dims[ndim] = dim; /* assign dimension if not yet known */ + else if (ctx->dims[ndim] != dim) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed json array"), + errdetail("Multidimensional arrays must have " + "sub-arrays with matching dimensions."))); - json_hash = get_json_object_as_hash(json, funcname); + /* reset the current array dimension size counter */ + ctx->sizes[ndim] = 0; - /* - * if the input json is empty, we can only skip the rest if we were - * passed in a non-null record, since otherwise there may be issues - * with domain nulls. - */ - if (hash_get_num_entries(json_hash) == 0 && rec) - { - hash_destroy(json_hash); - ReleaseTupleDesc(tupdesc); - PG_RETURN_POINTER(rec); - } - } - else - { - jb = PG_GETARG_JSONB(json_arg_num); + /* increment the parent dimension counter if it is a nested sub-array */ + if (ndim > 0) + ctx->sizes[ndim - 1]++; +} - /* same logic as for json */ - if (JB_ROOT_COUNT(jb) == 0 && rec) - { - ReleaseTupleDesc(tupdesc); - PG_RETURN_POINTER(rec); - } - } +static void +populate_array_element(PopulateArrayContext *ctx, int ndim, JsValue *jsv) +{ + Datum element; + bool element_isnull; - ncolumns = tupdesc->natts; + /* populate the array element */ + element = populate_record_field(ctx->aio->element_info, + ctx->aio->element_type, + ctx->aio->element_typmod, + NULL, ctx->mcxt, PointerGetDatum(NULL), + jsv, &element_isnull); - if (rec) - { - /* Build a temporary HeapTuple control structure */ - tuple.t_len = HeapTupleHeaderGetDatumLength(rec); - ItemPointerSetInvalid(&(tuple.t_self)); - tuple.t_tableOid = InvalidOid; - tuple.t_data = rec; - } + accumArrayResult(ctx->astate, element, element_isnull, + ctx->aio->element_type, ctx->acxt); - /* - * We arrange to look up the needed I/O info just once per series of - * calls, assuming the record type doesn't change underneath us. - */ - my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; - if (my_extra == NULL || - my_extra->ncolumns != ncolumns) - { - fcinfo->flinfo->fn_extra = - MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, - offsetof(RecordIOData, columns) + - ncolumns * sizeof(ColumnIOData)); - my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; - my_extra->record_type = InvalidOid; - my_extra->record_typmod = 0; - my_extra->ncolumns = ncolumns; - MemSet(my_extra->columns, 0, sizeof(ColumnIOData) * ncolumns); - } + Assert(ndim > 0); + ctx->sizes[ndim - 1]++; /* increment current dimension counter */ +} - if (have_record_arg && (my_extra->record_type != tupType || - my_extra->record_typmod != tupTypmod)) - { - MemSet(my_extra, 0, - offsetof(RecordIOData, columns) + - ncolumns * sizeof(ColumnIOData)); - my_extra->record_type = tupType; - my_extra->record_typmod = tupTypmod; - my_extra->ncolumns = ncolumns; - } +/* json object start handler for populate_array_json() */ +static void +populate_array_object_start(void *_state) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + int ndim = state->lex->lex_level; - values = (Datum *) palloc(ncolumns * sizeof(Datum)); - nulls = (bool *) palloc(ncolumns * sizeof(bool)); + if (state->ctx->ndims <= 0) + populate_array_assign_ndims(state->ctx, ndim); + else if (ndim < state->ctx->ndims) + populate_array_report_expected_array(state->ctx, ndim); +} - if (rec) - { - /* Break down the tuple into fields */ - heap_deform_tuple(&tuple, tupdesc, values, nulls); - } - else +/* json array end handler for populate_array_json() */ +static void +populate_array_array_end(void *_state) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + PopulateArrayContext *ctx = state->ctx; + int ndim = state->lex->lex_level; + + if (ctx->ndims <= 0) + populate_array_assign_ndims(ctx, ndim + 1); + + if (ndim < ctx->ndims) + populate_array_check_dimension(ctx, ndim); +} + +/* json array element start handler for populate_array_json() */ +static void +populate_array_element_start(void *_state, bool isnull) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + int ndim = state->lex->lex_level; + + if (state->ctx->ndims <= 0 || ndim == state->ctx->ndims) { - for (i = 0; i < ncolumns; ++i) - { - values[i] = (Datum) 0; - nulls[i] = true; - } + /* remember current array element start */ + state->element_start = state->lex->token_start; + state->element_type = state->lex->token_type; + state->element_scalar = NULL; } +} - for (i = 0; i < ncolumns; ++i) +/* json array element end handler for populate_array_json() */ +static void +populate_array_element_end(void *_state, bool isnull) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + PopulateArrayContext *ctx = state->ctx; + int ndim = state->lex->lex_level; + + Assert(ctx->ndims > 0); + + if (ndim == ctx->ndims) { - ColumnIOData *column_info = &my_extra->columns[i]; - Oid column_type = tupdesc->attrs[i]->atttypid; - JsonbValue *v = NULL; - JsonHashEntry *hashentry = NULL; + JsValue jsv; - /* Ignore dropped columns in datatype */ - if (tupdesc->attrs[i]->attisdropped) + jsv.is_json = true; + jsv.val.json.type = state->element_type; + + if (isnull) { - nulls[i] = true; - continue; + Assert(jsv.val.json.type == JSON_TOKEN_NULL); + jsv.val.json.str = NULL; + jsv.val.json.len = 0; } - - if (jtype == JSONOID) + else if (state->element_scalar) { - hashentry = hash_search(json_hash, - NameStr(tupdesc->attrs[i]->attname), - HASH_FIND, NULL); + jsv.val.json.str = state->element_scalar; + jsv.val.json.len = -1; /* null-terminated */ } else { - char *key = NameStr(tupdesc->attrs[i]->attname); - - v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT, key, - strlen(key)); + jsv.val.json.str = state->element_start; + jsv.val.json.len = (state->lex->prev_token_terminator - + state->element_start) * sizeof(char); } - /* - * we can't just skip here if the key wasn't found since we might have - * a domain to deal with. If we were passed in a non-null record - * datum, we assume that the existing values are valid (if they're - * not, then it's not our fault), but if we were passed in a null, - * then every field which we don't populate needs to be run through - * the input function just in case it's a domain type. - */ - if (((jtype == JSONOID && hashentry == NULL) || - (jtype == JSONBOID && v == NULL)) && rec) - continue; + populate_array_element(ctx, ndim, &jsv); + } +} - /* - * Prepare to convert the column value from text - */ - if (column_info->column_type != column_type) - { - getTypeInputInfo(column_type, - &column_info->typiofunc, - &column_info->typioparam); - fmgr_info_cxt(column_info->typiofunc, &column_info->proc, - fcinfo->flinfo->fn_mcxt); - column_info->column_type = column_type; - } - if ((jtype == JSONOID && (hashentry == NULL || hashentry->isnull)) || - (jtype == JSONBOID && (v == NULL || v->type == jbvNull))) - { - /* - * need InputFunctionCall to happen even for nulls, so that domain - * checks are done - */ - values[i] = InputFunctionCall(&column_info->proc, NULL, - column_info->typioparam, - tupdesc->attrs[i]->atttypmod); - nulls[i] = true; - } - else - { - char *s = NULL; +/* json scalar handler for populate_array_json() */ +static void +populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + PopulateArrayContext *ctx = state->ctx; + int ndim = state->lex->lex_level; - if (jtype == JSONOID) - { - /* already done the hard work in the json case */ - s = hashentry->val; - } - else - { - if (v->type == jbvString) - s = pnstrdup(v->val.string.val, v->val.string.len); - else if (v->type == jbvBool) - s = pnstrdup((v->val.boolean) ? "t" : "f", 1); - else if (v->type == jbvNumeric) - s = DatumGetCString(DirectFunctionCall1(numeric_out, - PointerGetDatum(v->val.numeric))); - else if (v->type == jbvBinary) - s = JsonbToCString(NULL, (JsonbContainer *) v->val.binary.data, v->val.binary.len); - else - elog(ERROR, "unrecognized jsonb type: %d", (int) v->type); - } + if (ctx->ndims <= 0) + populate_array_assign_ndims(ctx, ndim); + else if (ndim < ctx->ndims) + populate_array_report_expected_array(ctx, ndim); - values[i] = InputFunctionCall(&column_info->proc, s, - column_info->typioparam, - tupdesc->attrs[i]->atttypmod); - nulls[i] = false; - } + if (ndim == ctx->ndims) + { + /* remember the scalar element token */ + state->element_scalar = token; + /* element_type must already be set in populate_array_element_start() */ + Assert(state->element_type == tokentype); } +} + +/* parse a json array and populate array */ +static void +populate_array_json(PopulateArrayContext *ctx, char *json, int len) +{ + PopulateArrayState state; + JsonSemAction sem; - rettuple = heap_form_tuple(tupdesc, values, nulls); + state.lex = makeJsonLexContextCstringLen(json, len, true); + state.ctx = ctx; - ReleaseTupleDesc(tupdesc); + memset(&sem, 0, sizeof(sem)); + sem.semstate = (void *) &state; + sem.object_start = populate_array_object_start; + sem.array_end = populate_array_array_end; + sem.array_element_start = populate_array_element_start; + sem.array_element_end = populate_array_element_end; + sem.scalar = populate_array_scalar; - if (json_hash) - hash_destroy(json_hash); + pg_parse_json(state.lex, &sem); - PG_RETURN_DATUM(HeapTupleGetDatum(rettuple)); + /* number of dimensions should be already known */ + Assert(ctx->ndims > 0 && ctx->dims); + + pfree(state.lex); } /* - * get_json_object_as_hash - * - * decompose a json object into a hash table. + * populate_array_dim_jsonb() -- Iterate recursively through jsonb sub-array + * elements and accumulate result using given ArrayBuildState. */ -static HTAB * -get_json_object_as_hash(text *json, const char *funcname) +static void +populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */ + JsonbValue *jbv, /* jsonb sub-array */ + int ndim) /* current dimension */ { - HASHCTL ctl; - HTAB *tab; - JHashState *state; - JsonLexContext *lex = makeJsonLexContext(json, true); - JsonSemAction *sem; + JsonbContainer *jbc = jbv->val.binary.data; + JsonbIterator *it; + JsonbIteratorToken tok; + JsonbValue val; + JsValue jsv; - memset(&ctl, 0, sizeof(ctl)); - ctl.keysize = NAMEDATALEN; - ctl.entrysize = sizeof(JsonHashEntry); - ctl.hcxt = CurrentMemoryContext; - tab = hash_create("json object hashtable", - 100, - &ctl, - HASH_ELEM | HASH_CONTEXT); + check_stack_depth(); - state = palloc0(sizeof(JHashState)); - sem = palloc0(sizeof(JsonSemAction)); + if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc)) + populate_array_report_expected_array(ctx, ndim - 1); - state->function_name = funcname; - state->hash = tab; - state->lex = lex; + Assert(!JsonContainerIsScalar(jbc)); - sem->semstate = (void *) state; - sem->array_start = hash_array_start; - sem->scalar = hash_scalar; - sem->object_field_start = hash_object_field_start; - sem->object_field_end = hash_object_field_end; + it = JsonbIteratorInit(jbc); - pg_parse_json(lex, sem); + tok = JsonbIteratorNext(&it, &val, true); + Assert(tok == WJB_BEGIN_ARRAY); - return tab; -} + tok = JsonbIteratorNext(&it, &val, true); -static void -hash_object_field_start(void *state, char *fname, bool isnull) -{ - JHashState *_state = (JHashState *) state; + /* + * If the number of dimensions is not yet known and + * we have found end of the array, or the first child element is not + * an array, then assign the number of dimensions now. + */ + if (ctx->ndims <= 0 && + (tok == WJB_END_ARRAY || + (tok == WJB_ELEM && + (val.type != jbvBinary || + !JsonContainerIsArray(val.val.binary.data))))) + populate_array_assign_ndims(ctx, ndim); + + jsv.is_json = false; + jsv.val.jsonb = &val; + + /* process all the array elements */ + while (tok == WJB_ELEM) + { + /* + * Recurse only if the dimensions of dimensions is still unknown or + * if it is not the innermost dimension. + */ + if (ctx->ndims > 0 && ndim >= ctx->ndims) + populate_array_element(ctx, ndim, &jsv); + else + { + /* populate child sub-array */ + populate_array_dim_jsonb(ctx, &val, ndim + 1); + + /* number of dimensions should be already known */ + Assert(ctx->ndims > 0 && ctx->dims); + + populate_array_check_dimension(ctx, ndim); + } + + tok = JsonbIteratorNext(&it, &val, true); + } + + Assert(tok == WJB_END_ARRAY); + + /* free iterator, iterating until WJB_DONE */ + tok = JsonbIteratorNext(&it, &val, true); + Assert(tok == WJB_DONE && !it); +} + +/* recursively populate an array from json/jsonb */ +static Datum +populate_array(ArrayIOData *aio, + const char *colname, + MemoryContext mcxt, + JsValue *jsv) +{ + PopulateArrayContext ctx; + Datum result; + int *lbs; + int i; + + ctx.aio = aio; + ctx.mcxt = mcxt; + ctx.acxt = CurrentMemoryContext; + ctx.astate = initArrayResult(aio->element_type, ctx.acxt, true); + ctx.colname = colname; + ctx.ndims = 0; /* unknown yet */ + ctx.dims = NULL; + ctx.sizes = NULL; + + if (jsv->is_json) + populate_array_json(&ctx, jsv->val.json.str, + jsv->val.json.len >= 0 ? jsv->val.json.len + : strlen(jsv->val.json.str)); + else + { + populate_array_dim_jsonb(&ctx, jsv->val.jsonb, 1); + ctx.dims[0] = ctx.sizes[0]; + } + + Assert(ctx.ndims > 0); + + lbs = palloc(sizeof(int) * ctx.ndims); + + for (i = 0; i < ctx.ndims; i++) + lbs[i] = 1; + + result = makeMdArrayResult(ctx.astate, ctx.ndims, ctx.dims, lbs, + ctx.acxt, true); + + pfree(ctx.dims); + pfree(ctx.sizes); + pfree(lbs); + + return result; +} + +static void +JsValueToJsObject(JsValue *jsv, JsObject *jso) +{ + jso->is_json = jsv->is_json; + + if (jsv->is_json) + { + /* convert plain-text json into a hash table */ + jso->val.json_hash = + get_json_object_as_hash(jsv->val.json.str, + jsv->val.json.len >= 0 + ? jsv->val.json.len + : strlen(jsv->val.json.str), + "populate_composite"); + } + else + { + JsonbValue *jbv = jsv->val.jsonb; + + if (jbv->type == jbvBinary && + JsonContainerIsObject(jbv->val.binary.data)) + jso->val.jsonb_cont = jbv->val.binary.data; + else + jso->val.jsonb_cont = NULL; + } +} + +/* recursively populate a composite (row type) value from json/jsonb */ +static Datum +populate_composite(CompositeIOData *io, + Oid typid, + int32 typmod, + const char *colname, + MemoryContext mcxt, + HeapTupleHeader defaultval, + JsValue *jsv) +{ + HeapTupleHeader tuple; + JsObject jso; + + /* acquire cached tuple descriptor */ + if (!io->tupdesc || + io->tupdesc->tdtypeid != typid || + io->tupdesc->tdtypmod != typmod) + { + TupleDesc tupdesc = lookup_rowtype_tupdesc(typid, typmod); + MemoryContext oldcxt; + + if (io->tupdesc) + FreeTupleDesc(io->tupdesc); + + /* copy tuple desc without constraints into cache memory context */ + oldcxt = MemoryContextSwitchTo(mcxt); + io->tupdesc = CreateTupleDescCopy(tupdesc); + MemoryContextSwitchTo(oldcxt); + + ReleaseTupleDesc(tupdesc); + } + + /* prepare input value */ + JsValueToJsObject(jsv, &jso); + + /* populate resulting record tuple */ + tuple = populate_record(io->tupdesc, &io->record_io, + defaultval, mcxt, &jso); + + JsObjectFree(&jso); + + return HeapTupleHeaderGetDatum(tuple); +} + +/* populate non-null scalar value from json/jsonb value */ +static Datum +populate_scalar(ScalarIOData *io, Oid typid, int32 typmod, JsValue *jsv) +{ + Datum res; + char *str = NULL; + char *json = NULL; + + if (jsv->is_json) + { + int len = jsv->val.json.len; + + json = jsv->val.json.str; + Assert(json); + + /* already done the hard work in the json case */ + if ((typid == JSONOID || typid == JSONBOID) && + jsv->val.json.type == JSON_TOKEN_STRING) + { + /* + * Add quotes around string value (should be already escaped) + * if converting to json/jsonb. + */ + + if (len < 0) + len = strlen(json); + + str = palloc(len + sizeof(char) * 3); + str[0] = '"'; + memcpy(&str[1], json, len); + str[len + 1] = '"'; + str[len + 2] = '\0'; + } + else if (len >= 0) + { + /* Need to copy non-null-terminated string */ + str = palloc(len + 1 * sizeof(char)); + memcpy(str, json, len); + str[len] = '\0'; + } + else + str = json; /* null-terminated string */ + } + else + { + JsonbValue *jbv = jsv->val.jsonb; + + if (typid == JSONBOID) + { + Jsonb *jsonb = JsonbValueToJsonb(jbv); /* directly use jsonb */ + return JsonbGetDatum(jsonb); + } + /* convert jsonb to string for typio call */ + else if (typid == JSONOID && jbv->type != jbvBinary) + { + /* + * Convert scalar jsonb (non-scalars are passed here as jbvBinary) + * to json string, preserving quotes around top-level strings. + */ + Jsonb *jsonb = JsonbValueToJsonb(jbv); + str = JsonbToCString(NULL, &jsonb->root, VARSIZE(jsonb)); + } + else if (jbv->type == jbvString) /* quotes are stripped */ + str = pnstrdup(jbv->val.string.val, jbv->val.string.len); + else if (jbv->type == jbvBool) + str = pstrdup(jbv->val.boolean ? "true" : "false"); + else if (jbv->type == jbvNumeric) + str = DatumGetCString(DirectFunctionCall1(numeric_out, + PointerGetDatum(jbv->val.numeric))); + else if (jbv->type == jbvBinary) + str = JsonbToCString(NULL, jbv->val.binary.data, + jbv->val.binary.len); + else + elog(ERROR, "unrecognized jsonb type: %d", (int) jbv->type); + } + + res = InputFunctionCall(&io->typiofunc, str, io->typioparam, typmod); + + /* free temporary buffer */ + if (str != json) + pfree(str); + + return res; +} + +static Datum +populate_domain(DomainIOData *io, + Oid typid, + const char *colname, + MemoryContext mcxt, + JsValue *jsv, + bool isnull) +{ + Datum res; + + if (isnull) + res = (Datum) 0; + else + { + res = populate_record_field(io->base_io, + io->base_typid, io->base_typmod, + colname, mcxt, PointerGetDatum(NULL), + jsv, &isnull); + Assert(!isnull); + } + + domain_check(res, isnull, typid, &io->domain_info, mcxt); + + return res; +} + +/* prepare column metadata cache for the given type */ +static void +prepare_column_cache(ColumnIOData *column, + Oid typid, + int32 typmod, + MemoryContext mcxt, + bool json) +{ + HeapTuple tup; + Form_pg_type type; + + column->typid = typid; + column->typmod = typmod; + + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", typid); + + type = (Form_pg_type) GETSTRUCT(tup); + + if (type->typtype == TYPTYPE_DOMAIN) + { + column->typcat = TYPECAT_DOMAIN; + column->io.domain.base_typid = type->typbasetype; + column->io.domain.base_typmod = type->typtypmod; + column->io.domain.base_io = MemoryContextAllocZero(mcxt, + sizeof(ColumnIOData)); + column->io.domain.domain_info = NULL; + } + else if (type->typtype == TYPTYPE_COMPOSITE || typid == RECORDOID) + { + column->typcat = TYPECAT_COMPOSITE; + column->io.composite.record_io = NULL; + column->io.composite.tupdesc = NULL; + } + else if (type->typlen == -1 && OidIsValid(type->typelem)) + { + column->typcat = TYPECAT_ARRAY; + column->io.array.element_info = MemoryContextAllocZero(mcxt, + sizeof(ColumnIOData)); + column->io.array.element_type = type->typelem; + /* array element typemod stored in attribute's typmod */ + column->io.array.element_typmod = typmod; + } + else + column->typcat = TYPECAT_SCALAR; + + /* don't need input function when converting from jsonb to jsonb */ + if (json || typid != JSONBOID) + { + Oid typioproc; + + getTypeInputInfo(typid, &typioproc, &column->scalar_io.typioparam); + fmgr_info_cxt(typioproc, &column->scalar_io.typiofunc, mcxt); + } + + ReleaseSysCache(tup); +} + +/* recursively populate a record field or an array element from a json/jsonb value */ +static Datum +populate_record_field(ColumnIOData *col, + Oid typid, + int32 typmod, + const char *colname, + MemoryContext mcxt, + Datum defaultval, + JsValue *jsv, + bool *isnull) +{ + TypeCat typcat; + + check_stack_depth(); + + /* prepare column metadata cache for the given type */ + if (col->typid != typid || col->typmod != typmod) + prepare_column_cache(col, typid, typmod, mcxt, jsv->is_json); + + *isnull = JsValueIsNull(jsv); + + typcat = col->typcat; + + /* try to convert json string to a non-scalar type through input function */ + if (JsValueIsString(jsv) && + (typcat == TYPECAT_ARRAY || typcat == TYPECAT_COMPOSITE)) + typcat = TYPECAT_SCALAR; + + /* we must perform domain checks for NULLs */ + if (*isnull && typcat != TYPECAT_DOMAIN) + return (Datum) 0; + + switch (typcat) + { + case TYPECAT_SCALAR: + return populate_scalar(&col->scalar_io, typid, typmod, jsv); + + case TYPECAT_ARRAY: + return populate_array(&col->io.array, colname, mcxt, jsv); + + case TYPECAT_COMPOSITE: + return populate_composite(&col->io.composite, typid, typmod, + colname, mcxt, + DatumGetPointer(defaultval) + ? DatumGetHeapTupleHeader(defaultval) + : NULL, + jsv); + + case TYPECAT_DOMAIN: + return populate_domain(&col->io.domain, typid, colname, mcxt, + jsv, *isnull); + + default: + elog(ERROR, "unrecognized type category '%c'", typcat); + return (Datum) 0; + } +} + +static RecordIOData * +allocate_record_info(MemoryContext mcxt, int ncolumns) +{ + RecordIOData *data = (RecordIOData *) + MemoryContextAlloc(mcxt, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + + data->record_type = InvalidOid; + data->record_typmod = 0; + data->ncolumns = ncolumns; + MemSet(data->columns, 0, sizeof(ColumnIOData) * ncolumns); + + return data; +} + +static bool +JsObjectGetField(JsObject *obj, char *field, JsValue *jsv) +{ + jsv->is_json = obj->is_json; + + if (jsv->is_json) + { + JsonHashEntry *hashentry = hash_search(obj->val.json_hash, field, + HASH_FIND, NULL); + + jsv->val.json.type = hashentry ? hashentry->type : JSON_TOKEN_NULL; + jsv->val.json.str = jsv->val.json.type == JSON_TOKEN_NULL ? NULL : + hashentry->val; + jsv->val.json.len = jsv->val.json.str ? -1 : 0; /* null-terminated */ + + return hashentry != NULL; + } + else + { + jsv->val.jsonb = !obj->val.jsonb_cont ? NULL : + findJsonbValueFromContainerLen(obj->val.jsonb_cont, JB_FOBJECT, + field, strlen(field)); + + return jsv->val.jsonb != NULL; + } +} + +/* populate a record tuple from json/jsonb value */ +static HeapTupleHeader +populate_record(TupleDesc tupdesc, + RecordIOData **precord, + HeapTupleHeader defaultval, + MemoryContext mcxt, + JsObject *obj) +{ + RecordIOData *record = *precord; + Datum *values; + bool *nulls; + HeapTuple res; + int ncolumns = tupdesc->natts; + int i; + + /* + * if the input json is empty, we can only skip the rest if we were + * passed in a non-null record, since otherwise there may be issues + * with domain nulls. + */ + if (defaultval && JsObjectIsEmpty(obj)) + return defaultval; + + /* (re)allocate metadata cache */ + if (record == NULL || + record->ncolumns != ncolumns) + *precord = record = allocate_record_info(mcxt, ncolumns); + + /* invalidate metadata cache if the record type has changed */ + if (record->record_type != tupdesc->tdtypeid || + record->record_typmod != tupdesc->tdtypmod) + { + MemSet(record, 0, offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + record->record_type = tupdesc->tdtypeid; + record->record_typmod = tupdesc->tdtypmod; + record->ncolumns = ncolumns; + } + + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + + if (defaultval) + { + HeapTupleData tuple; + + /* Build a temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(defaultval); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = defaultval; + + /* Break down the tuple into fields */ + heap_deform_tuple(&tuple, tupdesc, values, nulls); + } + else + { + for (i = 0; i < ncolumns; ++i) + { + values[i] = (Datum) 0; + nulls[i] = true; + } + } + + for (i = 0; i < ncolumns; ++i) + { + Form_pg_attribute att = tupdesc->attrs[i]; + char *colname = NameStr(att->attname); + JsValue field = { 0 }; + bool found; + + /* Ignore dropped columns in datatype */ + if (att->attisdropped) + { + nulls[i] = true; + continue; + } + + found = JsObjectGetField(obj, colname, &field); + + /* + * we can't just skip here if the key wasn't found since we might have + * a domain to deal with. If we were passed in a non-null record + * datum, we assume that the existing values are valid (if they're + * not, then it's not our fault), but if we were passed in a null, + * then every field which we don't populate needs to be run through + * the input function just in case it's a domain type. + */ + if (defaultval && !found) + continue; + + values[i] = populate_record_field(&record->columns[i], + att->atttypid, + att->atttypmod, + colname, + mcxt, + nulls[i] ? (Datum) 0 : values[i], + &field, + &nulls[i]); + } + + res = heap_form_tuple(tupdesc, values, nulls); + + pfree(values); + pfree(nulls); + + return res->t_data; +} + +static Datum +populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, + bool have_record_arg) +{ + int json_arg_num = have_record_arg ? 1 : 0; + Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num); + JsValue jsv = { 0 }; + HeapTupleHeader rec = NULL; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc = NULL; + Datum rettuple; + JsonbValue jbv; + MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt; + PopulateRecordCache *cache = fcinfo->flinfo->fn_extra; + + Assert(jtype == JSONOID || jtype == JSONBOID); + + /* + * We arrange to look up the needed I/O info just once per series of + * calls, assuming the record type doesn't change underneath us. + */ + if (!cache) + fcinfo->flinfo->fn_extra = cache = + MemoryContextAllocZero(fnmcxt, sizeof(*cache)); + + if (have_record_arg) + { + Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + + if (cache->argtype != argtype) + { + if (!type_is_rowtype(argtype)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("first argument of %s must be a row type", + funcname))); + + cache->argtype = argtype; + } + + if (PG_ARGISNULL(0)) + { + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + /* + * We have no tuple to look at, so the only source of type info is + * the argtype. The lookup_rowtype_tupdesc call below will error + * out if we don't have a known composite type oid here. + */ + tupType = argtype; + tupTypmod = -1; + } + else + { + rec = PG_GETARG_HEAPTUPLEHEADER(0); + + if (PG_ARGISNULL(1)) + PG_RETURN_POINTER(rec); + + /* Extract type info from the tuple itself */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); + } + } + else + { + /* json{b}_to_record case */ + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"), + errhint("Try calling the function in the FROM clause " + "using a column definition list."))); + + Assert(tupdesc); + + /* + * Add tupdesc to the cache and set the appropriate values of + * tupType/tupTypmod for proper cache usage in populate_composite(). + */ + cache->io.tupdesc = tupdesc; + + tupType = tupdesc->tdtypeid; + tupTypmod = tupdesc->tdtypmod; + } + + jsv.is_json = jtype == JSONOID; + + if (jsv.is_json) + { + text *json = PG_GETARG_TEXT_PP(json_arg_num); + + jsv.val.json.str = VARDATA_ANY(json); + jsv.val.json.len = VARSIZE_ANY_EXHDR(json); + jsv.val.json.type = JSON_TOKEN_INVALID; /* not used in populate_composite() */ + } + else + { + Jsonb *jb = PG_GETARG_JSONB(json_arg_num); + + jsv.val.jsonb = &jbv; + + /* fill binary jsonb value pointing to jb */ + jbv.type = jbvBinary; + jbv.val.binary.data = &jb->root; + jbv.val.binary.len = VARSIZE(jb) - VARHDRSZ; + } + + rettuple = populate_composite(&cache->io, tupType, tupTypmod, + NULL, fnmcxt, rec, &jsv); + + if (tupdesc) + { + cache->io.tupdesc = NULL; + ReleaseTupleDesc(tupdesc); + } + + PG_RETURN_DATUM(rettuple); +} + +/* + * get_json_object_as_hash + * + * decompose a json object into a hash table. + */ +static HTAB * +get_json_object_as_hash(char *json, int len, const char *funcname) +{ + HASHCTL ctl; + HTAB *tab; + JHashState *state; + JsonLexContext *lex = makeJsonLexContextCstringLen(json, len, true); + JsonSemAction *sem; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(JsonHashEntry); + ctl.hcxt = CurrentMemoryContext; + tab = hash_create("json object hashtable", + 100, + &ctl, + HASH_ELEM | HASH_CONTEXT); + + state = palloc0(sizeof(JHashState)); + sem = palloc0(sizeof(JsonSemAction)); + + state->function_name = funcname; + state->hash = tab; + state->lex = lex; + + sem->semstate = (void *) state; + sem->array_start = hash_array_start; + sem->scalar = hash_scalar; + sem->object_field_start = hash_object_field_start; + sem->object_field_end = hash_object_field_end; + + pg_parse_json(lex, sem); + + return tab; +} + +static void +hash_object_field_start(void *state, char *fname, bool isnull) +{ + JHashState *_state = (JHashState *) state; if (_state->lex->lex_level > 1) return; + /* remember token type */ + _state->saved_token_type = _state->lex->token_type; + if (_state->lex->token_type == JSON_TOKEN_ARRAY_START || _state->lex->token_type == JSON_TOKEN_OBJECT_START) { @@ -2491,7 +3331,9 @@ hash_object_field_end(void *state, char *fname, bool isnull) * that, a later field with the same name overrides the earlier field. */ - hashentry->isnull = isnull; + hashentry->type = _state->saved_token_type; + Assert(isnull == (hashentry->type == JSON_TOKEN_NULL)); + if (_state->save_json_start != NULL) { int len = _state->lex->prev_token_terminator - _state->save_json_start; @@ -2530,7 +3372,11 @@ hash_scalar(void *state, char *token, JsonTokenType tokentype) errmsg("cannot call %s on a scalar", _state->function_name))); if (_state->lex->lex_level == 1) + { _state->saved_scalar = token; + /* saved_token_type must already be set in hash_object_field_start() */ + Assert(_state->saved_token_type == tokentype); + } } @@ -2569,121 +3415,21 @@ json_to_recordset(PG_FUNCTION_ARGS) } static void -make_row_from_rec_and_jsonb(Jsonb *element, PopulateRecordsetState *state) +populate_recordset_record(PopulateRecordsetState *state, JsObject *obj) { - Datum *values; - bool *nulls; - int i; - RecordIOData *my_extra = state->my_extra; - int ncolumns = my_extra->ncolumns; - TupleDesc tupdesc = state->ret_tdesc; - HeapTupleHeader rec = state->rec; - HeapTuple rettuple; - - values = (Datum *) palloc(ncolumns * sizeof(Datum)); - nulls = (bool *) palloc(ncolumns * sizeof(bool)); - - if (state->rec) - { - HeapTupleData tuple; - - /* Build a temporary HeapTuple control structure */ - tuple.t_len = HeapTupleHeaderGetDatumLength(state->rec); - ItemPointerSetInvalid(&(tuple.t_self)); - tuple.t_tableOid = InvalidOid; - tuple.t_data = state->rec; - - /* Break down the tuple into fields */ - heap_deform_tuple(&tuple, tupdesc, values, nulls); - } - else - { - for (i = 0; i < ncolumns; ++i) - { - values[i] = (Datum) 0; - nulls[i] = true; - } - } - - for (i = 0; i < ncolumns; ++i) - { - ColumnIOData *column_info = &my_extra->columns[i]; - Oid column_type = tupdesc->attrs[i]->atttypid; - JsonbValue *v = NULL; - char *key; - - /* Ignore dropped columns in datatype */ - if (tupdesc->attrs[i]->attisdropped) - { - nulls[i] = true; - continue; - } - - key = NameStr(tupdesc->attrs[i]->attname); - - v = findJsonbValueFromContainerLen(&element->root, JB_FOBJECT, - key, strlen(key)); - - /* - * We can't just skip here if the key wasn't found since we might have - * a domain to deal with. If we were passed in a non-null record - * datum, we assume that the existing values are valid (if they're - * not, then it's not our fault), but if we were passed in a null, - * then every field which we don't populate needs to be run through - * the input function just in case it's a domain type. - */ - if (v == NULL && rec) - continue; - - /* - * Prepare to convert the column value from text - */ - if (column_info->column_type != column_type) - { - getTypeInputInfo(column_type, - &column_info->typiofunc, - &column_info->typioparam); - fmgr_info_cxt(column_info->typiofunc, &column_info->proc, - state->fn_mcxt); - column_info->column_type = column_type; - } - if (v == NULL || v->type == jbvNull) - { - /* - * Need InputFunctionCall to happen even for nulls, so that domain - * checks are done - */ - values[i] = InputFunctionCall(&column_info->proc, NULL, - column_info->typioparam, - tupdesc->attrs[i]->atttypmod); - nulls[i] = true; - } - else - { - char *s = NULL; - - if (v->type == jbvString) - s = pnstrdup(v->val.string.val, v->val.string.len); - else if (v->type == jbvBool) - s = pnstrdup((v->val.boolean) ? "t" : "f", 1); - else if (v->type == jbvNumeric) - s = DatumGetCString(DirectFunctionCall1(numeric_out, - PointerGetDatum(v->val.numeric))); - else if (v->type == jbvBinary) - s = JsonbToCString(NULL, (JsonbContainer *) v->val.binary.data, v->val.binary.len); - else - elog(ERROR, "unrecognized jsonb type: %d", (int) v->type); - - values[i] = InputFunctionCall(&column_info->proc, s, - column_info->typioparam, - tupdesc->attrs[i]->atttypmod); - nulls[i] = false; - } - } - - rettuple = heap_form_tuple(tupdesc, values, nulls); - - tuplestore_puttuple(state->tuple_store, rettuple); + HeapTupleData tuple; + HeapTupleHeader tuphead = populate_record(state->ret_tdesc, + state->my_extra, + state->rec, + state->fn_mcxt, + obj); + + tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = tuphead; + + tuplestore_puttuple(state->tuple_store, &tuple); } /* @@ -2697,12 +3443,8 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num); ReturnSetInfo *rsi; MemoryContext old_cxt; - Oid tupType; - int32 tupTypmod; HeapTupleHeader rec; TupleDesc tupdesc; - RecordIOData *my_extra; - int ncolumns; PopulateRecordsetState *state; if (have_record_arg) @@ -2748,38 +3490,6 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, else rec = PG_GETARG_HEAPTUPLEHEADER(0); - tupType = tupdesc->tdtypeid; - tupTypmod = tupdesc->tdtypmod; - ncolumns = tupdesc->natts; - - /* - * We arrange to look up the needed I/O info just once per series of - * calls, assuming the record type doesn't change underneath us. - */ - my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; - if (my_extra == NULL || - my_extra->ncolumns != ncolumns) - { - fcinfo->flinfo->fn_extra = - MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, - offsetof(RecordIOData, columns) + - ncolumns * sizeof(ColumnIOData)); - my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; - my_extra->record_type = InvalidOid; - my_extra->record_typmod = 0; - } - - if (my_extra->record_type != tupType || - my_extra->record_typmod != tupTypmod) - { - MemSet(my_extra, 0, - offsetof(RecordIOData, columns) + - ncolumns * sizeof(ColumnIOData)); - my_extra->record_type = tupType; - my_extra->record_typmod = tupTypmod; - my_extra->ncolumns = ncolumns; - } - state = palloc0(sizeof(PopulateRecordsetState)); /* make these in a sufficiently long-lived memory context */ @@ -2792,7 +3502,7 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, MemoryContextSwitchTo(old_cxt); state->function_name = funcname; - state->my_extra = my_extra; + state->my_extra = (RecordIOData **) &fcinfo->flinfo->fn_extra; state->rec = rec; state->fn_mcxt = fcinfo->flinfo->fn_mcxt; @@ -2843,14 +3553,19 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, if (r == WJB_ELEM) { - Jsonb *element = JsonbValueToJsonb(&v); + JsObject obj; - if (!JB_ROOT_IS_OBJECT(element)) + if (v.type != jbvBinary || + !JsonContainerIsObject(v.val.binary.data)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument of %s must be an array of objects", funcname))); - make_row_from_rec_and_jsonb(element, state); + + obj.is_json = false; + obj.val.jsonb_cont = v.val.binary.data; + + populate_recordset_record(state, &obj); } } } @@ -2894,115 +3609,20 @@ static void populate_recordset_object_end(void *state) { PopulateRecordsetState *_state = (PopulateRecordsetState *) state; - HTAB *json_hash = _state->json_hash; - Datum *values; - bool *nulls; - int i; - RecordIOData *my_extra = _state->my_extra; - int ncolumns = my_extra->ncolumns; - TupleDesc tupdesc = _state->ret_tdesc; - JsonHashEntry *hashentry; - HeapTupleHeader rec = _state->rec; - HeapTuple rettuple; + JsObject obj; /* Nested objects require no special processing */ if (_state->lex->lex_level > 1) return; - /* Otherwise, construct and return a tuple based on this level-1 object */ - values = (Datum *) palloc(ncolumns * sizeof(Datum)); - nulls = (bool *) palloc(ncolumns * sizeof(bool)); - - if (_state->rec) - { - HeapTupleData tuple; - - /* Build a temporary HeapTuple control structure */ - tuple.t_len = HeapTupleHeaderGetDatumLength(_state->rec); - ItemPointerSetInvalid(&(tuple.t_self)); - tuple.t_tableOid = InvalidOid; - tuple.t_data = _state->rec; - - /* Break down the tuple into fields */ - heap_deform_tuple(&tuple, tupdesc, values, nulls); - } - else - { - for (i = 0; i < ncolumns; ++i) - { - values[i] = (Datum) 0; - nulls[i] = true; - } - } - - for (i = 0; i < ncolumns; ++i) - { - ColumnIOData *column_info = &my_extra->columns[i]; - Oid column_type = tupdesc->attrs[i]->atttypid; - char *value; - - /* Ignore dropped columns in datatype */ - if (tupdesc->attrs[i]->attisdropped) - { - nulls[i] = true; - continue; - } - - hashentry = hash_search(json_hash, - NameStr(tupdesc->attrs[i]->attname), - HASH_FIND, NULL); - - /* - * we can't just skip here if the key wasn't found since we might have - * a domain to deal with. If we were passed in a non-null record - * datum, we assume that the existing values are valid (if they're - * not, then it's not our fault), but if we were passed in a null, - * then every field which we don't populate needs to be run through - * the input function just in case it's a domain type. - */ - if (hashentry == NULL && rec) - continue; - - /* - * Prepare to convert the column value from text - */ - if (column_info->column_type != column_type) - { - getTypeInputInfo(column_type, - &column_info->typiofunc, - &column_info->typioparam); - fmgr_info_cxt(column_info->typiofunc, &column_info->proc, - _state->fn_mcxt); - column_info->column_type = column_type; - } - if (hashentry == NULL || hashentry->isnull) - { - /* - * need InputFunctionCall to happen even for nulls, so that domain - * checks are done - */ - values[i] = InputFunctionCall(&column_info->proc, NULL, - column_info->typioparam, - tupdesc->attrs[i]->atttypmod); - nulls[i] = true; - } - else - { - value = hashentry->val; - - values[i] = InputFunctionCall(&column_info->proc, value, - column_info->typioparam, - tupdesc->attrs[i]->atttypmod); - nulls[i] = false; - } - } - - rettuple = heap_form_tuple(tupdesc, values, nulls); + obj.is_json = true; + obj.val.json_hash = _state->json_hash; - tuplestore_puttuple(_state->tuple_store, rettuple); + /* Otherwise, construct and return a tuple based on this level-1 object */ + populate_recordset_record(_state, &obj); /* Done with hash for this object */ - hash_destroy(json_hash); + hash_destroy(_state->json_hash); _state->json_hash = NULL; } @@ -3048,6 +3668,8 @@ populate_recordset_object_field_start(void *state, char *fname, bool isnull) if (_state->lex->lex_level > 2) return; + _state->saved_token_type = _state->lex->token_type; + if (_state->lex->token_type == JSON_TOKEN_ARRAY_START || _state->lex->token_type == JSON_TOKEN_OBJECT_START) { @@ -3089,7 +3711,9 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull) * that, a later field with the same name overrides the earlier field. */ - hashentry->isnull = isnull; + hashentry->type = _state->saved_token_type; + Assert(isnull == (hashentry->type == JSON_TOKEN_NULL)); + if (_state->save_json_start != NULL) { int len = _state->lex->prev_token_terminator - _state->save_json_start; diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index a229472d54..b25e20ca20 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -1313,6 +1313,31 @@ select * from json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8 -- populate_record create type jpop as (a text, b int, c timestamp); +CREATE DOMAIN js_int_not_null AS int NOT NULL; +CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); +CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); +CREATE TYPE jsrec AS ( + i int, + ia _int4, + ia1 int[], + ia2 int[][], + ia3 int[][][], + ia1d js_int_array_1d, + ia2d js_int_array_2d, + t text, + ta text[], + c char(10), + ca char(10)[], + ts timestamp, + js json, + jsb jsonb, + jsa json[], + rec jpop, + reca jpop[] +); +CREATE TYPE jsrec_i_not_null AS ( + i js_int_not_null +); select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q; a | b | c --------+---+--- @@ -1351,6 +1376,370 @@ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a": select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}') q; ERROR: invalid input syntax for type timestamp: "[100,200,false]" +select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{}') q; + a | b | c +---+---+-------------------------- + x | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"x": 43.2}') q; +ERROR: domain js_int_not_null does not allow null values +SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": null}') q; +ERROR: domain js_int_not_null does not allow null values +SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": 12345}') q; + i +------- + 12345 +(1 row) + +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": null}') q; + ia +---- + +(1 row) + +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": 123}') q; +ERROR: expected json array +HINT: see the value of key "ia" +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [1, "2", null, 4]}') q; + ia +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1, 2], [3, 4]]}') q; + ia +--------------- + {{1,2},{3,4}} +(1 row) + +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], 2]}') q; +ERROR: expected json array +HINT: see the array element [1] of key "ia" +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], [2, 3]]}') q; +ERROR: malformed json array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": "{1,2,3}"}') q; + ia +--------- + {1,2,3} +(1 row) + +SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": null}') q; + ia1 +----- + +(1 row) + +SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": 123}') q; +ERROR: expected json array +HINT: see the value of key "ia1" +SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": [1, "2", null, 4]}') q; + ia1 +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": [[1, 2, 3]]}') q; + ia1 +----------- + {{1,2,3}} +(1 row) + +SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": null}') q; + ia1d +------ + +(1 row) + +SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": 123}') q; +ERROR: expected json array +HINT: see the value of key "ia1d" +SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": [1, "2", null, 4]}') q; +ERROR: value for domain js_int_array_1d violates check constraint "js_int_array_1d_check" +SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": [1, "2", null]}') q; + ia1d +------------ + {1,2,NULL} +(1 row) + +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [1, "2", null, 4]}') q; + ia2 +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], [null, 4]]}') q; + ia2 +------------------ + {{1,2},{NULL,4}} +(1 row) + +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[], []]}') q; + ia2 +----- + {} +(1 row) + +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], [3]]}') q; +ERROR: malformed json array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], 3, 4]}') q; +ERROR: expected json array +HINT: see the array element [1] of key "ia2" +SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2"], [null, 4]]}') q; +ERROR: value for domain js_int_array_2d violates check constraint "js_int_array_2d_check" +SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q; + ia2d +---------------------- + {{1,2,3},{NULL,5,6}} +(1 row) + +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [1, "2", null, 4]}') q; + ia3 +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [[1, 2], [null, 4]]}') q; + ia3 +------------------ + {{1,2},{NULL,4}} +(1 row) + +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[], []], [[], []], [[], []] ]}') q; + ia3 +----- + {} +(1 row) + +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2]], [[3, 4]] ]}') q; + ia3 +------------------- + {{{1,2}},{{3,4}}} +(1 row) + +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8]] ]}') q; + ia3 +------------------------------- + {{{1,2},{3,4}},{{5,6},{7,8}}} +(1 row) + +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8], [9, 10]] ]}') q; +ERROR: malformed json array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": null}') q; + ta +---- + +(1 row) + +SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": 123}') q; +ERROR: expected json array +HINT: see the value of key "ta" +SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": [1, "2", null, 4]}') q; + ta +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": [[1, 2, 3], {"k": "v"}]}') q; +ERROR: expected json array +HINT: see the array element [1] of key "ta" +SELECT c FROM json_populate_record(NULL::jsrec, '{"c": null}') q; + c +--- + +(1 row) + +SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaa"}') q; + c +------------ + aaa +(1 row) + +SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaaaaaaaaa"}') q; + c +------------ + aaaaaaaaaa +(1 row) + +SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaaaaaaaaaaaa"}') q; +ERROR: value too long for type character(10) +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": null}') q; + ca +---- + +(1 row) + +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": 123}') q; +ERROR: expected json array +HINT: see the value of key "ca" +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": [1, "2", null, 4]}') q; + ca +----------------------------------------------- + {"1 ","2 ",NULL,"4 "} +(1 row) + +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": ["aaaaaaaaaaaaaaaa"]}') q; +ERROR: value too long for type character(10) +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": [[1, 2, 3], {"k": "v"}]}') q; +ERROR: expected json array +HINT: see the array element [1] of key "ca" +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": null}') q; + js +---- + +(1 row) + +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": true}') q; + js +------ + true +(1 row) + +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": 123.45}') q; + js +-------- + 123.45 +(1 row) + +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": "123.45"}') q; + js +---------- + "123.45" +(1 row) + +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": "abc"}') q; + js +------- + "abc" +(1 row) + +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": [123, "123", null, {"key": "value"}]}') q; + js +-------------------------------------- + [123, "123", null, {"key": "value"}] +(1 row) + +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": {"a": "bbb", "b": null, "c": 123.45}}') q; + js +-------------------------------------- + {"a": "bbb", "b": null, "c": 123.45} +(1 row) + +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": null}') q; + jsb +----- + +(1 row) + +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": true}') q; + jsb +------ + true +(1 row) + +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": 123.45}') q; + jsb +-------- + 123.45 +(1 row) + +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": "123.45"}') q; + jsb +---------- + "123.45" +(1 row) + +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": "abc"}') q; + jsb +------- + "abc" +(1 row) + +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": [123, "123", null, {"key": "value"}]}') q; + jsb +-------------------------------------- + [123, "123", null, {"key": "value"}] +(1 row) + +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": {"a": "bbb", "b": null, "c": 123.45}}') q; + jsb +-------------------------------------- + {"a": "bbb", "b": null, "c": 123.45} +(1 row) + +SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": null}') q; + jsa +----- + +(1 row) + +SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": 123}') q; +ERROR: expected json array +HINT: see the value of key "jsa" +SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": [1, "2", null, 4]}') q; + jsa +-------------------- + {1,"\"2\"",NULL,4} +(1 row) + +SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": ["aaa", null, [1, 2, "3", {}], { "k" : "v" }]}') q; + jsa +---------------------------------------------------------- + {"\"aaa\"",NULL,"[1, 2, \"3\", {}]","{ \"k\" : \"v\" }"} +(1 row) + +SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": 123}') q; +ERROR: cannot call populate_composite on a scalar +SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": [1, 2]}') q; +ERROR: cannot call populate_composite on an array +SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}') q; + rec +----------------------------------- + (abc,,"Thu Jan 02 00:00:00 2003") +(1 row) + +SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": "(abc,42,01.02.2003)"}') q; + rec +------------------------------------- + (abc,42,"Thu Jan 02 00:00:00 2003") +(1 row) + +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": 123}') q; +ERROR: expected json array +HINT: see the value of key "reca" +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": [1, 2]}') q; +ERROR: cannot call populate_composite on a scalar +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]}') q; + reca +-------------------------------------------------------- + {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} +(1 row) + +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": ["(abc,42,01.02.2003)"]}') q; + reca +------------------------------------------- + {"(abc,42,\"Thu Jan 02 00:00:00 2003\")"} +(1 row) + +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": "{\"(abc,42,01.02.2003)\"}"}') q; + reca +------------------------------------------- + {"(abc,42,\"Thu Jan 02 00:00:00 2003\")"} +(1 row) + +SELECT rec FROM json_populate_record( + row(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + row('x',3,'2012-12-31 15:30:56')::jpop,NULL)::jsrec, + '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' +) q; + rec +------------------------------------ + (abc,3,"Thu Jan 02 00:00:00 2003") +(1 row) + -- populate_recordset select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; a | b | c @@ -1417,6 +1806,28 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3 {"z":true} | 3 | Fri Jan 20 10:42:53 2012 (2 rows) +-- test type info caching in json_populate_record() +CREATE TEMP TABLE jspoptest (js json); +INSERT INTO jspoptest +SELECT '{ + "jsa": [1, "2", null, 4], + "rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}, + "reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}] +}'::json +FROM generate_series(1, 3); +SELECT (json_populate_record(NULL::jsrec, js)).* FROM jspoptest; + i | ia | ia1 | ia2 | ia3 | ia1d | ia2d | t | ta | c | ca | ts | js | jsb | jsa | rec | reca +---+----+-----+-----+-----+------+------+---+----+---+----+----+----+-----+--------------------+-----------------------------------+-------------------------------------------------------- + | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} + | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} + | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} +(3 rows) + +DROP TYPE jsrec; +DROP TYPE jsrec_i_not_null; +DROP DOMAIN js_int_not_null; +DROP DOMAIN js_int_array_1d; +DROP DOMAIN js_int_array_2d; --json_typeof() function select value, json_typeof(value) from (values (json '123.4'), @@ -1609,11 +2020,11 @@ select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":fa (2 rows) select *, c is null as c_is_null -from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::json) - as t(a int, b json, c text, x int); - a | b | c | x | c_is_null ----+-----------------+---+---+----------- - 1 | {"c":16, "d":2} | | 8 | t +from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8, "ca": ["1 2", 3], "ia": [[1,2],[3,4]], "r": {"a": "aaa", "b": 123}}'::json) + as t(a int, b json, c text, x int, ca char(5)[], ia int[][], r jpop); + a | b | c | x | ca | ia | r | c_is_null +---+-----------------+---+---+-------------------+---------------+------------+----------- + 1 | {"c":16, "d":2} | | 8 | {"1 2 ","3 "} | {{1,2},{3,4}} | (aaa,123,) | t (1 row) select *, c is null as c_is_null @@ -1624,6 +2035,51 @@ from json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json) 1 | {"c":16, "d":2} | | 8 | t (1 row) +select * from json_to_record('{"ia": null}') as x(ia _int4); + ia +---- + +(1 row) + +select * from json_to_record('{"ia": 123}') as x(ia _int4); +ERROR: expected json array +HINT: see the value of key "ia" +select * from json_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4); + ia +-------------- + {1,2,NULL,4} +(1 row) + +select * from json_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4); + ia +--------------- + {{1,2},{3,4}} +(1 row) + +select * from json_to_record('{"ia": [[1], 2]}') as x(ia _int4); +ERROR: expected json array +HINT: see the array element [1] of key "ia" +select * from json_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4); +ERROR: malformed json array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +select * from json_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]); + ia2 +--------- + {1,2,3} +(1 row) + +select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]); + ia2 +--------------- + {{1,2},{3,4}} +(1 row) + +select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); + ia2 +----------------- + {{{1},{2},{3}}} +(1 row) + -- json_strip_nulls select json_strip_nulls(null); json_strip_nulls diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index e93a15f98a..f199eb4f70 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -1897,6 +1897,31 @@ SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7, -- populate_record CREATE TYPE jbpop AS (a text, b int, c timestamp); +CREATE DOMAIN jsb_int_not_null AS int NOT NULL; +CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); +CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); +CREATE TYPE jsbrec AS ( + i int, + ia _int4, + ia1 int[], + ia2 int[][], + ia3 int[][][], + ia1d jsb_int_array_1d, + ia2d jsb_int_array_2d, + t text, + ta text[], + c char(10), + ca char(10)[], + ts timestamp, + js json, + jsb jsonb, + jsa json[], + rec jbpop, + reca jbpop[] +); +CREATE TYPE jsbrec_i_not_null AS ( + i jsb_int_not_null +); SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q; a | b | c --------+---+--- @@ -1935,6 +1960,382 @@ SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}') q; ERROR: invalid input syntax for type timestamp: "[100, 200, false]" +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop, '{}') q; + a | b | c +---+---+-------------------------- + x | 3 | Mon Dec 31 15:30:56 2012 +(1 row) + +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"x": 43.2}') q; +ERROR: domain jsb_int_not_null does not allow null values +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": null}') q; +ERROR: domain jsb_int_not_null does not allow null values +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": 12345}') q; + i +------- + 12345 +(1 row) + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": null}') q; + ia +---- + +(1 row) + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": 123}') q; +ERROR: expected json array +HINT: see the value of key "ia" +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [1, "2", null, 4]}') q; + ia +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1, 2], [3, 4]]}') q; + ia +--------------- + {{1,2},{3,4}} +(1 row) + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], 2]}') q; +ERROR: expected json array +HINT: see the array element [1] of key "ia" +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], [2, 3]]}') q; +ERROR: malformed json array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": "{1,2,3}"}') q; + ia +--------- + {1,2,3} +(1 row) + +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": null}') q; + ia1 +----- + +(1 row) + +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": 123}') q; +ERROR: expected json array +HINT: see the value of key "ia1" +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": [1, "2", null, 4]}') q; + ia1 +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": [[1, 2, 3]]}') q; + ia1 +----------- + {{1,2,3}} +(1 row) + +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": null}') q; + ia1d +------ + +(1 row) + +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": 123}') q; +ERROR: expected json array +HINT: see the value of key "ia1d" +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": [1, "2", null, 4]}') q; +ERROR: value for domain jsb_int_array_1d violates check constraint "jsb_int_array_1d_check" +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": [1, "2", null]}') q; + ia1d +------------ + {1,2,NULL} +(1 row) + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [1, "2", null, 4]}') q; + ia2 +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], [null, 4]]}') q; + ia2 +------------------ + {{1,2},{NULL,4}} +(1 row) + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[], []]}') q; + ia2 +----- + {} +(1 row) + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], [3]]}') q; +ERROR: malformed json array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], 3, 4]}') q; +ERROR: expected json array +HINT: see the array element [1] of key "ia2" +SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2"], [null, 4]]}') q; +ERROR: value for domain jsb_int_array_2d violates check constraint "jsb_int_array_2d_check" +SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q; + ia2d +---------------------- + {{1,2,3},{NULL,5,6}} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [1, "2", null, 4]}') q; + ia3 +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [[1, 2], [null, 4]]}') q; + ia3 +------------------ + {{1,2},{NULL,4}} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[], []], [[], []], [[], []] ]}') q; + ia3 +----- + {} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2]], [[3, 4]] ]}') q; + ia3 +------------------- + {{{1,2}},{{3,4}}} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8]] ]}') q; + ia3 +------------------------------- + {{{1,2},{3,4}},{{5,6},{7,8}}} +(1 row) + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8], [9, 10]] ]}') q; +ERROR: malformed json array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": null}') q; + ta +---- + +(1 row) + +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": 123}') q; +ERROR: expected json array +HINT: see the value of key "ta" +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": [1, "2", null, 4]}') q; + ta +-------------- + {1,2,NULL,4} +(1 row) + +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": [[1, 2, 3], {"k": "v"}]}') q; +ERROR: expected json array +HINT: see the array element [1] of key "ta" +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": null}') q; + c +--- + +(1 row) + +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaa"}') q; + c +------------ + aaa +(1 row) + +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaaaaaaaaa"}') q; + c +------------ + aaaaaaaaaa +(1 row) + +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaaaaaaaaaaaa"}') q; +ERROR: value too long for type character(10) +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": null}') q; + ca +---- + +(1 row) + +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": 123}') q; +ERROR: expected json array +HINT: see the value of key "ca" +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": [1, "2", null, 4]}') q; + ca +----------------------------------------------- + {"1 ","2 ",NULL,"4 "} +(1 row) + +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": ["aaaaaaaaaaaaaaaa"]}') q; +ERROR: value too long for type character(10) +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": [[1, 2, 3], {"k": "v"}]}') q; +ERROR: expected json array +HINT: see the array element [1] of key "ca" +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": null}') q; + js +---- + +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": true}') q; + js +------ + true +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": 123.45}') q; + js +-------- + 123.45 +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": "123.45"}') q; + js +---------- + "123.45" +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": "abc"}') q; + js +------- + "abc" +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": [123, "123", null, {"key": "value"}]}') q; + js +-------------------------------------- + [123, "123", null, {"key": "value"}] +(1 row) + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": {"a": "bbb", "b": null, "c": 123.45}}') q; + js +-------------------------------------- + {"a": "bbb", "b": null, "c": 123.45} +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": null}') q; + jsb +----- + +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": true}') q; + jsb +------ + true +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": 123.45}') q; + jsb +-------- + 123.45 +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": "123.45"}') q; + jsb +---------- + "123.45" +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": "abc"}') q; + jsb +------- + "abc" +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": [123, "123", null, {"key": "value"}]}') q; + jsb +-------------------------------------- + [123, "123", null, {"key": "value"}] +(1 row) + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": {"a": "bbb", "b": null, "c": 123.45}}') q; + jsb +-------------------------------------- + {"a": "bbb", "b": null, "c": 123.45} +(1 row) + +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": null}') q; + jsa +----- + +(1 row) + +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": 123}') q; +ERROR: expected json array +HINT: see the value of key "jsa" +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": [1, "2", null, 4]}') q; + jsa +-------------------- + {1,"\"2\"",NULL,4} +(1 row) + +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": ["aaa", null, [1, 2, "3", {}], { "k" : "v" }]}') q; + jsa +------------------------------------------------------- + {"\"aaa\"",NULL,"[1, 2, \"3\", {}]","{\"k\": \"v\"}"} +(1 row) + +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": 123}') q; + rec +------ + (,,) +(1 row) + +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": [1, 2]}') q; + rec +------ + (,,) +(1 row) + +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}') q; + rec +----------------------------------- + (abc,,"Thu Jan 02 00:00:00 2003") +(1 row) + +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": "(abc,42,01.02.2003)"}') q; + rec +------------------------------------- + (abc,42,"Thu Jan 02 00:00:00 2003") +(1 row) + +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": 123}') q; +ERROR: expected json array +HINT: see the value of key "reca" +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": [1, 2]}') q; + reca +----------------- + {"(,,)","(,,)"} +(1 row) + +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]}') q; + reca +-------------------------------------------------------- + {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} +(1 row) + +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": ["(abc,42,01.02.2003)"]}') q; + reca +------------------------------------------- + {"(abc,42,\"Thu Jan 02 00:00:00 2003\")"} +(1 row) + +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": "{\"(abc,42,01.02.2003)\"}"}') q; + reca +------------------------------------------- + {"(abc,42,\"Thu Jan 02 00:00:00 2003\")"} +(1 row) + +SELECT rec FROM jsonb_populate_record( + row(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + row('x',3,'2012-12-31 15:30:56')::jbpop,NULL)::jsbrec, + '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' +) q; + rec +------------------------------------ + (abc,3,"Thu Jan 02 00:00:00 2003") +(1 row) + -- populate_recordset SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; a | b | c @@ -2011,11 +2412,11 @@ select * from jsonb_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar", (2 rows) select *, c is null as c_is_null -from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::jsonb) - as t(a int, b jsonb, c text, x int); - a | b | c | x | c_is_null ----+-------------------+---+---+----------- - 1 | {"c": 16, "d": 2} | | 8 | t +from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8, "ca": ["1 2", 3], "ia": [[1,2],[3,4]], "r": {"a": "aaa", "b": 123}}'::jsonb) + as t(a int, b jsonb, c text, x int, ca char(5)[], ia int[][], r jbpop); + a | b | c | x | ca | ia | r | c_is_null +---+-------------------+---+---+-------------------+---------------+------------+----------- + 1 | {"c": 16, "d": 2} | | 8 | {"1 2 ","3 "} | {{1,2},{3,4}} | (aaa,123,) | t (1 row) select *, c is null as c_is_null @@ -2026,6 +2427,73 @@ from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb) 1 | {"c": 16, "d": 2} | | 8 | t (1 row) +select * from jsonb_to_record('{"ia": null}') as x(ia _int4); + ia +---- + +(1 row) + +select * from jsonb_to_record('{"ia": 123}') as x(ia _int4); +ERROR: expected json array +HINT: see the value of key "ia" +select * from jsonb_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4); + ia +-------------- + {1,2,NULL,4} +(1 row) + +select * from jsonb_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4); + ia +--------------- + {{1,2},{3,4}} +(1 row) + +select * from jsonb_to_record('{"ia": [[1], 2]}') as x(ia _int4); +ERROR: expected json array +HINT: see the array element [1] of key "ia" +select * from jsonb_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4); +ERROR: malformed json array +DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. +select * from jsonb_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]); + ia2 +--------- + {1,2,3} +(1 row) + +select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]); + ia2 +--------------- + {{1,2},{3,4}} +(1 row) + +select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); + ia2 +----------------- + {{{1},{2},{3}}} +(1 row) + +-- test type info caching in jsonb_populate_record() +CREATE TEMP TABLE jsbpoptest (js jsonb); +INSERT INTO jsbpoptest +SELECT '{ + "jsa": [1, "2", null, 4], + "rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}, + "reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}] +}'::jsonb +FROM generate_series(1, 3); +SELECT (jsonb_populate_record(NULL::jsbrec, js)).* FROM jsbpoptest; + i | ia | ia1 | ia2 | ia3 | ia1d | ia2d | t | ta | c | ca | ts | js | jsb | jsa | rec | reca +---+----+-----+-----+-----+------+------+---+----+---+----+----+----+-----+--------------------+-----------------------------------+-------------------------------------------------------- + | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} + | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} + | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"} +(3 rows) + +DROP TYPE jsbrec; +DROP TYPE jsbrec_i_not_null; +DROP DOMAIN jsb_int_not_null; +DROP DOMAIN jsb_int_array_1d; +DROP DOMAIN jsb_int_array_2d; -- indexing SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; count diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 0973737289..506e3a8fc5 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -384,6 +384,34 @@ select * from json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8 -- populate_record create type jpop as (a text, b int, c timestamp); +CREATE DOMAIN js_int_not_null AS int NOT NULL; +CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); +CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); + +CREATE TYPE jsrec AS ( + i int, + ia _int4, + ia1 int[], + ia2 int[][], + ia3 int[][][], + ia1d js_int_array_1d, + ia2d js_int_array_2d, + t text, + ta text[], + c char(10), + ca char(10)[], + ts timestamp, + js json, + jsb jsonb, + jsa json[], + rec jpop, + reca jpop[] +); + +CREATE TYPE jsrec_i_not_null AS ( + i js_int_not_null +); + select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q; select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q; @@ -394,6 +422,100 @@ select * from json_populate_record(null::jpop,'{"a":[100,200,false],"x":43.2}') select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":[100,200,false],"x":43.2}') q; select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}') q; +select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{}') q; + +SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"x": 43.2}') q; +SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": null}') q; +SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": 12345}') q; + +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": null}') q; +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": 123}') q; +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [1, "2", null, 4]}') q; +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1, 2], [3, 4]]}') q; +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], 2]}') q; +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], [2, 3]]}') q; +SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": "{1,2,3}"}') q; + +SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": null}') q; +SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": 123}') q; +SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": [1, "2", null, 4]}') q; +SELECT ia1 FROM json_populate_record(NULL::jsrec, '{"ia1": [[1, 2, 3]]}') q; + +SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": null}') q; +SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": 123}') q; +SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": [1, "2", null, 4]}') q; +SELECT ia1d FROM json_populate_record(NULL::jsrec, '{"ia1d": [1, "2", null]}') q; + +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [1, "2", null, 4]}') q; +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], [null, 4]]}') q; +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[], []]}') q; +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], [3]]}') q; +SELECT ia2 FROM json_populate_record(NULL::jsrec, '{"ia2": [[1, 2], 3, 4]}') q; + +SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2"], [null, 4]]}') q; +SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q; + +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [1, "2", null, 4]}') q; +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [[1, 2], [null, 4]]}') q; +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[], []], [[], []], [[], []] ]}') q; +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2]], [[3, 4]] ]}') q; +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8]] ]}') q; +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8], [9, 10]] ]}') q; + +SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": null}') q; +SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": 123}') q; +SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": [1, "2", null, 4]}') q; +SELECT ta FROM json_populate_record(NULL::jsrec, '{"ta": [[1, 2, 3], {"k": "v"}]}') q; + +SELECT c FROM json_populate_record(NULL::jsrec, '{"c": null}') q; +SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaa"}') q; +SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaaaaaaaaa"}') q; +SELECT c FROM json_populate_record(NULL::jsrec, '{"c": "aaaaaaaaaaaaa"}') q; + +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": null}') q; +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": 123}') q; +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": [1, "2", null, 4]}') q; +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": ["aaaaaaaaaaaaaaaa"]}') q; +SELECT ca FROM json_populate_record(NULL::jsrec, '{"ca": [[1, 2, 3], {"k": "v"}]}') q; + +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": null}') q; +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": true}') q; +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": 123.45}') q; +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": "123.45"}') q; +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": "abc"}') q; +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": [123, "123", null, {"key": "value"}]}') q; +SELECT js FROM json_populate_record(NULL::jsrec, '{"js": {"a": "bbb", "b": null, "c": 123.45}}') q; + +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": null}') q; +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": true}') q; +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": 123.45}') q; +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": "123.45"}') q; +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": "abc"}') q; +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": [123, "123", null, {"key": "value"}]}') q; +SELECT jsb FROM json_populate_record(NULL::jsrec, '{"jsb": {"a": "bbb", "b": null, "c": 123.45}}') q; + +SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": null}') q; +SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": 123}') q; +SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": [1, "2", null, 4]}') q; +SELECT jsa FROM json_populate_record(NULL::jsrec, '{"jsa": ["aaa", null, [1, 2, "3", {}], { "k" : "v" }]}') q; + +SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": 123}') q; +SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": [1, 2]}') q; +SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}') q; +SELECT rec FROM json_populate_record(NULL::jsrec, '{"rec": "(abc,42,01.02.2003)"}') q; + +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": 123}') q; +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": [1, 2]}') q; +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]}') q; +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": ["(abc,42,01.02.2003)"]}') q; +SELECT reca FROM json_populate_record(NULL::jsrec, '{"reca": "{\"(abc,42,01.02.2003)\"}"}') q; + +SELECT rec FROM json_populate_record( + row(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + row('x',3,'2012-12-31 15:30:56')::jpop,NULL)::jsrec, + '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' +) q; + -- populate_recordset select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; @@ -410,6 +532,25 @@ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b": select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q; +-- test type info caching in json_populate_record() +CREATE TEMP TABLE jspoptest (js json); + +INSERT INTO jspoptest +SELECT '{ + "jsa": [1, "2", null, 4], + "rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}, + "reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}] +}'::json +FROM generate_series(1, 3); + +SELECT (json_populate_record(NULL::jsrec, js)).* FROM jspoptest; + +DROP TYPE jsrec; +DROP TYPE jsrec_i_not_null; +DROP DOMAIN js_int_not_null; +DROP DOMAIN js_int_array_1d; +DROP DOMAIN js_int_array_2d; + --json_typeof() function select value, json_typeof(value) from (values (json '123.4'), @@ -526,13 +667,24 @@ select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":fa as x(a int, b json, c boolean); select *, c is null as c_is_null -from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::json) - as t(a int, b json, c text, x int); +from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8, "ca": ["1 2", 3], "ia": [[1,2],[3,4]], "r": {"a": "aaa", "b": 123}}'::json) + as t(a int, b json, c text, x int, ca char(5)[], ia int[][], r jpop); select *, c is null as c_is_null from json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json) as t(a int, b json, c text, x int); +select * from json_to_record('{"ia": null}') as x(ia _int4); +select * from json_to_record('{"ia": 123}') as x(ia _int4); +select * from json_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4); +select * from json_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4); +select * from json_to_record('{"ia": [[1], 2]}') as x(ia _int4); +select * from json_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4); + +select * from json_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]); +select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]); +select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); + -- json_strip_nulls select json_strip_nulls(null); diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 576e1448fd..57fff3bfb3 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -484,6 +484,34 @@ SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7, -- populate_record CREATE TYPE jbpop AS (a text, b int, c timestamp); +CREATE DOMAIN jsb_int_not_null AS int NOT NULL; +CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3); +CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3); + +CREATE TYPE jsbrec AS ( + i int, + ia _int4, + ia1 int[], + ia2 int[][], + ia3 int[][][], + ia1d jsb_int_array_1d, + ia2d jsb_int_array_2d, + t text, + ta text[], + c char(10), + ca char(10)[], + ts timestamp, + js json, + jsb jsonb, + jsa json[], + rec jbpop, + reca jbpop[] +); + +CREATE TYPE jsbrec_i_not_null AS ( + i jsb_int_not_null +); + SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q; SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q; @@ -494,6 +522,100 @@ SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}' SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}') q; SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}') q; +SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop, '{}') q; + +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"x": 43.2}') q; +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": null}') q; +SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": 12345}') q; + +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": null}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": 123}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [1, "2", null, 4]}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1, 2], [3, 4]]}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], 2]}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], [2, 3]]}') q; +SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": "{1,2,3}"}') q; + +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": null}') q; +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": 123}') q; +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": [1, "2", null, 4]}') q; +SELECT ia1 FROM jsonb_populate_record(NULL::jsbrec, '{"ia1": [[1, 2, 3]]}') q; + +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": null}') q; +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": 123}') q; +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": [1, "2", null, 4]}') q; +SELECT ia1d FROM jsonb_populate_record(NULL::jsbrec, '{"ia1d": [1, "2", null]}') q; + +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [1, "2", null, 4]}') q; +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], [null, 4]]}') q; +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[], []]}') q; +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], [3]]}') q; +SELECT ia2 FROM jsonb_populate_record(NULL::jsbrec, '{"ia2": [[1, 2], 3, 4]}') q; + +SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2"], [null, 4]]}') q; +SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q; + +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [1, "2", null, 4]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [[1, 2], [null, 4]]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[], []], [[], []], [[], []] ]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2]], [[3, 4]] ]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8]] ]}') q; +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [ [[1, 2], [3, 4]], [[5, 6], [7, 8], [9, 10]] ]}') q; + +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": null}') q; +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": 123}') q; +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": [1, "2", null, 4]}') q; +SELECT ta FROM jsonb_populate_record(NULL::jsbrec, '{"ta": [[1, 2, 3], {"k": "v"}]}') q; + +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": null}') q; +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaa"}') q; +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaaaaaaaaa"}') q; +SELECT c FROM jsonb_populate_record(NULL::jsbrec, '{"c": "aaaaaaaaaaaaa"}') q; + +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": null}') q; +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": 123}') q; +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": [1, "2", null, 4]}') q; +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": ["aaaaaaaaaaaaaaaa"]}') q; +SELECT ca FROM jsonb_populate_record(NULL::jsbrec, '{"ca": [[1, 2, 3], {"k": "v"}]}') q; + +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": null}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": true}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": 123.45}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": "123.45"}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": "abc"}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": [123, "123", null, {"key": "value"}]}') q; +SELECT js FROM jsonb_populate_record(NULL::jsbrec, '{"js": {"a": "bbb", "b": null, "c": 123.45}}') q; + +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": null}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": true}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": 123.45}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": "123.45"}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": "abc"}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": [123, "123", null, {"key": "value"}]}') q; +SELECT jsb FROM jsonb_populate_record(NULL::jsbrec, '{"jsb": {"a": "bbb", "b": null, "c": 123.45}}') q; + +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": null}') q; +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": 123}') q; +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": [1, "2", null, 4]}') q; +SELECT jsa FROM jsonb_populate_record(NULL::jsbrec, '{"jsa": ["aaa", null, [1, 2, "3", {}], { "k" : "v" }]}') q; + +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": 123}') q; +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": [1, 2]}') q; +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}') q; +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": "(abc,42,01.02.2003)"}') q; + +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": 123}') q; +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": [1, 2]}') q; +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]}') q; +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": ["(abc,42,01.02.2003)"]}') q; +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": "{\"(abc,42,01.02.2003)\"}"}') q; + +SELECT rec FROM jsonb_populate_record( + row(NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, + row('x',3,'2012-12-31 15:30:56')::jbpop,NULL)::jsbrec, + '{"rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}}' +) q; + -- populate_recordset SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q; @@ -515,13 +637,43 @@ select * from jsonb_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar", as x(a int, b text, c boolean); select *, c is null as c_is_null -from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::jsonb) - as t(a int, b jsonb, c text, x int); +from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8, "ca": ["1 2", 3], "ia": [[1,2],[3,4]], "r": {"a": "aaa", "b": 123}}'::jsonb) + as t(a int, b jsonb, c text, x int, ca char(5)[], ia int[][], r jbpop); select *, c is null as c_is_null from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb) as t(a int, b jsonb, c text, x int); +select * from jsonb_to_record('{"ia": null}') as x(ia _int4); +select * from jsonb_to_record('{"ia": 123}') as x(ia _int4); +select * from jsonb_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4); +select * from jsonb_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4); +select * from jsonb_to_record('{"ia": [[1], 2]}') as x(ia _int4); +select * from jsonb_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4); + +select * from jsonb_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]); +select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]); +select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]); + +-- test type info caching in jsonb_populate_record() +CREATE TEMP TABLE jsbpoptest (js jsonb); + +INSERT INTO jsbpoptest +SELECT '{ + "jsa": [1, "2", null, 4], + "rec": {"a": "abc", "c": "01.02.2003", "x": 43.2}, + "reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}] +}'::jsonb +FROM generate_series(1, 3); + +SELECT (jsonb_populate_record(NULL::jsbrec, js)).* FROM jsbpoptest; + +DROP TYPE jsbrec; +DROP TYPE jsbrec_i_not_null; +DROP DOMAIN jsb_int_not_null; +DROP DOMAIN jsb_int_array_1d; +DROP DOMAIN jsb_int_array_2d; + -- indexing SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}'; SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}'; -- 2.40.0