summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2005-03-31 22:46:33 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2005-03-31 22:46:33 +0000
commit47888fe84227aaf3decffc7204554bdec54d2b29 (patch)
tree73703aa272d2b9899626002190f0fbd3b1e579fb /src
parentfb13881f423193a8342e0fe098f581e511b09d67 (diff)
downloadpostgresql-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.c126
-rw-r--r--src/backend/catalog/pg_aggregate.c11
-rw-r--r--src/backend/catalog/pg_proc.c191
-rw-r--r--src/backend/commands/functioncmds.c167
-rw-r--r--src/backend/executor/functions.c102
-rw-r--r--src/backend/executor/nodeFunctionscan.c29
-rw-r--r--src/backend/optimizer/util/clauses.c5
-rw-r--r--src/backend/parser/gram.y19
-rw-r--r--src/backend/parser/parse_func.c53
-rw-r--r--src/backend/parser/parse_relation.c156
-rw-r--r--src/backend/utils/cache/lsyscache.c38
-rw-r--r--src/backend/utils/fmgr/fmgr.c30
-rw-r--r--src/backend/utils/fmgr/funcapi.c643
-rw-r--r--src/include/catalog/pg_proc.h9
-rw-r--r--src/include/executor/functions.h4
-rw-r--r--src/include/fmgr.h6
-rw-r--r--src/include/funcapi.h55
-rw-r--r--src/include/utils/lsyscache.h12
-rw-r--r--src/test/regress/expected/rangefuncs.out131
-rw-r--r--src/test/regress/output/create_function_2.source2
-rw-r--r--src/test/regress/sql/rangefuncs.sql62
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,
+ &parameterTypes,
+ &allParameterTypes,
+ &parameterModes,
+ &parameterNames,
+ &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;