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 | |
parent | fb13881f423193a8342e0fe098f581e511b09d67 (diff) | |
download | postgresql-47888fe84227aaf3decffc7204554bdec54d2b29.tar.gz |
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')
-rw-r--r-- | src/backend/access/common/tupdesc.c | 126 | ||||
-rw-r--r-- | src/backend/catalog/pg_aggregate.c | 11 | ||||
-rw-r--r-- | src/backend/catalog/pg_proc.c | 191 | ||||
-rw-r--r-- | src/backend/commands/functioncmds.c | 167 | ||||
-rw-r--r-- | src/backend/executor/functions.c | 102 | ||||
-rw-r--r-- | src/backend/executor/nodeFunctionscan.c | 29 | ||||
-rw-r--r-- | src/backend/optimizer/util/clauses.c | 5 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 19 | ||||
-rw-r--r-- | src/backend/parser/parse_func.c | 53 | ||||
-rw-r--r-- | src/backend/parser/parse_relation.c | 156 | ||||
-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 | ||||
-rw-r--r-- | src/include/catalog/pg_proc.h | 9 | ||||
-rw-r--r-- | src/include/executor/functions.h | 4 | ||||
-rw-r--r-- | src/include/fmgr.h | 6 | ||||
-rw-r--r-- | src/include/funcapi.h | 55 | ||||
-rw-r--r-- | src/include/utils/lsyscache.h | 12 | ||||
-rw-r--r-- | src/test/regress/expected/rangefuncs.out | 131 | ||||
-rw-r--r-- | src/test/regress/output/create_function_2.source | 2 | ||||
-rw-r--r-- | src/test/regress/sql/rangefuncs.sql | 62 |
21 files changed, 1332 insertions, 519 deletions
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 0d1d402210..fac8551dfd 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.109 2005/03/07 04:42:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.110 2005/03/31 22:46:04 tgl Exp $ * * NOTES * some of the executor utility code such as "ExecTypeFromTL" should be @@ -19,16 +19,11 @@ #include "postgres.h" -#include "funcapi.h" #include "access/heapam.h" -#include "catalog/namespace.h" #include "catalog/pg_type.h" -#include "nodes/parsenodes.h" #include "parser/parse_type.h" #include "utils/builtins.h" -#include "utils/lsyscache.h" #include "utils/syscache.h" -#include "utils/typcache.h" /* @@ -548,122 +543,3 @@ BuildDescForRelation(List *schema) 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; -} diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c index 246c3a0188..4428bc7ecb 100644 --- a/src/backend/catalog/pg_aggregate.c +++ b/src/backend/catalog/pg_aggregate.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.71 2005/03/29 03:01:30 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.72 2005/03/31 22:46:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -180,7 +180,7 @@ AggregateCreate(const char *aggName, false, /* doesn't return a set */ finaltype, /* returnType */ INTERNALlanguageId, /* languageObjectId */ - 0, + InvalidOid, /* no validator */ "aggregate_dummy", /* placeholder proc */ "-", /* probin */ true, /* isAgg */ @@ -189,9 +189,10 @@ AggregateCreate(const char *aggName, false, /* isStrict (not needed for agg) */ PROVOLATILE_IMMUTABLE, /* volatility (not * needed for agg) */ - 1, /* parameterCount */ - fnArgs, /* parameterTypes */ - NULL); /* parameterNames */ + buildoidvector(fnArgs, 1), /* paramTypes */ + PointerGetDatum(NULL), /* allParamTypes */ + PointerGetDatum(NULL), /* parameterModes */ + PointerGetDatum(NULL)); /* parameterNames */ /* * Okay to create the pg_aggregate entry. diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index a991ce901c..b56eb54200 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.125 2005/03/29 19:44:23 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.126 2005/03/31 22:46:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,6 +21,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/functions.h" +#include "funcapi.h" #include "miscadmin.h" #include "mb/pg_wchar.h" #include "parser/parse_type.h" @@ -40,8 +41,6 @@ Datum fmgr_internal_validator(PG_FUNCTION_ARGS); Datum fmgr_c_validator(PG_FUNCTION_ARGS); Datum fmgr_sql_validator(PG_FUNCTION_ARGS); -static Datum create_parameternames_array(int parameterCount, - const char *parameterNames[]); static void sql_function_parse_error_callback(void *arg); static int match_prosrc_to_query(const char *prosrc, const char *queryText, int cursorpos); @@ -51,6 +50,10 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal, /* ---------------------------------------------------------------- * ProcedureCreate + * + * Note: allParameterTypes, parameterModes, parameterNames are either arrays + * of the proper types or NULL. We declare them Datum, not "ArrayType *", + * to avoid importing array.h into pg_proc.h. * ---------------------------------------------------------------- */ Oid @@ -67,26 +70,29 @@ ProcedureCreate(const char *procedureName, bool security_definer, bool isStrict, char volatility, - int parameterCount, - const Oid *parameterTypes, - const char *parameterNames[]) + oidvector *parameterTypes, + Datum allParameterTypes, + Datum parameterModes, + Datum parameterNames) { - int i; + Oid retval; + int parameterCount; + int allParamCount; + Oid *allParams; + bool genericInParam = false; Relation rel; HeapTuple tup; HeapTuple oldtup; char nulls[Natts_pg_proc]; Datum values[Natts_pg_proc]; char replaces[Natts_pg_proc]; - oidvector *proargtypes; - Datum namesarray; Oid relid; NameData procname; TupleDesc tupDesc; - Oid retval; bool is_update; ObjectAddress myself, referenced; + int i; /* * sanity checks @@ -94,55 +100,88 @@ ProcedureCreate(const char *procedureName, Assert(PointerIsValid(prosrc)); Assert(PointerIsValid(probin)); + parameterCount = parameterTypes->dim1; if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg("functions cannot have more than %d arguments", FUNC_MAX_ARGS))); + /* note: the above is correct, we do NOT count output arguments */ + + if (allParameterTypes != PointerGetDatum(NULL)) + { + /* + * We expect the array to be a 1-D OID array; verify that. We + * don't need to use deconstruct_array() since the array data is + * just going to look like a C array of OID values. + */ + allParamCount = ARR_DIMS(DatumGetPointer(allParameterTypes))[0]; + if (ARR_NDIM(DatumGetPointer(allParameterTypes)) != 1 || + allParamCount <= 0 || + ARR_ELEMTYPE(DatumGetPointer(allParameterTypes)) != OIDOID) + elog(ERROR, "allParameterTypes is not a 1-D Oid array"); + allParams = (Oid *) ARR_DATA_PTR(DatumGetPointer(allParameterTypes)); + Assert(allParamCount >= parameterCount); + /* we assume caller got the contents right */ + } + else + { + allParamCount = parameterCount; + allParams = parameterTypes->values; + } /* * Do not allow return type ANYARRAY or ANYELEMENT unless at least one - * argument is also ANYARRAY or ANYELEMENT + * input argument is also ANYARRAY or ANYELEMENT */ - if (returnType == ANYARRAYOID || returnType == ANYELEMENTOID) + for (i = 0; i < parameterCount; i++) { - bool genericParam = false; + if (parameterTypes->values[i] == ANYARRAYOID || + parameterTypes->values[i] == ANYELEMENTOID) + { + genericInParam = true; + break; + } + } - for (i = 0; i < parameterCount; i++) + if (!genericInParam) + { + bool genericOutParam = false; + + if (allParameterTypes != PointerGetDatum(NULL)) { - if (parameterTypes[i] == ANYARRAYOID || - parameterTypes[i] == ANYELEMENTOID) + for (i = 0; i < allParamCount; i++) { - genericParam = true; - break; + if (allParams[i] == ANYARRAYOID || + allParams[i] == ANYELEMENTOID) + { + genericOutParam = true; + break; + } } } - if (!genericParam) + if (returnType == ANYARRAYOID || returnType == ANYELEMENTOID || + genericOutParam) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("cannot determine result data type"), errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type."))); } - /* Convert param types to oidvector */ - /* (Probably we should make caller pass it this way to start with) */ - proargtypes = buildoidvector(parameterTypes, parameterCount); - - /* Process param names, if given */ - namesarray = create_parameternames_array(parameterCount, parameterNames); - /* * don't allow functions of complex types that have the same name as * existing attributes of the type */ - if (parameterCount == 1 && OidIsValid(parameterTypes[0]) && - (relid = typeidTypeRelid(parameterTypes[0])) != InvalidOid && + if (parameterCount == 1 && + OidIsValid(parameterTypes->values[0]) && + (relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid && get_attnum(relid, procedureName) != InvalidAttrNumber) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_COLUMN), errmsg("\"%s\" is already an attribute of type %s", - procedureName, format_type_be(parameterTypes[0])))); + procedureName, + format_type_be(parameterTypes->values[0])))); /* * All seems OK; prepare the data to be inserted into pg_proc. @@ -167,12 +206,17 @@ ProcedureCreate(const char *procedureName, values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility); values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount); values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType); - values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(proargtypes); - /* XXX for now, just null out the new columns */ - nulls[Anum_pg_proc_proallargtypes - 1] = 'n'; - nulls[Anum_pg_proc_proargmodes - 1] = 'n'; - if (namesarray != PointerGetDatum(NULL)) - values[Anum_pg_proc_proargnames - 1] = namesarray; + values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes); + if (allParameterTypes != PointerGetDatum(NULL)) + values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes; + else + nulls[Anum_pg_proc_proallargtypes - 1] = 'n'; + if (parameterModes != PointerGetDatum(NULL)) + values[Anum_pg_proc_proargmodes - 1] = parameterModes; + else + nulls[Anum_pg_proc_proargmodes - 1] = 'n'; + if (parameterNames != PointerGetDatum(NULL)) + values[Anum_pg_proc_proargnames - 1] = parameterNames; else nulls[Anum_pg_proc_proargnames - 1] = 'n'; values[Anum_pg_proc_prosrc - 1] = DirectFunctionCall1(textin, @@ -188,7 +232,7 @@ ProcedureCreate(const char *procedureName, /* Check for pre-existing definition */ oldtup = SearchSysCache(PROCNAMEARGSNSP, PointerGetDatum(procedureName), - PointerGetDatum(proargtypes), + PointerGetDatum(parameterTypes), ObjectIdGetDatum(procNamespace), 0); @@ -214,9 +258,33 @@ ProcedureCreate(const char *procedureName, returnsSet != oldproc->proretset) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("cannot change return type of existing function"), + errmsg("cannot change return type of existing function"), errhint("Use DROP FUNCTION first."))); + /* + * If it returns RECORD, check for possible change of record type + * implied by OUT parameters + */ + if (returnType == RECORDOID) + { + TupleDesc olddesc; + TupleDesc newdesc; + + olddesc = build_function_result_tupdesc_t(oldtup); + newdesc = build_function_result_tupdesc_d(allParameterTypes, + parameterModes, + parameterNames); + if (olddesc == NULL && newdesc == NULL) + /* ok, both are runtime-defined RECORDs */ ; + else if (olddesc == NULL || newdesc == NULL || + !equalTupleDescs(olddesc, newdesc)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("cannot change return type of existing function"), + errdetail("Row type defined by OUT parameters is different."), + errhint("Use DROP FUNCTION first."))); + } + /* Can't change aggregate status, either */ if (oldproc->proisagg != isAgg) { @@ -285,11 +353,11 @@ ProcedureCreate(const char *procedureName, referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); - /* dependency on input types */ - for (i = 0; i < parameterCount; i++) + /* dependency on parameter types */ + for (i = 0; i < allParamCount; i++) { referenced.classId = RelOid_pg_type; - referenced.objectId = parameterTypes[i]; + referenced.objectId = allParams[i]; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } @@ -310,42 +378,6 @@ ProcedureCreate(const char *procedureName, } -/* - * create_parameternames_array - build proargnames value from an array - * of C strings. Returns a NULL pointer if no names provided. - */ -static Datum -create_parameternames_array(int parameterCount, const char *parameterNames[]) -{ - Datum elems[FUNC_MAX_ARGS]; - bool found = false; - ArrayType *names; - int i; - - if (!parameterNames) - return PointerGetDatum(NULL); - - for (i = 0; i < parameterCount; i++) - { - const char *s = parameterNames[i]; - - if (s && *s) - found = true; - else - s = ""; - - elems[i] = DirectFunctionCall1(textin, CStringGetDatum(s)); - } - - if (!found) - return PointerGetDatum(NULL); - - names = construct_array(elems, parameterCount, TEXTOID, -1, false, 'i'); - - return PointerGetDatum(names); -} - - /* * Validator for internal functions @@ -461,7 +493,6 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) Datum tmp; char *prosrc; ErrorContextCallback sqlerrcontext; - char functyptype; bool haspolyarg; int i; @@ -472,11 +503,9 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) elog(ERROR, "cache lookup failed for function %u", funcoid); proc = (Form_pg_proc) GETSTRUCT(tuple); - functyptype = get_typtype(proc->prorettype); - /* Disallow pseudotype result */ /* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */ - if (functyptype == 'p' && + if (get_typtype(proc->prorettype) == 'p' && proc->prorettype != RECORDOID && proc->prorettype != VOIDOID && proc->prorettype != ANYARRAYOID && @@ -535,7 +564,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS) querytree_list = pg_parse_and_rewrite(prosrc, proc->proargtypes.values, proc->pronargs); - (void) check_sql_fn_retval(proc->prorettype, functyptype, + (void) check_sql_fn_retval(funcoid, proc->prorettype, querytree_list, NULL); } else diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index c2c521bbfe..5776738045 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.58 2005/03/29 17:58:49 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.59 2005/03/31 22:46:07 tgl Exp $ * * DESCRIPTION * These routines take the parse tree and pick out the @@ -55,7 +55,7 @@ /* - * Examine the "returns" clause returnType of the CREATE FUNCTION statement + * Examine the RETURNS clause of the CREATE FUNCTION statement * and return information about it as *prorettype_p and *returnsSet. * * This is more complex than the average typename lookup because we want to @@ -131,38 +131,44 @@ compute_return_type(TypeName *returnType, Oid languageOid, /* * Interpret the parameter list of the CREATE FUNCTION statement. + * + * Results are stored into output parameters. parameterTypes must always + * be created, but the other arrays are set to NULL if not needed. + * requiredResultType is set to InvalidOid if there are no OUT parameters, + * else it is set to the OID of the implied result type. */ -static int -examine_parameter_list(List *parameter, Oid languageOid, - Oid *parameterTypes, const char *parameterNames[]) +static void +examine_parameter_list(List *parameters, Oid languageOid, + oidvector **parameterTypes, + ArrayType **allParameterTypes, + ArrayType **parameterModes, + ArrayType **parameterNames, + Oid *requiredResultType) { - int parameterCount = 0; + int parameterCount = list_length(parameters); + Oid *inTypes; + int inCount = 0; + Datum *allTypes; + Datum *paramModes; + Datum *paramNames; + int outCount = 0; + bool have_names = false; ListCell *x; + int i; - MemSet(parameterTypes, 0, FUNC_MAX_ARGS * sizeof(Oid)); - MemSet(parameterNames, 0, FUNC_MAX_ARGS * sizeof(char *)); + inTypes = (Oid *) palloc(parameterCount * sizeof(Oid)); + allTypes = (Datum *) palloc(parameterCount * sizeof(Datum)); + paramModes = (Datum *) palloc(parameterCount * sizeof(Datum)); + paramNames = (Datum *) palloc0(parameterCount * sizeof(Datum)); - foreach(x, parameter) + /* Scan the list and extract data into work arrays */ + i = 0; + foreach(x, parameters) { FunctionParameter *fp = (FunctionParameter *) lfirst(x); TypeName *t = fp->argType; Oid toid; - if (parameterCount >= FUNC_MAX_ARGS) - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg("functions cannot have more than %d arguments", - FUNC_MAX_ARGS))); - - if (fp->mode == FUNC_PARAM_OUT) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("CREATE FUNCTION / OUT parameters are not implemented"))); - if (fp->mode == FUNC_PARAM_INOUT) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("CREATE FUNCTION / INOUT parameters are not implemented"))); - toid = LookupTypeName(t); if (OidIsValid(toid)) { @@ -194,16 +200,66 @@ examine_parameter_list(List *parameter, Oid languageOid, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("functions cannot accept set arguments"))); - parameterTypes[parameterCount] = toid; + if (fp->mode != FUNC_PARAM_OUT) + inTypes[inCount++] = toid; + + if (fp->mode != FUNC_PARAM_IN) + { + if (outCount == 0) /* save first OUT param's type */ + *requiredResultType = toid; + outCount++; + } + + allTypes[i] = ObjectIdGetDatum(toid); - parameterNames[parameterCount] = fp->name; + paramModes[i] = CharGetDatum(fp->mode); - parameterCount++; + if (fp->name && fp->name[0]) + { + paramNames[i] = DirectFunctionCall1(textin, + CStringGetDatum(fp->name)); + have_names = true; + } + + i++; } - return parameterCount; + /* Now construct the proper outputs as needed */ + *parameterTypes = buildoidvector(inTypes, inCount); + + if (outCount > 0) + { + *allParameterTypes = construct_array(allTypes, parameterCount, OIDOID, + sizeof(Oid), true, 'i'); + *parameterModes = construct_array(paramModes, parameterCount, CHAROID, + 1, true, 'c'); + if (outCount > 1) + *requiredResultType = RECORDOID; + /* otherwise we set requiredResultType correctly above */ + } + else + { + *allParameterTypes = NULL; + *parameterModes = NULL; + *requiredResultType = InvalidOid; + } + + if (have_names) + { + for (i = 0; i < parameterCount; i++) + { + if (paramNames[i] == PointerGetDatum(NULL)) + paramNames[i] = DirectFunctionCall1(textin, + CStringGetDatum("")); + } + *parameterNames = construct_array(paramNames, parameterCount, TEXTOID, + -1, false, 'i'); + } + else + *parameterNames = NULL; } + /* * Recognize one of the options that can be passed to both CREATE * FUNCTION and ALTER FUNCTION and return it via one of the out @@ -321,6 +377,7 @@ compute_attributes_sql_style(List *options, defel->defname); } + /* process required items */ if (as_item) *as = (List *) as_item->arg; else @@ -335,6 +392,7 @@ compute_attributes_sql_style(List *options, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("no language specified"))); + /* process optional items */ if (volatility_item) *volatility_p = interpret_func_volatility(volatility_item); if (strict_item) @@ -445,9 +503,11 @@ CreateFunction(CreateFunctionStmt *stmt) char *funcname; Oid namespaceId; AclResult aclresult; - int parameterCount; - Oid parameterTypes[FUNC_MAX_ARGS]; - const char *parameterNames[FUNC_MAX_ARGS]; + oidvector *parameterTypes; + ArrayType *allParameterTypes; + ArrayType *parameterModes; + ArrayType *parameterNames; + Oid requiredResultType; bool isStrict, security; char volatility; @@ -465,7 +525,7 @@ CreateFunction(CreateFunctionStmt *stmt) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceId)); - /* defaults attributes */ + /* default attributes */ isStrict = false; security = false; volatility = PROVOLATILE_VOLATILE; @@ -523,11 +583,39 @@ CreateFunction(CreateFunctionStmt *stmt) * Convert remaining parameters of CREATE to form wanted by * ProcedureCreate. */ - compute_return_type(stmt->returnType, languageOid, - &prorettype, &returnsSet); - - parameterCount = examine_parameter_list(stmt->parameters, languageOid, - parameterTypes, parameterNames); + examine_parameter_list(stmt->parameters, languageOid, + ¶meterTypes, + &allParameterTypes, + ¶meterModes, + ¶meterNames, + &requiredResultType); + + if (stmt->returnType) + { + /* explicit RETURNS clause */ + compute_return_type(stmt->returnType, languageOid, + &prorettype, &returnsSet); + if (OidIsValid(requiredResultType) && prorettype != requiredResultType) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("function result type must be %s because of OUT parameters", + format_type_be(requiredResultType)))); + } + else if (OidIsValid(requiredResultType)) + { + /* default RETURNS clause from OUT parameters */ + prorettype = requiredResultType; + returnsSet = false; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("function result type must be specified"))); + /* Alternative possibility: default to RETURNS VOID */ + prorettype = VOIDOID; + returnsSet = false; + } compute_attributes_with_style(stmt->withClause, &isStrict, &volatility); @@ -572,9 +660,10 @@ CreateFunction(CreateFunctionStmt *stmt) security, isStrict, volatility, - parameterCount, parameterTypes, - parameterNames); + PointerGetDatum(allParameterTypes), + PointerGetDatum(parameterModes), + PointerGetDatum(parameterNames)); } diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 2ddc614cf7..d2e101a2d6 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.94 2005/03/29 00:16:59 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.95 2005/03/31 22:46:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,6 +20,7 @@ #include "commands/trigger.h" #include "executor/executor.h" #include "executor/functions.h" +#include "funcapi.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_type.h" @@ -277,8 +278,8 @@ init_sql_fcache(FmgrInfo *finfo) * form. */ if (haspolyarg || fcache->returnsTuple) - fcache->returnsTuple = check_sql_fn_retval(rettype, - get_typtype(rettype), + fcache->returnsTuple = check_sql_fn_retval(foid, + rettype, queryTree_list, &fcache->junkFilter); @@ -858,7 +859,7 @@ ShutdownSQLFunction(Datum arg) * tuple result), *junkFilter is set to NULL. */ bool -check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList, +check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, JunkFilter **junkFilter) { Query *parse; @@ -866,12 +867,8 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList, List *tlist; ListCell *tlistitem; int tlistlen; - Oid typerelid; + char fn_typtype; Oid restype; - Relation reln; - int relnatts; /* physical number of columns in rel */ - int rellogcols; /* # of nondeleted columns in rel */ - int colindex; /* physical column index */ if (junkFilter) *junkFilter = NULL; /* default result */ @@ -922,13 +919,10 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList, */ tlistlen = ExecCleanTargetListLength(tlist); - typerelid = typeidTypeRelid(rettype); + fn_typtype = get_typtype(rettype); if (fn_typtype == 'b' || fn_typtype == 'd') { - /* Shouldn't have a typerelid */ - Assert(typerelid == InvalidOid); - /* * For base-type returns, the target list should have exactly one * entry, and its type should agree with what the user declared. @@ -950,10 +944,13 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList, errdetail("Actual return type is %s.", format_type_be(restype)))); } - else if (fn_typtype == 'c') + else if (fn_typtype == 'c' || rettype == RECORDOID) { - /* Must have a typerelid */ - Assert(typerelid != InvalidOid); + /* Returns a rowtype */ + TupleDesc tupdesc; + int tupnatts; /* physical number of columns in tuple */ + int tuplogcols; /* # of nondeleted columns in tuple */ + int colindex; /* physical column index */ /* * If the target list is of length 1, and the type of the varnode @@ -969,16 +966,27 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList, return false; /* NOT returning whole tuple */ } + /* Is the rowtype fixed, or determined only at runtime? */ + if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + { + /* + * Assume we are returning the whole tuple. + * Crosschecking against what the caller expects will happen at + * runtime. + */ + if (junkFilter) + *junkFilter = ExecInitJunkFilter(tlist, false, NULL); + return true; + } + Assert(tupdesc); + /* - * Otherwise verify that the targetlist matches the return tuple - * type. This part of the typechecking is a hack. We look up the - * relation that is the declared return type, and scan the - * non-deleted attributes to ensure that they match the datatypes - * of the non-resjunk columns. + * Verify that the targetlist matches the return tuple type. + * We scan the non-deleted attributes to ensure that they match the + * datatypes of the non-resjunk columns. */ - reln = relation_open(typerelid, AccessShareLock); - relnatts = reln->rd_rel->relnatts; - rellogcols = 0; /* we'll count nondeleted cols as we go */ + tupnatts = tupdesc->natts; + tuplogcols = 0; /* we'll count nondeleted cols as we go */ colindex = 0; foreach(tlistitem, tlist) @@ -994,15 +1002,15 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList, do { colindex++; - if (colindex > relnatts) + if (colindex > tupnatts) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", format_type_be(rettype)), errdetail("Final SELECT returns too many columns."))); - attr = reln->rd_att->attrs[colindex - 1]; + attr = tupdesc->attrs[colindex - 1]; } while (attr->attisdropped); - rellogcols++; + tuplogcols++; tletype = exprType((Node *) tle->expr); atttype = attr->atttypid; @@ -1014,19 +1022,19 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList, errdetail("Final SELECT returns %s instead of %s at column %d.", format_type_be(tletype), format_type_be(atttype), - rellogcols))); + tuplogcols))); } for (;;) { colindex++; - if (colindex > relnatts) + if (colindex > tupnatts) break; - if (!reln->rd_att->attrs[colindex - 1]->attisdropped) - rellogcols++; + if (!tupdesc->attrs[colindex - 1]->attisdropped) + tuplogcols++; } - if (tlistlen != rellogcols) + if (tlistlen != tuplogcols) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("return type mismatch in function declared to return %s", @@ -1036,40 +1044,12 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList, /* Set up junk filter if needed */ if (junkFilter) *junkFilter = ExecInitJunkFilterConversion(tlist, - CreateTupleDescCopy(reln->rd_att), + CreateTupleDescCopy(tupdesc), NULL); - relation_close(reln, AccessShareLock); - /* Report that we are returning entire tuple result */ return true; } - else if (rettype == RECORDOID) - { - /* - * If the target list is of length 1, and the type of the varnode - * in the target list matches the declared return type, this is - * okay. This can happen, for example, where the body of the - * function is 'SELECT func2()', where func2 has the same return - * type as the function that's calling it. - */ - if (tlistlen == 1) - { - restype = ((TargetEntry *) linitial(tlist))->resdom->restype; - if (IsBinaryCoercible(restype, rettype)) - return false; /* NOT returning whole tuple */ - } - - /* - * Otherwise assume we are returning the whole tuple. - * Crosschecking against what the caller expects will happen at - * runtime. - */ - if (junkFilter) - *junkFilter = ExecInitJunkFilter(tlist, false, NULL); - - return true; - } else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID) { /* This should already have been caught ... */ diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index edf5c67635..4cbbb5a65f 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.31 2005/03/16 21:38:07 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.32 2005/03/31 22:46:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -22,18 +22,10 @@ */ #include "postgres.h" -#include "access/heapam.h" -#include "catalog/pg_type.h" -#include "executor/execdebug.h" -#include "executor/execdefs.h" -#include "executor/execdesc.h" #include "executor/nodeFunctionscan.h" +#include "funcapi.h" #include "parser/parsetree.h" -#include "parser/parse_expr.h" -#include "parser/parse_type.h" #include "utils/builtins.h" -#include "utils/lsyscache.h" -#include "utils/typcache.h" static TupleTableSlot *FunctionNext(FunctionScanState *node); @@ -180,18 +172,21 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate) */ rte = rt_fetch(node->scan.scanrelid, estate->es_range_table); Assert(rte->rtekind == RTE_FUNCTION); - funcrettype = exprType(rte->funcexpr); /* * Now determine if the function returns a simple or composite type, * and build an appropriate tupdesc. */ - functypclass = get_type_func_class(funcrettype); + functypclass = get_expr_result_type(rte->funcexpr, + &funcrettype, + &tupdesc); if (functypclass == TYPEFUNC_COMPOSITE) { /* Composite data type, e.g. a table's row type */ - tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(funcrettype, -1)); + Assert(tupdesc); + /* Must copy it out of typcache for safety */ + tupdesc = CreateTupleDescCopy(tupdesc); } else if (functypclass == TYPEFUNC_SCALAR) { @@ -216,14 +211,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate) elog(ERROR, "function in FROM has unsupported return type"); } - /* - * For RECORD results, make sure a typmod has been assigned. (The - * function should do this for itself, but let's cover things in case - * it doesn't.) - */ - if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0) - assign_record_type_typmod(tupdesc); - scanstate->tupdesc = tupdesc; ExecSetSlotDescriptor(scanstate->ss.ss_ScanTupleSlot, tupdesc, false); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 76255de53d..2cf4fcd663 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.191 2005/03/29 00:17:02 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.192 2005/03/31 22:46:09 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -2319,8 +2319,7 @@ inline_function(Oid funcid, Oid result_type, List *args, * probably not important, but let's be careful.) */ if (polymorphic) - (void) check_sql_fn_retval(result_type, get_typtype(result_type), - querytree_list, NULL); + (void) check_sql_fn_retval(funcid, result_type, querytree_list, NULL); /* * Additional validity checks on the expression. It mustn't return a diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 6249795726..6bbf4a4de1 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.485 2005/03/29 17:58:50 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.486 2005/03/31 22:46:11 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -2544,7 +2544,7 @@ def_elem: ColLabel '=' def_arg ; /* Note: any simple identifier will be returned as a type name! */ -def_arg: func_return { $$ = (Node *)$1; } +def_arg: func_type { $$ = (Node *)$1; } | qual_all_Op { $$ = (Node *)$1; } | NumericOnly { $$ = (Node *)$1; } | Sconst { $$ = (Node *)makeString($1); } @@ -3282,6 +3282,18 @@ CreateFunctionStmt: n->withClause = $9; $$ = (Node *)n; } + | CREATE opt_or_replace FUNCTION func_name func_args + createfunc_opt_list opt_definition + { + CreateFunctionStmt *n = makeNode(CreateFunctionStmt); + n->replace = $2; + n->funcname = $4; + n->parameters = $5; + n->returnType = NULL; + n->options = $6; + n->withClause = $7; + $$ = (Node *)n; + } ; opt_or_replace: @@ -3367,7 +3379,7 @@ param_name: function_name func_return: func_type { - /* We can catch over-specified arguments here if we want to, + /* We can catch over-specified results here if we want to, * but for now better to silently swallow typmod, etc. * - thomas 2000-03-22 */ @@ -3424,7 +3436,6 @@ common_func_opt_item: { $$ = makeDefElem("volatility", (Node *)makeString("volatile")); } - | EXTERNAL SECURITY DEFINER { $$ = makeDefElem("security", (Node *)makeInteger(TRUE)); diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 07d52ef544..64a325f10b 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.176 2005/03/29 03:01:31 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.177 2005/03/31 22:46:13 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,6 +18,7 @@ #include "catalog/catname.h" #include "catalog/pg_inherits.h" #include "catalog/pg_proc.h" +#include "funcapi.h" #include "lib/stringinfo.h" #include "nodes/makefuncs.h" #include "parser/parse_agg.h" @@ -1154,10 +1155,8 @@ make_fn_arguments(ParseState *pstate, static Node * ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg) { - Oid argtype; - Oid argrelid; - AttrNumber attnum; - FieldSelect *fselect; + TupleDesc tupdesc; + int i; /* * Special case for whole-row Vars so that we can resolve (foo.*).bar @@ -1180,27 +1179,31 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg) /* * Else do it the hard way. Note that if the arg is of RECORD type, - * we will never recognize a column name, and always assume the item - * must be a function. + * and isn't resolvable as a function with OUT params, we will never + * be able to recognize a column name here. */ - argtype = exprType(first_arg); - argrelid = typeidTypeRelid(argtype); - if (!argrelid) - return NULL; /* can only happen if RECORD */ - - attnum = get_attnum(argrelid, funcname); - if (attnum == InvalidAttrNumber) - return NULL; /* funcname does not match any column */ - - /* Success, so generate a FieldSelect expression */ - fselect = makeNode(FieldSelect); - fselect->arg = (Expr *) first_arg; - fselect->fieldnum = attnum; - get_atttypetypmod(argrelid, attnum, - &fselect->resulttype, - &fselect->resulttypmod); - - return (Node *) fselect; + if (get_expr_result_type(first_arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + return NULL; /* unresolvable RECORD type */ + + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute att = tupdesc->attrs[i]; + + if (strcmp(funcname, NameStr(att->attname)) == 0 && + !att->attisdropped) + { + /* Success, so generate a FieldSelect expression */ + FieldSelect *fselect = makeNode(FieldSelect); + + fselect->arg = (Expr *) first_arg; + fselect->fieldnum = i + 1; + fselect->resulttype = att->atttypid; + fselect->resulttypmod = att->atttypmod; + return (Node *) fselect; + } + } + + return NULL; /* funcname does not match any column */ } /* diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 6360e402f8..6e391f4eb8 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.102 2004/12/31 22:00:27 pgsql Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.103 2005/03/31 22:46:13 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,21 +17,20 @@ #include <ctype.h> #include "access/heapam.h" -#include "access/htup.h" #include "catalog/heap.h" #include "catalog/namespace.h" #include "catalog/pg_type.h" +#include "funcapi.h" #include "nodes/makefuncs.h" #include "parser/parsetree.h" -#include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" #include "parser/parse_type.h" -#include "rewrite/rewriteManip.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" + /* GUC parameter */ bool add_missing_from; @@ -46,6 +45,10 @@ static void expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, bool include_dropped, List **colnames, List **colvars); +static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, + int rtindex, int sublevels_up, + bool include_dropped, + List **colnames, List **colvars); static int specialAttNum(const char *attname); static void warnAutoRange(ParseState *pstate, RangeVar *relation); @@ -965,8 +968,9 @@ addRangeTableEntryForFunction(ParseState *pstate, bool inFromCl) { RangeTblEntry *rte = makeNode(RangeTblEntry); - Oid funcrettype = exprType(funcexpr); TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; Alias *alias = rangefunc->alias; List *coldeflist = rangefunc->coldeflist; Alias *eref; @@ -982,58 +986,37 @@ addRangeTableEntryForFunction(ParseState *pstate, rte->eref = eref; /* - * Now determine if the function returns a simple or composite type, - * and check/add column aliases. + * Now determine if the function returns a simple or composite type. + */ + functypclass = get_expr_result_type(funcexpr, + &funcrettype, + &tupdesc); + + /* + * A coldeflist is required if the function returns RECORD and hasn't + * got a predetermined record type, and is prohibited otherwise. */ if (coldeflist != NIL) { - /* - * we *only* allow a coldeflist for functions returning a RECORD - * pseudo-type - */ - if (funcrettype != RECORDOID) + if (functypclass != TYPEFUNC_RECORD) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("a column definition list is only allowed for functions returning \"record\""))); } else { - /* - * ... and a coldeflist is *required* for functions returning a - * RECORD pseudo-type - */ - if (funcrettype == RECORDOID) + if (functypclass == TYPEFUNC_RECORD) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("a column definition list is required for functions returning \"record\""))); } - functypclass = get_type_func_class(funcrettype); - if (functypclass == TYPEFUNC_COMPOSITE) { /* Composite data type, e.g. a table's row type */ - Oid funcrelid = typeidTypeRelid(funcrettype); - Relation rel; - - if (!OidIsValid(funcrelid)) /* shouldn't happen */ - elog(ERROR, "invalid typrelid for complex type %u", funcrettype); - - /* - * Get the rel's relcache entry. This access ensures that we have - * an up-to-date relcache entry for the rel. - */ - rel = relation_open(funcrelid, AccessShareLock); - + Assert(tupdesc); /* Build the column alias list */ - buildRelationAliases(rel->rd_att, alias, eref); - - /* - * Drop the rel refcount, but keep the access lock till end of - * transaction so that the table can't be deleted or have its - * schema modified underneath us. - */ - relation_close(rel, NoLock); + buildRelationAliases(tupdesc, alias, eref); } else if (functypclass == TYPEFUNC_SCALAR) { @@ -1308,24 +1291,19 @@ expandRTE(List *rtable, int rtindex, int sublevels_up, case RTE_FUNCTION: { /* Function RTE */ - Oid funcrettype = exprType(rte->funcexpr); - TypeFuncClass functypclass = get_type_func_class(funcrettype); + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + functypclass = get_expr_result_type(rte->funcexpr, + &funcrettype, + &tupdesc); if (functypclass == TYPEFUNC_COMPOSITE) { - /* - * Composite data type, i.e. a table's row type - * - * Same as ordinary relation RTE - */ - Oid funcrelid = typeidTypeRelid(funcrettype); - - if (!OidIsValid(funcrelid)) /* shouldn't happen */ - elog(ERROR, "invalid typrelid for complex type %u", - funcrettype); - - expandRelation(funcrelid, rte->eref, rtindex, sublevels_up, - include_dropped, colnames, colvars); + /* Composite data type, e.g. a table's row type */ + Assert(tupdesc); + expandTupleDesc(tupdesc, rte->eref, rtindex, sublevels_up, + include_dropped, colnames, colvars); } else if (functypclass == TYPEFUNC_SCALAR) { @@ -1467,17 +1445,30 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, List **colnames, List **colvars) { Relation rel; - int varattno; - int maxattrs; - int numaliases; + /* Get the tupledesc and turn it over to expandTupleDesc */ rel = relation_open(relid, AccessShareLock); - maxattrs = RelationGetNumberOfAttributes(rel); - numaliases = list_length(eref->colnames); + expandTupleDesc(rel->rd_att, eref, rtindex, sublevels_up, include_dropped, + colnames, colvars); + relation_close(rel, AccessShareLock); +} + +/* + * expandTupleDesc -- expandRTE subroutine + */ +static void +expandTupleDesc(TupleDesc tupdesc, Alias *eref, + int rtindex, int sublevels_up, + bool include_dropped, + List **colnames, List **colvars) +{ + int maxattrs = tupdesc->natts; + int numaliases = list_length(eref->colnames); + int varattno; for (varattno = 0; varattno < maxattrs; varattno++) { - Form_pg_attribute attr = rel->rd_att->attrs[varattno]; + Form_pg_attribute attr = tupdesc->attrs[varattno]; if (attr->attisdropped) { @@ -1519,8 +1510,6 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up, *colvars = lappend(*colvars, varnode); } } - - relation_close(rel, AccessShareLock); } /* @@ -1662,33 +1651,29 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, case RTE_FUNCTION: { /* Function RTE */ - Oid funcrettype = exprType(rte->funcexpr); - TypeFuncClass functypclass = get_type_func_class(funcrettype); - List *coldeflist = rte->coldeflist; + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + + functypclass = get_expr_result_type(rte->funcexpr, + &funcrettype, + &tupdesc); if (functypclass == TYPEFUNC_COMPOSITE) { - /* - * Composite data type, i.e. a table's row type - * - * Same as ordinary relation RTE - */ - Oid funcrelid = typeidTypeRelid(funcrettype); - HeapTuple tp; + /* Composite data type, e.g. a table's row type */ Form_pg_attribute att_tup; - if (!OidIsValid(funcrelid)) /* shouldn't happen */ - elog(ERROR, "invalid typrelid for complex type %u", - funcrettype); + Assert(tupdesc); + /* this is probably a can't-happen case */ + if (attnum < 1 || attnum > tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %d of relation \"%s\" does not exist", + attnum, + rte->eref->aliasname))); - tp = SearchSysCache(ATTNUM, - ObjectIdGetDatum(funcrelid), - Int16GetDatum(attnum), - 0, 0); - if (!HeapTupleIsValid(tp)) /* shouldn't happen */ - elog(ERROR, "cache lookup failed for attribute %d of relation %u", - attnum, funcrelid); - att_tup = (Form_pg_attribute) GETSTRUCT(tp); + att_tup = tupdesc->attrs[attnum - 1]; /* * If dropped column, pretend it ain't there. See @@ -1699,10 +1684,9 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\" does not exist", NameStr(att_tup->attname), - get_rel_name(funcrelid)))); + rte->eref->aliasname))); *vartype = att_tup->atttypid; *vartypmod = att_tup->atttypmod; - ReleaseSysCache(tp); } else if (functypclass == TYPEFUNC_SCALAR) { @@ -1712,7 +1696,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, } else if (functypclass == TYPEFUNC_RECORD) { - ColumnDef *colDef = list_nth(coldeflist, attnum - 1); + ColumnDef *colDef = list_nth(rte->coldeflist, attnum - 1); *vartype = typenameTypeId(colDef->typename); *vartypmod = -1; diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 0b40a20b25..3abbf65fa4 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 a8bb5fc0ac..0e9716de01 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 2c3845b7bf..160847de1e 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; +} diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index d86ce3ef0e..cb90a5e04c 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.356 2005/03/29 19:44:23 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.357 2005/03/31 22:46:18 tgl Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -3668,9 +3668,10 @@ extern Oid ProcedureCreate(const char *procedureName, bool security_definer, bool isStrict, char volatility, - int parameterCount, - const Oid *parameterTypes, - const char *parameterNames[]); + oidvector *parameterTypes, + Datum allParameterTypes, + Datum parameterModes, + Datum parameterNames); extern bool function_parse_error_transpose(const char *prosrc); diff --git a/src/include/executor/functions.h b/src/include/executor/functions.h index fdc23b2812..88ca87fd86 100644 --- a/src/include/executor/functions.h +++ b/src/include/executor/functions.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.24 2004/12/31 22:03:29 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.25 2005/03/31 22:46:22 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,7 +20,7 @@ extern Datum fmgr_sql(PG_FUNCTION_ARGS); -extern bool check_sql_fn_retval(Oid rettype, char fn_typtype, +extern bool check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList, JunkFilter **junkFilter); diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 0c11130053..3dc53ccb16 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -11,13 +11,16 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/fmgr.h,v 1.37 2005/03/22 20:13:09 tgl Exp $ + * $PostgreSQL: pgsql/src/include/fmgr.h,v 1.38 2005/03/31 22:46:24 tgl Exp $ * *------------------------------------------------------------------------- */ #ifndef FMGR_H #define FMGR_H +/* We don't want to include primnodes.h here, so make a stub reference */ +struct Node; + /* * All functions that can be called directly by fmgr must have this signature. @@ -402,6 +405,7 @@ extern void clear_external_function_hash(void *filehandle); extern Oid fmgr_internal_function(const char *proname); extern Oid get_fn_expr_rettype(FmgrInfo *flinfo); extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum); +extern Oid get_call_expr_argtype(struct Node *expr, int argnum); /* * Routines in dfmgr.c diff --git a/src/include/funcapi.h b/src/include/funcapi.h index 00549897d5..c4d1809948 100644 --- a/src/include/funcapi.h +++ b/src/include/funcapi.h @@ -9,7 +9,7 @@ * * Copyright (c) 2002-2005, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.15 2005/01/01 05:43:08 momjian Exp $ + * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.16 2005/03/31 22:46:24 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -124,7 +124,57 @@ typedef struct FuncCallContext } FuncCallContext; /*---------- - * Support to ease writing Functions returning composite types + * Support to ease writing functions returning composite types + * + * External declarations: + * 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 or the rowtype could not be + * determined. NB: the tupledesc should be copied if it is to be + * accessed over a long period. + * get_expr_result_type: + * Given an expression node, return the same info as for + * get_call_result_type. Note: the cases in which rowtypes cannot be + * determined are different from the cases for get_call_result_type. + * get_func_result_type: + * Given only a function's OID, return the same info as for + * get_call_result_type. Note: the cases in which rowtypes cannot be + * determined are different from the cases for get_call_result_type. + * Do *not* use this if you can use one of the others. + *---------- + */ + +/* Type categories for get_call_result_type and siblings */ +typedef enum TypeFuncClass +{ + TYPEFUNC_SCALAR, /* scalar result type */ + TYPEFUNC_COMPOSITE, /* determinable rowtype result */ + TYPEFUNC_RECORD, /* indeterminate rowtype result */ + TYPEFUNC_OTHER /* bogus type, eg pseudotype */ +} TypeFuncClass; + +extern TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo, + Oid *resultTypeId, + TupleDesc *resultTupleDesc); +extern TypeFuncClass get_expr_result_type(Node *expr, + Oid *resultTypeId, + TupleDesc *resultTupleDesc); +extern TypeFuncClass get_func_result_type(Oid functionId, + Oid *resultTypeId, + TupleDesc *resultTupleDesc); + +extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes, + Datum proargmodes, + Datum proargnames); +extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple); + + +/*---------- + * Support to ease writing functions returning composite types * * External declarations: * TupleDesc RelationNameGetTupleDesc(const char *relname) - Use to get a @@ -160,7 +210,6 @@ typedef struct FuncCallContext /* obsolete version of above */ #define TupleGetDatum(_slot, _tuple) PointerGetDatum((_tuple)->t_data) -/* from tupdesc.c */ extern TupleDesc RelationNameGetTupleDesc(const char *relname); extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases); diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 1c5398a71b..845a886ba8 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.95 2005/03/29 00:17:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.96 2005/03/31 22:46:27 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,15 +24,6 @@ typedef enum IOFuncSelector IOFunc_send } IOFuncSelector; -/* Type categories for get_type_func_class */ -typedef enum TypeFuncClass -{ - TYPEFUNC_SCALAR, - TYPEFUNC_COMPOSITE, - TYPEFUNC_RECORD, - TYPEFUNC_OTHER -} TypeFuncClass; - extern bool op_in_opclass(Oid opno, Oid opclass); extern void get_op_opclass_properties(Oid opno, Oid opclass, int *strategy, Oid *subtype, @@ -94,7 +85,6 @@ extern char get_typstorage(Oid typid); extern int32 get_typtypmod(Oid typid); extern Node *get_typdefault(Oid typid); extern char get_typtype(Oid typid); -extern TypeFuncClass get_type_func_class(Oid typid); extern Oid get_typ_typrelid(Oid typid); extern Oid get_element_type(Oid typid); extern Oid get_array_type(Oid typid); diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 7acbbe9bc6..6caa7a1c7b 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -396,3 +396,134 @@ DROP FUNCTION foorescan(int,int); DROP FUNCTION foorescan(int); DROP TABLE foorescan; DROP TABLE barrescan; +-- +-- Test cases involving OUT parameters +-- +CREATE FUNCTION foo(in f1 int, out f2 int) +AS 'select $1+1' LANGUAGE sql; +SELECT foo(42); + foo +----- + 43 +(1 row) + +SELECT * FROM foo(42); + foo +----- + 43 +(1 row) + +SELECT * FROM foo(42) AS p(x); + x +---- + 43 +(1 row) + +-- explicit spec of return type is OK +CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS int +AS 'select $1+1' LANGUAGE sql; +-- error, wrong result type +CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS float +AS 'select $1+1' LANGUAGE sql; +ERROR: function result type must be integer because of OUT parameters +-- with multiple OUT params you must get a RECORD result +CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text) RETURNS int +AS 'select $1+1' LANGUAGE sql; +ERROR: function result type must be record because of OUT parameters +CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text) +RETURNS record +AS 'select $1+1' LANGUAGE sql; +ERROR: cannot change return type of existing function +HINT: Use DROP FUNCTION first. +CREATE OR REPLACE FUNCTION foor(in f1 int, out f2 int, out text) +AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql; +SELECT f1, foor(f1) FROM int4_tbl; + f1 | foor +-------------+---------------------------- + 0 | (-1,0z) + 123456 | (123455,123456z) + -123456 | (-123457,-123456z) + 2147483647 | (2147483646,2147483647z) + -2147483647 | (-2147483648,-2147483647z) +(5 rows) + +SELECT * FROM foor(42); + f2 | column2 +----+--------- + 41 | 42z +(1 row) + +SELECT * FROM foor(42) AS p(a,b); + a | b +----+----- + 41 | 42z +(1 row) + +CREATE OR REPLACE FUNCTION foob(in f1 int, inout f2 int, out text) +AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql; +SELECT f1, foob(f1, f1/2) FROM int4_tbl; + f1 | foob +-------------+---------------------------- + 0 | (-1,0z) + 123456 | (61727,123456z) + -123456 | (-61729,-123456z) + 2147483647 | (1073741822,2147483647z) + -2147483647 | (-1073741824,-2147483647z) +(5 rows) + +SELECT * FROM foob(42, 99); + f2 | column2 +----+--------- + 98 | 42z +(1 row) + +SELECT * FROM foob(42, 99) AS p(a,b); + a | b +----+----- + 98 | 42z +(1 row) + +-- Can reference function with or without OUT params for DROP, etc +DROP FUNCTION foo(int); +DROP FUNCTION foor(in f2 int, out f1 int, out text); +DROP FUNCTION foob(in f1 int, inout f2 int); +-- +-- For my next trick, polymorphic OUT parameters +-- +CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray) +AS 'select $1, array[$1,$1]' LANGUAGE sql; +SELECT dup(22); + dup +---------------- + (22,"{22,22}") +(1 row) + +SELECT dup('xyz'); -- fails +ERROR: could not determine anyarray/anyelement type because input has type "unknown" +SELECT dup('xyz'::text); + dup +------------------- + (xyz,"{xyz,xyz}") +(1 row) + +SELECT * FROM dup('xyz'::text); + f2 | f3 +-----+----------- + xyz | {xyz,xyz} +(1 row) + +-- equivalent specification +CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray) +AS 'select $1, array[$1,$1]' LANGUAGE sql; +SELECT dup(22); + dup +---------------- + (22,"{22,22}") +(1 row) + +DROP FUNCTION dup(anyelement); +-- fails, no way to deduce outputs +CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray) +AS 'select $1, array[$1,$1]' LANGUAGE sql; +ERROR: cannot determine result data type +DETAIL: A function returning "anyarray" or "anyelement" must have at least one argument of either type. diff --git a/src/test/regress/output/create_function_2.source b/src/test/regress/output/create_function_2.source index f724963d35..dd308261d9 100644 --- a/src/test/regress/output/create_function_2.source +++ b/src/test/regress/output/create_function_2.source @@ -13,8 +13,8 @@ CREATE FUNCTION hobbies_by_name(hobbies_r.name%TYPE) RETURNS hobbies_r.person%TYPE AS 'select person from hobbies_r where name = $1' LANGUAGE 'sql'; -NOTICE: type reference hobbies_r.person%TYPE converted to text NOTICE: type reference hobbies_r.name%TYPE converted to text +NOTICE: type reference hobbies_r.person%TYPE converted to text CREATE FUNCTION equipment(hobbies_r) RETURNS setof equipment_r AS 'select * from equipment_r where hobby = $1.name' diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index 2f1c8e7513..50495897ac 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -199,3 +199,65 @@ DROP FUNCTION foorescan(int,int); DROP FUNCTION foorescan(int); DROP TABLE foorescan; DROP TABLE barrescan; + +-- +-- Test cases involving OUT parameters +-- + +CREATE FUNCTION foo(in f1 int, out f2 int) +AS 'select $1+1' LANGUAGE sql; +SELECT foo(42); +SELECT * FROM foo(42); +SELECT * FROM foo(42) AS p(x); + +-- explicit spec of return type is OK +CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS int +AS 'select $1+1' LANGUAGE sql; +-- error, wrong result type +CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS float +AS 'select $1+1' LANGUAGE sql; +-- with multiple OUT params you must get a RECORD result +CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text) RETURNS int +AS 'select $1+1' LANGUAGE sql; +CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text) +RETURNS record +AS 'select $1+1' LANGUAGE sql; + +CREATE OR REPLACE FUNCTION foor(in f1 int, out f2 int, out text) +AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql; +SELECT f1, foor(f1) FROM int4_tbl; +SELECT * FROM foor(42); +SELECT * FROM foor(42) AS p(a,b); + +CREATE OR REPLACE FUNCTION foob(in f1 int, inout f2 int, out text) +AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql; +SELECT f1, foob(f1, f1/2) FROM int4_tbl; +SELECT * FROM foob(42, 99); +SELECT * FROM foob(42, 99) AS p(a,b); + +-- Can reference function with or without OUT params for DROP, etc +DROP FUNCTION foo(int); +DROP FUNCTION foor(in f2 int, out f1 int, out text); +DROP FUNCTION foob(in f1 int, inout f2 int); + +-- +-- For my next trick, polymorphic OUT parameters +-- + +CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray) +AS 'select $1, array[$1,$1]' LANGUAGE sql; +SELECT dup(22); +SELECT dup('xyz'); -- fails +SELECT dup('xyz'::text); +SELECT * FROM dup('xyz'::text); + +-- equivalent specification +CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray) +AS 'select $1, array[$1,$1]' LANGUAGE sql; +SELECT dup(22); + +DROP FUNCTION dup(anyelement); + +-- fails, no way to deduce outputs +CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray) +AS 'select $1, array[$1,$1]' LANGUAGE sql; |