diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 10e3186..55cacfb 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -11249,12 +11249,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") @@ -11343,12 +11343,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 17ee4e4..bb72b8e 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -93,7 +93,7 @@ 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, @@ -149,6 +149,33 @@ static void setPathArray(JsonbIterator **it, Datum *path_elems, int level, Jsonb *newval, uint32 nelems, int op_type); static void addJsonbToParseState(JsonbParseState **jbps, Jsonb *jb); +/* helper functions for populate_record[set] */ +typedef struct ColumnIOData ColumnIOData; +typedef struct RecordIOData RecordIOData; + +static HeapTupleHeader +populate_record(TupleDesc tupdesc, + RecordIOData **record_info, + HeapTupleHeader template, + MemoryContext mcxt, + Oid jtype, + HTAB *json_hash, + JsonbContainer *cont); + +static Datum +populate_record_field(ColumnIOData *col, + Oid typid, + int32 typmod, + int32 ndims, + const char *colname, + MemoryContext mcxt, + Datum defaultval, + Oid jtype, + char *json, + bool json_is_string, + JsonbValue *jval, + bool *isnull); + /* state for json_object_keys */ typedef struct OkeysState { @@ -216,6 +243,7 @@ typedef struct JhashState HTAB *hash; char *saved_scalar; char *save_json_start; + bool saved_scalar_is_string; } JHashState; /* hashtable element */ @@ -223,26 +251,67 @@ typedef struct JsonHashEntry { char fname[NAMEDATALEN]; /* hash key (MUST BE FIRST) */ char *val; - char *json; bool isnull; + bool isstring; } 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; -typedef struct RecordIOData +/* structure to cache metadata needed for populate_array() */ +typedef struct ArrayIOData +{ + Oid element_type; /* array element type id */ + int32 element_typmod; /* array element type modifier */ + ColumnIOData *element_info; /* metadata cache */ + int ndims; /* number of dimensions of array */ +} 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 *recordio; /* metadata cache for populate_record() */ + TupleDesc tupdesc; /* cached tuple descriptor */ +} CompositeIOData; + +/* 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 */ + int32 ndims; /* number of array dimensions if column is array */ + char type_category; /* column type category: + * 's' - scalar, + * 'a' - array, + * 'c' - composite */ + bool type_is_domain; /* column type is domain */ + void *domain_info; /* opaque cache for domain checks */ + union + { + ScalarIOData scalar; + ArrayIOData array; + CompositeIOData composite; + } 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 @@ -252,13 +321,30 @@ typedef struct PopulateRecordsetState HTAB *json_hash; char *saved_scalar; char *save_json_start; + bool saved_scalar_is_string; Tuplestorestate *tuple_store; TupleDesc ret_tdesc; HeapTupleHeader rec; - RecordIOData *my_extra; + RecordIOData **my_extra; MemoryContext fn_mcxt; /* used to stash IO funcs */ } PopulateRecordsetState; +/* state for populate_array_json() */ +typedef struct PopulateArrayState +{ + JsonLexContext *lex; /* json lexer */ + ArrayBuildState *astate; /* array build state */ + ArrayIOData *aio; /* metadata cache */ + MemoryContext mcxt; /* metadata cache memory context */ + const char *colname; /* column name (for diagnostics only) */ + int *sizes; /* current dimension size counters */ + int *dims; /* array dimensions */ + int ndims; /* number of dimensions in array */ + char *element_start; /* start of the current array element */ + char *element_scalar; /* current array element token if it is a scalar */ + bool element_scalar_is_string; /* is current scalar array element string */ +} PopulateArrayState; + /* state for json_strip_nulls */ typedef struct StripnullState { @@ -267,10 +353,6 @@ typedef struct StripnullState bool skip_next_null; } StripnullState; -/* Turn a jsonb object into a record */ -static void make_row_from_rec_and_jsonb(Jsonb *element, - PopulateRecordsetState *state); - /* * SQL function json_object_keys * @@ -2098,158 +2180,653 @@ 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) +/* json object start handler for populate_array_json() */ +static void +populate_array_object_start(void *_state) { - 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; + PopulateArrayState *state = (PopulateArrayState *) _state; - Assert(jtype == JSONOID || jtype == JSONBOID); + if (state->lex->lex_level < state->ndims) + elog(ERROR, + state->colname ? "expected json array in value of key \"%s\"" + : "expected json array", state->colname); +} - if (have_record_arg) +/* json array start handler for populate_array_json() */ +static void +populate_array_array_start(void *_state) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + int ndim = state->lex->lex_level; + + /* reset current array dimension size counter */ + if (ndim < state->ndims) + state->sizes[ndim] = 0; +} + +/* json array end handler for populate_array_json() */ +static void +populate_array_array_end(void *_state) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + int ndim = state->lex->lex_level; + + if (ndim < state->ndims) { - Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + /* check sub-arrays dimensions */ + int dim = state->sizes[ndim]; /* current dimension counter */ - if (!type_is_rowtype(argtype)) + if (state->dims[ndim] == -1) + state->dims[ndim] = dim; /* assign dimension if it is unknown yet */ + else if (state->dims[ndim] != dim) ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("first argument of %s must be a row type", - funcname))); + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed json array: \"%s\"", state->lex->input), + errdetail("Multidimensional arrays must have " + "sub-arrays with matching dimensions."))); - if (PG_ARGISNULL(0)) + /* increment parent dimension counter if it is a nested sub-array */ + if (ndim > 0) + state->sizes[ndim - 1]++; + } +} + +/* 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 (ndim == state->ndims) + { + /* remember current array element start */ + state->element_start = state->lex->token_start; + state->element_scalar = NULL; + } +} + +/* json array element end handler for populate_array_json() */ +static void +populate_array_element_end(void *_state, bool isnull) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + int ndim = state->lex->lex_level; + + if (ndim == state->ndims) + { + char *str; + Datum element; + bool element_isnull; + + if (isnull) + str = NULL; + else if (state->element_scalar) + str = state->element_scalar; + else { - if (PG_ARGISNULL(1)) - PG_RETURN_NULL(); + size_t len = state->lex->prev_token_terminator - + state->element_start; - /* - * 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; + str = palloc(len + 1); + memcpy(str, state->element_start, len); + str[len] = 0; + } + + /* populate array element */ + element = populate_record_field(state->aio->element_info, + state->aio->element_type, + state->aio->element_typmod, + 0, NULL, state->mcxt, + PointerGetDatum(NULL), + JSONOID, str, + state->element_scalar && + state->element_scalar_is_string, + NULL, &element_isnull); + + accumArrayResult(state->astate, element, element_isnull, + state->aio->element_type, NULL); + + Assert(ndim > 0); + state->sizes[ndim - 1]++; /* increment current dimension counter */ + } +} + +/* json scalar handler for populate_array_json() */ +static void +populate_array_scalar(void *_state, char *token, JsonTokenType tokentype) +{ + PopulateArrayState *state = (PopulateArrayState *) _state; + + if (state->lex->lex_level < state->ndims) + elog(ERROR, + state->colname ? "expected json array in value of key \"%s\"" + : "expected json array", state->colname); + + if (state->lex->lex_level == state->ndims) + { + /* remeber scalar element token */ + state->element_scalar = token; + state->element_scalar_is_string = tokentype == JSON_TOKEN_STRING; + } +} + +/* parse json array and populate array using given ArrayBuildState */ +static void +populate_array_json(ArrayBuildState *astate, /* array build state */ + char *json, /* json array */ + const char *colname, /* for diagnostics only */ + ArrayIOData *aio, /* metadata cache */ + MemoryContext mcxt, /* cache memory context */ + int ndims, /* number of dimensions */ + int *dims) /* dimensions (output) */ +{ + PopulateArrayState state; + JsonSemAction sem; + + state.lex = makeJsonLexContextCstringLen(json, strlen(json), true); + state.astate = astate; + state.aio = aio; + state.mcxt = mcxt; + state.colname = colname; + state.dims = dims; + state.ndims = ndims; + state.sizes = palloc0(sizeof(int) * ndims); + + memset(&sem, 0, sizeof(sem)); + sem.semstate = (void *) &state; + sem.object_start = populate_array_object_start; + sem.array_start = populate_array_array_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; + + pg_parse_json(state.lex, &sem); + + pfree(state.lex); + pfree(state.sizes); +} + +/* + * populate_array_dim_jsonb() -- Iterate recursively through jsonb sub-array + * elements and accumulate result using given ArrayBuildState. + * + * This function computes child sub-array dimensions and place it + * into dims[ndim..ndims-1]. + * + * Returns computed sub-array dimension (placed by caller into dims[ndim - 1]). + */ +static int +populate_array_dim_jsonb(ArrayBuildState *astate, /* array build state */ + JsonbValue *jbv, /* jsonb sub-array */ + const char *colname,/* for diagnostics only */ + ArrayIOData *aio, /* metadata cache */ + MemoryContext mcxt, /* cache memory context */ + int ndims, /* number of dimensions */ + int ndim, /* curr. dimension number */ + int *dims) /* dimensions (output) */ +{ + JsonbContainer *jbc = jbv->val.binary.data; + JsonbIterator *it; + JsonbIteratorToken tok; + JsonbValue val; + int size = 0; /* current dimension size counter */ + + check_stack_depth(); + + if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc)) + elog(ERROR, + colname ? "expected json array in value of key \"%s\"" + : "expected json array", colname); + + Assert(!JsonContainerIsScalar(jbc)); + + it = JsonbIteratorInit(jbc); + + while ((tok = JsonbIteratorNext(&it, &val, true)) != WJB_DONE) + { + if (tok != WJB_ELEM) + continue; + + if (ndim < ndims) + { + /* populate child sub-array */ + int dim = populate_array_dim_jsonb(astate, &val, colname, aio, + mcxt, ndims, ndim + 1, dims); + + /* check sub-array dimension */ + if (dims[ndim] == -1) + dims[ndim] = dim; /* assign dimension if it is unknown yet */ + else if (dims[ndim] != dim) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed json array: \"%s\"", + JsonbToCString(NULL, jbc, jbv->val.binary.len)), + errdetail("Multidimensional arrays must have " + "sub-arrays with matching dimensions."))); } else { - rec = PG_GETARG_HEAPTUPLEHEADER(0); + /* populate current array element */ + bool isnull; + Datum elem = populate_record_field(aio->element_info, + aio->element_type, + aio->element_typmod, + 0, NULL, mcxt, + PointerGetDatum(NULL), + JSONBOID, NULL, false, + &val, &isnull); + + accumArrayResult(astate, elem, isnull, aio->element_type, NULL); + } - if (PG_ARGISNULL(1)) - PG_RETURN_POINTER(rec); + size++; + } - /* Extract type info from the tuple itself */ - tupType = HeapTupleHeaderGetTypeId(rec); - tupTypmod = HeapTupleHeaderGetTypMod(rec); - } + return size; +} - tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); +/* populate recursively json/jsonb array */ +static Datum +populate_array(ArrayIOData *aio, /* metadata cache */ + const char *colname, /* for diagnostics only */ + MemoryContext mcxt, /* cache memory context */ + Oid jtype, /* json/jsonb */ + char *json, /* valid only if json */ + JsonbValue *jbv) /* valid only if jsonb */ +{ + ArrayBuildState *astate; + Datum result; + MemoryContext acxt = CurrentMemoryContext; + int *dims; + int *lbs; + int ndims = aio->ndims; + int i; + + Assert(ndims > 0); + + dims = palloc(sizeof(int) * ndims); + lbs = palloc(sizeof(int) * ndims); + + for (i = 0; i < ndims; i++) + { + dims[i] = -1; /* uknown yet */ + lbs[i] = 1; } + + astate = initArrayResult(aio->element_type, acxt, true); + + if (jtype == JSONOID) + populate_array_json(astate, json, colname, aio, mcxt, ndims, dims); else + dims[0] = populate_array_dim_jsonb(astate, jbv, colname, aio, mcxt, + ndims, 1, dims); + + result = makeMdArrayResult(astate, ndims, dims, lbs, acxt, true); + + pfree(dims); + pfree(lbs); + + return result; +} + +/* populate recursively composite (row type) value from json/jsonb */ +static Datum +populate_composite(CompositeIOData *io, /* metadata cache */ + Oid typeid, /* row type id */ + int32 typmod, /* row type modifier */ + const char *colname, /* for diagnostics only */ + MemoryContext mcxt, /* cache memory context */ + HeapTupleHeader defaultval, /* default row value if any */ + Oid jtype, /* json/jsonb */ + char *json, /* valid only if json */ + int jsonlen, /* valid only if json */ + JsonbValue *jbv) /* valid only if jsonb only */ +{ + HeapTupleHeader tuple; + HTAB *json_hash = NULL; + JsonbContainer *container = NULL; + + /* acquire cached tuple descriptor */ + if (!io->tupdesc || + io->tupdesc->tdtypeid != typeid || + io->tupdesc->tdtypmod != typmod) { - /* json{b}_to_record case */ - if (PG_ARGISNULL(0)) - PG_RETURN_NULL(); + TupleDesc tupdesc = lookup_rowtype_tupdesc(typeid, typmod); + MemoryContext oldcxt; - 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."))); + 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 */ if (jtype == JSONOID) { - /* just get the text */ - json = PG_GETARG_TEXT_P(json_arg_num); + /* convert plain-text json into a hash table */ + json_hash = get_json_object_as_hash(json, jsonlen, + "populate_composite"); + } + else + { + if (jbv->type != jbvBinary || + !JsonContainerIsObject(jbv->val.binary.data)) + elog(ERROR, + colname ? "expected json object in value of key \"%s\"" + : "expected json object", colname); + container = jbv->val.binary.data; + } - json_hash = get_json_object_as_hash(json, funcname); + /* populate resulting record tuple */ + tuple = populate_record(io->tupdesc, &io->recordio, defaultval, mcxt, + jtype, json_hash, container); - /* - * 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) + if (jtype == JSONOID) + hash_destroy(json_hash); + + return HeapTupleHeaderGetDatum(tuple); +} + +/* populate non-null scalar value from json/jsonb value */ +static Datum +populate_scalar(ScalarIOData *io, /* metadata cache */ + Oid typid, /* scalar type id */ + int32 typmod, /* scalar type modifier */ + Oid jtype, /* json/jsonb */ + char *json, /* valid only if json */ + bool json_is_string, /* valid only if json */ + JsonbValue *jbv) /* valid only if jsonb */ +{ + Datum res; + char *str = NULL; + + if (jtype == JSONOID) + { + /* already done the hard work in the json case */ + if ((typid == JSONOID || typid == JSONBOID) && json_is_string) { - hash_destroy(json_hash); - ReleaseTupleDesc(tupdesc); - PG_RETURN_POINTER(rec); + /* + * Add quotes around string value (should be already escaped) + * if converting to json or jsonb. + */ + size_t len = strlen(json); + + str = palloc(len + 3); + str[0] = '"'; + memcpy(&str[1], json, len); + str[len + 1] = '"'; + str[len + 2] = 0; + } + else + { + str = json; } } else { - jb = PG_GETARG_JSONB(json_arg_num); - - /* same logic as for json */ - if (JB_ROOT_COUNT(jb) == 0 && rec) + if (typid == JSONBOID) { - ReleaseTupleDesc(tupdesc); - PG_RETURN_POINTER(rec); + 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); } - ncolumns = tupdesc->natts; + res = InputFunctionCall(&io->typiofunc, str, io->typioparam, typmod); + + /* free temporary buffer */ + if (str != json) + pfree(str); - if (rec) + return res; +} + +/* prepare column metadata cache for the given type */ +static void +prepare_column_cache(ColumnIOData *column, /* metadata cache */ + Oid typid, /* column type id */ + int32 typmod, /* column type modifier */ + int32 ndims, /* number of array dimensions */ + MemoryContext mcxt, /* cache memory context */ + Oid jtype) /* json/jsonb */ +{ + /* if the type is a domain, get its base type id and type modifier */ + int32 base_typmod = typmod; + Oid base_typid = getBaseTypeAndTypmod(typid, &base_typmod); + bool type_is_domain = base_typid != typid; + + if (type_is_rowtype(base_typid)) { - /* Build a temporary HeapTuple control structure */ - tuple.t_len = HeapTupleHeaderGetDatumLength(rec); - ItemPointerSetInvalid(&(tuple.t_self)); - tuple.t_tableOid = InvalidOid; - tuple.t_data = rec; + column->type_category = 'c'; /* composite */ + column->io.composite.recordio = NULL; + column->io.composite.tupdesc = NULL; } + else + { + Oid element_type = get_element_type(base_typid); - /* - * 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) + if (OidIsValid(element_type)) + { + /* type is an array */ + int32 base_ndims = ndims; + + if (type_is_domain) + { + Assert(ndims == 0); /* arrays of domains are not supported */ + + /* get number of dimensions of domain array */ + base_ndims = get_type_ndims(typid); + } + + Assert(base_ndims > 0); + + column->type_category = 'a'; /* array */ + column->io.array.element_info = + MemoryContextAllocZero(mcxt, sizeof(ColumnIOData)); + column->io.array.element_type = element_type; + /* array element typemod stored in attribute typmod */ + column->io.array.element_typmod = base_typmod; + column->io.array.ndims = base_ndims; + } + else + { + /* type is a scalar */ + column->type_category = 's'; /* scalar */ + + /* don't need input function when converting from jsonb to jsonb */ + if (jtype != JSONBOID || base_typid != JSONBOID) + { + Oid typioproc; + + getTypeInputInfo(base_typid, &typioproc, + &column->io.scalar.typioparam); + fmgr_info_cxt(typioproc, &column->io.scalar.typiofunc, mcxt); + } + } + } + + column->typid = typid; + column->typmod = typmod; + column->ndims = ndims; + column->type_is_domain = type_is_domain; + column->domain_info = NULL; +} + +/* populate recursively a record field or an array element from json/jsonb value */ +static Datum +populate_record_field(ColumnIOData *col, /* metadata cache */ + Oid typid, /* field type id */ + int32 typmod, /* field type modifier */ + int32 ndims, /* number of array dimensions */ + const char *colname, /* for diagnostics only */ + MemoryContext mcxt, /* cache memory context */ + Datum defaultval, /* default record value if any */ + Oid jtype, /* json/jsonb */ + char *json, /* valid only if json */ + bool json_is_string, /* valid only if json */ + JsonbValue *jval, /* valid only if jsonb */ + bool *isnull) /* value is NULL */ +{ + Datum res; + + check_stack_depth(); + + /* prepare column metadata cache for the given type */ + if (col->typid != typid || col->typmod != typmod || col->ndims != ndims) + prepare_column_cache(col, typid, typmod, ndims, mcxt, jtype); + + if (jtype == JSONOID ? json == NULL : jval == NULL || jval->type == jbvNull) + { + *isnull = true; + res = PointerGetDatum(NULL); + } + else if (jtype == JSONOID && json_is_string && col->type_category != 's') + { + /* don't convert a string json value to a non-scalar */ + elog(ERROR, colname ? "expected json %s in value of key \"%s\"" + : "expected json %s", + col->type_category == 'a' ? "array" : "object", colname); + } + else { - 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); + *isnull = false; + + switch (col->type_category) + { + case 's': + res = populate_scalar(&col->io.scalar, typid, typmod, + jtype, json, json_is_string, jval); + break; + + case 'a': + res = populate_array(&col->io.array, colname, mcxt, + jtype, json, jval); + break; + + case 'c': + res = populate_composite(&col->io.composite, typid, typmod, + colname, mcxt, + DatumGetPointer(defaultval) + ? DatumGetHeapTupleHeader(defaultval) + : NULL, + jtype, + json, + jtype == JSONOID ? strlen(json) : 0, + jval); + break; + + default: + elog(ERROR, "unrecognized type category '%c'", + col->type_category); + break; + } } - if (have_record_arg && (my_extra->record_type != tupType || - my_extra->record_typmod != tupTypmod)) + if (col->type_is_domain) + domain_check(res, *isnull, col->typid, &col->domain_info, mcxt); + + return res; +} + +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; +} + +/* populate record tuple from json/jsonb value */ +static HeapTupleHeader +populate_record(TupleDesc tupdesc, /* record tuple descriptor */ + RecordIOData **precord, /* metadata cache location */ + HeapTupleHeader defaultval, /* default record value if any */ + MemoryContext mcxt, /* cache memory context */ + Oid jtype, /* json/jsonb */ + HTAB *json_hash, /* valid only if json */ + JsonbContainer *cont) /* valid only if jsonb */ +{ + 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 ((jtype == JSONOID ? hash_get_num_entries(json_hash) == 0 + : JsonContainerSize(cont) == 0) && defaultval) + 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(my_extra, 0, - offsetof(RecordIOData, columns) + - ncolumns * sizeof(ColumnIOData)); - my_extra->record_type = tupType; - my_extra->record_typmod = tupTypmod; - my_extra->ncolumns = ncolumns; + 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 (rec) + 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); } @@ -2264,31 +2841,23 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, for (i = 0; i < ncolumns; ++i) { - ColumnIOData *column_info = &my_extra->columns[i]; - Oid column_type = tupdesc->attrs[i]->atttypid; - JsonbValue *v = NULL; - JsonHashEntry *hashentry = NULL; + Form_pg_attribute att = tupdesc->attrs[i]; + char *colname = NameStr(att->attname); + JsonbValue *val = NULL; + JsonHashEntry *hashentry = NULL; /* Ignore dropped columns in datatype */ - if (tupdesc->attrs[i]->attisdropped) + if (att->attisdropped) { nulls[i] = true; continue; } if (jtype == JSONOID) - { - hashentry = hash_search(json_hash, - NameStr(tupdesc->attrs[i]->attname), - HASH_FIND, NULL); - } + hashentry = hash_search(json_hash, colname, HASH_FIND, NULL); else - { - char *key = NameStr(tupdesc->attrs[i]->attname); - - v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT, key, - strlen(key)); - } + val = findJsonbValueFromContainerLen(cont, JB_FOBJECT, colname, + strlen(colname)); /* * we can't just skip here if the key wasn't found since we might have @@ -2298,73 +2867,165 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, * 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) + if ((jtype == JSONOID ? hashentry == NULL : val == NULL) && defaultval) continue; - /* - * Prepare to convert the column value from text - */ - if (column_info->column_type != column_type) + values[i] = populate_record_field(&record->columns[i], + att->atttypid, + att->atttypmod, + att->attndims, + colname, + mcxt, + nulls[i] ? PointerGetDatum(NULL) + : values[i], + jtype, + hashentry && !hashentry->isnull ? + hashentry->val : NULL, + hashentry && hashentry->isstring, + val, + &nulls[i]); + } + + res = heap_form_tuple(tupdesc, values, nulls); + + pfree(values); + pfree(nulls); + + return res->t_data; +} + +/* 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; + +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); + text *json; + Jsonb *jb; + JsonbValue jbv; + HeapTupleHeader rec = NULL; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc = NULL; + Datum rettuple; + 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) { - 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 (!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 ((jtype == JSONOID && (hashentry == NULL || hashentry->isnull)) || - (jtype == JSONBOID && (v == NULL || v->type == jbvNull))) + + if (PG_ARGISNULL(0)) { + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + /* - * need InputFunctionCall to happen even for nulls, so that domain - * checks are done + * 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. */ - values[i] = InputFunctionCall(&column_info->proc, NULL, - column_info->typioparam, - tupdesc->attrs[i]->atttypmod); - nulls[i] = true; + tupType = argtype; + tupTypmod = -1; } else { - char *s = NULL; + rec = PG_GETARG_HEAPTUPLEHEADER(0); - 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 (PG_ARGISNULL(1)) + PG_RETURN_POINTER(rec); - values[i] = InputFunctionCall(&column_info->proc, s, - column_info->typioparam, - tupdesc->attrs[i]->atttypmod); - nulls[i] = false; + /* 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."))); - rettuple = heap_form_tuple(tupdesc, values, nulls); + Assert(tupdesc); - ReleaseTupleDesc(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; - if (json_hash) - hash_destroy(json_hash); + tupType = tupdesc->tdtypeid; + tupTypmod = tupdesc->tdtypmod; + } + + if (jtype == JSONOID) + { + json = PG_GETARG_TEXT_P(json_arg_num); + } + else + { + jb = PG_GETARG_JSONB(json_arg_num); + + /* 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, + jtype, + jtype == JSONOID ? VARDATA(json) : NULL, + jtype == JSONOID ? VARSIZE(json) - VARHDRSZ : 0, + jtype == JSONOID ? NULL : &jbv); + + if (tupdesc) + { + cache->io.tupdesc = NULL; + ReleaseTupleDesc(tupdesc); + } - PG_RETURN_DATUM(HeapTupleGetDatum(rettuple)); + PG_RETURN_DATUM(rettuple); } /* @@ -2373,12 +3034,12 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname, * decompose a json object into a hash table. */ static HTAB * -get_json_object_as_hash(text *json, const char *funcname) +get_json_object_as_hash(char *json, int len, const char *funcname) { HASHCTL ctl; HTAB *tab; JHashState *state; - JsonLexContext *lex = makeJsonLexContext(json, true); + JsonLexContext *lex = makeJsonLexContextCstringLen(json, len, true); JsonSemAction *sem; memset(&ctl, 0, sizeof(ctl)); @@ -2460,6 +3121,7 @@ hash_object_field_end(void *state, char *fname, bool isnull) */ hashentry->isnull = isnull; + if (_state->save_json_start != NULL) { int len = _state->lex->prev_token_terminator - _state->save_json_start; @@ -2468,11 +3130,13 @@ hash_object_field_end(void *state, char *fname, bool isnull) memcpy(val, _state->save_json_start, len); val[len] = '\0'; hashentry->val = val; + hashentry->isstring = false; } else { /* must have had a scalar instead */ hashentry->val = _state->saved_scalar; + hashentry->isstring = _state->saved_scalar_is_string; } } @@ -2498,7 +3162,10 @@ 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; + _state->saved_scalar_is_string = tokentype == JSON_TOKEN_STRING; + } } @@ -2537,121 +3204,24 @@ json_to_recordset(PG_FUNCTION_ARGS) } static void -make_row_from_rec_and_jsonb(Jsonb *element, PopulateRecordsetState *state) +populate_recordset_record(PopulateRecordsetState *state, + Oid jtype, HTAB *json_hash, JsonbContainer *jsonb) { - 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, + jtype, + json_hash, + jsonb); + + tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = tuphead; + + tuplestore_puttuple(state->tuple_store, &tuple); } /* @@ -2665,12 +3235,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) @@ -2716,38 +3282,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 */ @@ -2760,7 +3294,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; @@ -2811,14 +3345,15 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, if (r == WJB_ELEM) { - Jsonb *element = JsonbValueToJsonb(&v); - - 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); + + populate_recordset_record(state, JSONBOID, NULL, + v.val.binary.data); } } } @@ -2862,115 +3397,16 @@ 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; /* 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); - - tuplestore_puttuple(_state->tuple_store, rettuple); + populate_recordset_record(_state, JSONOID, _state->json_hash, NULL); /* Done with hash for this object */ - hash_destroy(json_hash); + hash_destroy(_state->json_hash); _state->json_hash = NULL; } @@ -3066,11 +3502,13 @@ populate_recordset_object_field_end(void *state, char *fname, bool isnull) memcpy(val, _state->save_json_start, len); val[len] = '\0'; hashentry->val = val; + hashentry->isstring = false; } else { /* must have had a scalar instead */ hashentry->val = _state->saved_scalar; + hashentry->isstring = _state->saved_scalar_is_string; } } diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 13ae6ad..641005b 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -2492,6 +2492,26 @@ get_array_type(Oid typid) } /* + * get_type_ndims + * + * Given the type OID, get the typndims. + */ +int32 +get_type_ndims(Oid typid) +{ + HeapTuple tp; + int32 result = -1; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + result = ((Form_pg_type) GETSTRUCT(tp))->typndims; + ReleaseSysCache(tp); + } + return result; +} + +/* * get_promoted_array_type * * The "promoted" type is what you'd get from an ARRAY(SELECT ...) diff --git a/src/include/utils/jsonb.h b/src/include/utils/jsonb.h index 470d5b1..850a825 100644 --- a/src/include/utils/jsonb.h +++ b/src/include/utils/jsonb.h @@ -218,6 +218,11 @@ typedef struct #define JB_ROOT_IS_OBJECT(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FOBJECT) #define JB_ROOT_IS_ARRAY(jbp_) ( *(uint32*) VARDATA(jbp_) & JB_FARRAY) +#define JsonContainerSize(jc) ((jc)->header & JB_CMASK) +#define JsonContainerIsObject(jc) ((jc)->header & JB_FOBJECT) +#define JsonContainerIsArray(jc) ((jc)->header & JB_FARRAY) +#define JsonContainerIsScalar(jc) ((jc)->header & JB_FSCALAR) + enum jbvType { diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index dcb8980..d25cac7 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -136,6 +136,7 @@ extern Oid get_element_type(Oid typid); extern Oid get_array_type(Oid typid); extern Oid get_promoted_array_type(Oid typid); extern Oid get_base_element_type(Oid typid); +extern int32 get_type_ndims(Oid typid); extern void getTypeInputInfo(Oid type, Oid *typInput, Oid *typIOParam); extern void getTypeOutputInfo(Oid type, Oid *typOutput, bool *typIsVarlena); extern void getTypeBinaryInputInfo(Oid type, Oid *typReceive, Oid *typIOParam); diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index efcdc41..3edc398 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -1304,6 +1304,30 @@ 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, + 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 --------+---+--- @@ -1342,6 +1366,302 @@ 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 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 in 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; +ERROR: invalid input syntax for integer: "[1, 2, 3]" +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 in 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; +ERROR: expected json array in value of key "ia2" +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: "[[1, 2], [3]]" +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 in value 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; +ERROR: expected json array in value of key "ia3" +SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [[1, 2], [null, 4]]}') q; +ERROR: expected json array in value of key "ia3" +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: "[ [[1, 2], [3, 4]], [[5, 6], [7, 8], [9, 10]] ]" +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 in 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; + ta +-------------------------------- + {"[1, 2, 3]","{\"k\": \"v\"}"} +(1 row) + +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 in 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; + ca +--------------------------------- + {"[1, 2, 3] ","{\"k\": \"v\"}"} +(1 row) + +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 in 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 reca FROM json_populate_record(NULL::jsrec, '{"reca": 123}') q; +ERROR: expected json array in 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 rec FROM json_populate_record( + row(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 @@ -1408,6 +1728,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 | 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'), @@ -1600,11 +1942,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 diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index e2cb08a..4c7727e 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -1888,6 +1888,30 @@ 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, + 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 --------+---+--- @@ -1926,6 +1950,302 @@ 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 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 in 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; +ERROR: invalid input syntax for integer: "[1, 2, 3]" +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 in 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; +ERROR: expected json array in value of key "ia2" +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: "[[1, 2], [3]]" +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 in value 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; +ERROR: expected json array in value of key "ia3" +SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [[1, 2], [null, 4]]}') q; +ERROR: expected json array in value of key "ia3" +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: "[[[1, 2], [3, 4]], [[5, 6], [7, 8], [9, 10]]]" +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 in 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; + ta +-------------------------------- + {"[1, 2, 3]","{\"k\": \"v\"}"} +(1 row) + +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 in 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; + ca +--------------------------------- + {"[1, 2, 3] ","{\"k\": \"v\"}"} +(1 row) + +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 in 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; +ERROR: expected json object in value of key "rec" +SELECT rec FROM jsonb_populate_record(NULL::jsbrec, '{"rec": [1, 2]}') q; +ERROR: expected json object in value of key "rec" +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 reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": 123}') q; +ERROR: expected json array in value of key "reca" +SELECT reca FROM jsonb_populate_record(NULL::jsbrec, '{"reca": [1, 2]}') q; +ERROR: expected json object +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 rec FROM jsonb_populate_record( + row(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 @@ -2002,11 +2322,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 @@ -2017,6 +2337,28 @@ from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb) 1 | {"c": 16, "d": 2} | | 8 | t (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 | 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 603288b..965a0e0 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -378,6 +378,33 @@ 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, + 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; @@ -388,6 +415,89 @@ 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 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 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 rec FROM json_populate_record( + row(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; @@ -404,6 +514,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'), @@ -520,8 +649,8 @@ 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) diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 6b4c796..4fde752 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -478,6 +478,33 @@ 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, + 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; @@ -488,6 +515,89 @@ 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 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 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 rec FROM jsonb_populate_record( + row(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; @@ -509,13 +619,32 @@ 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); +-- 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"}';