Hi.
The first attached patch implements recursive processing of nested
objects and arrays in json[b]_populate_record[set](),
json[b]_to_record[set](). See regression tests for examples.
It also fixes the following errors/inconsistencies caused by lost
quoting of string json values:
[master]=# select * from json_to_record('{"js": "a"}') as rec(js json);
ERROR: invalid input syntax for type json
DETAIL: Token "a" is invalid.
CONTEXT: JSON data, line 1: a
[master]=# select * from json_to_record('{"js": "true"}') as rec(js json);
js
------
true
[patched]=# select * from json_to_record('{"js": "a"}') as rec(js json);
js
-----
"a"
[patched]=# select * from json_to_record('{"js": "true"}') as rec(js json);
js
--------
"true"
The second patch adds assignment of correct ndims to array fields of
RECORD function result types.
Without this patch, attndims in tuple descriptors of RECORD types is
always 0 and the corresponding assertion fails in the next test:
[patched]=# select json_to_record('{"a": [1, 2, 3]}') as rec(a int[]);
Should I submit these patches to commitfest?
--
Nikita Glukhov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index eca98df..12049a4 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 <replaceable>base</>
(see note below).
</entry>
- <entry><literal>select * from json_populate_record(null::myrowtype, '{"a":1,"b":2}')</literal></entry>
+ <entry><literal>select * from json_populate_record(null::myrowtype, '{"a": 1, "b": ["2", "a b"], "c": {"d": 4, "e": "a b c"}}')</literal></entry>
<entry>
<programlisting>
- a | b
----+---
- 1 | 2
+ a | b | c
+---+-----------+-------------
+ 1 | {2,"a b"} | (4,"a b c")
</programlisting>
</entry>
</row>
@@ -11343,12 +11343,12 @@ table2-mapping
explicitly define the structure of the record with an <literal>AS</>
clause.
</entry>
- <entry><literal>select * from json_to_record('{"a":1,"b":[1,2,3],"c":"bar"}') as x(a int, b text, d text) </literal></entry>
+ <entry><literal>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) </literal></entry>
<entry>
<programlisting>
- a | b | d
----+---------+---
- 1 | [1,2,3] |
+ a | b | c | d | r
+---+---------+---------+---+---------------
+ 1 | [1,2,3] | {1,2,3} | | (123,"a b c")
</programlisting>
</entry>
</row>
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 17ee4e4..729826c 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 type,
+ 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,55 @@ 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
+typedef struct ScalarIOData
{
- Oid column_type;
Oid typiofunc;
Oid typioparam;
FmgrInfo proc;
-} ColumnIOData;
+} ScalarIOData;
-typedef struct RecordIOData
+typedef struct ArrayIOData
+{
+ Oid element_type;
+ int32 element_typmod;
+ ColumnIOData *element_info;
+ int ndims;
+} ArrayIOData;
+
+/*
+ * CompositeIOData is a pointer to a RecordIOData because variable-length
+ * struct RecordIOData can't be used directly in ColumnIOData.io union
+ */
+typedef RecordIOData *CompositeIOData;
+
+/* these two are stolen from hstore / record_out, used in populate_record* */
+struct ColumnIOData
+{
+ Oid typid;
+ int32 typmod;
+ int32 ndims;
+ char type_category; /* 's' - scalar, 'a' - array, 'c' - composite */
+ bool type_is_domain;
+ void *domain_info;
+ union
+ {
+ ScalarIOData scalar;
+ ArrayIOData array;
+ CompositeIOData composite;
+ } io;
+};
+
+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 +309,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;
+ ArrayBuildState *astate;
+ ArrayIOData *aio;
+ MemoryContext mcxt;
+ const char *colname;
+ int *sizes;
+ int *dims;
+ int ndims;
+ char *element_start;
+ char *element_scalar;
+ bool element_scalar_is_string;
+} PopulateArrayState;
+
/* state for json_strip_nulls */
typedef struct StripnullState
{
@@ -267,10 +341,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 +2168,580 @@ 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)
+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)
+static void
+populate_array_array_start(void *_state)
+{
+ PopulateArrayState *state = (PopulateArrayState *) _state;
+ int ndim = state->lex->lex_level;
+
+ if (ndim < state->ndims)
+ state->sizes[ndim] = 0;
+}
+
+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);
+ int dim = state->sizes[ndim];
- if (!type_is_rowtype(argtype))
+ if (state->dims[ndim] == -1)
+ state->dims[ndim] = dim;
+ 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))
+ if (ndim > 0)
+ state->sizes[ndim - 1]++;
+ }
+}
+
+static void
+populate_array_element_start(void *_state, bool isnull)
+{
+ PopulateArrayState *state = (PopulateArrayState *) _state;
+ int ndim = state->lex->lex_level;
+
+ if (ndim == state->ndims)
+ {
+ state->element_start = state->lex->token_start;
+ state->element_scalar = NULL;
+ }
+}
+
+static void
+populate_array_element_end(void *_state, bool isnull)
+{
+ PopulateArrayState *state = (PopulateArrayState *) _state;
+ int ndim = state->lex->lex_level;
+
+ if (ndim == state->ndims)
+ {
+ size_t len = state->lex->prev_token_terminator - state->element_start;
+ 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();
+ str = palloc(len + 1);
+ memcpy(str, state->element_start, len);
+ str[len] = 0;
+ }
- /*
- * 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;
+ 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]++;
+ }
+}
+
+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)
+ {
+ state->element_scalar = token;
+ state->element_scalar_is_string = tokentype == JSON_TOKEN_STRING;
+ }
+}
+
+static void
+populate_array_json(ArrayBuildState *astate,
+ char *json,
+ const char *colname,
+ ArrayIOData *aio,
+ MemoryContext mcxt,
+ int *dims,
+ int ndims)
+{
+ 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);
+}
+
+static int
+populate_array_dim_jsonb(ArrayBuildState *astate,
+ JsonbValue *jbv,
+ const char *column_name,
+ ArrayIOData *aio,
+ MemoryContext mcxt,
+ int *dims,
+ int ndims,
+ int ndim)
+{
+ JsonbContainer *jbc = jbv->val.binary.data;
+ JsonbIterator *it;
+ JsonbIteratorToken tok;
+ JsonbValue val;
+ int size = 0;
+
+ check_stack_depth();
+
+ if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc))
+ elog(ERROR,
+ column_name ? "expected json array in value of key \"%s\""
+ : "expected json array", column_name);
+
+ Assert(!JsonContainerIsScalar(jbc));
+
+ it = JsonbIteratorInit(jbc);
+
+ while ((tok = JsonbIteratorNext(&it, &val, true)) != WJB_DONE)
+ {
+ if (tok != WJB_ELEM)
+ continue;
+
+ if (ndim < ndims)
+ {
+ int dim = populate_array_dim_jsonb(astate, &val, column_name, aio,
+ mcxt, dims, ndims, ndim + 1);
+ if (dims[ndim] == -1)
+ dims[ndim] = dim;
+ 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);
+ 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);
+static Datum
+populate_array(ArrayIOData *aio,
+ const char *colname,
+ MemoryContext mcxt,
+ Oid jtype,
+ char *json,
+ JsonbValue *jbv)
+{
+ 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;
+ lbs[i] = 1;
}
+
+ astate = initArrayResult(aio->element_type, acxt, true);
+
+ if (jtype == JSONOID)
+ populate_array_json(astate, json, colname, aio, mcxt, dims, ndims);
else
- {
- /* json{b}_to_record case */
- if (PG_ARGISNULL(0))
- PG_RETURN_NULL();
+ dims[0] = populate_array_dim_jsonb(astate, jbv, colname, aio, mcxt,
+ dims, ndims, 1);
- 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.")));
- }
+ result = makeMdArrayResult(astate, ndims, dims, lbs, acxt, true);
+
+ pfree(dims);
+ pfree(lbs);
+
+ return result;
+}
+
+static Datum
+populate_composite(CompositeIOData *io,
+ Oid type,
+ int32 typmod,
+ const char *colname,
+ MemoryContext mcxt,
+ HeapTupleHeader defaultval,
+ Oid jtype,
+ char *json,
+ JsonbValue *jbv)
+{
+ HeapTupleHeader tuple;
+ TupleDesc tupdesc;
+ HTAB *json_hash = NULL;
+ JsonbContainer *container = NULL;
+
+ tupdesc = lookup_rowtype_tupdesc(type, typmod); /* FIXME cache */
if (jtype == JSONOID)
+ json_hash = get_json_object_as_hash(json, strlen(json),
+ "populate_composite");
+ else
{
- /* just get the text */
- json = PG_GETARG_TEXT_P(json_arg_num);
+ 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);
+ tuple = populate_record(tupdesc, io, 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);
+
+ ReleaseTupleDesc(tupdesc);
+
+ return HeapTupleHeaderGetDatum(tuple);
+}
+
+static Datum
+populate_scalar(ScalarIOData *io,
+ Oid typid,
+ int32 typmod,
+ Oid jtype,
+ char *json,
+ bool json_is_string,
+ JsonbValue *jbv)
+{
+ 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)
+ {
+ Jsonb *jsonb = JsonbValueToJsonb(jbv);
+ return JsonbGetDatum(jsonb);
+ }
+ else if (typid == JSONOID && jbv->type != jbvBinary)
{
- ReleaseTupleDesc(tupdesc);
- PG_RETURN_POINTER(rec);
+ Jsonb *jsonb = JsonbValueToJsonb(jbv);
+ str = JsonbToCString(NULL, &jsonb->root, VARSIZE(jsonb));
}
+ else if (jbv->type == jbvString)
+ 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->proc, str, io->typioparam, typmod);
+
+ if (str != json)
+ pfree(str);
+
+ return res;
+}
- if (rec)
+static void
+prepare_column_info(ColumnIOData *column,
+ Oid typid,
+ int32 typmod,
+ int32 ndims,
+ MemoryContext mcxt,
+ Oid jtype)
+{
+ int32 base_typmod = typmod;
+ Oid base_typid = getBaseTypeAndTypmod(typid, &base_typmod);
+
+ 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 = NULL;
}
+ else
+ {
+ Oid element_type = get_element_type(base_typid);
+
+ if (OidIsValid(element_type))
+ {
+ int32 base_ndims = ndims;
+
+ if (base_typid != typid)
+ {
+ Assert(ndims == 0);
+ 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;
+ column->io.array.element_typmod = base_typmod;
+ column->io.array.ndims = base_ndims;
+ }
+ else
+ {
+ column->type_category = 's'; /* scalar */
+
+ /* don't need input function when converting from jsonb to jsonb */
+ if (jtype != JSONBOID || base_typid != JSONBOID)
+ {
+ getTypeInputInfo(base_typid,
+ &column->io.scalar.typiofunc,
+ &column->io.scalar.typioparam);
+ fmgr_info_cxt(column->io.scalar.typiofunc,
+ &column->io.scalar.proc, mcxt);
+
+ }
+ }
+ }
+
+ column->typid = typid;
+ column->typmod = typmod;
+ column->ndims = ndims;
+ column->type_is_domain = base_typid != typid;
+ column->domain_info = NULL;
+}
+
+static Datum
+populate_record_field(ColumnIOData *col,
+ Oid type,
+ int32 typmod,
+ int32 ndims,
+ const char *colname,
+ MemoryContext mcxt,
+ Datum defaultval,
+ Oid jtype,
+ char *json,
+ bool json_is_string,
+ JsonbValue *jval,
+ bool *isnull)
+{
+ Datum res;
+
+ check_stack_depth();
/*
- * 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.
+ * Prepare to convert the column value from text
*/
- my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
- if (my_extra == NULL ||
- my_extra->ncolumns != ncolumns)
+ if (col->typid != type || col->typmod != typmod || col->ndims != ndims)
+ prepare_column_info(col, type, 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')
{
- 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);
+ /* don't convert string json value to non-scalar */
+ elog(ERROR, colname ? "expected json %s in value of key \"%s\""
+ : "expected json %s",
+ col->type_category == 'a' ? "array" : "object", colname);
}
+ else
+ {
+ *isnull = false;
- if (have_record_arg && (my_extra->record_type != tupType ||
- my_extra->record_typmod != tupTypmod))
+ switch (col->type_category)
+ {
+ case 's':
+ res = populate_scalar(&col->io.scalar, type, 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, type, typmod,
+ colname, mcxt,
+ DatumGetPointer(defaultval)
+ ? DatumGetHeapTupleHeader(defaultval)
+ : NULL,
+ jtype, json, jval);
+ break;
+
+ default:
+ elog(ERROR, "unrecognized type category '%c'",
+ col->type_category);
+ break;
+ }
+ }
+
+ if (col->type_is_domain)
+ domain_check(res, *isnull, col->typid, &col->domain_info, mcxt);
+
+ return res;
+}
+
+static RecordIOData *
+alloc_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 HeapTupleHeader
+populate_record(TupleDesc tupdesc,
+ RecordIOData **precord,
+ HeapTupleHeader defaultval,
+ MemoryContext mcxt,
+ Oid jtype,
+ HTAB *json_hash,
+ JsonbContainer *cont)
+{
+ 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;
+
+ if (record == NULL ||
+ record->ncolumns != ncolumns)
+ *precord = record = alloc_record_info(mcxt, ncolumns);
+
+ 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 +2756,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 +2782,133 @@ 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)
- {
- 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)))
+ 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;
+}
+
+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 = NULL;
+ HTAB *json_hash = NULL;
+ HeapTupleHeader rec = NULL;
+ Oid tupType = InvalidOid;
+ int32 tupTypmod = -1;
+ TupleDesc tupdesc;
+ HeapTupleHeader rettuple;
+
+ Assert(jtype == JSONOID || jtype == JSONBOID);
+
+ if (have_record_arg)
+ {
+ Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+
+ if (!type_is_rowtype(argtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("first argument of %s must be a row type",
+ funcname)));
+
+ 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);
}
+
+ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
}
+ 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);
+ if (jtype == JSONOID)
+ {
+ /* just get the text */
+ json = PG_GETARG_TEXT_P(json_arg_num);
+ json_hash = get_json_object_as_hash(VARDATA(json),
+ VARSIZE(json) - VARHDRSZ,
+ funcname);
+ }
+ else
+ jb = PG_GETARG_JSONB(json_arg_num);
+
+ /*
+ * 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.
+ */
+ rettuple = populate_record(tupdesc,
+ (RecordIOData **) &fcinfo->flinfo->fn_extra,
+ rec,
+ fcinfo->flinfo->fn_mcxt,
+ jtype,
+ json_hash,
+ &jb->root);
if (json_hash)
hash_destroy(json_hash);
- PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
+ ReleaseTupleDesc(tupdesc);
+
+ PG_RETURN_HEAPTUPLEHEADER(rettuple);
}
/*
@@ -2373,12 +2917,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 +3004,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 +3013,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 +3045,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 +3087,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 +3118,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 +3165,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 +3177,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 +3228,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 +3280,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 +3385,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"}';
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index b56d0e3..60510c3 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -673,7 +673,7 @@ BuildDescForRelation(List *schema)
* BuildDescFromLists
*
* Build a TupleDesc given lists of column names (as String nodes),
- * column type OIDs, typmods, and collation OIDs.
+ * column type OIDs, typmods, collation OIDs and ndims.
*
* No constraints are generated.
*
@@ -681,7 +681,8 @@ BuildDescForRelation(List *schema)
* with functions returning RECORD.
*/
TupleDesc
-BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
+BuildDescFromLists(List *names, List *types, List *typmods, List *collations,
+ List *ndims)
{
int natts;
AttrNumber attnum;
@@ -689,12 +690,14 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
ListCell *l2;
ListCell *l3;
ListCell *l4;
+ ListCell *l5;
TupleDesc desc;
natts = list_length(names);
Assert(natts == list_length(types));
Assert(natts == list_length(typmods));
Assert(natts == list_length(collations));
+ Assert(natts == list_length(ndims));
/*
* allocate a new tuple descriptor
@@ -706,12 +709,15 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
l2 = list_head(types);
l3 = list_head(typmods);
l4 = list_head(collations);
+ l5 = list_head(ndims);
+
foreach(l1, names)
{
char *attname = strVal(lfirst(l1));
Oid atttypid;
int32 atttypmod;
Oid attcollation;
+ int32 attndims;
atttypid = lfirst_oid(l2);
l2 = lnext(l2);
@@ -719,10 +725,12 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
l3 = lnext(l3);
attcollation = lfirst_oid(l4);
l4 = lnext(l4);
+ attndims = lfirst_oid(l5);
+ l5 = lnext(l5);
attnum++;
- TupleDescInitEntry(desc, attnum, attname, atttypid, atttypmod, 0);
+ TupleDescInitEntry(desc, attnum, attname, atttypid, atttypmod, attndims);
TupleDescInitEntryCollation(desc, attnum, attcollation);
}
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 5a0f324..6d93cb3 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -410,7 +410,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
tupdesc = BuildDescFromLists(rtfunc->funccolnames,
rtfunc->funccoltypes,
rtfunc->funccoltypmods,
- rtfunc->funccolcollations);
+ rtfunc->funccolcollations,
+ rtfunc->funccolndims);
/*
* For RECORD results, make sure a typmod has been assigned. (The
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d973225..08f5ce5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2181,6 +2181,7 @@ _copyRangeTblFunction(const RangeTblFunction *from)
COPY_NODE_FIELD(funccoltypes);
COPY_NODE_FIELD(funccoltypmods);
COPY_NODE_FIELD(funccolcollations);
+ COPY_NODE_FIELD(funccolndims);
COPY_BITMAPSET_FIELD(funcparams);
return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index edc1797..d7efd00 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2490,6 +2490,7 @@ _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
COMPARE_NODE_FIELD(funccoltypes);
COMPARE_NODE_FIELD(funccoltypmods);
COMPARE_NODE_FIELD(funccolcollations);
+ COMPARE_NODE_FIELD(funccolndims);
COMPARE_BITMAPSET_FIELD(funcparams);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7258c03..97d4439 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2880,6 +2880,7 @@ _outRangeTblFunction(StringInfo str, const RangeTblFunction *node)
WRITE_NODE_FIELD(funccoltypes);
WRITE_NODE_FIELD(funccoltypmods);
WRITE_NODE_FIELD(funccolcollations);
+ WRITE_NODE_FIELD(funccolndims);
WRITE_BITMAPSET_FIELD(funcparams);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d608530..ac3d141 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1359,6 +1359,7 @@ _readRangeTblFunction(void)
READ_NODE_FIELD(funccoltypes);
READ_NODE_FIELD(funccoltypmods);
READ_NODE_FIELD(funccolcollations);
+ READ_NODE_FIELD(funccolndims);
READ_BITMAPSET_FIELD(funcparams);
READ_DONE();
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 58f7050..19f5fe2 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1444,6 +1444,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
rtfunc->funccoltypes = NIL;
rtfunc->funccoltypmods = NIL;
rtfunc->funccolcollations = NIL;
+ rtfunc->funccolndims = NIL;
rtfunc->funcparams = NULL; /* not set until planning */
/*
@@ -1509,6 +1510,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
Oid attrtype;
int32 attrtypmod;
Oid attrcollation;
+ int32 attrndims;
attrname = n->colname;
if (n->typeName->setof)
@@ -1520,12 +1522,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
typenameTypeIdAndMod(pstate, n->typeName,
&attrtype, &attrtypmod);
attrcollation = GetColumnDefCollation(pstate, n, attrtype);
+ attrndims = list_length(n->typeName->arrayBounds);
TupleDescInitEntry(tupdesc,
(AttrNumber) i,
attrname,
attrtype,
attrtypmod,
- 0);
+ attrndims);
TupleDescInitEntryCollation(tupdesc,
(AttrNumber) i,
attrcollation);
@@ -1537,6 +1540,8 @@ addRangeTableEntryForFunction(ParseState *pstate,
attrtypmod);
rtfunc->funccolcollations = lappend_oid(rtfunc->funccolcollations,
attrcollation);
+ rtfunc->funccolndims = lappend_int(rtfunc->funccolndims,
+ attrndims);
i++;
}
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index de18f74..9ed44fd 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -125,6 +125,7 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
extern TupleDesc BuildDescForRelation(List *schema);
-extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods, List *collations);
+extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods,
+ List *collations, List *ndims);
#endif /* TUPDESC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fc532fb..d27db6e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -969,7 +969,8 @@ typedef struct RangeTblEntry
* If the function had a column definition list (required for an
* otherwise-unspecified RECORD result), funccolnames lists the names given
* in the definition list, funccoltypes lists their declared column types,
- * funccoltypmods lists their typmods, funccolcollations their collations.
+ * funccoltypmods lists their typmods, funccolcollations their collations,
+ * funccolndims lists their numbers of array dimensions.
* Otherwise, those fields are NIL.
*
* Notice we don't attempt to store info about the results of functions
@@ -989,6 +990,7 @@ typedef struct RangeTblFunction
List *funccoltypes; /* OID list of column type OIDs */
List *funccoltypmods; /* integer list of column typmods */
List *funccolcollations; /* OID list of column collation OIDs */
+ List *funccolndims; /* integer list of column array ndims */
/* This is set during planning for use by the executor: */
Bitmapset *funcparams; /* PARAM_EXEC Param IDs affecting this func */
} RangeTblFunction;
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers