aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/utils/adt/jsonfuncs.c219
-rw-r--r--src/test/regress/expected/json.out29
-rw-r--r--src/test/regress/expected/jsonb.out29
-rw-r--r--src/test/regress/sql/json.sql6
-rw-r--r--src/test/regress/sql/jsonb.sql6
5 files changed, 179 insertions, 110 deletions
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index fe351edb2b2..667f9d9563e 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -225,13 +225,13 @@ struct RecordIOData
ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
};
-/* per-query cache for populate_recordset */
-typedef struct PopulateRecordsetCache
+/* per-query cache for populate_record_worker and populate_recordset_worker */
+typedef struct PopulateRecordCache
{
Oid argtype; /* declared type of the record argument */
ColumnIOData c; /* metadata cache for populate_composite() */
MemoryContext fn_mcxt; /* where this is stored */
-} PopulateRecordsetCache;
+} PopulateRecordCache;
/* per-call state for populate_recordset */
typedef struct PopulateRecordsetState
@@ -244,16 +244,9 @@ typedef struct PopulateRecordsetState
JsonTokenType saved_token_type;
Tuplestorestate *tuple_store;
HeapTupleHeader rec;
- PopulateRecordsetCache *cache;
+ PopulateRecordCache *cache;
} PopulateRecordsetState;
-/* structure to cache metadata needed for populate_record_worker() */
-typedef struct PopulateRecordCache
-{
- Oid argtype; /* declared type of the record argument */
- ColumnIOData c; /* metadata cache for populate_composite() */
-} PopulateRecordCache;
-
/* common data for populate_array_json() and populate_array_dim_jsonb() */
typedef struct PopulateArrayContext
{
@@ -429,6 +422,12 @@ static Datum populate_record_worker(FunctionCallInfo fcinfo, const char *funcnam
static HeapTupleHeader populate_record(TupleDesc tupdesc, RecordIOData **record_p,
HeapTupleHeader defaultval, MemoryContext mcxt,
JsObject *obj);
+static void get_record_type_from_argument(FunctionCallInfo fcinfo,
+ const char *funcname,
+ PopulateRecordCache *cache);
+static void get_record_type_from_query(FunctionCallInfo fcinfo,
+ const char *funcname,
+ PopulateRecordCache *cache);
static void JsValueToJsObject(JsValue *jsv, JsObject *jso);
static Datum populate_composite(CompositeIOData *io, Oid typid,
const char *colname, MemoryContext mcxt,
@@ -3203,6 +3202,70 @@ populate_record(TupleDesc tupdesc,
}
/*
+ * Setup for json{b}_populate_record{set}: result type will be same as first
+ * argument's type --- unless first argument is "null::record", which we can't
+ * extract type info from; we handle that later.
+ */
+static void
+get_record_type_from_argument(FunctionCallInfo fcinfo,
+ const char *funcname,
+ PopulateRecordCache *cache)
+{
+ cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ prepare_column_cache(&cache->c,
+ cache->argtype, -1,
+ cache->fn_mcxt, false);
+ if (cache->c.typcat != TYPECAT_COMPOSITE &&
+ cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ /* translator: %s is a function name, eg json_to_record */
+ errmsg("first argument of %s must be a row type",
+ funcname)));
+}
+
+/*
+ * Setup for json{b}_to_record{set}: result type is specified by calling
+ * query. We'll also use this code for json{b}_populate_record{set},
+ * if we discover that the first argument is a null of type RECORD.
+ *
+ * Here it is syntactically impossible to specify the target type
+ * as domain-over-composite.
+ */
+static void
+get_record_type_from_query(FunctionCallInfo fcinfo,
+ const char *funcname,
+ PopulateRecordCache *cache)
+{
+ TupleDesc tupdesc;
+ MemoryContext old_cxt;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s is a function name, eg json_to_record */
+ errmsg("could not determine row type for result of %s",
+ funcname),
+ errhint("Provide a non-null record argument, "
+ "or call the function in the FROM clause "
+ "using a column definition list.")));
+
+ Assert(tupdesc);
+ cache->argtype = tupdesc->tdtypeid;
+
+ /* If we go through this more than once, avoid memory leak */
+ if (cache->c.io.composite.tupdesc)
+ FreeTupleDesc(cache->c.io.composite.tupdesc);
+
+ /* Save identified tupdesc */
+ old_cxt = MemoryContextSwitchTo(cache->fn_mcxt);
+ cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc);
+ cache->c.io.composite.base_typid = tupdesc->tdtypeid;
+ cache->c.io.composite.base_typmod = tupdesc->tdtypmod;
+ MemoryContextSwitchTo(old_cxt);
+}
+
+/*
* common worker for json{b}_populate_record() and json{b}_to_record()
* is_json and have_record_arg identify the specific function
*/
@@ -3227,63 +3290,24 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
{
fcinfo->flinfo->fn_extra = cache =
MemoryContextAllocZero(fnmcxt, sizeof(*cache));
+ cache->fn_mcxt = fnmcxt;
if (have_record_arg)
- {
- /*
- * json{b}_populate_record case: result type will be same as first
- * argument's.
- */
- cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
- prepare_column_cache(&cache->c,
- cache->argtype, -1,
- fnmcxt, false);
- if (cache->c.typcat != TYPECAT_COMPOSITE &&
- cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("first argument of %s must be a row type",
- funcname)));
- }
+ get_record_type_from_argument(fcinfo, funcname, cache);
else
- {
- /*
- * json{b}_to_record case: result type is specified by calling
- * query. Here it is syntactically impossible to specify the
- * target type as domain-over-composite.
- */
- TupleDesc tupdesc;
- MemoryContext old_cxt;
-
- 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);
- cache->argtype = tupdesc->tdtypeid;
-
- /* Save identified tupdesc */
- old_cxt = MemoryContextSwitchTo(fnmcxt);
- cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc);
- cache->c.io.composite.base_typid = tupdesc->tdtypeid;
- cache->c.io.composite.base_typmod = tupdesc->tdtypmod;
- MemoryContextSwitchTo(old_cxt);
- }
+ get_record_type_from_query(fcinfo, funcname, cache);
}
/* Collect record arg if we have one */
- if (have_record_arg && !PG_ARGISNULL(0))
+ if (!have_record_arg)
+ rec = NULL; /* it's json{b}_to_record() */
+ else if (!PG_ARGISNULL(0))
{
rec = PG_GETARG_HEAPTUPLEHEADER(0);
/*
* When declared arg type is RECORD, identify actual record type from
- * the tuple itself. Note the lookup_rowtype_tupdesc call in
- * update_cached_tupdesc will fail if we're unable to do this.
+ * the tuple itself.
*/
if (cache->argtype == RECORDOID)
{
@@ -3292,8 +3316,21 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
}
}
else
+ {
rec = NULL;
+ /*
+ * When declared arg type is RECORD, identify actual record type from
+ * calling query, or fail if we can't.
+ */
+ if (cache->argtype == RECORDOID)
+ {
+ get_record_type_from_query(fcinfo, funcname, cache);
+ /* This can't change argtype, which is important for next time */
+ Assert(cache->argtype == RECORDOID);
+ }
+ }
+
/* If no JSON argument, just return the record (if any) unchanged */
if (PG_ARGISNULL(json_arg_num))
{
@@ -3517,7 +3554,7 @@ json_to_recordset(PG_FUNCTION_ARGS)
static void
populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
{
- PopulateRecordsetCache *cache = state->cache;
+ PopulateRecordCache *cache = state->cache;
HeapTupleHeader tuphead;
HeapTupleData tuple;
@@ -3559,7 +3596,7 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
ReturnSetInfo *rsi;
MemoryContext old_cxt;
HeapTupleHeader rec;
- PopulateRecordsetCache *cache = fcinfo->flinfo->fn_extra;
+ PopulateRecordCache *cache = fcinfo->flinfo->fn_extra;
PopulateRecordsetState *state;
rsi = (ReturnSetInfo *) fcinfo->resultinfo;
@@ -3585,60 +3622,21 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
cache->fn_mcxt = fcinfo->flinfo->fn_mcxt;
if (have_record_arg)
- {
- /*
- * json{b}_populate_recordset case: result type will be same as
- * first argument's.
- */
- cache->argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
- prepare_column_cache(&cache->c,
- cache->argtype, -1,
- cache->fn_mcxt, false);
- if (cache->c.typcat != TYPECAT_COMPOSITE &&
- cache->c.typcat != TYPECAT_COMPOSITE_DOMAIN)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("first argument of %s must be a row type",
- funcname)));
- }
+ get_record_type_from_argument(fcinfo, funcname, cache);
else
- {
- /*
- * json{b}_to_recordset case: result type is specified by calling
- * query. Here it is syntactically impossible to specify the
- * target type as domain-over-composite.
- */
- TupleDesc tupdesc;
-
- 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);
- cache->argtype = tupdesc->tdtypeid;
-
- /* Save identified tupdesc */
- old_cxt = MemoryContextSwitchTo(cache->fn_mcxt);
- cache->c.io.composite.tupdesc = CreateTupleDescCopy(tupdesc);
- cache->c.io.composite.base_typid = tupdesc->tdtypeid;
- cache->c.io.composite.base_typmod = tupdesc->tdtypmod;
- MemoryContextSwitchTo(old_cxt);
- }
+ get_record_type_from_query(fcinfo, funcname, cache);
}
/* Collect record arg if we have one */
- if (have_record_arg && !PG_ARGISNULL(0))
+ if (!have_record_arg)
+ rec = NULL; /* it's json{b}_to_recordset() */
+ else if (!PG_ARGISNULL(0))
{
rec = PG_GETARG_HEAPTUPLEHEADER(0);
/*
* When declared arg type is RECORD, identify actual record type from
- * the tuple itself. Note the lookup_rowtype_tupdesc call in
- * update_cached_tupdesc will fail if we're unable to do this.
+ * the tuple itself.
*/
if (cache->argtype == RECORDOID)
{
@@ -3647,8 +3645,21 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
}
}
else
+ {
rec = NULL;
+ /*
+ * When declared arg type is RECORD, identify actual record type from
+ * calling query, or fail if we can't.
+ */
+ if (cache->argtype == RECORDOID)
+ {
+ get_record_type_from_query(fcinfo, funcname, cache);
+ /* This can't change argtype, which is important for next time */
+ Assert(cache->argtype == RECORDOID);
+ }
+ }
+
/* if the json is null send back an empty set */
if (PG_ARGISNULL(json_arg_num))
PG_RETURN_NULL();
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index 5b8e67784f5..69b0a6ee6d6 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -1744,13 +1744,21 @@ SELECT rec FROM json_populate_record(
-- anonymous record type
SELECT json_populate_record(null::record, '{"x": 0, "y": 1}');
-ERROR: record type has not been registered
+ERROR: could not determine row type for result of json_populate_record
+HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list.
SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
json_populate_record
----------------------
(0,1)
(1 row)
+SELECT * FROM
+ json_populate_record(null::record, '{"x": 776}') AS (x int, y int);
+ x | y
+-----+---
+ 776 |
+(1 row)
+
-- composite domain
SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}');
json_populate_record
@@ -1834,7 +1842,8 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
-- anonymous record type
SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
-ERROR: record type has not been registered
+ERROR: could not determine row type for result of json_populate_recordset
+HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list.
SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
json_populate_recordset
-------------------------
@@ -1851,9 +1860,17 @@ FROM (VALUES (1),(2)) v(i);
2 | (2,43)
(4 rows)
+SELECT * FROM
+ json_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int);
+ x | y
+-----+---
+ 776 |
+(1 row)
+
-- empty array is a corner case
SELECT json_populate_recordset(null::record, '[]');
-ERROR: record type has not been registered
+ERROR: could not determine row type for result of json_populate_recordset
+HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list.
SELECT json_populate_recordset(row(1,2), '[]');
json_populate_recordset
-------------------------
@@ -1864,6 +1881,12 @@ SELECT * FROM json_populate_recordset(NULL::jpop,'[]') q;
---+---+---
(0 rows)
+SELECT * FROM
+ json_populate_recordset(null::record, '[]') AS (x int, y int);
+ x | y
+---+---
+(0 rows)
+
-- composite domain
SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');
json_populate_recordset
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index 469079c5d8f..0f917557942 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -2433,13 +2433,21 @@ SELECT rec FROM jsonb_populate_record(
-- anonymous record type
SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}');
-ERROR: record type has not been registered
+ERROR: could not determine row type for result of jsonb_populate_record
+HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list.
SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
jsonb_populate_record
-----------------------
(0,1)
(1 row)
+SELECT * FROM
+ jsonb_populate_record(null::record, '{"x": 776}') AS (x int, y int);
+ x | y
+-----+---
+ 776 |
+(1 row)
+
-- composite domain
SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}');
jsonb_populate_record
@@ -2516,7 +2524,8 @@ SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200
-- anonymous record type
SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
-ERROR: record type has not been registered
+ERROR: could not determine row type for result of jsonb_populate_recordset
+HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list.
SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
jsonb_populate_recordset
--------------------------
@@ -2533,9 +2542,17 @@ FROM (VALUES (1),(2)) v(i);
2 | (2,43)
(4 rows)
+SELECT * FROM
+ jsonb_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int);
+ x | y
+-----+---
+ 776 |
+(1 row)
+
-- empty array is a corner case
SELECT jsonb_populate_recordset(null::record, '[]');
-ERROR: record type has not been registered
+ERROR: could not determine row type for result of jsonb_populate_recordset
+HINT: Provide a non-null record argument, or call the function in the FROM clause using a column definition list.
SELECT jsonb_populate_recordset(row(1,2), '[]');
jsonb_populate_recordset
--------------------------
@@ -2546,6 +2563,12 @@ SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[]') q;
---+---+---
(0 rows)
+SELECT * FROM
+ jsonb_populate_recordset(null::record, '[]') AS (x int, y int);
+ x | y
+---+---
+(0 rows)
+
-- composite domain
SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]');
jsonb_populate_recordset
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index a52beaa27a1..e0216645923 100644
--- a/src/test/regress/sql/json.sql
+++ b/src/test/regress/sql/json.sql
@@ -522,6 +522,8 @@ SELECT rec FROM json_populate_record(
-- anonymous record type
SELECT json_populate_record(null::record, '{"x": 0, "y": 1}');
SELECT json_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
+SELECT * FROM
+ json_populate_record(null::record, '{"x": 776}') AS (x int, y int);
-- composite domain
SELECT json_populate_record(null::j_ordered_pair, '{"x": 0, "y": 1}');
@@ -549,11 +551,15 @@ SELECT json_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
SELECT json_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
SELECT i, json_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]')
FROM (VALUES (1),(2)) v(i);
+SELECT * FROM
+ json_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int);
-- empty array is a corner case
SELECT json_populate_recordset(null::record, '[]');
SELECT json_populate_recordset(row(1,2), '[]');
SELECT * FROM json_populate_recordset(NULL::jpop,'[]') q;
+SELECT * FROM
+ json_populate_recordset(null::record, '[]') AS (x int, y int);
-- composite domain
SELECT json_populate_recordset(null::j_ordered_pair, '[{"x": 0, "y": 1}]');
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index ba870872e80..58703a90b70 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -642,6 +642,8 @@ SELECT rec FROM jsonb_populate_record(
-- anonymous record type
SELECT jsonb_populate_record(null::record, '{"x": 0, "y": 1}');
SELECT jsonb_populate_record(row(1,2), '{"f1": 0, "f2": 1}');
+SELECT * FROM
+ jsonb_populate_record(null::record, '{"x": 776}') AS (x int, y int);
-- composite domain
SELECT jsonb_populate_record(null::jb_ordered_pair, '{"x": 0, "y": 1}');
@@ -665,11 +667,15 @@ SELECT jsonb_populate_recordset(null::record, '[{"x": 0, "y": 1}]');
SELECT jsonb_populate_recordset(row(1,2), '[{"f1": 0, "f2": 1}]');
SELECT i, jsonb_populate_recordset(row(i,50), '[{"f1":"42"},{"f2":"43"}]')
FROM (VALUES (1),(2)) v(i);
+SELECT * FROM
+ jsonb_populate_recordset(null::record, '[{"x": 776}]') AS (x int, y int);
-- empty array is a corner case
SELECT jsonb_populate_recordset(null::record, '[]');
SELECT jsonb_populate_recordset(row(1,2), '[]');
SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[]') q;
+SELECT * FROM
+ jsonb_populate_recordset(null::record, '[]') AS (x int, y int);
-- composite domain
SELECT jsonb_populate_recordset(null::jb_ordered_pair, '[{"x": 0, "y": 1}]');