Commit cf35346e authored by Andrew Dunstan's avatar Andrew Dunstan

Make json_populate_record and friends operate recursively

With this change array fields are populated from json(b) arrays, and
composite fields are populated from json(b) objects.

Along the way, some significant code refactoring is done to remove
redundancy in the way to populate_record[_set] and to_record[_set]
functions operate, and some significant efficiency gains are made by
caching tuple descriptors.

Nikita Glukhov, edited some by me.

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