summaryrefslogtreecommitdiff
path: root/src/backend/utils
diff options
context:
space:
mode:
authorTom Lane2017-10-26 17:47:45 +0000
committerTom Lane2017-10-26 17:47:45 +0000
commit37a795a60b4f4b1def11c615525ec5e0e9449e05 (patch)
treea5aa9d7e51ef4fd0e353223bd691f7e85018a032 /src/backend/utils
parent08f1e1f0a47b4b0e87b07b9794698747b279c711 (diff)
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/[email protected]
Diffstat (limited to 'src/backend/utils')
-rw-r--r--src/backend/utils/adt/domains.c9
-rw-r--r--src/backend/utils/adt/jsonfuncs.c431
-rw-r--r--src/backend/utils/adt/ruleutils.c18
-rw-r--r--src/backend/utils/cache/lsyscache.c18
-rw-r--r--src/backend/utils/cache/typcache.c130
-rw-r--r--src/backend/utils/fmgr/funcapi.c93
6 files changed, 491 insertions, 208 deletions
diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c
index e61d91bd885..86f916ff436 100644
--- a/src/backend/utils/adt/domains.c
+++ b/src/backend/utils/adt/domains.c
@@ -82,9 +82,10 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
* Verify that domainType represents a valid domain type. We need to be
* careful here because domain_in and domain_recv can be called from SQL,
* possibly with incorrect arguments. We use lookup_type_cache mainly
- * because it will throw a clean user-facing error for a bad OID.
+ * because it will throw a clean user-facing error for a bad OID; but also
+ * it can cache the underlying base type info.
*/
- typentry = lookup_type_cache(domainType, 0);
+ typentry = lookup_type_cache(domainType, TYPECACHE_DOMAIN_BASE_INFO);
if (typentry->typtype != TYPTYPE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
@@ -92,8 +93,8 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt)
format_type_be(domainType))));
/* Find out the base type */
- my_extra->typtypmod = -1;
- baseType = getBaseTypeAndTypmod(domainType, &my_extra->typtypmod);
+ baseType = typentry->domainBaseType;
+ my_extra->typtypmod = typentry->domainBaseTypmod;
/* Look up underlying I/O function */
if (binary)
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();
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 84759b6149e..b1e70a0d19e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -6731,17 +6731,12 @@ get_name_for_var_field(Var *var, int fieldno,
/*
* If it's a Var of type RECORD, we have to find what the Var refers to;
- * if not, we can use get_expr_result_type. If that fails, we try
- * lookup_rowtype_tupdesc, which will probably fail too, but will ereport
- * an acceptable message.
+ * if not, we can use get_expr_result_tupdesc().
*/
if (!IsA(var, Var) ||
var->vartype != RECORDOID)
{
- if (get_expr_result_type((Node *) var, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
- tupleDesc = lookup_rowtype_tupdesc_copy(exprType((Node *) var),
- exprTypmod((Node *) var));
- Assert(tupleDesc);
+ tupleDesc = get_expr_result_tupdesc((Node *) var, false);
/* Got the tupdesc, so we can extract the field name */
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
@@ -7044,14 +7039,9 @@ get_name_for_var_field(Var *var, int fieldno,
/*
* We now have an expression we can't expand any more, so see if
- * get_expr_result_type() can do anything with it. If not, pass to
- * lookup_rowtype_tupdesc() which will probably fail, but will give an
- * appropriate error message while failing.
+ * get_expr_result_tupdesc() can do anything with it.
*/
- if (get_expr_result_type(expr, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
- tupleDesc = lookup_rowtype_tupdesc_copy(exprType(expr),
- exprTypmod(expr));
- Assert(tupleDesc);
+ tupleDesc = get_expr_result_tupdesc(expr, false);
/* Got the tupdesc, so we can extract the field name */
Assert(fieldno >= 1 && fieldno <= tupleDesc->natts);
return NameStr(TupleDescAttr(tupleDesc, fieldno - 1)->attname);
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index b7a14dc87e1..48961e31aa9 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2398,12 +2398,26 @@ get_typtype(Oid typid)
* type_is_rowtype
*
* Convenience function to determine whether a type OID represents
- * a "rowtype" type --- either RECORD or a named composite type.
+ * a "rowtype" type --- either RECORD or a named composite type
+ * (including a domain over a named composite type).
*/
bool
type_is_rowtype(Oid typid)
{
- return (typid == RECORDOID || get_typtype(typid) == TYPTYPE_COMPOSITE);
+ if (typid == RECORDOID)
+ return true; /* easy case */
+ switch (get_typtype(typid))
+ {
+ case TYPTYPE_COMPOSITE:
+ return true;
+ case TYPTYPE_DOMAIN:
+ if (get_typtype(getBaseType(typid)) == TYPTYPE_COMPOSITE)
+ return true;
+ break;
+ default:
+ break;
+ }
+ return false;
}
/*
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 61ce7dc2a7b..7aadc5d6ef7 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -96,6 +96,7 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL;
#define TCFLAGS_HAVE_FIELD_EQUALITY 0x004000
#define TCFLAGS_HAVE_FIELD_COMPARE 0x008000
#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x010000
+#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x020000
/*
* Data stored about a domain type's constraints. Note that we do not create
@@ -747,7 +748,15 @@ lookup_type_cache(Oid type_id, int flags)
/*
* If requested, get information about a domain type
*/
- if ((flags & TYPECACHE_DOMAIN_INFO) &&
+ if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
+ typentry->domainBaseType == InvalidOid &&
+ typentry->typtype == TYPTYPE_DOMAIN)
+ {
+ typentry->domainBaseTypmod = -1;
+ typentry->domainBaseType =
+ getBaseTypeAndTypmod(type_id, &typentry->domainBaseTypmod);
+ }
+ if ((flags & TYPECACHE_DOMAIN_CONSTR_INFO) &&
(typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 &&
typentry->typtype == TYPTYPE_DOMAIN)
{
@@ -1166,7 +1175,7 @@ InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref,
MemoryContext refctx, bool need_exprstate)
{
/* Look up the typcache entry --- we assume it survives indefinitely */
- ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO);
+ ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
ref->need_exprstate = need_exprstate;
/* For safety, establish the callback before acquiring a refcount */
ref->refctx = refctx;
@@ -1257,7 +1266,7 @@ DomainHasConstraints(Oid type_id)
* Note: a side effect is to cause the typcache's domain data to become
* valid. This is fine since we'll likely need it soon if there is any.
*/
- typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO);
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
return (typentry->domainData != NULL);
}
@@ -1405,6 +1414,29 @@ cache_record_field_properties(TypeCacheEntry *typentry)
DecrTupleDescRefCount(tupdesc);
}
+ else if (typentry->typtype == TYPTYPE_DOMAIN)
+ {
+ /* If it's domain over composite, copy base type's properties */
+ TypeCacheEntry *baseentry;
+
+ /* load up basetype info if we didn't already */
+ if (typentry->domainBaseType == InvalidOid)
+ {
+ typentry->domainBaseTypmod = -1;
+ typentry->domainBaseType =
+ getBaseTypeAndTypmod(typentry->type_id,
+ &typentry->domainBaseTypmod);
+ }
+ baseentry = lookup_type_cache(typentry->domainBaseType,
+ TYPECACHE_EQ_OPR |
+ TYPECACHE_CMP_PROC);
+ if (baseentry->typtype == TYPTYPE_COMPOSITE)
+ {
+ typentry->flags |= TCFLAGS_DOMAIN_BASE_IS_COMPOSITE;
+ typentry->flags |= baseentry->flags & (TCFLAGS_HAVE_FIELD_EQUALITY |
+ TCFLAGS_HAVE_FIELD_COMPARE);
+ }
+ }
typentry->flags |= TCFLAGS_CHECKED_FIELD_PROPERTIES;
}
@@ -1619,6 +1651,53 @@ lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod)
}
/*
+ * lookup_rowtype_tupdesc_domain
+ *
+ * Same as lookup_rowtype_tupdesc_noerror(), except that the type can also be
+ * a domain over a named composite type; so this is effectively equivalent to
+ * lookup_rowtype_tupdesc_noerror(getBaseType(type_id), typmod, noError)
+ * except for being a tad faster.
+ *
+ * Note: the reason we don't fold the look-through-domain behavior into plain
+ * lookup_rowtype_tupdesc() is that we want callers to know they might be
+ * dealing with a domain. Otherwise they might construct a tuple that should
+ * be of the domain type, but not apply domain constraints.
+ */
+TupleDesc
+lookup_rowtype_tupdesc_domain(Oid type_id, int32 typmod, bool noError)
+{
+ TupleDesc tupDesc;
+
+ if (type_id != RECORDOID)
+ {
+ /*
+ * Check for domain or named composite type. We might as well load
+ * whichever data is needed.
+ */
+ TypeCacheEntry *typentry;
+
+ typentry = lookup_type_cache(type_id,
+ TYPECACHE_TUPDESC |
+ TYPECACHE_DOMAIN_BASE_INFO);
+ if (typentry->typtype == TYPTYPE_DOMAIN)
+ return lookup_rowtype_tupdesc_noerror(typentry->domainBaseType,
+ typentry->domainBaseTypmod,
+ noError);
+ if (typentry->tupDesc == NULL && !noError)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type %s is not composite",
+ format_type_be(type_id))));
+ tupDesc = typentry->tupDesc;
+ }
+ else
+ tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError);
+ if (tupDesc != NULL)
+ PinTupleDesc(tupDesc);
+ return tupDesc;
+}
+
+/*
* Hash function for the hash table of RecordCacheEntry.
*/
static uint32
@@ -1929,29 +2008,40 @@ TypeCacheRelCallback(Datum arg, Oid relid)
hash_seq_init(&status, TypeCacheHash);
while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
{
- if (typentry->typtype != TYPTYPE_COMPOSITE)
- continue; /* skip non-composites */
+ if (typentry->typtype == TYPTYPE_COMPOSITE)
+ {
+ /* Skip if no match, unless we're zapping all composite types */
+ if (relid != typentry->typrelid && relid != InvalidOid)
+ continue;
- /* Skip if no match, unless we're zapping all composite types */
- if (relid != typentry->typrelid && relid != InvalidOid)
- continue;
+ /* Delete tupdesc if we have it */
+ if (typentry->tupDesc != NULL)
+ {
+ /*
+ * Release our refcount, and free the tupdesc if none remain.
+ * (Can't use DecrTupleDescRefCount because this reference is
+ * not logged in current resource owner.)
+ */
+ Assert(typentry->tupDesc->tdrefcount > 0);
+ if (--typentry->tupDesc->tdrefcount == 0)
+ FreeTupleDesc(typentry->tupDesc);
+ typentry->tupDesc = NULL;
+ }
- /* Delete tupdesc if we have it */
- if (typentry->tupDesc != NULL)
+ /* Reset equality/comparison/hashing validity information */
+ typentry->flags = 0;
+ }
+ else if (typentry->typtype == TYPTYPE_DOMAIN)
{
/*
- * Release our refcount, and free the tupdesc if none remain.
- * (Can't use DecrTupleDescRefCount because this reference is not
- * logged in current resource owner.)
+ * If it's domain over composite, reset flags. (We don't bother
+ * trying to determine whether the specific base type needs a
+ * reset.) Note that if we haven't determined whether the base
+ * type is composite, we don't need to reset anything.
*/
- Assert(typentry->tupDesc->tdrefcount > 0);
- if (--typentry->tupDesc->tdrefcount == 0)
- FreeTupleDesc(typentry->tupDesc);
- typentry->tupDesc = NULL;
+ if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)
+ typentry->flags = 0;
}
-
- /* Reset equality/comparison/hashing validity information */
- typentry->flags = 0;
}
}
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index b4f856eb13b..bfd5031b9d6 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -39,7 +39,7 @@ static TypeFuncClass internal_get_result_type(Oid funcid,
static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
oidvector *declared_args,
Node *call_expr);
-static TypeFuncClass get_type_func_class(Oid typid);
+static TypeFuncClass get_type_func_class(Oid typid, Oid *base_typeid);
/*
@@ -246,14 +246,17 @@ get_expr_result_type(Node *expr,
{
/* handle as a generic expression; no chance to resolve RECORD */
Oid typid = exprType(expr);
+ Oid base_typid;
if (resultTypeId)
*resultTypeId = typid;
if (resultTupleDesc)
*resultTupleDesc = NULL;
- result = get_type_func_class(typid);
- if (result == TYPEFUNC_COMPOSITE && resultTupleDesc)
- *resultTupleDesc = lookup_rowtype_tupdesc_copy(typid, -1);
+ result = get_type_func_class(typid, &base_typid);
+ if ((result == TYPEFUNC_COMPOSITE ||
+ result == TYPEFUNC_COMPOSITE_DOMAIN) &&
+ resultTupleDesc)
+ *resultTupleDesc = lookup_rowtype_tupdesc_copy(base_typid, -1);
}
return result;
@@ -296,6 +299,7 @@ internal_get_result_type(Oid funcid,
HeapTuple tp;
Form_pg_proc procform;
Oid rettype;
+ Oid base_rettype;
TupleDesc tupdesc;
/* First fetch the function's pg_proc row to inspect its rettype */
@@ -363,12 +367,13 @@ internal_get_result_type(Oid funcid,
*resultTupleDesc = NULL; /* default result */
/* Classify the result type */
- result = get_type_func_class(rettype);
+ result = get_type_func_class(rettype, &base_rettype);
switch (result)
{
case TYPEFUNC_COMPOSITE:
+ case TYPEFUNC_COMPOSITE_DOMAIN:
if (resultTupleDesc)
- *resultTupleDesc = lookup_rowtype_tupdesc_copy(rettype, -1);
+ *resultTupleDesc = lookup_rowtype_tupdesc_copy(base_rettype, -1);
/* Named composite types can't have any polymorphic columns */
break;
case TYPEFUNC_SCALAR:
@@ -394,6 +399,46 @@ internal_get_result_type(Oid funcid,
}
/*
+ * get_expr_result_tupdesc
+ * Get a tupdesc describing the result of a composite-valued expression
+ *
+ * If expression is not composite or rowtype can't be determined, returns NULL
+ * if noError is true, else throws error.
+ *
+ * This is a simpler version of get_expr_result_type() for use when the caller
+ * is only interested in determinate rowtype results.
+ */
+TupleDesc
+get_expr_result_tupdesc(Node *expr, bool noError)
+{
+ TupleDesc tupleDesc;
+ TypeFuncClass functypclass;
+
+ functypclass = get_expr_result_type(expr, NULL, &tupleDesc);
+
+ if (functypclass == TYPEFUNC_COMPOSITE ||
+ functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
+ return tupleDesc;
+
+ if (!noError)
+ {
+ Oid exprTypeId = exprType(expr);
+
+ if (exprTypeId != RECORDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type %s is not composite",
+ format_type_be(exprTypeId))));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("record type has not been registered")));
+ }
+
+ return NULL;
+}
+
+/*
* Given the result tuple descriptor for a function with OUT parameters,
* replace any polymorphic columns (ANYELEMENT etc) with correct data types
* deduced from the input arguments. Returns TRUE if able to deduce all types,
@@ -741,23 +786,31 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
/*
* get_type_func_class
* Given the type OID, obtain its TYPEFUNC classification.
+ * Also, if it's a domain, return the base type OID.
*
* This is intended to centralize a bunch of formerly ad-hoc code for
* classifying types. The categories used here are useful for deciding
* how to handle functions returning the datatype.
*/
static TypeFuncClass
-get_type_func_class(Oid typid)
+get_type_func_class(Oid typid, Oid *base_typeid)
{
+ *base_typeid = typid;
+
switch (get_typtype(typid))
{
case TYPTYPE_COMPOSITE:
return TYPEFUNC_COMPOSITE;
case TYPTYPE_BASE:
- case TYPTYPE_DOMAIN:
case TYPTYPE_ENUM:
case TYPTYPE_RANGE:
return TYPEFUNC_SCALAR;
+ case TYPTYPE_DOMAIN:
+ *base_typeid = typid = getBaseType(typid);
+ if (get_typtype(typid) == TYPTYPE_COMPOSITE)
+ return TYPEFUNC_COMPOSITE_DOMAIN;
+ else /* domain base type can't be a pseudotype */
+ return TYPEFUNC_SCALAR;
case TYPTYPE_PSEUDO:
if (typid == RECORDOID)
return TYPEFUNC_RECORD;
@@ -1320,16 +1373,20 @@ RelationNameGetTupleDesc(const char *relname)
TupleDesc
TypeGetTupleDesc(Oid typeoid, List *colaliases)
{
- TypeFuncClass functypclass = get_type_func_class(typeoid);
+ Oid base_typeoid;
+ TypeFuncClass functypclass = get_type_func_class(typeoid, &base_typeoid);
TupleDesc tupdesc = NULL;
/*
- * Build a suitable tupledesc representing the output rows
+ * Build a suitable tupledesc representing the output rows. We
+ * intentionally do not support TYPEFUNC_COMPOSITE_DOMAIN here, as it's
+ * unlikely that legacy callers of this obsolete function would be
+ * prepared to apply domain constraints.
*/
if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
- tupdesc = lookup_rowtype_tupdesc_copy(typeoid, -1);
+ tupdesc = lookup_rowtype_tupdesc_copy(base_typeoid, -1);
if (colaliases != NIL)
{
@@ -1424,7 +1481,8 @@ extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start,
Datum *args_res;
bool *nulls_res;
Oid *types_res;
- int nargs, i;
+ int nargs,
+ i;
*args = NULL;
*types = NULL;
@@ -1460,7 +1518,7 @@ extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start,
else
{
nargs = PG_NARGS() - variadic_start;
- Assert (nargs > 0);
+ Assert(nargs > 0);
nulls_res = (bool *) palloc0(nargs * sizeof(bool));
args_res = (Datum *) palloc0(nargs * sizeof(Datum));
types_res = (Oid *) palloc0(nargs * sizeof(Oid));
@@ -1473,11 +1531,10 @@ extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start,
/*
* Turn a constant (more or less literal) value that's of unknown
- * type into text if required . Unknowns come in as a cstring
- * pointer.
- * Note: for functions declared as taking type "any", the parser
- * will not do any type conversion on unknown-type literals (that
- * is, undecorated strings or NULLs).
+ * type into text if required. Unknowns come in as a cstring
+ * pointer. Note: for functions declared as taking type "any", the
+ * parser will not do any type conversion on unknown-type literals
+ * (that is, undecorated strings or NULLs).
*/
if (convert_unknown &&
types_res[i] == UNKNOWNOID &&