aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/jsonfuncs.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2017-10-26 13:47:45 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2017-10-26 13:47:45 -0400
commit37a795a60b4f4b1def11c615525ec5e0e9449e05 (patch)
treea5aa9d7e51ef4fd0e353223bd691f7e85018a032 /src/backend/utils/adt/jsonfuncs.c
parent08f1e1f0a47b4b0e87b07b9794698747b279c711 (diff)
downloadpostgresql-37a795a60b4f4b1def11c615525ec5e0e9449e05.tar.gz
postgresql-37a795a60b4f4b1def11c615525ec5e0e9449e05.zip
Support domains over composite types.
This is the last major omission in our domains feature: you can now make a domain over anything that's not a pseudotype. The major complication from an implementation standpoint is that places that might be creating tuples of a domain type now need to be prepared to apply domain_check(). It seems better that unprepared code fail with an error like "<type> is not composite" than that it silently fail to apply domain constraints. Therefore, relevant infrastructure like get_func_result_type() and lookup_rowtype_tupdesc() has been adjusted to treat domain-over-composite as a distinct case that unprepared code won't recognize, rather than just transparently treating it the same as plain composite. This isn't a 100% solution to the possibility of overlooked domain checks, but it catches most places. In passing, improve typcache.c's support for domains (it can now cache the identity of a domain's base type), and rewrite the argument handling logic in jsonfuncs.c's populate_record[set]_worker to reduce duplicative per-call lookups. I believe this is code-complete so far as the core and contrib code go. The PLs need varying amounts of work, which will be tackled in followup patches. Discussion: https://postgr.es/m/4206.1499798337@sss.pgh.pa.us
Diffstat (limited to 'src/backend/utils/adt/jsonfuncs.c')
-rw-r--r--src/backend/utils/adt/jsonfuncs.c431
1 files changed, 281 insertions, 150 deletions
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index d36fd9e9290..242d8fe7432 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -169,6 +169,11 @@ typedef struct CompositeIOData
*/
RecordIOData *record_io; /* metadata cache for populate_record() */
TupleDesc tupdesc; /* cached tuple descriptor */
+ /* these fields differ from target type only if domain over composite: */
+ Oid base_typid; /* base type id */
+ int32 base_typmod; /* base type modifier */
+ /* this field is used only if target type is domain over composite: */
+ void *domain_info; /* opaque cache for domain checks */
} CompositeIOData;
/* structure to cache metadata needed for populate_domain() */
@@ -186,6 +191,7 @@ typedef enum TypeCat
TYPECAT_SCALAR = 's',
TYPECAT_ARRAY = 'a',
TYPECAT_COMPOSITE = 'c',
+ TYPECAT_COMPOSITE_DOMAIN = 'C',
TYPECAT_DOMAIN = 'd'
} TypeCat;
@@ -217,7 +223,15 @@ struct RecordIOData
ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
};
-/* state for populate_recordset */
+/* per-query cache for populate_recordset */
+typedef struct PopulateRecordsetCache
+{
+ Oid argtype; /* declared type of the record argument */
+ ColumnIOData c; /* metadata cache for populate_composite() */
+ MemoryContext fn_mcxt; /* where this is stored */
+} PopulateRecordsetCache;
+
+/* per-call state for populate_recordset */
typedef struct PopulateRecordsetState
{
JsonLexContext *lex;
@@ -227,17 +241,15 @@ typedef struct PopulateRecordsetState
char *save_json_start;
JsonTokenType saved_token_type;
Tuplestorestate *tuple_store;
- TupleDesc ret_tdesc;
HeapTupleHeader rec;
- RecordIOData **my_extra;
- MemoryContext fn_mcxt; /* used to stash IO funcs */
+ PopulateRecordsetCache *cache;
} 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() */
+ 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() */
@@ -415,16 +427,13 @@ 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 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,
+static Datum populate_composite(CompositeIOData *io, Oid typid,
const char *colname, MemoryContext mcxt,
- HeapTupleHeader defaultval, JsValue *jsv);
+ HeapTupleHeader defaultval, JsValue *jsv, bool isnull);
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);
+ MemoryContext mcxt, bool need_scalar);
static Datum populate_record_field(ColumnIOData *col, Oid typid, int32 typmod,
const char *colname, MemoryContext mcxt, Datum defaultval,
JsValue *jsv, bool *isnull);
@@ -2704,25 +2713,16 @@ JsValueToJsObject(JsValue *jsv, JsObject *jso)
}
}
-/* 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)
+/* acquire or update cached tuple descriptor for a composite type */
+static void
+update_cached_tupdesc(CompositeIOData *io, MemoryContext mcxt)
{
- HeapTupleHeader tuple;
- JsObject jso;
-
- /* acquire cached tuple descriptor */
if (!io->tupdesc ||
- io->tupdesc->tdtypeid != typid ||
- io->tupdesc->tdtypmod != typmod)
+ io->tupdesc->tdtypeid != io->base_typid ||
+ io->tupdesc->tdtypmod != io->base_typmod)
{
- TupleDesc tupdesc = lookup_rowtype_tupdesc(typid, typmod);
+ TupleDesc tupdesc = lookup_rowtype_tupdesc(io->base_typid,
+ io->base_typmod);
MemoryContext oldcxt;
if (io->tupdesc)
@@ -2735,17 +2735,50 @@ populate_composite(CompositeIOData *io,
ReleaseTupleDesc(tupdesc);
}
+}
+
+/* recursively populate a composite (row type) value from json/jsonb */
+static Datum
+populate_composite(CompositeIOData *io,
+ Oid typid,
+ const char *colname,
+ MemoryContext mcxt,
+ HeapTupleHeader defaultval,
+ JsValue *jsv,
+ bool isnull)
+{
+ Datum result;
- /* prepare input value */
- JsValueToJsObject(jsv, &jso);
+ /* acquire/update cached tuple descriptor */
+ update_cached_tupdesc(io, mcxt);
- /* populate resulting record tuple */
- tuple = populate_record(io->tupdesc, &io->record_io,
- defaultval, mcxt, &jso);
+ if (isnull)
+ result = (Datum) 0;
+ else
+ {
+ HeapTupleHeader tuple;
+ JsObject jso;
+
+ /* prepare input value */
+ JsValueToJsObject(jsv, &jso);
+
+ /* populate resulting record tuple */
+ tuple = populate_record(io->tupdesc, &io->record_io,
+ defaultval, mcxt, &jso);
+ result = HeapTupleHeaderGetDatum(tuple);
- JsObjectFree(&jso);
+ JsObjectFree(&jso);
+ }
+
+ /*
+ * If it's domain over composite, check domain constraints. (This should
+ * probably get refactored so that we can see the TYPECAT value, but for
+ * now, we can tell by comparing typid to base_typid.)
+ */
+ if (typid != io->base_typid && typid != RECORDOID)
+ domain_check(result, isnull, typid, &io->domain_info, mcxt);
- return HeapTupleHeaderGetDatum(tuple);
+ return result;
}
/* populate non-null scalar value from json/jsonb value */
@@ -2867,7 +2900,7 @@ prepare_column_cache(ColumnIOData *column,
Oid typid,
int32 typmod,
MemoryContext mcxt,
- bool json)
+ bool need_scalar)
{
HeapTuple tup;
Form_pg_type type;
@@ -2883,18 +2916,43 @@ prepare_column_cache(ColumnIOData *column,
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;
+ /*
+ * We can move directly to the bottom base type; domain_check() will
+ * take care of checking all constraints for a stack of domains.
+ */
+ Oid base_typid;
+ int32 base_typmod = typmod;
+
+ base_typid = getBaseTypeAndTypmod(typid, &base_typmod);
+ if (get_typtype(base_typid) == TYPTYPE_COMPOSITE)
+ {
+ /* domain over composite has its own code path */
+ column->typcat = TYPECAT_COMPOSITE_DOMAIN;
+ column->io.composite.record_io = NULL;
+ column->io.composite.tupdesc = NULL;
+ column->io.composite.base_typid = base_typid;
+ column->io.composite.base_typmod = base_typmod;
+ column->io.composite.domain_info = NULL;
+ }
+ else
+ {
+ /* domain over anything else */
+ column->typcat = TYPECAT_DOMAIN;
+ column->io.domain.base_typid = base_typid;
+ column->io.domain.base_typmod = base_typmod;
+ 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;
+ column->io.composite.base_typid = typid;
+ column->io.composite.base_typmod = typmod;
+ column->io.composite.domain_info = NULL;
}
else if (type->typlen == -1 && OidIsValid(type->typelem))
{
@@ -2906,10 +2964,13 @@ prepare_column_cache(ColumnIOData *column,
column->io.array.element_typmod = typmod;
}
else
+ {
column->typcat = TYPECAT_SCALAR;
+ need_scalar = true;
+ }
- /* don't need input function when converting from jsonb to jsonb */
- if (json || typid != JSONBOID)
+ /* caller can force us to look up scalar_io info even for non-scalars */
+ if (need_scalar)
{
Oid typioproc;
@@ -2935,9 +2996,12 @@ populate_record_field(ColumnIOData *col,
check_stack_depth();
- /* prepare column metadata cache for the given type */
+ /*
+ * Prepare column metadata cache for the given type. Force lookup of the
+ * scalar_io data so that the json string hack below will work.
+ */
if (col->typid != typid || col->typmod != typmod)
- prepare_column_cache(col, typid, typmod, mcxt, jsv->is_json);
+ prepare_column_cache(col, typid, typmod, mcxt, true);
*isnull = JsValueIsNull(jsv);
@@ -2945,11 +3009,15 @@ populate_record_field(ColumnIOData *col,
/* try to convert json string to a non-scalar type through input function */
if (JsValueIsString(jsv) &&
- (typcat == TYPECAT_ARRAY || typcat == TYPECAT_COMPOSITE))
+ (typcat == TYPECAT_ARRAY ||
+ typcat == TYPECAT_COMPOSITE ||
+ typcat == TYPECAT_COMPOSITE_DOMAIN))
typcat = TYPECAT_SCALAR;
- /* we must perform domain checks for NULLs */
- if (*isnull && typcat != TYPECAT_DOMAIN)
+ /* we must perform domain checks for NULLs, otherwise exit immediately */
+ if (*isnull &&
+ typcat != TYPECAT_DOMAIN &&
+ typcat != TYPECAT_COMPOSITE_DOMAIN)
return (Datum) 0;
switch (typcat)
@@ -2961,12 +3029,13 @@ populate_record_field(ColumnIOData *col,
return populate_array(&col->io.array, colname, mcxt, jsv);
case TYPECAT_COMPOSITE:
- return populate_composite(&col->io.composite, typid, typmod,
+ case TYPECAT_COMPOSITE_DOMAIN:
+ return populate_composite(&col->io.composite, typid,
colname, mcxt,
DatumGetPointer(defaultval)
? DatumGetHeapTupleHeader(defaultval)
: NULL,
- jsv);
+ jsv, *isnull);
case TYPECAT_DOMAIN:
return populate_domain(&col->io.domain, typid, colname, mcxt,
@@ -3137,10 +3206,7 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
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;
+ HeapTupleHeader rec;
Datum rettuple;
JsonbValue jbv;
MemoryContext fnmcxt = fcinfo->flinfo->fn_mcxt;
@@ -3149,77 +3215,88 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
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 first time through, identify input/result record type. Note that
+ * this stanza looks only at fcinfo context, which can't change during the
+ * query; so we may not be able to fully resolve a RECORD input type yet.
*/
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 (have_record_arg)
{
- if (!type_is_rowtype(argtype))
+ /*
+ * 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)));
-
- cache->argtype = argtype;
}
-
- if (PG_ARGISNULL(0))
+ else
{
- 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.
+ * 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.
*/
- tupType = argtype;
- tupTypmod = -1;
- }
- else
- {
- rec = PG_GETARG_HEAPTUPLEHEADER(0);
+ TupleDesc tupdesc;
+ MemoryContext old_cxt;
- if (PG_ARGISNULL(1))
- PG_RETURN_POINTER(rec);
-
- /* Extract type info from the tuple itself */
- tupType = HeapTupleHeaderGetTypeId(rec);
- tupTypmod = HeapTupleHeaderGetTypMod(rec);
+ 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);
}
}
- 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);
+ /* Collect record arg if we have one */
+ if (have_record_arg && !PG_ARGISNULL(0))
+ {
+ rec = PG_GETARG_HEAPTUPLEHEADER(0);
/*
- * Add tupdesc to the cache and set the appropriate values of
- * tupType/tupTypmod for proper cache usage in populate_composite().
+ * 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.
*/
- cache->io.tupdesc = tupdesc;
+ if (cache->argtype == RECORDOID)
+ {
+ cache->c.io.composite.base_typid = HeapTupleHeaderGetTypeId(rec);
+ cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec);
+ }
+ }
+ else
+ rec = NULL;
- tupType = tupdesc->tdtypeid;
- tupTypmod = tupdesc->tdtypmod;
+ /* If no JSON argument, just return the record (if any) unchanged */
+ if (PG_ARGISNULL(json_arg_num))
+ {
+ if (rec)
+ PG_RETURN_POINTER(rec);
+ else
+ PG_RETURN_NULL();
}
jsv.is_json = jtype == JSONOID;
@@ -3245,14 +3322,8 @@ populate_record_worker(FunctionCallInfo fcinfo, const char *funcname,
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);
- }
+ rettuple = populate_composite(&cache->c.io.composite, cache->argtype,
+ NULL, fnmcxt, rec, &jsv, false);
PG_RETURN_DATUM(rettuple);
}
@@ -3438,13 +3509,28 @@ json_to_recordset(PG_FUNCTION_ARGS)
static void
populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
{
+ PopulateRecordsetCache *cache = state->cache;
+ HeapTupleHeader tuphead;
HeapTupleData tuple;
- HeapTupleHeader tuphead = populate_record(state->ret_tdesc,
- state->my_extra,
- state->rec,
- state->fn_mcxt,
- obj);
+ /* acquire/update cached tuple descriptor */
+ update_cached_tupdesc(&cache->c.io.composite, cache->fn_mcxt);
+
+ /* replace record fields from json */
+ tuphead = populate_record(cache->c.io.composite.tupdesc,
+ &cache->c.io.composite.record_io,
+ state->rec,
+ cache->fn_mcxt,
+ obj);
+
+ /* if it's domain over composite, check domain constraints */
+ if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
+ domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ cache->argtype,
+ &cache->c.io.composite.domain_info,
+ cache->fn_mcxt);
+
+ /* ok, save into tuplestore */
tuple.t_len = HeapTupleHeaderGetDatumLength(tuphead);
ItemPointerSetInvalid(&(tuple.t_self));
tuple.t_tableOid = InvalidOid;
@@ -3465,25 +3551,13 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
ReturnSetInfo *rsi;
MemoryContext old_cxt;
HeapTupleHeader rec;
- TupleDesc tupdesc;
+ PopulateRecordsetCache *cache = fcinfo->flinfo->fn_extra;
PopulateRecordsetState *state;
- if (have_record_arg)
- {
- Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
-
- if (!type_is_rowtype(argtype))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("first argument of %s must be a row type",
- funcname)));
- }
-
rsi = (ReturnSetInfo *) fcinfo->resultinfo;
if (!rsi || !IsA(rsi, ReturnSetInfo) ||
- (rsi->allowedModes & SFRM_Materialize) == 0 ||
- rsi->expectedDesc == NULL)
+ (rsi->allowedModes & SFRM_Materialize) == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that "
@@ -3492,40 +3566,97 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
rsi->returnMode = SFRM_Materialize;
/*
- * get the tupdesc from the result set info - it must be a record type
- * because we already checked that arg1 is a record type, or we're in a
- * to_record function which returns a setof record.
+ * If first time through, identify input/result record type. Note that
+ * this stanza looks only at fcinfo context, which can't change during the
+ * query; so we may not be able to fully resolve a RECORD input type yet.
*/
- 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")));
+ if (!cache)
+ {
+ fcinfo->flinfo->fn_extra = cache =
+ MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, sizeof(*cache));
+ 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)));
+ }
+ 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);
+ }
+ }
+
+ /* Collect record arg if we have one */
+ if (have_record_arg && !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.
+ */
+ if (cache->argtype == RECORDOID)
+ {
+ cache->c.io.composite.base_typid = HeapTupleHeaderGetTypeId(rec);
+ cache->c.io.composite.base_typmod = HeapTupleHeaderGetTypMod(rec);
+ }
+ }
+ else
+ rec = NULL;
/* if the json is null send back an empty set */
if (PG_ARGISNULL(json_arg_num))
PG_RETURN_NULL();
- if (!have_record_arg || PG_ARGISNULL(0))
- rec = NULL;
- else
- rec = PG_GETARG_HEAPTUPLEHEADER(0);
-
state = palloc0(sizeof(PopulateRecordsetState));
- /* make these in a sufficiently long-lived memory context */
+ /* make tuplestore in a sufficiently long-lived memory context */
old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
- state->ret_tdesc = CreateTupleDescCopy(tupdesc);
- BlessTupleDesc(state->ret_tdesc);
state->tuple_store = tuplestore_begin_heap(rsi->allowedModes &
SFRM_Materialize_Random,
false, work_mem);
MemoryContextSwitchTo(old_cxt);
state->function_name = funcname;
- state->my_extra = (RecordIOData **) &fcinfo->flinfo->fn_extra;
+ state->cache = cache;
state->rec = rec;
- state->fn_mcxt = fcinfo->flinfo->fn_mcxt;
if (jtype == JSONOID)
{
@@ -3592,7 +3723,7 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
}
rsi->setResult = state->tuple_store;
- rsi->setDesc = state->ret_tdesc;
+ rsi->setDesc = cache->c.io.composite.tupdesc;
PG_RETURN_NULL();
}