diff options
| author | Tom Lane | 2017-10-26 17:47:45 +0000 |
|---|---|---|
| committer | Tom Lane | 2017-10-26 17:47:45 +0000 |
| commit | 37a795a60b4f4b1def11c615525ec5e0e9449e05 (patch) | |
| tree | a5aa9d7e51ef4fd0e353223bd691f7e85018a032 /src/backend/utils | |
| parent | 08f1e1f0a47b4b0e87b07b9794698747b279c711 (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.c | 9 | ||||
| -rw-r--r-- | src/backend/utils/adt/jsonfuncs.c | 431 | ||||
| -rw-r--r-- | src/backend/utils/adt/ruleutils.c | 18 | ||||
| -rw-r--r-- | src/backend/utils/cache/lsyscache.c | 18 | ||||
| -rw-r--r-- | src/backend/utils/cache/typcache.c | 130 | ||||
| -rw-r--r-- | src/backend/utils/fmgr/funcapi.c | 93 |
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 && |
