diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2005-03-31 22:46:33 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2005-03-31 22:46:33 +0000 |
commit | 47888fe84227aaf3decffc7204554bdec54d2b29 (patch) | |
tree | 73703aa272d2b9899626002190f0fbd3b1e579fb /src/backend/utils | |
parent | fb13881f423193a8342e0fe098f581e511b09d67 (diff) | |
download | postgresql-47888fe84227aaf3decffc7204554bdec54d2b29.tar.gz postgresql-47888fe84227aaf3decffc7204554bdec54d2b29.zip |
First phase of OUT-parameters project. We can now define and use SQL
functions with OUT parameters. The various PLs still need work, as does
pg_dump. Rudimentary docs and regression tests included.
Diffstat (limited to 'src/backend/utils')
-rw-r--r-- | src/backend/utils/cache/lsyscache.c | 38 | ||||
-rw-r--r-- | src/backend/utils/fmgr/fmgr.c | 30 | ||||
-rw-r--r-- | src/backend/utils/fmgr/funcapi.c | 643 |
3 files changed, 664 insertions, 47 deletions
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 0b40a20b251..3abbf65fa48 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.121 2005/03/29 00:17:11 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.122 2005/03/31 22:46:14 tgl Exp $ * * NOTES * Eventually, the index information should go through here, too. @@ -1544,42 +1544,6 @@ get_typtype(Oid typid) } /* - * get_type_func_class - * - * Given the type OID, obtain its TYPEFUNC classification. - * - * 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. - */ -TypeFuncClass -get_type_func_class(Oid typid) -{ - switch (get_typtype(typid)) - { - case 'c': - return TYPEFUNC_COMPOSITE; - case 'b': - case 'd': - return TYPEFUNC_SCALAR; - case 'p': - if (typid == RECORDOID) - return TYPEFUNC_RECORD; - /* - * We treat VOID and CSTRING as legitimate scalar datatypes, - * mostly for the convenience of the JDBC driver (which wants - * to be able to do "SELECT * FROM foo()" for all legitimately - * user-callable functions). - */ - if (typid == VOIDOID || typid == CSTRINGOID) - return TYPEFUNC_SCALAR; - return TYPEFUNC_OTHER; - } - /* shouldn't get here, probably */ - return TYPEFUNC_OTHER; -} - -/* * get_typ_typrelid * * Given the type OID, get the typrelid (InvalidOid if not a complex diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index a8bb5fc0ac5..0e9716de013 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.92 2005/03/29 03:01:31 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.93 2005/03/31 22:46:16 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -403,7 +403,7 @@ fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple) * We want to raise an error here only if the info function returns * something bogus. * - * This function is broken out of fmgr_info_C_lang() so that ProcedureCreate() + * This function is broken out of fmgr_info_C_lang so that fmgr_c_validator * can validate the information record for a function not yet entered into * pg_proc. */ @@ -576,8 +576,8 @@ fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo, /* - * Specialized lookup routine for ProcedureCreate(): given the alleged name - * of an internal function, return the OID of the function. + * Specialized lookup routine for fmgr_internal_validator: given the alleged + * name of an internal function, return the OID of the function. * If the name is not recognized, return InvalidOid. */ Oid @@ -1869,10 +1869,6 @@ get_fn_expr_rettype(FmgrInfo *flinfo) Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum) { - Node *expr; - List *args; - Oid argtype; - /* * can't return anything useful if we have no FmgrInfo or if its * fn_expr node has not been initialized @@ -1880,7 +1876,23 @@ get_fn_expr_argtype(FmgrInfo *flinfo, int argnum) if (!flinfo || !flinfo->fn_expr) return InvalidOid; - expr = flinfo->fn_expr; + return get_call_expr_argtype(flinfo->fn_expr, argnum); +} + +/* + * Get the actual type OID of a specific function argument (counting from 0), + * but working from the calling expression tree instead of FmgrInfo + * + * Returns InvalidOid if information is not available + */ +Oid +get_call_expr_argtype(Node *expr, int argnum) +{ + List *args; + Oid argtype; + + if (expr == NULL) + return InvalidOid; if (IsA(expr, FuncExpr)) args = ((FuncExpr *) expr)->args; diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 2c3845b7bf4..160847de1e0 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -7,17 +7,37 @@ * Copyright (c) 2002-2005, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.18 2005/01/01 05:43:08 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.19 2005/03/31 22:46:16 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "access/heapam.h" #include "funcapi.h" +#include "catalog/namespace.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" #include "utils/syscache.h" +#include "utils/typcache.h" + static void shutdown_MultiFuncCall(Datum arg); +static TypeFuncClass internal_get_result_type(Oid funcid, + Node *call_expr, + ReturnSetInfo *rsinfo, + Oid *resultTypeId, + TupleDesc *resultTupleDesc); +static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc, + oidvector *declared_args, + Node *call_expr); +static TypeFuncClass get_type_func_class(Oid typid); + /* * init_MultiFuncCall @@ -156,3 +176,624 @@ shutdown_MultiFuncCall(Datum arg) pfree(funcctx); } + + +/* + * get_call_result_type + * Given a function's call info record, determine the kind of datatype + * it is supposed to return. If resultTypeId isn't NULL, *resultTypeId + * receives the actual datatype OID (this is mainly useful for scalar + * result types). If resultTupleDesc isn't NULL, *resultTupleDesc + * receives a pointer to a TupleDesc when the result is of a composite + * type, or NULL when it's a scalar result. NB: the tupledesc should + * be copied if it is to be accessed over a long period. + * + * One hard case that this handles is resolution of actual rowtypes for + * functions returning RECORD (from either the function's OUT parameter + * list, or a ReturnSetInfo context node). TYPEFUNC_RECORD is returned + * only when we couldn't resolve the actual rowtype for lack of information. + * + * The other hard case that this handles is resolution of polymorphism. + * We will never return ANYELEMENT or ANYARRAY, either as a scalar result + * type or as a component of a rowtype. + * + * This function is relatively expensive --- in a function returning set, + * try to call it only the first time through. + */ +TypeFuncClass +get_call_result_type(FunctionCallInfo fcinfo, + Oid *resultTypeId, + TupleDesc *resultTupleDesc) +{ + return internal_get_result_type(fcinfo->flinfo->fn_oid, + fcinfo->flinfo->fn_expr, + (ReturnSetInfo *) fcinfo->resultinfo, + resultTypeId, + resultTupleDesc); +} + +/* + * get_expr_result_type + * As above, but work from a calling expression node tree + */ +TypeFuncClass +get_expr_result_type(Node *expr, + Oid *resultTypeId, + TupleDesc *resultTupleDesc) +{ + TypeFuncClass result; + + if (expr && IsA(expr, FuncExpr)) + result = internal_get_result_type(((FuncExpr *) expr)->funcid, + expr, + NULL, + resultTypeId, + resultTupleDesc); + else + { + /* handle as a generic expression; no chance to resolve RECORD */ + Oid typid = exprType(expr); + + if (resultTypeId) + *resultTypeId = typid; + if (resultTupleDesc) + *resultTupleDesc = NULL; + result = get_type_func_class(typid); + if (result == TYPEFUNC_COMPOSITE && resultTupleDesc) + *resultTupleDesc = lookup_rowtype_tupdesc(typid, -1); + } + + return result; +} + +/* + * get_expr_result_type + * As above, but work from a function's OID only + * + * This will not be able to resolve pure-RECORD results nor polymorphism. + */ +TypeFuncClass +get_func_result_type(Oid functionId, + Oid *resultTypeId, + TupleDesc *resultTupleDesc) +{ + return internal_get_result_type(functionId, + NULL, + NULL, + resultTypeId, + resultTupleDesc); +} + +/* + * internal_get_result_type -- workhorse code implementing all the above + * + * funcid must always be supplied. call_expr and rsinfo can be NULL if not + * available. We will return TYPEFUNC_RECORD, and store NULL into + * *resultTupleDesc, if we cannot deduce the complete result rowtype from + * the available information. + */ +static TypeFuncClass +internal_get_result_type(Oid funcid, + Node *call_expr, + ReturnSetInfo *rsinfo, + Oid *resultTypeId, + TupleDesc *resultTupleDesc) +{ + TypeFuncClass result; + HeapTuple tp; + Form_pg_proc procform; + Oid rettype; + TupleDesc tupdesc; + + /* First fetch the function's pg_proc row to inspect its rettype */ + tp = SearchSysCache(PROCOID, + ObjectIdGetDatum(funcid), + 0, 0, 0); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + procform = (Form_pg_proc) GETSTRUCT(tp); + + rettype = procform->prorettype; + + /* Check for OUT parameters defining a RECORD result */ + tupdesc = build_function_result_tupdesc_t(tp); + if (tupdesc) + { + /* + * It has OUT parameters, so it's basically like a regular + * composite type, except we have to be able to resolve any + * polymorphic OUT parameters. + */ + if (resultTypeId) + *resultTypeId = rettype; + + if (resolve_polymorphic_tupdesc(tupdesc, + &procform->proargtypes, + call_expr)) + { + if (tupdesc->tdtypeid == RECORDOID && + tupdesc->tdtypmod < 0) + assign_record_type_typmod(tupdesc); + if (resultTupleDesc) + *resultTupleDesc = tupdesc; + result = TYPEFUNC_COMPOSITE; + } + else + { + if (resultTupleDesc) + *resultTupleDesc = NULL; + result = TYPEFUNC_RECORD; + } + + ReleaseSysCache(tp); + + return result; + } + + /* + * If scalar polymorphic result, try to resolve it. + */ + if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) + { + Oid newrettype = exprType(call_expr); + + if (newrettype == InvalidOid) /* this probably should not happen */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not determine actual result type for function \"%s\" declared to return type %s", + NameStr(procform->proname), + format_type_be(rettype)))); + rettype = newrettype; + } + + if (resultTypeId) + *resultTypeId = rettype; + if (resultTupleDesc) + *resultTupleDesc = NULL; /* default result */ + + /* Classify the result type */ + result = get_type_func_class(rettype); + switch (result) + { + case TYPEFUNC_COMPOSITE: + if (resultTupleDesc) + *resultTupleDesc = lookup_rowtype_tupdesc(rettype, -1); + /* Named composite types can't have any polymorphic columns */ + break; + case TYPEFUNC_SCALAR: + break; + case TYPEFUNC_RECORD: + /* We must get the tupledesc from call context */ + if (rsinfo && IsA(rsinfo, ReturnSetInfo) && + rsinfo->expectedDesc != NULL) + { + result = TYPEFUNC_COMPOSITE; + if (resultTupleDesc) + *resultTupleDesc = rsinfo->expectedDesc; + /* Assume no polymorphic columns here, either */ + } + break; + default: + break; + } + + ReleaseSysCache(tp); + + return result; +} + +/* + * Given the result tuple descriptor for a function with OUT parameters, + * replace any polymorphic columns (ANYELEMENT/ANYARRAY) with correct data + * types deduced from the input arguments. Returns TRUE if able to deduce + * all types, FALSE if not. + */ +static bool +resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, + Node *call_expr) +{ + int natts = tupdesc->natts; + int nargs = declared_args->dim1; + bool have_anyelement_result = false; + bool have_anyarray_result = false; + Oid anyelement_type = InvalidOid; + Oid anyarray_type = InvalidOid; + int i; + + /* See if there are any polymorphic outputs; quick out if not */ + for (i = 0; i < natts; i++) + { + switch (tupdesc->attrs[i]->atttypid) + { + case ANYELEMENTOID: + have_anyelement_result = true; + break; + case ANYARRAYOID: + have_anyarray_result = true; + break; + default: + break; + } + } + if (!have_anyelement_result && !have_anyarray_result) + return true; + + /* + * Otherwise, extract actual datatype(s) from input arguments. (We assume + * the parser already validated consistency of the arguments.) + */ + if (!call_expr) + return false; /* no hope */ + + for (i = 0; i < nargs; i++) + { + switch (declared_args->values[i]) + { + case ANYELEMENTOID: + if (!OidIsValid(anyelement_type)) + anyelement_type = get_call_expr_argtype(call_expr, i); + break; + case ANYARRAYOID: + if (!OidIsValid(anyarray_type)) + anyarray_type = get_call_expr_argtype(call_expr, i); + break; + default: + break; + } + } + + /* If nothing found, parser messed up */ + if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type)) + return false; + + /* If needed, deduce one polymorphic type from the other */ + if (have_anyelement_result && !OidIsValid(anyelement_type)) + anyelement_type = resolve_generic_type(ANYELEMENTOID, + anyarray_type, + ANYARRAYOID); + if (have_anyarray_result && !OidIsValid(anyarray_type)) + anyarray_type = resolve_generic_type(ANYARRAYOID, + anyelement_type, + ANYELEMENTOID); + + /* And finally replace the tuple column types as needed */ + for (i = 0; i < natts; i++) + { + switch (tupdesc->attrs[i]->atttypid) + { + case ANYELEMENTOID: + TupleDescInitEntry(tupdesc, i+1, + NameStr(tupdesc->attrs[i]->attname), + anyelement_type, + -1, + 0); + break; + case ANYARRAYOID: + TupleDescInitEntry(tupdesc, i+1, + NameStr(tupdesc->attrs[i]->attname), + anyarray_type, + -1, + 0); + break; + default: + break; + } + } + + return true; +} + +/* + * get_type_func_class + * Given the type OID, obtain its TYPEFUNC classification. + * + * 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) +{ + switch (get_typtype(typid)) + { + case 'c': + return TYPEFUNC_COMPOSITE; + case 'b': + case 'd': + return TYPEFUNC_SCALAR; + case 'p': + if (typid == RECORDOID) + return TYPEFUNC_RECORD; + /* + * We treat VOID and CSTRING as legitimate scalar datatypes, + * mostly for the convenience of the JDBC driver (which wants + * to be able to do "SELECT * FROM foo()" for all legitimately + * user-callable functions). + */ + if (typid == VOIDOID || typid == CSTRINGOID) + return TYPEFUNC_SCALAR; + return TYPEFUNC_OTHER; + } + /* shouldn't get here, probably */ + return TYPEFUNC_OTHER; +} + + +/* + * build_function_result_tupdesc_t + * + * Given a pg_proc row for a function, return a tuple descriptor for the + * result rowtype, or NULL if the function does not have OUT parameters. + * + * Note that this does not handle resolution of ANYELEMENT/ANYARRAY types; + * that is deliberate. + */ +TupleDesc +build_function_result_tupdesc_t(HeapTuple procTuple) +{ + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(procTuple); + Datum proallargtypes; + Datum proargmodes; + Datum proargnames; + bool isnull; + + /* Return NULL if the function isn't declared to return RECORD */ + if (procform->prorettype != RECORDOID) + return NULL; + + /* If there are no OUT parameters, return NULL */ + if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes) || + heap_attisnull(procTuple, Anum_pg_proc_proargmodes)) + return NULL; + + /* Get the data out of the tuple */ + proallargtypes = SysCacheGetAttr(PROCOID, procTuple, + Anum_pg_proc_proallargtypes, + &isnull); + Assert(!isnull); + proargmodes = SysCacheGetAttr(PROCOID, procTuple, + Anum_pg_proc_proargmodes, + &isnull); + Assert(!isnull); + proargnames = SysCacheGetAttr(PROCOID, procTuple, + Anum_pg_proc_proargnames, + &isnull); + if (isnull) + proargnames = PointerGetDatum(NULL); /* just to be sure */ + + return build_function_result_tupdesc_d(proallargtypes, + proargmodes, + proargnames); +} + +/* + * build_function_result_tupdesc_d + * + * Build a RECORD function's tupledesc from the pg_proc proallargtypes, + * proargmodes, and proargnames arrays. This is split out for the + * convenience of ProcedureCreate, which needs to be able to compute the + * tupledesc before actually creating the function. + * + * Returns NULL if there are not at least two OUT or INOUT arguments. + */ +TupleDesc +build_function_result_tupdesc_d(Datum proallargtypes, + Datum proargmodes, + Datum proargnames) +{ + TupleDesc desc; + ArrayType *arr; + int numargs; + Oid *argtypes; + char *argmodes; + Datum *argnames = NULL; + Oid *outargtypes; + char **outargnames; + int numoutargs; + int nargnames; + int i; + + /* Can't have output args if columns are null */ + if (proallargtypes == PointerGetDatum(NULL) || + proargmodes == PointerGetDatum(NULL)) + return NULL; + + /* + * We expect the arrays to be 1-D arrays of the right types; verify that. + * For the OID and char arrays, we don't need to use deconstruct_array() + * since the array data is just going to look like a C array of values. + */ + arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */ + numargs = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numargs < 0 || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "proallargtypes is not a 1-D Oid array"); + argtypes = (Oid *) ARR_DATA_PTR(arr); + arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != numargs || + ARR_ELEMTYPE(arr) != CHAROID) + elog(ERROR, "proargmodes is not a 1-D char array"); + argmodes = (char *) ARR_DATA_PTR(arr); + if (proargnames != PointerGetDatum(NULL)) + { + arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != numargs || + ARR_ELEMTYPE(arr) != TEXTOID) + elog(ERROR, "proargnames is not a 1-D text array"); + deconstruct_array(arr, TEXTOID, -1, false, 'i', + &argnames, &nargnames); + Assert(nargnames == numargs); + } + + /* zero elements probably shouldn't happen, but handle it gracefully */ + if (numargs <= 0) + return NULL; + + /* extract output-argument types and names */ + outargtypes = (Oid *) palloc(numargs * sizeof(Oid)); + outargnames = (char **) palloc(numargs * sizeof(char *)); + numoutargs = 0; + for (i = 0; i < numargs; i++) + { + char *pname; + + if (argmodes[i] == PROARGMODE_IN) + continue; + Assert(argmodes[i] == PROARGMODE_OUT || + argmodes[i] == PROARGMODE_INOUT); + outargtypes[numoutargs] = argtypes[i]; + if (argnames) + pname = DatumGetCString(DirectFunctionCall1(textout, argnames[i])); + else + pname = NULL; + if (pname == NULL || pname[0] == '\0') + { + /* Parameter is not named, so gin up a column name */ + pname = (char *) palloc(32); + snprintf(pname, 32, "column%d", numoutargs + 1); + } + outargnames[numoutargs] = pname; + numoutargs++; + } + + /* + * If there is no output argument, or only one, the function does not + * return tuples. + */ + if (numoutargs < 2) + return NULL; + + desc = CreateTemplateTupleDesc(numoutargs, false); + for (i = 0; i < numoutargs; i++) + { + TupleDescInitEntry(desc, i+1, + outargnames[i], + outargtypes[i], + -1, + 0); + } + + return desc; +} + + +/* + * RelationNameGetTupleDesc + * + * Given a (possibly qualified) relation name, build a TupleDesc. + */ +TupleDesc +RelationNameGetTupleDesc(const char *relname) +{ + RangeVar *relvar; + Relation rel; + TupleDesc tupdesc; + List *relname_list; + + /* Open relation and copy the tuple description */ + relname_list = stringToQualifiedNameList(relname, "RelationNameGetTupleDesc"); + relvar = makeRangeVarFromNameList(relname_list); + rel = relation_openrv(relvar, AccessShareLock); + tupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); + relation_close(rel, AccessShareLock); + + return tupdesc; +} + +/* + * TypeGetTupleDesc + * + * Given a type Oid, build a TupleDesc. + * + * If the type is composite, *and* a colaliases List is provided, *and* + * the List is of natts length, use the aliases instead of the relation + * attnames. (NB: this usage is deprecated since it may result in + * creation of unnecessary transient record types.) + * + * If the type is a base type, a single item alias List is required. + */ +TupleDesc +TypeGetTupleDesc(Oid typeoid, List *colaliases) +{ + TypeFuncClass functypclass = get_type_func_class(typeoid); + TupleDesc tupdesc = NULL; + + /* + * Build a suitable tupledesc representing the output rows + */ + if (functypclass == TYPEFUNC_COMPOSITE) + { + /* Composite data type, e.g. a table's row type */ + tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(typeoid, -1)); + + if (colaliases != NIL) + { + int natts = tupdesc->natts; + int varattno; + + /* does the list length match the number of attributes? */ + if (list_length(colaliases) != natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("number of aliases does not match number of columns"))); + + /* OK, use the aliases instead */ + for (varattno = 0; varattno < natts; varattno++) + { + char *label = strVal(list_nth(colaliases, varattno)); + + if (label != NULL) + namestrcpy(&(tupdesc->attrs[varattno]->attname), label); + } + + /* The tuple type is now an anonymous record type */ + tupdesc->tdtypeid = RECORDOID; + tupdesc->tdtypmod = -1; + } + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + char *attname; + + /* the alias list is required for base types */ + if (colaliases == NIL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("no column alias was provided"))); + + /* the alias list length must be 1 */ + if (list_length(colaliases) != 1) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("number of aliases does not match number of columns"))); + + /* OK, get the column alias */ + attname = strVal(linitial(colaliases)); + + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + attname, + typeoid, + -1, + 0); + } + else if (functypclass == TYPEFUNC_RECORD) + { + /* XXX can't support this because typmod wasn't passed in ... */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not determine row description for function returning record"))); + } + else + { + /* crummy error message, but parser should have caught this */ + elog(ERROR, "function in FROM has unsupported return type"); + } + + return tupdesc; +} |