On 01/08/2017 09:52 PM, Tom Lane wrote:
The example you quoted at the start of the thread doesn't fail for me
in HEAD, so I surmise that it's falling foul of some assertion you added
in the 0001 patch, but if so I think that assertion is wrong. attndims
is really syntactic sugar only and doesn't affect anything meaningful
semantically. Here is an example showing why it shouldn't:
regression=# create table foo (d0 _int4, d1 int[], d2 int[3][4]);
CREATE TABLE
regression=# select attname,atttypid,attndims from pg_attribute where attrelid =
'foo'::regclass and attnum > 0;
attname | atttypid | attndims
---------+----------+----------
d0 | 1007 | 0
d1 | 1007 | 1
d2 | 1007 | 2
(3 rows)
Columns d0,d1,d2 are really all of the same type, and any code that
treats d0 and d1 differently is certainly broken.
Thank you for this example with raw _int4 type. I didn't expect that
attndims can legally be zero. There was really wrong assertion "ndims > 0"
that was necessary because I used attndims for verification of a
number of dimensions of a populated json array.
I have fixed the first patch: when the number of dimensionsis unknown
we determine it simply by the number of successive opening brackets at
the start of a json array. But I'm still using for verification non-zero
ndims values that can be get from composite type attribute (attndims) or
from domain array type (typndims) through specially added function
get_type_ndims().
On 01/08/2017 09:52 PM, Tom Lane wrote:
I do not see the point of the second one of these, and it adds no test
case showing why it would be needed.
I also have added special test cases for json_to_record() showing difference
in behavior that the second patch brings in (see changes in json.out and
jsonb.out).
--
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 10e3186..55cacfb 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -11249,12 +11249,12 @@ table2-mapping
whose columns match the record type defined by <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..04959cb 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -93,7 +93,7 @@ static void elements_array_element_end(void *state, bool isnull);
static void elements_scalar(void *state, char *token, JsonTokenType tokentype);
/* turn a json object into a hash table */
-static HTAB *get_json_object_as_hash(text *json, const char *funcname);
+static HTAB *get_json_object_as_hash(char *json, int len, const char *funcname);
/* common worker for populate_record and to_record */
static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
@@ -149,6 +149,33 @@ static void setPathArray(JsonbIterator **it, Datum *path_elems,
int level, Jsonb *newval, uint32 nelems, int op_type);
static void addJsonbToParseState(JsonbParseState **jbps, Jsonb *jb);
+/* helper functions for populate_record[set] */
+typedef struct ColumnIOData ColumnIOData;
+typedef struct RecordIOData RecordIOData;
+
+static HeapTupleHeader
+populate_record(TupleDesc tupdesc,
+ RecordIOData **record_info,
+ HeapTupleHeader template,
+ MemoryContext mcxt,
+ Oid jtype,
+ HTAB *json_hash,
+ JsonbContainer *cont);
+
+static Datum
+populate_record_field(ColumnIOData *col,
+ Oid typid,
+ int32 typmod,
+ int32 ndims,
+ const char *colname,
+ MemoryContext mcxt,
+ Datum defaultval,
+ Oid jtype,
+ char *json,
+ bool json_is_string,
+ JsonbValue *jval,
+ bool *isnull);
+
/* state for json_object_keys */
typedef struct OkeysState
{
@@ -216,6 +243,7 @@ typedef struct JhashState
HTAB *hash;
char *saved_scalar;
char *save_json_start;
+ bool saved_scalar_is_string;
} JHashState;
/* hashtable element */
@@ -223,26 +251,67 @@ typedef struct JsonHashEntry
{
char fname[NAMEDATALEN]; /* hash key (MUST BE FIRST) */
char *val;
- char *json;
bool isnull;
+ bool isstring;
} JsonHashEntry;
-/* these two are stolen from hstore / record_out, used in populate_record* */
-typedef struct ColumnIOData
+/* structure to cache type I/O metadata needed for populate_scalar() */
+typedef struct ScalarIOData
{
- Oid column_type;
- Oid typiofunc;
Oid typioparam;
- FmgrInfo proc;
-} ColumnIOData;
+ FmgrInfo typiofunc;
+} ScalarIOData;
+
+/* structure to cache metadata needed for populate_array() */
+typedef struct ArrayIOData
+{
+ Oid element_type; /* array element type id */
+ int32 element_typmod; /* array element type modifier */
+ ColumnIOData *element_info; /* metadata cache */
+ int ndims; /* number of dimensions of array */
+} ArrayIOData;
+
+/* structure to cache metadata needed for populate_composite() */
+typedef struct CompositeIOData
+{
+ /*
+ * We use pointer to a RecordIOData here because variable-length
+ * struct RecordIOData can't be used directly in ColumnIOData.io union
+ */
+ RecordIOData *recordio; /* metadata cache for populate_record() */
+ TupleDesc tupdesc; /* cached tuple descriptor */
+} CompositeIOData;
+
+/* these two are stolen from hstore / record_out, used in populate_record* */
-typedef struct RecordIOData
+/* structure to cache record metadata needed for populate_record_field() */
+struct ColumnIOData
+{
+ Oid typid; /* column type id */
+ int32 typmod; /* column type modifier */
+ int32 ndims; /* number of array dimensions if column is array */
+ char type_category; /* column type category:
+ * 's' - scalar,
+ * 'a' - array,
+ * 'c' - composite */
+ bool type_is_domain; /* column type is domain */
+ void *domain_info; /* opaque cache for domain checks */
+ union
+ {
+ ScalarIOData scalar;
+ ArrayIOData array;
+ CompositeIOData composite;
+ } io; /* metadata cache for various column type categories */
+};
+
+/* structure to cache record metadata needed for populate_record() */
+struct RecordIOData
{
Oid record_type;
int32 record_typmod;
int ncolumns;
ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
-} RecordIOData;
+};
/* state for populate_recordset */
typedef struct PopulateRecordsetState
@@ -252,13 +321,37 @@ 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;
+/* common data for populate_array_json() and populate_array_dim_jsonb() */
+typedef struct PopulateArrayContext
+{
+ ArrayBuildState *astate; /* array build state */
+ ArrayIOData *aio; /* metadata cache */
+ MemoryContext acxt; /* array build memory context */
+ MemoryContext mcxt; /* cache memory context */
+ const char *colname; /* for diagnostics only */
+ int *dims; /* dimensions */
+ int *sizes; /* current dimension counters */
+ int ndims; /* number of dimensions */
+} PopulateArrayContext;
+
+/* state for populate_array_json() */
+typedef struct PopulateArrayState
+{
+ JsonLexContext *lex; /* json lexer */
+ PopulateArrayContext *ctx; /* context */
+ char *element_start; /* start of the current array element */
+ char *element_scalar; /* current array element token if it is a scalar */
+ bool element_scalar_is_string; /* is current scalar array element string */
+} PopulateArrayState;
+
/* state for json_strip_nulls */
typedef struct StripnullState
{
@@ -267,10 +360,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,273 +2187,912 @@ json_to_record(PG_FUNCTION_ARGS)
return populate_record_worker(fcinfo, "json_to_record", false);
}
-static Datum
-populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
- bool have_record_arg)
+/* helper function for diagnostics */
+static void
+populate_array_report_expected_array(PopulateArrayContext *ctx, int ndim)
{
- int json_arg_num = have_record_arg ? 1 : 0;
- Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
- text *json;
- Jsonb *jb = NULL;
- HTAB *json_hash = NULL;
- HeapTupleHeader rec = NULL;
- Oid tupType = InvalidOid;
- int32 tupTypmod = -1;
- TupleDesc tupdesc;
- HeapTupleData tuple;
- HeapTuple rettuple;
- RecordIOData *my_extra;
- int ncolumns;
- int i;
- Datum *values;
- bool *nulls;
+ StringInfoData msg;
+ int i;
- Assert(jtype == JSONOID || jtype == JSONBOID);
+ initStringInfo(&msg);
- if (have_record_arg)
+ appendStringInfoString(&msg, "expected json array");
+
+ if (ndim > 0)
{
- Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Assert(ctx->ndims > 0 && ndim < ctx->ndims);
- if (!type_is_rowtype(argtype))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("first argument of %s must be a row type",
- funcname)));
+ appendStringInfoString(&msg, " in array element ");
- if (PG_ARGISNULL(0))
- {
- if (PG_ARGISNULL(1))
- PG_RETURN_NULL();
+ for (i = 0; i < ndim; i++)
+ appendStringInfo(&msg, "[%d]", ctx->sizes[i]);
+ }
- /*
- * have no tuple to look at, so the only source of type info is
- * the argtype. The lookup_rowtype_tupdesc call below will error
- * out if we don't have a known composite type oid here.
- */
- tupType = argtype;
- tupTypmod = -1;
- }
- else
- {
- rec = PG_GETARG_HEAPTUPLEHEADER(0);
+ if (ctx->colname)
+ appendStringInfo(&msg, "%s of key \"%s\"",
+ ndim > 0 ? "" : " in value", ctx->colname);
- if (PG_ARGISNULL(1))
- PG_RETURN_POINTER(rec);
+ elog(ERROR, "%s", msg.data);
+}
- /* Extract type info from the tuple itself */
- tupType = HeapTupleHeaderGetTypeId(rec);
- tupTypmod = HeapTupleHeaderGetTypMod(rec);
- }
+/* assign number of dimensions of the populated array when it became known */
+static void
+populate_array_assign_ndims(PopulateArrayContext *ctx, int ndims)
+{
+ int i;
+
+ Assert(ctx->ndims <= 0);
- tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+ if (ndims <= 0)
+ populate_array_report_expected_array(ctx, ndims);
+
+ ctx->ndims = ndims;
+ ctx->dims = palloc(sizeof(int) * ndims);
+ ctx->sizes = palloc0(sizeof(int) * ndims);
+
+ for (i = 0; i < ndims; i++)
+ ctx->dims[i] = -1; /* dimensions are unknown yet */
+}
+
+/* check populated subarray dimension */
+static void
+populate_array_check_dimension(PopulateArrayContext *ctx, int ndim,
+ Oid jtype, char *json, JsonbValue *jbv)
+{
+ int dim = ctx->sizes[ndim]; /* current dimension counter */
+
+ if (ctx->dims[ndim] == -1)
+ ctx->dims[ndim] = dim; /* assign dimension if unknown yet */
+ else if (ctx->dims[ndim] != dim)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed json array: \"%s\"",
+ jtype == JSONOID ? json :
+ JsonbToCString(NULL, jbv->val.binary.data,
+ jbv->val.binary.len)),
+ errdetail("Multidimensional arrays must have "
+ "sub-arrays with matching dimensions.")));
+
+ /* reset current array dimension size counter */
+ ctx->sizes[ndim] = 0;
+
+ /* increment parent dimension counter if it is a nested sub-array */
+ if (ndim > 0)
+ ctx->sizes[ndim - 1]++;
+}
+
+static void populate_array_element(PopulateArrayContext *ctx,
+ int ndim,
+ Oid jtype,
+ char *json,
+ bool jsonIsString,
+ JsonbValue *jbv)
+{
+ Datum element;
+ bool element_isnull;
+
+ /* populate array element */
+ element = populate_record_field(ctx->aio->element_info,
+ ctx->aio->element_type,
+ ctx->aio->element_typmod,
+ 0, NULL, ctx->mcxt,
+ PointerGetDatum(NULL),
+ jtype, json, jsonIsString, jbv,
+ &element_isnull);
+
+ accumArrayResult(ctx->astate, element, element_isnull,
+ ctx->aio->element_type, ctx->acxt);
+
+ Assert(ndim > 0);
+ ctx->sizes[ndim - 1]++; /* increment current dimension counter */
+}
+
+/* json object start handler for populate_array_json() */
+static void
+populate_array_object_start(void *_state)
+{
+ PopulateArrayState *state = (PopulateArrayState *) _state;
+ int ndim = state->lex->lex_level;
+
+ if (state->ctx->ndims <= 0)
+ populate_array_assign_ndims(state->ctx, ndim);
+ else if (ndim < state->ctx->ndims)
+ populate_array_report_expected_array(state->ctx, ndim);
+}
+
+/* json array end handler for populate_array_json() */
+static void
+populate_array_array_end(void *_state)
+{
+ PopulateArrayState *state = (PopulateArrayState *) _state;
+ PopulateArrayContext *ctx = state->ctx;
+ int ndim = state->lex->lex_level;
+
+ if (ctx->ndims <= 0)
+ populate_array_assign_ndims(ctx, ndim + 1);
+
+ if (ndim < ctx->ndims)
+ populate_array_check_dimension(ctx, ndim,
+ JSONOID, state->lex->input, NULL);
+}
+
+/* json array element start handler for populate_array_json() */
+static void
+populate_array_element_start(void *_state, bool isnull)
+{
+ PopulateArrayState *state = (PopulateArrayState *) _state;
+ int ndim = state->lex->lex_level;
+
+ if (ndim <= 0 || ndim == state->ctx->ndims)
+ {
+ /* remember current array element start */
+ state->element_start = state->lex->token_start;
+ state->element_scalar = NULL;
}
- else
+}
+
+/* json array element end handler for populate_array_json() */
+static void
+populate_array_element_end(void *_state, bool isnull)
+{
+ PopulateArrayState *state = (PopulateArrayState *) _state;
+ PopulateArrayContext *ctx = state->ctx;
+ int ndim = state->lex->lex_level;
+
+ Assert(ctx->ndims > 0);
+
+ if (ndim == ctx->ndims)
{
- /* json{b}_to_record case */
- if (PG_ARGISNULL(0))
- PG_RETURN_NULL();
+ char *str;
- if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("function returning record called in context "
- "that cannot accept type record"),
- errhint("Try calling the function in the FROM clause "
- "using a column definition list.")));
+ if (isnull)
+ str = NULL;
+ else if (state->element_scalar)
+ str = state->element_scalar;
+ else
+ {
+ size_t len = state->lex->prev_token_terminator -
+ state->element_start;
+
+ str = palloc(len + 1);
+ memcpy(str, state->element_start, len);
+ str[len] = 0;
+ }
+
+ populate_array_element(ctx, ndim, JSONOID, str,
+ state->element_scalar &&
+ state->element_scalar_is_string, NULL);
}
+}
- if (jtype == JSONOID)
+/* json scalar handler for populate_array_json() */
+static void
+populate_array_scalar(void *_state, char *token, JsonTokenType tokentype)
+{
+ PopulateArrayState *state = (PopulateArrayState *) _state;
+ PopulateArrayContext *ctx = state->ctx;
+ int ndim = state->lex->lex_level;
+
+ if (ctx->ndims <= 0)
+ populate_array_assign_ndims(ctx, ndim);
+ else if (ndim < ctx->ndims)
+ populate_array_report_expected_array(ctx, ndim);
+
+ if (ndim == ctx->ndims)
{
- /* just get the text */
- json = PG_GETARG_TEXT_P(json_arg_num);
+ /* remeber scalar element token */
+ state->element_scalar = token;
+ state->element_scalar_is_string = tokentype == JSON_TOKEN_STRING;
+ }
+}
+
+/* parse json array and populate array */
+static void
+populate_array_json(PopulateArrayContext *ctx, char *json)
+{
+ PopulateArrayState state;
+ JsonSemAction sem;
+
+ state.lex = makeJsonLexContextCstringLen(json, strlen(json), true);
+ state.ctx = ctx;
+
+ memset(&sem, 0, sizeof(sem));
+ sem.semstate = (void *) &state;
+ sem.object_start = populate_array_object_start;
+ sem.array_end = populate_array_array_end;
+ sem.array_element_start = populate_array_element_start;
+ sem.array_element_end = populate_array_element_end;
+ sem.scalar = populate_array_scalar;
- json_hash = get_json_object_as_hash(json, funcname);
+ pg_parse_json(state.lex, &sem);
+ /* number of dimensions should be already known */
+ Assert(ctx->ndims > 0 && ctx->dims);
+
+ pfree(state.lex);
+}
+
+/*
+ * populate_array_dim_jsonb() -- Iterate recursively through jsonb sub-array
+ * elements and accumulate result using given ArrayBuildState.
+ */
+static void
+populate_array_dim_jsonb(PopulateArrayContext *ctx, /* context */
+ JsonbValue *jbv, /* jsonb sub-array */
+ int ndim) /* current dimension */
+{
+ JsonbContainer *jbc = jbv->val.binary.data;
+ JsonbIterator *it;
+ JsonbIteratorToken tok;
+ JsonbValue val;
+
+ check_stack_depth();
+
+ if (jbv->type != jbvBinary || !JsonContainerIsArray(jbc))
+ populate_array_report_expected_array(ctx, ndim - 1);
+
+ Assert(!JsonContainerIsScalar(jbc));
+
+ it = JsonbIteratorInit(jbc);
+
+ tok = JsonbIteratorNext(&it, &val, true);
+ Assert(tok == WJB_BEGIN_ARRAY);
+
+ tok = JsonbIteratorNext(&it, &val, true);
+
+ /*
+ * If the number of dimensions is yet unknown and
+ * we have found end of array or the first child element is not an array
+ * then assign number of dimensions now.
+ */
+ if (ctx->ndims <= 0 &&
+ (tok == WJB_END_ARRAY ||
+ (tok == WJB_ELEM &&
+ (val.type != jbvBinary ||
+ !JsonContainerIsArray(val.val.binary.data)))))
+ populate_array_assign_ndims(ctx, ndim);
+
+ /* process all array elements */
+ while (tok == WJB_ELEM)
+ {
/*
- * 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.
+ * Recurse only if the dimensions of dimensions is yet unknown or
+ * if it is not the innermost dimension.
*/
- if (hash_get_num_entries(json_hash) == 0 && rec)
+ if (ctx->ndims > 0 && ndim >= ctx->ndims)
+ populate_array_element(ctx, ndim, JSONBOID, NULL, false, &val);
+ else
{
- hash_destroy(json_hash);
- ReleaseTupleDesc(tupdesc);
- PG_RETURN_POINTER(rec);
- }
- }
- else
- {
- jb = PG_GETARG_JSONB(json_arg_num);
+ /* populate child sub-array */
+ populate_array_dim_jsonb(ctx, &val, ndim + 1);
- /* same logic as for json */
- if (JB_ROOT_COUNT(jb) == 0 && rec)
- {
- ReleaseTupleDesc(tupdesc);
- PG_RETURN_POINTER(rec);
+ /* number of dimensions should be already known */
+ Assert(ctx->ndims > 0 && ctx->dims);
+
+ populate_array_check_dimension(ctx, ndim, JSONBOID, NULL, jbv);
}
+
+ tok = JsonbIteratorNext(&it, &val, true);
}
- ncolumns = tupdesc->natts;
+ Assert(tok == WJB_END_ARRAY);
- if (rec)
- {
- /* Build a temporary HeapTuple control structure */
- tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
- ItemPointerSetInvalid(&(tuple.t_self));
- tuple.t_tableOid = InvalidOid;
- tuple.t_data = rec;
- }
+ /* free iterator, iterating until WJB_DONE */
+ tok = JsonbIteratorNext(&it, &val, true);
+ Assert(tok == WJB_DONE && !it);
+}
+
+/* populate recursively json/jsonb array */
+static Datum
+populate_array(ArrayIOData *aio, /* metadata cache */
+ const char *colname, /* for diagnostics only */
+ MemoryContext mcxt, /* cache memory context */
+ Oid jtype, /* json/jsonb */
+ char *json, /* valid only if json */
+ JsonbValue *jbv) /* valid only if jsonb */
+{
+ PopulateArrayContext ctx;
+ Datum result;
+ int *lbs;
+ int i;
+
+ ctx.aio = aio;
+ ctx.mcxt = mcxt;
+ ctx.acxt = CurrentMemoryContext;
+ ctx.astate = initArrayResult(aio->element_type, ctx.acxt, true);
+ ctx.colname = colname;
+ ctx.ndims = 0;
/*
- * We arrange to look up the needed I/O info just once per series of
- * calls, assuming the record type doesn't change underneath us.
+ * If the number of dimensions is already known, assign it and allocate
+ * dimensions array now, else allocate it later when it becomes known.
*/
- my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
- if (my_extra == NULL ||
- my_extra->ncolumns != ncolumns)
+ if (aio->ndims > 0)
+ populate_array_assign_ndims(&ctx, aio->ndims);
+ else
{
- fcinfo->flinfo->fn_extra =
- MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
- offsetof(RecordIOData, columns) +
- ncolumns * sizeof(ColumnIOData));
- my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
- my_extra->record_type = InvalidOid;
- my_extra->record_typmod = 0;
- my_extra->ncolumns = ncolumns;
- MemSet(my_extra->columns, 0, sizeof(ColumnIOData) * ncolumns);
+ ctx.dims = NULL;
+ ctx.sizes = NULL;
}
- if (have_record_arg && (my_extra->record_type != tupType ||
- my_extra->record_typmod != tupTypmod))
+ if (jtype == JSONOID)
+ populate_array_json(&ctx, json);
+ else
{
- MemSet(my_extra, 0,
- offsetof(RecordIOData, columns) +
- ncolumns * sizeof(ColumnIOData));
- my_extra->record_type = tupType;
- my_extra->record_typmod = tupTypmod;
- my_extra->ncolumns = ncolumns;
+ populate_array_dim_jsonb(&ctx, jbv, 1);
+ ctx.dims[0] = ctx.sizes[0];
}
- values = (Datum *) palloc(ncolumns * sizeof(Datum));
- nulls = (bool *) palloc(ncolumns * sizeof(bool));
+ Assert(ctx.ndims > 0);
+
+ lbs = palloc(sizeof(int) * ctx.ndims);
+
+ for (i = 0; i < ctx.ndims; i++)
+ lbs[i] = 1;
+
+ result = makeMdArrayResult(ctx.astate, ctx.ndims, ctx.dims, lbs,
+ ctx.acxt, true);
+
+ pfree(ctx.dims);
+ pfree(ctx.sizes);
+ pfree(lbs);
+
+ return result;
+}
- if (rec)
+/* populate recursively composite (row type) value from json/jsonb */
+static Datum
+populate_composite(CompositeIOData *io, /* metadata cache */
+ Oid typeid, /* row type id */
+ int32 typmod, /* row type modifier */
+ const char *colname, /* for diagnostics only */
+ MemoryContext mcxt, /* cache memory context */
+ HeapTupleHeader defaultval, /* default row value if any */
+ Oid jtype, /* json/jsonb */
+ char *json, /* valid only if json */
+ int jsonlen, /* valid only if json */
+ JsonbValue *jbv) /* valid only if jsonb only */
+{
+ HeapTupleHeader tuple;
+ HTAB *json_hash = NULL;
+ JsonbContainer *container = NULL;
+
+ /* acquire cached tuple descriptor */
+ if (!io->tupdesc ||
+ io->tupdesc->tdtypeid != typeid ||
+ io->tupdesc->tdtypmod != typmod)
{
- /* Break down the tuple into fields */
- heap_deform_tuple(&tuple, tupdesc, values, nulls);
+ TupleDesc tupdesc = lookup_rowtype_tupdesc(typeid, typmod);
+ MemoryContext oldcxt;
+
+ if (io->tupdesc)
+ FreeTupleDesc(io->tupdesc);
+
+ /* copy tuple desc without constraints into cache memory context */
+ oldcxt = MemoryContextSwitchTo(mcxt);
+ io->tupdesc = CreateTupleDescCopy(tupdesc);
+ MemoryContextSwitchTo(oldcxt);
+
+ ReleaseTupleDesc(tupdesc);
+ }
+
+ /* prepare input value */
+ if (jtype == JSONOID)
+ {
+ /* convert plain-text json into a hash table */
+ json_hash = get_json_object_as_hash(json, jsonlen,
+ "populate_composite");
}
else
{
- for (i = 0; i < ncolumns; ++i)
- {
- values[i] = (Datum) 0;
- nulls[i] = true;
- }
+ 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;
}
- 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;
+ /* populate resulting record tuple */
+ tuple = populate_record(io->tupdesc, &io->recordio, defaultval, mcxt,
+ jtype, json_hash, container);
- /* Ignore dropped columns in datatype */
- if (tupdesc->attrs[i]->attisdropped)
- {
- nulls[i] = true;
- continue;
- }
+ if (jtype == JSONOID)
+ hash_destroy(json_hash);
- if (jtype == JSONOID)
+ return HeapTupleHeaderGetDatum(tuple);
+}
+
+/* populate non-null scalar value from json/jsonb value */
+static Datum
+populate_scalar(ScalarIOData *io, /* metadata cache */
+ Oid typid, /* scalar type id */
+ int32 typmod, /* scalar type modifier */
+ Oid jtype, /* json/jsonb */
+ char *json, /* valid only if json */
+ bool json_is_string, /* valid only if json */
+ JsonbValue *jbv) /* valid only if jsonb */
+{
+ Datum res;
+ char *str = NULL;
+
+ if (jtype == JSONOID)
+ {
+ /* already done the hard work in the json case */
+ if ((typid == JSONOID || typid == JSONBOID) && json_is_string)
{
- hashentry = hash_search(json_hash,
- NameStr(tupdesc->attrs[i]->attname),
- HASH_FIND, NULL);
+ /*
+ * 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
{
- char *key = NameStr(tupdesc->attrs[i]->attname);
-
- v = findJsonbValueFromContainerLen(&jb->root, JB_FOBJECT, key,
- strlen(key));
+ str = json;
}
-
- /*
- * we can't just skip here if the key wasn't found since we might have
- * a domain to deal with. If we were passed in a non-null record
- * datum, we assume that the existing values are valid (if they're
- * not, then it's not our fault), but if we were passed in a null,
- * then every field which we don't populate needs to be run through
- * the input function just in case it's a domain type.
- */
- if (((jtype == JSONOID && hashentry == NULL) ||
- (jtype == JSONBOID && v == NULL)) && rec)
- continue;
-
- /*
- * Prepare to convert the column value from text
- */
- if (column_info->column_type != column_type)
+ }
+ else
+ {
+ if (typid == JSONBOID)
{
- 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;
+ Jsonb *jsonb = JsonbValueToJsonb(jbv); /* directly use jsonb */
+ return JsonbGetDatum(jsonb);
}
- if ((jtype == JSONOID && (hashentry == NULL || hashentry->isnull)) ||
- (jtype == JSONBOID && (v == NULL || v->type == jbvNull)))
+ /* convert jsonb to string for typio call */
+ else if (typid == JSONOID && jbv->type != jbvBinary)
{
/*
- * need InputFunctionCall to happen even for nulls, so that domain
- * checks are done
+ * Convert scalar jsonb (non-scalars are passed here as jbvBinary)
+ * to json string, preserving quotes around top-level strings.
*/
- values[i] = InputFunctionCall(&column_info->proc, NULL,
- column_info->typioparam,
- tupdesc->attrs[i]->atttypmod);
- nulls[i] = true;
+ Jsonb *jsonb = JsonbValueToJsonb(jbv);
+ str = JsonbToCString(NULL, &jsonb->root, VARSIZE(jsonb));
}
+ else if (jbv->type == jbvString) /* quotes are stripped */
+ str = pnstrdup(jbv->val.string.val, jbv->val.string.len);
+ else if (jbv->type == jbvBool)
+ str = pstrdup(jbv->val.boolean ? "true" : "false");
+ else if (jbv->type == jbvNumeric)
+ str = DatumGetCString(DirectFunctionCall1(numeric_out,
+ PointerGetDatum(jbv->val.numeric)));
+ else if (jbv->type == jbvBinary)
+ str = JsonbToCString(NULL, jbv->val.binary.data,
+ jbv->val.binary.len);
else
+ elog(ERROR, "unrecognized jsonb type: %d", (int) jbv->type);
+ }
+
+ res = InputFunctionCall(&io->typiofunc, str, io->typioparam, typmod);
+
+ /* free temporary buffer */
+ if (str != json)
+ pfree(str);
+
+ return res;
+}
+
+/* prepare column metadata cache for the given type */
+static void
+prepare_column_cache(ColumnIOData *column, /* metadata cache */
+ Oid typid, /* column type id */
+ int32 typmod, /* column type modifier */
+ int32 ndims, /* number of array dimensions */
+ MemoryContext mcxt, /* cache memory context */
+ Oid jtype) /* json/jsonb */
+{
+ /* if the type is a domain, get its base type id and type modifier */
+ int32 base_typmod = typmod;
+ Oid base_typid = getBaseTypeAndTypmod(typid, &base_typmod);
+ bool type_is_domain = base_typid != typid;
+
+ if (type_is_rowtype(base_typid))
+ {
+ column->type_category = 'c'; /* composite */
+ column->io.composite.recordio = NULL;
+ column->io.composite.tupdesc = NULL;
+ }
+ else
+ {
+ Oid element_type = get_element_type(base_typid);
+
+ if (OidIsValid(element_type))
{
- char *s = NULL;
+ /* type is an array */
+ int32 base_ndims = ndims;
- if (jtype == JSONOID)
+ if (type_is_domain)
{
- /* already done the hard work in the json case */
- s = hashentry->val;
+ Assert(ndims == 0); /* arrays of domains are not supported */
+
+ /* get number of dimensions of domain array */
+ base_ndims = get_type_ndims(typid);
}
- else
+
+ column->type_category = 'a'; /* array */
+ column->io.array.element_info =
+ MemoryContextAllocZero(mcxt, sizeof(ColumnIOData));
+ column->io.array.element_type = element_type;
+ /* array element typemod stored in attribute typmod */
+ column->io.array.element_typmod = base_typmod;
+ column->io.array.ndims = base_ndims;
+ }
+ else
+ {
+ /* type is a scalar */
+ column->type_category = 's'; /* scalar */
+
+ /* don't need input function when converting from jsonb to jsonb */
+ if (jtype != JSONBOID || base_typid != JSONBOID)
{
- 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);
- }
+ Oid typioproc;
- values[i] = InputFunctionCall(&column_info->proc, s,
- column_info->typioparam,
- tupdesc->attrs[i]->atttypmod);
- nulls[i] = false;
+ getTypeInputInfo(base_typid, &typioproc,
+ &column->io.scalar.typioparam);
+ fmgr_info_cxt(typioproc, &column->io.scalar.typiofunc, mcxt);
+ }
}
}
- rettuple = heap_form_tuple(tupdesc, values, nulls);
+ column->typid = typid;
+ column->typmod = typmod;
+ column->ndims = ndims;
+ column->type_is_domain = type_is_domain;
+ column->domain_info = NULL;
+}
- ReleaseTupleDesc(tupdesc);
+/* populate recursively a record field or an array element from json/jsonb value */
+static Datum
+populate_record_field(ColumnIOData *col, /* metadata cache */
+ Oid typid, /* field type id */
+ int32 typmod, /* field type modifier */
+ int32 ndims, /* number of array dimensions */
+ const char *colname, /* for diagnostics only */
+ MemoryContext mcxt, /* cache memory context */
+ Datum defaultval, /* default record value if any */
+ Oid jtype, /* json/jsonb */
+ char *json, /* valid only if json */
+ bool json_is_string, /* valid only if json */
+ JsonbValue *jval, /* valid only if jsonb */
+ bool *isnull) /* value is NULL */
+{
+ Datum res;
- if (json_hash)
- hash_destroy(json_hash);
+ check_stack_depth();
+
+ /* prepare column metadata cache for the given type */
+ if (col->typid != typid || col->typmod != typmod || col->ndims != ndims)
+ prepare_column_cache(col, typid, typmod, ndims, mcxt, jtype);
+
+ if (jtype == JSONOID ? json == NULL : jval == NULL || jval->type == jbvNull)
+ {
+ *isnull = true;
+ res = PointerGetDatum(NULL);
+ }
+ else if (jtype == JSONOID && json_is_string && col->type_category != 's')
+ {
+ /* don't convert a string json value to a non-scalar */
+ elog(ERROR, colname ? "expected json %s in value of key \"%s\""
+ : "expected json %s",
+ col->type_category == 'a' ? "array" : "object", colname);
+ }
+ else
+ {
+ *isnull = false;
+
+ switch (col->type_category)
+ {
+ case 's':
+ res = populate_scalar(&col->io.scalar, typid, typmod,
+ jtype, json, json_is_string, jval);
+ break;
+
+ case 'a':
+ res = populate_array(&col->io.array, colname, mcxt,
+ jtype, json, jval);
+ break;
+
+ case 'c':
+ res = populate_composite(&col->io.composite, typid, typmod,
+ colname, mcxt,
+ DatumGetPointer(defaultval)
+ ? DatumGetHeapTupleHeader(defaultval)
+ : NULL,
+ jtype,
+ json,
+ jtype == JSONOID ? strlen(json) : 0,
+ jval);
+ break;
+
+ default:
+ elog(ERROR, "unrecognized type category '%c'",
+ col->type_category);
+ break;
+ }
+ }
+
+ if (col->type_is_domain)
+ domain_check(res, *isnull, col->typid, &col->domain_info, mcxt);
+
+ return res;
+}
+
+static RecordIOData *
+allocate_record_info(MemoryContext mcxt, int ncolumns)
+{
+ RecordIOData *data = (RecordIOData *)
+ MemoryContextAlloc(mcxt,
+ offsetof(RecordIOData, columns) +
+ ncolumns * sizeof(ColumnIOData));
+
+ data->record_type = InvalidOid;
+ data->record_typmod = 0;
+ data->ncolumns = ncolumns;
+ MemSet(data->columns, 0, sizeof(ColumnIOData) * ncolumns);
+
+ return data;
+}
+
+/* populate record tuple from json/jsonb value */
+static HeapTupleHeader
+populate_record(TupleDesc tupdesc, /* record tuple descriptor */
+ RecordIOData **precord, /* metadata cache location */
+ HeapTupleHeader defaultval, /* default record value if any */
+ MemoryContext mcxt, /* cache memory context */
+ Oid jtype, /* json/jsonb */
+ HTAB *json_hash, /* valid only if json */
+ JsonbContainer *cont) /* valid only if jsonb */
+{
+ RecordIOData *record = *precord;
+ Datum *values;
+ bool *nulls;
+ HeapTuple res;
+ int ncolumns = tupdesc->natts;
+ int i;
+
+ /*
+ * if the input json is empty, we can only skip the rest if we were
+ * passed in a non-null record, since otherwise there may be issues
+ * with domain nulls.
+ */
+ if ((jtype == JSONOID ? hash_get_num_entries(json_hash) == 0
+ : JsonContainerSize(cont) == 0) && defaultval)
+ return defaultval;
+
+ /* (re)allocate metadata cache */
+ if (record == NULL ||
+ record->ncolumns != ncolumns)
+ *precord = record = allocate_record_info(mcxt, ncolumns);
+
+ /* invalidate metadata cache if the record type has changed */
+ if (record->record_type != tupdesc->tdtypeid ||
+ record->record_typmod != tupdesc->tdtypmod)
+ {
+ MemSet(record, 0, offsetof(RecordIOData, columns) +
+ ncolumns * sizeof(ColumnIOData));
+ record->record_type = tupdesc->tdtypeid;
+ record->record_typmod = tupdesc->tdtypmod;
+ record->ncolumns = ncolumns;
+ }
+
+ values = (Datum *) palloc(ncolumns * sizeof(Datum));
+ nulls = (bool *) palloc(ncolumns * sizeof(bool));
+
+ if (defaultval)
+ {
+ HeapTupleData tuple;
+
+ /* Build a temporary HeapTuple control structure */
+ tuple.t_len = HeapTupleHeaderGetDatumLength(defaultval);
+ ItemPointerSetInvalid(&(tuple.t_self));
+ tuple.t_tableOid = InvalidOid;
+ tuple.t_data = defaultval;
+
+ /* Break down the tuple into fields */
+ heap_deform_tuple(&tuple, tupdesc, values, nulls);
+ }
+ else
+ {
+ for (i = 0; i < ncolumns; ++i)
+ {
+ values[i] = (Datum) 0;
+ nulls[i] = true;
+ }
+ }
+
+ for (i = 0; i < ncolumns; ++i)
+ {
+ Form_pg_attribute att = tupdesc->attrs[i];
+ char *colname = NameStr(att->attname);
+ JsonbValue *val = NULL;
+ JsonHashEntry *hashentry = NULL;
+
+ /* Ignore dropped columns in datatype */
+ if (att->attisdropped)
+ {
+ nulls[i] = true;
+ continue;
+ }
+
+ if (jtype == JSONOID)
+ hashentry = hash_search(json_hash, colname, HASH_FIND, NULL);
+ else
+ val = findJsonbValueFromContainerLen(cont, JB_FOBJECT, colname,
+ strlen(colname));
+
+ /*
+ * we can't just skip here if the key wasn't found since we might have
+ * a domain to deal with. If we were passed in a non-null record
+ * datum, we assume that the existing values are valid (if they're
+ * not, then it's not our fault), but if we were passed in a null,
+ * then every field which we don't populate needs to be run through
+ * the input function just in case it's a domain type.
+ */
+ if ((jtype == JSONOID ? hashentry == NULL : val == NULL) && defaultval)
+ continue;
+
+ values[i] = populate_record_field(&record->columns[i],
+ att->atttypid,
+ att->atttypmod,
+ att->attndims,
+ colname,
+ mcxt,
+ nulls[i] ? PointerGetDatum(NULL)
+ : values[i],
+ jtype,
+ hashentry && !hashentry->isnull ?
+ hashentry->val : NULL,
+ hashentry && hashentry->isstring,
+ val,
+ &nulls[i]);
+ }
+
+ res = heap_form_tuple(tupdesc, values, nulls);
+
+ pfree(values);
+ pfree(nulls);
+
+ return res->t_data;
+}
+
+/* structure to cache metadata needed for populate_record_worker() */
+typedef struct PopulateRecordCache
+{
+ Oid argtype; /* verified row type of the first argument */
+ CompositeIOData io; /* metadata cache for populate_composite() */
+} PopulateRecordCache;
+
+static Datum
+populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
+ bool have_record_arg)
+{
+ int json_arg_num = have_record_arg ? 1 : 0;
+ Oid jtype = get_fn_expr_argtype(fcinfo->flinfo, json_arg_num);
+ text *json;
+ Jsonb *jb;
+ JsonbValue jbv;
+ HeapTupleHeader rec = NULL;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupdesc = NULL;
+ Datum rettuple;
+ MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt;
+ PopulateRecordCache *cache = fcinfo->flinfo->fn_extra;
+
+ Assert(jtype == JSONOID || jtype == JSONBOID);
+
+ /*
+ * We arrange to look up the needed I/O info just once per series of
+ * calls, assuming the record type doesn't change underneath us.
+ */
+ if (!cache)
+ fcinfo->flinfo->fn_extra = cache =
+ MemoryContextAllocZero(fnmcxt, sizeof(*cache));
+
+ if (have_record_arg)
+ {
+ Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+
+ if (cache->argtype != argtype)
+ {
+ if (!type_is_rowtype(argtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("first argument of %s must be a row type",
+ funcname)));
+
+ cache->argtype = argtype;
+ }
+
+ if (PG_ARGISNULL(0))
+ {
+ if (PG_ARGISNULL(1))
+ PG_RETURN_NULL();
+
+ /*
+ * have no tuple to look at, so the only source of type info is
+ * the argtype. The lookup_rowtype_tupdesc call below will error
+ * out if we don't have a known composite type oid here.
+ */
+ tupType = argtype;
+ tupTypmod = -1;
+ }
+ else
+ {
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
+
+ if (PG_ARGISNULL(1))
+ PG_RETURN_POINTER(rec);
+
+ /* Extract type info from the tuple itself */
+ tupType = HeapTupleHeaderGetTypeId(rec);
+ tupTypmod = HeapTupleHeaderGetTypMod(rec);
+ }
+ }
+ else
+ {
+ /* json{b}_to_record case */
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context "
+ "that cannot accept type record"),
+ errhint("Try calling the function in the FROM clause "
+ "using a column definition list.")));
+
+ Assert(tupdesc);
- PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
+ /*
+ * Add tupdesc to the cache and set the appropriate values of
+ * tupType/tupTypmod for proper cache usage in populate_composite().
+ */
+ cache->io.tupdesc = tupdesc;
+
+ tupType = tupdesc->tdtypeid;
+ tupTypmod = tupdesc->tdtypmod;
+ }
+
+ if (jtype == JSONOID)
+ {
+ json = PG_GETARG_TEXT_P(json_arg_num);
+ }
+ else
+ {
+ jb = PG_GETARG_JSONB(json_arg_num);
+
+ /* fill binary jsonb value pointing to jb */
+ jbv.type = jbvBinary;
+ jbv.val.binary.data = &jb->root;
+ jbv.val.binary.len = VARSIZE(jb) - VARHDRSZ;
+ }
+
+ rettuple = populate_composite(&cache->io,
+ tupType,
+ tupTypmod,
+ NULL,
+ fnmcxt,
+ rec,
+ jtype,
+ jtype == JSONOID ? VARDATA(json) : NULL,
+ jtype == JSONOID ? VARSIZE(json) - VARHDRSZ : 0,
+ jtype == JSONOID ? NULL : &jbv);
+
+ if (tupdesc)
+ {
+ cache->io.tupdesc = NULL;
+ ReleaseTupleDesc(tupdesc);
+ }
+
+ PG_RETURN_DATUM(rettuple);
}
/*
@@ -2373,12 +3101,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 +3188,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 +3197,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 +3229,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 +3271,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 +3302,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 +3349,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 +3361,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 +3412,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 +3464,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 +3569,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..a7de8ef 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -1304,6 +1304,31 @@ select * from json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8
-- populate_record
create type jpop as (a text, b int, c timestamp);
+CREATE DOMAIN js_int_not_null AS int NOT NULL;
+CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
+CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
+CREATE TYPE jsrec AS (
+ i int,
+ ia _int4,
+ ia1 int[],
+ ia2 int[][],
+ ia3 int[][][],
+ ia1d js_int_array_1d,
+ ia2d js_int_array_2d,
+ t text,
+ ta text[],
+ c char(10),
+ ca char(10)[],
+ ts timestamp,
+ js json,
+ jsb jsonb,
+ jsa json[],
+ rec jpop,
+ reca jpop[]
+);
+CREATE TYPE jsrec_i_not_null AS (
+ i js_int_not_null
+);
select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
a | b | c
--------+---+---
@@ -1342,6 +1367,327 @@ select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":
select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}') q;
ERROR: invalid input syntax for type timestamp: "[100,200,false]"
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{}') q;
+ a | b | c
+---+---+--------------------------
+ x | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"x": 43.2}') q;
+ERROR: domain js_int_not_null does not allow null values
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": null}') q;
+ERROR: domain js_int_not_null does not allow null values
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": 12345}') q;
+ i
+-------
+ 12345
+(1 row)
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": null}') q;
+ ia
+----
+
+(1 row)
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": 123}') q;
+ERROR: expected json array in value of key "ia"
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [1, "2", null, 4]}') q;
+ ia
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1, 2], [3, 4]]}') q;
+ ia
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], 2]}') q;
+ERROR: expected json array in array element [1] of key "ia"
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], [2, 3]]}') q;
+ERROR: malformed json array: "[[1], [2, 3]]"
+DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions.
+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 array element [0] 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 array element [1] of key "ia2"
+SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2"], [null, 4]]}') q;
+ERROR: value for domain js_int_array_2d violates check constraint "js_int_array_2d_check"
+SELECT ia2d FROM json_populate_record(NULL::jsrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q;
+ ia2d
+----------------------
+ {{1,2,3},{NULL,5,6}}
+(1 row)
+
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [1, "2", null, 4]}') q;
+ERROR: expected json array in array element [0] of key "ia3"
+SELECT ia3 FROM json_populate_record(NULL::jsrec, '{"ia3": [[1, 2], [null, 4]]}') q;
+ERROR: expected json array in array element [0][0] 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,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 +1754,28 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
{"z":true} | 3 | Fri Jan 20 10:42:53 2012
(2 rows)
+-- test type info caching in json_populate_record()
+CREATE TEMP TABLE jspoptest (js json);
+INSERT INTO jspoptest
+SELECT '{
+ "jsa": [1, "2", null, 4],
+ "rec": {"a": "abc", "c": "01.02.2003", "x": 43.2},
+ "reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]
+}'::json
+FROM generate_series(1, 3);
+SELECT (json_populate_record(NULL::jsrec, js)).* FROM jspoptest;
+ i | ia | ia1 | ia2 | ia3 | ia1d | ia2d | t | ta | c | ca | ts | js | jsb | jsa | rec | reca
+---+----+-----+-----+-----+------+------+---+----+---+----+----+----+-----+--------------------+-----------------------------------+--------------------------------------------------------
+ | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"}
+ | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"}
+ | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"}
+(3 rows)
+
+DROP TYPE jsrec;
+DROP TYPE jsrec_i_not_null;
+DROP DOMAIN js_int_not_null;
+DROP DOMAIN js_int_array_1d;
+DROP DOMAIN js_int_array_2d;
--json_typeof() function
select value, json_typeof(value)
from (values (json '123.4'),
@@ -1600,11 +1968,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
@@ -1615,6 +1983,49 @@ from json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json)
1 | {"c":16, "d":2} | | 8 | t
(1 row)
+select * from json_to_record('{"ia": null}') as x(ia _int4);
+ ia
+----
+
+(1 row)
+
+select * from json_to_record('{"ia": 123}') as x(ia _int4);
+ERROR: expected json array in value of key "ia"
+select * from json_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4);
+ ia
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+select * from json_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4);
+ ia
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+select * from json_to_record('{"ia": [[1], 2]}') as x(ia _int4);
+ERROR: expected json array in array element [1] of key "ia"
+select * from json_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4);
+ERROR: malformed json array: "[[1], [2, 3]]"
+DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions.
+select * from json_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
+ ia2
+---------
+ {1,2,3}
+(1 row)
+
+select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
+ ia2
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
+ ia2
+-----------------
+ {{{1},{2},{3}}}
+(1 row)
+
-- json_strip_nulls
select json_strip_nulls(null);
json_strip_nulls
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index e2cb08a..758adb2 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -1888,6 +1888,31 @@ SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,
-- populate_record
CREATE TYPE jbpop AS (a text, b int, c timestamp);
+CREATE DOMAIN jsb_int_not_null AS int NOT NULL;
+CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
+CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
+CREATE TYPE jsbrec AS (
+ i int,
+ ia _int4,
+ ia1 int[],
+ ia2 int[][],
+ ia3 int[][][],
+ ia1d jsb_int_array_1d,
+ ia2d jsb_int_array_2d,
+ t text,
+ ta text[],
+ c char(10),
+ ca char(10)[],
+ ts timestamp,
+ js json,
+ jsb jsonb,
+ jsa json[],
+ rec jbpop,
+ reca jbpop[]
+);
+CREATE TYPE jsbrec_i_not_null AS (
+ i jsb_int_not_null
+);
SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q;
a | b | c
--------+---+---
@@ -1926,6 +1951,327 @@ SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a
SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}') q;
ERROR: invalid input syntax for type timestamp: "[100, 200, false]"
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop, '{}') q;
+ a | b | c
+---+---+--------------------------
+ x | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+
+SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"x": 43.2}') q;
+ERROR: domain jsb_int_not_null does not allow null values
+SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": null}') q;
+ERROR: domain jsb_int_not_null does not allow null values
+SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": 12345}') q;
+ i
+-------
+ 12345
+(1 row)
+
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": null}') q;
+ ia
+----
+
+(1 row)
+
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": 123}') q;
+ERROR: expected json array in value of key "ia"
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [1, "2", null, 4]}') q;
+ ia
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1, 2], [3, 4]]}') q;
+ ia
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], 2]}') q;
+ERROR: expected json array in array element [1] of key "ia"
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], [2, 3]]}') q;
+ERROR: malformed json array: "[[1], [2, 3]]"
+DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions.
+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 array element [0] 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 array element [1] of key "ia2"
+SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2"], [null, 4]]}') q;
+ERROR: value for domain jsb_int_array_2d violates check constraint "jsb_int_array_2d_check"
+SELECT ia2d FROM jsonb_populate_record(NULL::jsbrec, '{"ia2d": [[1, "2", 3], [null, 5, 6]]}') q;
+ ia2d
+----------------------
+ {{1,2,3},{NULL,5,6}}
+(1 row)
+
+SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [1, "2", null, 4]}') q;
+ERROR: expected json array in array element [0] of key "ia3"
+SELECT ia3 FROM jsonb_populate_record(NULL::jsbrec, '{"ia3": [[1, 2], [null, 4]]}') q;
+ERROR: expected json array in array element [0][0] 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,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 +2348,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 +2363,71 @@ from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb)
1 | {"c": 16, "d": 2} | | 8 | t
(1 row)
+select * from jsonb_to_record('{"ia": null}') as x(ia _int4);
+ ia
+----
+
+(1 row)
+
+select * from jsonb_to_record('{"ia": 123}') as x(ia _int4);
+ERROR: expected json array in value of key "ia"
+select * from jsonb_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4);
+ ia
+--------------
+ {1,2,NULL,4}
+(1 row)
+
+select * from jsonb_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4);
+ ia
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+select * from jsonb_to_record('{"ia": [[1], 2]}') as x(ia _int4);
+ERROR: expected json array in array element [1] of key "ia"
+select * from jsonb_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4);
+ERROR: malformed json array: "[[1], [2, 3]]"
+DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions.
+select * from jsonb_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
+ ia2
+---------
+ {1,2,3}
+(1 row)
+
+select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
+ ia2
+---------------
+ {{1,2},{3,4}}
+(1 row)
+
+select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
+ ia2
+-----------------
+ {{{1},{2},{3}}}
+(1 row)
+
+-- test type info caching in jsonb_populate_record()
+CREATE TEMP TABLE jsbpoptest (js jsonb);
+INSERT INTO jsbpoptest
+SELECT '{
+ "jsa": [1, "2", null, 4],
+ "rec": {"a": "abc", "c": "01.02.2003", "x": 43.2},
+ "reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]
+}'::jsonb
+FROM generate_series(1, 3);
+SELECT (jsonb_populate_record(NULL::jsbrec, js)).* FROM jsbpoptest;
+ i | ia | ia1 | ia2 | ia3 | ia1d | ia2d | t | ta | c | ca | ts | js | jsb | jsa | rec | reca
+---+----+-----+-----+-----+------+------+---+----+---+----+----+----+-----+--------------------+-----------------------------------+--------------------------------------------------------
+ | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"}
+ | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"}
+ | | | | | | | | | | | | | | {1,"\"2\"",NULL,4} | (abc,,"Thu Jan 02 00:00:00 2003") | {"(abc,456,)",NULL,"(,,\"Thu Jan 02 00:00:00 2003\")"}
+(3 rows)
+
+DROP TYPE jsbrec;
+DROP TYPE jsbrec_i_not_null;
+DROP DOMAIN jsb_int_not_null;
+DROP DOMAIN jsb_int_array_1d;
+DROP DOMAIN jsb_int_array_2d;
-- indexing
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
count
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 603288b..e894199 100644
--- a/src/test/regress/sql/json.sql
+++ b/src/test/regress/sql/json.sql
@@ -378,6 +378,34 @@ select * from json_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8
-- populate_record
create type jpop as (a text, b int, c timestamp);
+CREATE DOMAIN js_int_not_null AS int NOT NULL;
+CREATE DOMAIN js_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
+CREATE DOMAIN js_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
+
+CREATE TYPE jsrec AS (
+ i int,
+ ia _int4,
+ ia1 int[],
+ ia2 int[][],
+ ia3 int[][][],
+ ia1d js_int_array_1d,
+ ia2d js_int_array_2d,
+ t text,
+ ta text[],
+ c char(10),
+ ca char(10)[],
+ ts timestamp,
+ js json,
+ jsb jsonb,
+ jsa json[],
+ rec jpop,
+ reca jpop[]
+);
+
+CREATE TYPE jsrec_i_not_null AS (
+ i js_int_not_null
+);
+
select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
@@ -388,6 +416,96 @@ select * from json_populate_record(null::jpop,'{"a":[100,200,false],"x":43.2}')
select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":[100,200,false],"x":43.2}') q;
select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"c":[100,200,false],"x":43.2}') q;
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{}') q;
+
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"x": 43.2}') q;
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": null}') q;
+SELECT i FROM json_populate_record(NULL::jsrec_i_not_null, '{"i": 12345}') q;
+
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": null}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": 123}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [1, "2", null, 4]}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1, 2], [3, 4]]}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], 2]}') q;
+SELECT ia FROM json_populate_record(NULL::jsrec, '{"ia": [[1], [2, 3]]}') q;
+
+SELECT 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,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 +522,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,13 +657,24 @@ select * from json_to_recordset('[{"a":1,"b":{"d":"foo"},"c":true},{"a":2,"c":fa
as x(a int, b json, c boolean);
select *, c is null as c_is_null
-from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::json)
- as t(a int, b json, c text, x int);
+from json_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8, "ca": ["1 2", 3], "ia": [[1,2],[3,4]], "r": {"a": "aaa", "b": 123}}'::json)
+ as t(a int, b json, c text, x int, ca char(5)[], ia int[][], r jpop);
select *, c is null as c_is_null
from json_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::json)
as t(a int, b json, c text, x int);
+select * from json_to_record('{"ia": null}') as x(ia _int4);
+select * from json_to_record('{"ia": 123}') as x(ia _int4);
+select * from json_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4);
+select * from json_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4);
+select * from json_to_record('{"ia": [[1], 2]}') as x(ia _int4);
+select * from json_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4);
+
+select * from json_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
+select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
+select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
+
-- json_strip_nulls
select json_strip_nulls(null);
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 6b4c796..b429fc8 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -478,6 +478,34 @@ SELECT * FROM jsonb_array_elements_text('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,
-- populate_record
CREATE TYPE jbpop AS (a text, b int, c timestamp);
+CREATE DOMAIN jsb_int_not_null AS int NOT NULL;
+CREATE DOMAIN jsb_int_array_1d AS int[] CHECK(array_length(VALUE, 1) = 3);
+CREATE DOMAIN jsb_int_array_2d AS int[][] CHECK(array_length(VALUE, 2) = 3);
+
+CREATE TYPE jsbrec AS (
+ i int,
+ ia _int4,
+ ia1 int[],
+ ia2 int[][],
+ ia3 int[][][],
+ ia1d jsb_int_array_1d,
+ ia2d jsb_int_array_2d,
+ t text,
+ ta text[],
+ c char(10),
+ ca char(10)[],
+ ts timestamp,
+ js json,
+ jsb jsonb,
+ jsa json[],
+ rec jbpop,
+ reca jbpop[]
+);
+
+CREATE TYPE jsbrec_i_not_null AS (
+ i jsb_int_not_null
+);
+
SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":"blurfl","x":43.2}') q;
SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":"blurfl","x":43.2}') q;
@@ -488,6 +516,96 @@ SELECT * FROM jsonb_populate_record(NULL::jbpop,'{"a":[100,200,false],"x":43.2}'
SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"a":[100,200,false],"x":43.2}') q;
SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop,'{"c":[100,200,false],"x":43.2}') q;
+SELECT * FROM jsonb_populate_record(row('x',3,'2012-12-31 15:30:56')::jbpop, '{}') q;
+
+SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"x": 43.2}') q;
+SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": null}') q;
+SELECT i FROM jsonb_populate_record(NULL::jsbrec_i_not_null, '{"i": 12345}') q;
+
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": null}') q;
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": 123}') q;
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [1, "2", null, 4]}') q;
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1, 2], [3, 4]]}') q;
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], 2]}') q;
+SELECT ia FROM jsonb_populate_record(NULL::jsbrec, '{"ia": [[1], [2, 3]]}') q;
+
+SELECT 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,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 +627,43 @@ select * from jsonb_to_recordset('[{"a":1,"b":"foo","d":false},{"a":2,"b":"bar",
as x(a int, b text, c boolean);
select *, c is null as c_is_null
-from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8}'::jsonb)
- as t(a int, b jsonb, c text, x int);
+from jsonb_to_record('{"a":1, "b":{"c":16, "d":2}, "x":8, "ca": ["1 2", 3], "ia": [[1,2],[3,4]], "r": {"a": "aaa", "b": 123}}'::jsonb)
+ as t(a int, b jsonb, c text, x int, ca char(5)[], ia int[][], r jbpop);
select *, c is null as c_is_null
from jsonb_to_recordset('[{"a":1, "b":{"c":16, "d":2}, "x":8}]'::jsonb)
as t(a int, b jsonb, c text, x int);
+select * from jsonb_to_record('{"ia": null}') as x(ia _int4);
+select * from jsonb_to_record('{"ia": 123}') as x(ia _int4);
+select * from jsonb_to_record('{"ia": [1, "2", null, 4]}') as x(ia _int4);
+select * from jsonb_to_record('{"ia": [[1, 2], [3, 4]]}') as x(ia _int4);
+select * from jsonb_to_record('{"ia": [[1], 2]}') as x(ia _int4);
+select * from jsonb_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4);
+
+select * from jsonb_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
+select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
+select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
+
+-- test type info caching in jsonb_populate_record()
+CREATE TEMP TABLE jsbpoptest (js jsonb);
+
+INSERT INTO jsbpoptest
+SELECT '{
+ "jsa": [1, "2", null, 4],
+ "rec": {"a": "abc", "c": "01.02.2003", "x": 43.2},
+ "reca": [{"a": "abc", "b": 456}, null, {"c": "01.02.2003", "x": 43.2}]
+}'::jsonb
+FROM generate_series(1, 3);
+
+SELECT (jsonb_populate_record(NULL::jsbrec, js)).* FROM jsbpoptest;
+
+DROP TYPE jsbrec;
+DROP TYPE jsbrec_i_not_null;
+DROP DOMAIN jsb_int_not_null;
+DROP DOMAIN jsb_int_array_1d;
+DROP DOMAIN jsb_int_array_2d;
+
-- indexing
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":null}';
SELECT count(*) FROM testjsonb WHERE j @> '{"wait":"CC"}';
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 6955298..6ca0c91 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 548a2aa..b0e0851 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 9fe9873..3b9439c 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 63f6336..4525298 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;
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index a7de8ef..696aa9c 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -2009,11 +2009,7 @@ select * from json_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4);
ERROR: malformed json array: "[[1], [2, 3]]"
DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions.
select * from json_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
- ia2
----------
- {1,2,3}
-(1 row)
-
+ERROR: expected json array in array element [0] of key "ia2"
select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
ia2
---------------
@@ -2021,11 +2017,7 @@ select * from json_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
(1 row)
select * from json_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
- ia2
------------------
- {{{1},{2},{3}}}
-(1 row)
-
+ERROR: invalid input syntax for integer: "[1]"
-- json_strip_nulls
select json_strip_nulls(null);
json_strip_nulls
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 758adb2..28c994f 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2389,11 +2389,7 @@ select * from jsonb_to_record('{"ia": [[1], [2, 3]]}') as x(ia _int4);
ERROR: malformed json array: "[[1], [2, 3]]"
DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions.
select * from jsonb_to_record('{"ia2": [1, 2, 3]}') as x(ia2 int[][]);
- ia2
----------
- {1,2,3}
-(1 row)
-
+ERROR: expected json array in array element [0] of key "ia2"
select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
ia2
---------------
@@ -2401,11 +2397,7 @@ select * from jsonb_to_record('{"ia2": [[1, 2], [3, 4]]}') as x(ia2 int4[][]);
(1 row)
select * from jsonb_to_record('{"ia2": [[[1], [2], [3]]]}') as x(ia2 int4[][]);
- ia2
------------------
- {{{1},{2},{3}}}
-(1 row)
-
+ERROR: invalid input syntax for integer: "[1]"
-- test type info caching in jsonb_populate_record()
CREATE TEMP TABLE jsbpoptest (js jsonb);
INSERT INTO jsbpoptest
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers