From 47888fe84227aaf3decffc7204554bdec54d2b29 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 31 Mar 2005 22:46:33 +0000
Subject: [PATCH] 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.

---
 doc/src/sgml/ref/create_function.sgml         |  76 ++-
 doc/src/sgml/xfunc.sgml                       | 108 ++-
 src/backend/access/common/tupdesc.c           | 126 +---
 src/backend/catalog/pg_aggregate.c            |  11 +-
 src/backend/catalog/pg_proc.c                 | 191 +++---
 src/backend/commands/functioncmds.c           | 167 +++--
 src/backend/executor/functions.c              | 102 ++-
 src/backend/executor/nodeFunctionscan.c       |  29 +-
 src/backend/optimizer/util/clauses.c          |   5 +-
 src/backend/parser/gram.y                     |  19 +-
 src/backend/parser/parse_func.c               |  53 +-
 src/backend/parser/parse_relation.c           | 156 ++---
 src/backend/utils/cache/lsyscache.c           |  38 +-
 src/backend/utils/fmgr/fmgr.c                 |  30 +-
 src/backend/utils/fmgr/funcapi.c              | 643 +++++++++++++++++-
 src/include/catalog/pg_proc.h                 |   9 +-
 src/include/executor/functions.h              |   4 +-
 src/include/fmgr.h                            |   6 +-
 src/include/funcapi.h                         |  55 +-
 src/include/utils/lsyscache.h                 |  12 +-
 src/test/regress/expected/rangefuncs.out      | 131 ++++
 .../regress/output/create_function_2.source   |   2 +-
 src/test/regress/sql/rangefuncs.sql           |  62 ++
 23 files changed, 1503 insertions(+), 532 deletions(-)

diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 0991e96a54a..768a42846b6 100644
--- a/doc/src/sgml/ref/create_function.sgml
+++ b/doc/src/sgml/ref/create_function.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.64 2005/01/04 00:39:53 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.65 2005/03/31 22:45:59 tgl Exp $
 -->
 
 <refentry id="SQL-CREATEFUNCTION">
@@ -19,8 +19,9 @@ $PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.64 2005/01/04 00:39
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
-    RETURNS <replaceable class="parameter">rettype</replaceable>
+CREATE [ OR REPLACE ] FUNCTION
+    <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+    [ RETURNS <replaceable class="parameter">rettype</replaceable> ]
   { LANGUAGE <replaceable class="parameter">langname</replaceable>
     | IMMUTABLE | STABLE | VOLATILE
     | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
@@ -57,7 +58,9 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
    tried, you would actually be creating a new, distinct function).
    Also, <command>CREATE OR REPLACE FUNCTION</command> will not let
    you change the return type of an existing function.  To do that,
-   you must drop and recreate the function.
+   you must drop and recreate the function.  (When using <literal>OUT</>
+   parameters, that means you can't change the names or types of any
+   <literal>OUT</> parameters except by dropping the function.)
   </para>
 
   <para>
@@ -88,6 +91,17 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><replaceable class="parameter">argmode</replaceable></term>
+
+     <listitem>
+      <para>
+       The mode of an argument: either <literal>IN</>, <literal>OUT</>,
+       or <literal>INOUT</>.  If omitted, the default is <literal>IN</>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><replaceable class="parameter">argname</replaceable></term>
 
@@ -95,7 +109,10 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
       <para>
        The name of an argument. Some languages (currently only PL/pgSQL) let
        you use the name in the function body.  For other languages the
-       argument name is just extra documentation.
+       name of an input argument is just extra documentation.  But the name
+       of an output argument is significant, since it defines the column
+       name in the result row type.  (If you omit the name for an output
+       argument, the system will choose a default column name.)
       </para>
      </listitem>
     </varlistentry>
@@ -137,6 +154,13 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
        Depending on the implementation language it may also be allowed
        to specify <quote>pseudotypes</> such as <type>cstring</>.
       </para>
+      <para>
+       When there are <literal>OUT</> or <literal>INOUT</> parameters,
+       the <literal>RETURNS</> clause may be omitted.  If present, it
+       must agree with the result type implied by the output parameters:
+       <literal>RECORD</> if there are multiple output parameters, or
+       the same type as the single output parameter.
+      </para>
       <para>
        The <literal>SETOF</literal>
        modifier indicates that the function will return a set of
@@ -361,6 +385,16 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
     names).
    </para>
 
+   <para>
+    Two functions are considered the same if they have the same names and
+    <emphasis>input</> argument types, ignoring any <literal>OUT</>
+    parameters.  Thus for example these declarations conflict:
+<programlisting>
+CREATE FUNCTION foo(int) ...
+CREATE FUNCTION foo(int, out text) ...
+</programlisting>
+   </para>
+
    <para>
     When repeated <command>CREATE FUNCTION</command> calls refer to
     the same object file, the file is only loaded once.  To unload and
@@ -393,7 +427,7 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
   <title>Examples</title>
 
   <para>
-   Here is a trivial example to help you get started.  For more
+   Here are some trivial examples to help you get started.  For more
    information and examples, see <xref linkend="xfunc">.
 <programlisting>
 CREATE FUNCTION add(integer, integer) RETURNS integer
@@ -407,13 +441,34 @@ CREATE FUNCTION add(integer, integer) RETURNS integer
   <para>
    Increment an integer, making use of an argument name, in
    <application>PL/pgSQL</application>:
-
 <programlisting>
 CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$
         BEGIN
                 RETURN i + 1;
         END;
 $$ LANGUAGE plpgsql;
+</programlisting>
+  </para>
+
+  <para>
+   Return a record containing multiple output parameters:
+<programlisting>
+CREATE FUNCTION dup(in int, out f1 int, out f2 text)
+    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
+    LANGUAGE SQL;
+
+SELECT * FROM dup(42);
+</programlisting>
+   You can do the same thing more verbosely with an explicitly named
+   composite type:
+<programlisting>
+CREATE TYPE dup_result AS (f1 int, f2 text);
+
+CREATE FUNCTION dup(int) RETURNS dup_result
+    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
+    LANGUAGE SQL;
+
+SELECT * FROM dup(42);
 </programlisting>
   </para>
  </refsect1>
@@ -428,6 +483,13 @@ $$ LANGUAGE plpgsql;
    not fully compatible.  The attributes are not portable, neither are the
    different available languages.
   </para>
+
+  <para>
+   For compatibility with some other database systems,
+   <replaceable class="parameter">argmode</replaceable> can be written
+   either before or after <replaceable class="parameter">argname</replaceable>.
+   But only the first way is standard-compliant.
+  </para>
  </refsect1>
 
 
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 83af1f93f70..079773d0d46 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.101 2005/03/16 21:38:04 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.102 2005/03/31 22:46:02 tgl Exp $
 -->
 
  <sect1 id="xfunc">
@@ -172,7 +172,7 @@ INSERT INTO $1 VALUES (42);
 </programlisting>
    </para>
 
-   <sect2>
+   <sect2 id="xfunc-sql-base-functions">
     <title><acronym>SQL</acronym> Functions on Base Types</title>
 
     <para>
@@ -484,7 +484,7 @@ SELECT emp.name, emp.double_salary FROM emp;
     </tip>
 
     <para>
-     Another way to use a function returning a row result is to pass the
+     Another way to use a function returning a composite type is to pass the
      result to another function that accepts the correct row type as input:
 
 <screen>
@@ -501,8 +501,89 @@ SELECT getname(new_emp());
     </para>     
 
     <para>
-     Another way to use a function that returns a composite type is to
-     call it as a table function, as described below.
+     Still another way to use a function that returns a composite type is to
+     call it as a table function, as described in <xref
+     linkend="xfunc-sql-table-functions">.
+    </para>
+   </sect2>
+
+   <sect2 id="xfunc-output-parameters">
+    <title>Functions with Output Parameters</title>
+
+   <indexterm>
+    <primary>function</primary>
+    <secondary>output parameter</secondary>
+   </indexterm>
+
+    <para>
+     An alternative way of describing a function's results is to define it
+     with <firstterm>output parameters</>, as in this example:
+
+<screen>
+CREATE FUNCTION add_em (IN x int, IN y int, OUT sum int)
+AS 'SELECT $1 + $2'
+LANGUAGE SQL;
+
+SELECT add_em(3,7);
+ add_em
+--------
+     10
+(1 row)
+</screen>
+
+     This is not essentially different from the version of <literal>add_em</>
+     shown in <xref linkend="xfunc-sql-base-functions">.  The real value of
+     output parameters is that they provide a convenient way of defining
+     functions that return several columns.  For example,
+
+<screen>
+CREATE FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int)
+AS 'SELECT $1 + $2, $1 * $2'
+LANGUAGE SQL;
+
+ SELECT * FROM sum_n_product(11,42);
+ sum | product
+-----+---------
+  53 |     462
+(1 row)
+</screen>
+
+     What has essentially happened here is that we have created an anonymous
+     composite type for the result of the function.  The above example has
+     the same end result as
+
+<screen>
+CREATE TYPE sum_prod AS (sum int, product int);
+
+CREATE FUNCTION sum_n_product (int, int) RETURNS sum_prod
+AS 'SELECT $1 + $2, $1 * $2'
+LANGUAGE SQL;
+</screen>
+
+     but not having to bother with the separate composite type definition
+     is often handy.
+    </para>
+
+    <para>
+     Notice that output parameters are not included in the calling argument
+     list when invoking such a function from SQL.  This is because
+     <productname>PostgreSQL</productname> considers only the input
+     parameters to define the function's calling signature.  That means
+     also that only the input parameters matter when referencing the function
+     for purposes such as dropping it.  We could drop the above function
+     with either of
+
+<screen>
+DROP FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int);
+DROP FUNCTION sum_n_product (int, int);
+</screen>
+    </para>
+
+    <para>
+     Parameters can be marked as <literal>IN</> (the default),
+     <literal>OUT</>, or <literal>INOUT</>.  An <literal>INOUT</>
+     parameter serves as both an input parameter (part of the calling
+     argument list) and an output parameter (part of the result record type).
     </para>
    </sect2>
 
@@ -692,6 +773,21 @@ CREATE FUNCTION invalid_func() RETURNS anyelement AS $$
 $$ LANGUAGE SQL;
 ERROR:  cannot determine result data type
 DETAIL:  A function returning "anyarray" or "anyelement" must have at least one argument of either type.
+</screen>
+    </para>
+
+    <para>
+     Polymorphism can be used with functions that have output arguments.
+     For example:
+<screen>
+CREATE FUNCTION dup (f1 anyelement, OUT f2 anyelement, OUT f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+
+SELECT * FROM dup(22);
+ f2 |   f3
+----+---------
+ 22 | {22,22}
+(1 row)
 </screen>
     </para>
    </sect2>
@@ -962,7 +1058,7 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision
   <sect1 id="xfunc-c">
    <title>C-Language Functions</title>
 
-   <indexterm zone="xfunc-sql">
+   <indexterm zone="xfunc-c">
     <primary>function</primary>
     <secondary>user-defined</secondary>
     <tertiary>in C</tertiary>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 0d1d4022106..fac8551dfd6 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 246c3a01887..4428bc7ecba 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 a991ce901c1..b56eb54200b 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 c2c521bbfea..5776738045b 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 2ddc614cf7e..d2e101a2d61 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 edf5c676352..4cbbb5a65ff 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 76255de53d4..2cf4fcd663d 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 62497957263..6bbf4a4de18 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 07d52ef5441..64a325f10b7 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 6360e402f86..6e391f4eb8b 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 0b40a20b251..3abbf65fa48 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.121 2005/03/29 00:17:11 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.122 2005/03/31 22:46:14 tgl Exp $
  *
  * NOTES
  *	  Eventually, the index information should go through here, too.
@@ -1543,42 +1543,6 @@ get_typtype(Oid typid)
 		return '\0';
 }
 
-/*
- * 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
  *
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index a8bb5fc0ac5..0e9716de013 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.92 2005/03/29 03:01:31 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.93 2005/03/31 22:46:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -403,7 +403,7 @@ fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple)
  * We want to raise an error here only if the info function returns
  * something bogus.
  *
- * This function is broken out of fmgr_info_C_lang() so that ProcedureCreate()
+ * This function is broken out of fmgr_info_C_lang so that fmgr_c_validator
  * can validate the information record for a function not yet entered into
  * pg_proc.
  */
@@ -576,8 +576,8 @@ fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo,
 
 
 /*
- * Specialized lookup routine for ProcedureCreate(): given the alleged name
- * of an internal function, return the OID of the function.
+ * Specialized lookup routine for fmgr_internal_validator: given the alleged
+ * name of an internal function, return the OID of the function.
  * If the name is not recognized, return InvalidOid.
  */
 Oid
@@ -1869,10 +1869,6 @@ get_fn_expr_rettype(FmgrInfo *flinfo)
 Oid
 get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
 {
-	Node	   *expr;
-	List	   *args;
-	Oid			argtype;
-
 	/*
 	 * can't return anything useful if we have no FmgrInfo or if its
 	 * fn_expr node has not been initialized
@@ -1880,7 +1876,23 @@ get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
 	if (!flinfo || !flinfo->fn_expr)
 		return InvalidOid;
 
-	expr = flinfo->fn_expr;
+	return get_call_expr_argtype(flinfo->fn_expr, argnum);
+}
+
+/*
+ * Get the actual type OID of a specific function argument (counting from 0),
+ * but working from the calling expression tree instead of FmgrInfo
+ *
+ * Returns InvalidOid if information is not available
+ */
+Oid
+get_call_expr_argtype(Node *expr, int argnum)
+{
+	List	   *args;
+	Oid			argtype;
+
+	if (expr == NULL)
+		return InvalidOid;
 
 	if (IsA(expr, FuncExpr))
 		args = ((FuncExpr *) expr)->args;
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 2c3845b7bf4..160847de1e0 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -7,17 +7,37 @@
  * Copyright (c) 2002-2005, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.18 2005/01/01 05:43:08 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.19 2005/03/31 22:46:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "funcapi.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
+
 
 static void shutdown_MultiFuncCall(Datum arg);
+static TypeFuncClass internal_get_result_type(Oid funcid,
+											  Node *call_expr,
+											  ReturnSetInfo *rsinfo,
+											  Oid *resultTypeId,
+											  TupleDesc *resultTupleDesc);
+static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
+										oidvector *declared_args,
+										Node *call_expr);
+static TypeFuncClass get_type_func_class(Oid typid);
+
 
 /*
  * init_MultiFuncCall
@@ -156,3 +176,624 @@ shutdown_MultiFuncCall(Datum arg)
 
 	pfree(funcctx);
 }
+
+
+/*
+ * get_call_result_type
+ *		Given a function's call info record, determine the kind of datatype
+ *		it is supposed to return.  If resultTypeId isn't NULL, *resultTypeId
+ *		receives the actual datatype OID (this is mainly useful for scalar
+ *		result types).  If resultTupleDesc isn't NULL, *resultTupleDesc
+ *		receives a pointer to a TupleDesc when the result is of a composite
+ *		type, or NULL when it's a scalar result.  NB: the tupledesc should
+ *		be copied if it is to be accessed over a long period.
+ *
+ * One hard case that this handles is resolution of actual rowtypes for
+ * functions returning RECORD (from either the function's OUT parameter
+ * list, or a ReturnSetInfo context node).  TYPEFUNC_RECORD is returned
+ * only when we couldn't resolve the actual rowtype for lack of information.
+ *
+ * The other hard case that this handles is resolution of polymorphism.
+ * We will never return ANYELEMENT or ANYARRAY, either as a scalar result
+ * type or as a component of a rowtype.
+ *
+ * This function is relatively expensive --- in a function returning set,
+ * try to call it only the first time through.
+ */
+TypeFuncClass
+get_call_result_type(FunctionCallInfo fcinfo,
+					 Oid *resultTypeId,
+					 TupleDesc *resultTupleDesc)
+{
+	return internal_get_result_type(fcinfo->flinfo->fn_oid,
+									fcinfo->flinfo->fn_expr,
+									(ReturnSetInfo *) fcinfo->resultinfo,
+									resultTypeId,
+									resultTupleDesc);
+}
+
+/*
+ * get_expr_result_type
+ *		As above, but work from a calling expression node tree
+ */
+TypeFuncClass
+get_expr_result_type(Node *expr,
+					 Oid *resultTypeId,
+					 TupleDesc *resultTupleDesc)
+{
+	TypeFuncClass result;
+
+	if (expr && IsA(expr, FuncExpr))
+		result = internal_get_result_type(((FuncExpr *) expr)->funcid,
+										  expr,
+										  NULL,
+										  resultTypeId,
+										  resultTupleDesc);
+	else
+	{
+		/* handle as a generic expression; no chance to resolve RECORD */
+		Oid		typid = exprType(expr);
+
+		if (resultTypeId)
+			*resultTypeId = typid;
+		if (resultTupleDesc)
+			*resultTupleDesc = NULL;
+		result = get_type_func_class(typid);
+		if (result == TYPEFUNC_COMPOSITE && resultTupleDesc)
+			*resultTupleDesc = lookup_rowtype_tupdesc(typid, -1);
+	}
+
+	return result;
+}
+
+/*
+ * get_expr_result_type
+ *		As above, but work from a function's OID only
+ *
+ * This will not be able to resolve pure-RECORD results nor polymorphism.
+ */
+TypeFuncClass
+get_func_result_type(Oid functionId,
+					 Oid *resultTypeId,
+					 TupleDesc *resultTupleDesc)
+{
+	return internal_get_result_type(functionId,
+									NULL,
+									NULL,
+									resultTypeId,
+									resultTupleDesc);
+}
+
+/*
+ * internal_get_result_type -- workhorse code implementing all the above
+ *
+ * funcid must always be supplied.  call_expr and rsinfo can be NULL if not
+ * available.  We will return TYPEFUNC_RECORD, and store NULL into
+ * *resultTupleDesc, if we cannot deduce the complete result rowtype from
+ * the available information.
+ */
+static TypeFuncClass
+internal_get_result_type(Oid funcid,
+						 Node *call_expr,
+						 ReturnSetInfo *rsinfo,
+						 Oid *resultTypeId,
+						 TupleDesc *resultTupleDesc)
+{
+	TypeFuncClass result;
+	HeapTuple	tp;
+	Form_pg_proc procform;
+	Oid			rettype;
+	TupleDesc	tupdesc;
+
+	/* First fetch the function's pg_proc row to inspect its rettype */
+	tp = SearchSysCache(PROCOID,
+						ObjectIdGetDatum(funcid),
+						0, 0, 0);
+	if (!HeapTupleIsValid(tp))
+		elog(ERROR, "cache lookup failed for function %u", funcid);
+	procform = (Form_pg_proc) GETSTRUCT(tp);
+
+	rettype = procform->prorettype;
+
+	/* Check for OUT parameters defining a RECORD result */
+	tupdesc = build_function_result_tupdesc_t(tp);
+	if (tupdesc)
+	{
+		/*
+		 * It has OUT parameters, so it's basically like a regular
+		 * composite type, except we have to be able to resolve any
+		 * polymorphic OUT parameters.
+		 */
+		if (resultTypeId)
+			*resultTypeId = rettype;
+
+		if (resolve_polymorphic_tupdesc(tupdesc,
+										&procform->proargtypes,
+										call_expr))
+		{
+			if (tupdesc->tdtypeid == RECORDOID &&
+				tupdesc->tdtypmod < 0)
+				assign_record_type_typmod(tupdesc);
+			if (resultTupleDesc)
+				*resultTupleDesc = tupdesc;
+			result = TYPEFUNC_COMPOSITE;
+		}
+		else
+		{
+			if (resultTupleDesc)
+				*resultTupleDesc = NULL;
+			result = TYPEFUNC_RECORD;
+		}
+
+		ReleaseSysCache(tp);
+
+		return result;
+	}
+
+	/*
+	 * If scalar polymorphic result, try to resolve it.
+	 */
+	if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
+	{
+		Oid		newrettype = exprType(call_expr);
+
+		if (newrettype == InvalidOid)	/* this probably should not happen */
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("could not determine actual result type for function \"%s\" declared to return type %s",
+							NameStr(procform->proname),
+							format_type_be(rettype))));
+		rettype = newrettype;
+	}
+
+	if (resultTypeId)
+		*resultTypeId = rettype;
+	if (resultTupleDesc)
+		*resultTupleDesc = NULL;		/* default result */
+
+	/* Classify the result type */
+	result = get_type_func_class(rettype);
+	switch (result)
+	{
+		case TYPEFUNC_COMPOSITE:
+			if (resultTupleDesc)
+				*resultTupleDesc = lookup_rowtype_tupdesc(rettype, -1);
+			/* Named composite types can't have any polymorphic columns */
+			break;
+		case TYPEFUNC_SCALAR:
+			break;
+		case TYPEFUNC_RECORD:
+			/* We must get the tupledesc from call context */
+			if (rsinfo && IsA(rsinfo, ReturnSetInfo) &&
+				rsinfo->expectedDesc != NULL)
+			{
+				result = TYPEFUNC_COMPOSITE;
+				if (resultTupleDesc)
+					*resultTupleDesc = rsinfo->expectedDesc;
+				/* Assume no polymorphic columns here, either */
+			}
+			break;
+		default:
+			break;
+	}
+
+	ReleaseSysCache(tp);
+
+	return result;
+}
+
+/*
+ * Given the result tuple descriptor for a function with OUT parameters,
+ * replace any polymorphic columns (ANYELEMENT/ANYARRAY) with correct data
+ * types deduced from the input arguments.  Returns TRUE if able to deduce
+ * all types, FALSE if not.
+ */
+static bool
+resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
+							Node *call_expr)
+{
+	int			natts = tupdesc->natts;
+	int			nargs = declared_args->dim1;
+	bool		have_anyelement_result = false;
+	bool		have_anyarray_result = false;
+	Oid			anyelement_type = InvalidOid;
+	Oid			anyarray_type = InvalidOid;
+	int			i;
+
+	/* See if there are any polymorphic outputs; quick out if not */
+	for (i = 0; i < natts; i++)
+	{
+		switch (tupdesc->attrs[i]->atttypid)
+		{
+			case ANYELEMENTOID:
+				have_anyelement_result = true;
+				break;
+			case ANYARRAYOID:
+				have_anyarray_result = true;
+				break;
+			default:
+				break;
+		}
+	}
+	if (!have_anyelement_result && !have_anyarray_result)
+		return true;
+
+	/*
+	 * Otherwise, extract actual datatype(s) from input arguments.  (We assume
+	 * the parser already validated consistency of the arguments.)
+	 */
+	if (!call_expr)
+		return false;			/* no hope */
+
+	for (i = 0; i < nargs; i++)
+	{
+		switch (declared_args->values[i])
+		{
+			case ANYELEMENTOID:
+				if (!OidIsValid(anyelement_type))
+					anyelement_type = get_call_expr_argtype(call_expr, i);
+				break;
+			case ANYARRAYOID:
+				if (!OidIsValid(anyarray_type))
+					anyarray_type = get_call_expr_argtype(call_expr, i);
+				break;
+			default:
+				break;
+		}
+	}
+
+	/* If nothing found, parser messed up */
+	if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type))
+		return false;
+
+	/* If needed, deduce one polymorphic type from the other */
+	if (have_anyelement_result && !OidIsValid(anyelement_type))
+		anyelement_type = resolve_generic_type(ANYELEMENTOID,
+											   anyarray_type,
+											   ANYARRAYOID);
+	if (have_anyarray_result && !OidIsValid(anyarray_type))
+		anyarray_type = resolve_generic_type(ANYARRAYOID,
+											 anyelement_type,
+											 ANYELEMENTOID);
+
+	/* And finally replace the tuple column types as needed */
+	for (i = 0; i < natts; i++)
+	{
+		switch (tupdesc->attrs[i]->atttypid)
+		{
+			case ANYELEMENTOID:
+				TupleDescInitEntry(tupdesc, i+1,
+								   NameStr(tupdesc->attrs[i]->attname),
+								   anyelement_type,
+								   -1,
+								   0);
+				break;
+			case ANYARRAYOID:
+				TupleDescInitEntry(tupdesc, i+1,
+								   NameStr(tupdesc->attrs[i]->attname),
+								   anyarray_type,
+								   -1,
+								   0);
+				break;
+			default:
+				break;
+		}
+	}
+
+	return true;
+}
+
+/*
+ * get_type_func_class
+ *		Given the type OID, obtain its TYPEFUNC classification.
+ *
+ * This is intended to centralize a bunch of formerly ad-hoc code for
+ * classifying types.  The categories used here are useful for deciding
+ * how to handle functions returning the datatype.
+ */
+static TypeFuncClass
+get_type_func_class(Oid typid)
+{
+	switch (get_typtype(typid))
+	{
+		case 'c':
+			return TYPEFUNC_COMPOSITE;
+		case 'b':
+		case 'd':
+			return TYPEFUNC_SCALAR;
+		case 'p':
+			if (typid == RECORDOID)
+				return TYPEFUNC_RECORD;
+			/*
+			 * We treat VOID and CSTRING as legitimate scalar datatypes,
+			 * mostly for the convenience of the JDBC driver (which wants
+			 * to be able to do "SELECT * FROM foo()" for all legitimately
+			 * user-callable functions).
+			 */
+			if (typid == VOIDOID || typid == CSTRINGOID)
+				return TYPEFUNC_SCALAR;
+			return TYPEFUNC_OTHER;
+	}
+	/* shouldn't get here, probably */
+	return TYPEFUNC_OTHER;
+}
+
+
+/*
+ * build_function_result_tupdesc_t
+ *
+ * Given a pg_proc row for a function, return a tuple descriptor for the
+ * result rowtype, or NULL if the function does not have OUT parameters.
+ *
+ * Note that this does not handle resolution of ANYELEMENT/ANYARRAY types;
+ * that is deliberate.
+ */
+TupleDesc
+build_function_result_tupdesc_t(HeapTuple procTuple)
+{
+	Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(procTuple);
+	Datum		proallargtypes;
+	Datum		proargmodes;
+	Datum		proargnames;
+	bool		isnull;
+
+	/* Return NULL if the function isn't declared to return RECORD */
+	if (procform->prorettype != RECORDOID)
+		return NULL;
+
+	/* If there are no OUT parameters, return NULL */
+	if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes) ||
+		heap_attisnull(procTuple, Anum_pg_proc_proargmodes))
+		return NULL;
+
+	/* Get the data out of the tuple */
+	proallargtypes = SysCacheGetAttr(PROCOID, procTuple,
+									 Anum_pg_proc_proallargtypes,
+									 &isnull);
+	Assert(!isnull);
+	proargmodes = SysCacheGetAttr(PROCOID, procTuple,
+								  Anum_pg_proc_proargmodes,
+								  &isnull);
+	Assert(!isnull);
+	proargnames = SysCacheGetAttr(PROCOID, procTuple,
+								  Anum_pg_proc_proargnames,
+								  &isnull);
+	if (isnull)
+		proargnames = PointerGetDatum(NULL); /* just to be sure */
+
+	return build_function_result_tupdesc_d(proallargtypes,
+										   proargmodes,
+										   proargnames);
+}
+
+/*
+ * build_function_result_tupdesc_d
+ *
+ * Build a RECORD function's tupledesc from the pg_proc proallargtypes,
+ * proargmodes, and proargnames arrays.  This is split out for the
+ * convenience of ProcedureCreate, which needs to be able to compute the
+ * tupledesc before actually creating the function.
+ *
+ * Returns NULL if there are not at least two OUT or INOUT arguments.
+ */
+TupleDesc
+build_function_result_tupdesc_d(Datum proallargtypes,
+								Datum proargmodes,
+								Datum proargnames)
+{
+	TupleDesc	desc;
+	ArrayType  *arr;
+	int			numargs;
+	Oid		   *argtypes;
+	char	   *argmodes;
+	Datum	   *argnames = NULL;
+	Oid		   *outargtypes;
+	char	  **outargnames;
+	int			numoutargs;
+	int			nargnames;
+	int			i;
+
+	/* Can't have output args if columns are null */
+	if (proallargtypes == PointerGetDatum(NULL) ||
+		proargmodes == PointerGetDatum(NULL))
+		return NULL;
+
+	/*
+	 * We expect the arrays to be 1-D arrays of the right types; verify that.
+	 * For the OID and char arrays, we don't need to use deconstruct_array()
+	 * since the array data is just going to look like a C array of values.
+	 */
+	arr = DatumGetArrayTypeP(proallargtypes);	/* ensure not toasted */
+	numargs = ARR_DIMS(arr)[0];
+	if (ARR_NDIM(arr) != 1 ||
+		numargs < 0 ||
+		ARR_ELEMTYPE(arr) != OIDOID)
+		elog(ERROR, "proallargtypes is not a 1-D Oid array");
+	argtypes = (Oid *) ARR_DATA_PTR(arr);
+	arr = DatumGetArrayTypeP(proargmodes);		/* ensure not toasted */
+	if (ARR_NDIM(arr) != 1 ||
+		ARR_DIMS(arr)[0] != numargs ||
+		ARR_ELEMTYPE(arr) != CHAROID)
+		elog(ERROR, "proargmodes is not a 1-D char array");
+	argmodes = (char *) ARR_DATA_PTR(arr);
+	if (proargnames != PointerGetDatum(NULL))
+	{
+		arr = DatumGetArrayTypeP(proargnames);	/* ensure not toasted */
+		if (ARR_NDIM(arr) != 1 ||
+			ARR_DIMS(arr)[0] != numargs ||
+			ARR_ELEMTYPE(arr) != TEXTOID)
+			elog(ERROR, "proargnames is not a 1-D text array");
+		deconstruct_array(arr, TEXTOID, -1, false, 'i',
+						  &argnames, &nargnames);
+		Assert(nargnames == numargs);
+	}
+
+	/* zero elements probably shouldn't happen, but handle it gracefully */
+	if (numargs <= 0)
+		return NULL;
+
+	/* extract output-argument types and names */
+	outargtypes = (Oid *) palloc(numargs * sizeof(Oid));
+	outargnames = (char **) palloc(numargs * sizeof(char *));
+	numoutargs = 0;
+	for (i = 0; i < numargs; i++)
+	{
+		char	*pname;
+
+		if (argmodes[i] == PROARGMODE_IN)
+			continue;
+		Assert(argmodes[i] == PROARGMODE_OUT ||
+			   argmodes[i] == PROARGMODE_INOUT);
+		outargtypes[numoutargs] = argtypes[i];
+		if (argnames)
+			pname = DatumGetCString(DirectFunctionCall1(textout, argnames[i]));
+		else
+			pname = NULL;
+		if (pname == NULL || pname[0] == '\0')
+		{
+			/* Parameter is not named, so gin up a column name */
+			pname = (char *) palloc(32);
+			snprintf(pname, 32, "column%d", numoutargs + 1);
+		}
+		outargnames[numoutargs] = pname;
+		numoutargs++;
+	}
+
+	/*
+	 * If there is no output argument, or only one, the function does not
+	 * return tuples.
+	 */
+	if (numoutargs < 2)
+		return NULL;
+
+	desc = CreateTemplateTupleDesc(numoutargs, false);
+	for (i = 0; i < numoutargs; i++)
+	{
+		TupleDescInitEntry(desc, i+1,
+						   outargnames[i],
+						   outargtypes[i],
+						   -1,
+						   0);
+	}
+
+	return desc;
+}
+
+
+/*
+ * RelationNameGetTupleDesc
+ *
+ * Given a (possibly qualified) relation name, build a TupleDesc.
+ */
+TupleDesc
+RelationNameGetTupleDesc(const char *relname)
+{
+	RangeVar   *relvar;
+	Relation	rel;
+	TupleDesc	tupdesc;
+	List	   *relname_list;
+
+	/* Open relation and copy the tuple description */
+	relname_list = stringToQualifiedNameList(relname, "RelationNameGetTupleDesc");
+	relvar = makeRangeVarFromNameList(relname_list);
+	rel = relation_openrv(relvar, AccessShareLock);
+	tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+	relation_close(rel, AccessShareLock);
+
+	return tupdesc;
+}
+
+/*
+ * TypeGetTupleDesc
+ *
+ * Given a type Oid, build a TupleDesc.
+ *
+ * If the type is composite, *and* a colaliases List is provided, *and*
+ * the List is of natts length, use the aliases instead of the relation
+ * attnames.  (NB: this usage is deprecated since it may result in
+ * creation of unnecessary transient record types.)
+ *
+ * If the type is a base type, a single item alias List is required.
+ */
+TupleDesc
+TypeGetTupleDesc(Oid typeoid, List *colaliases)
+{
+	TypeFuncClass functypclass = get_type_func_class(typeoid);
+	TupleDesc	tupdesc = NULL;
+
+	/*
+	 * Build a suitable tupledesc representing the output rows
+	 */
+	if (functypclass == TYPEFUNC_COMPOSITE)
+	{
+		/* Composite data type, e.g. a table's row type */
+		tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(typeoid, -1));
+
+		if (colaliases != NIL)
+		{
+			int			natts = tupdesc->natts;
+			int			varattno;
+
+			/* does the list length match the number of attributes? */
+			if (list_length(colaliases) != natts)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATATYPE_MISMATCH),
+						 errmsg("number of aliases does not match number of columns")));
+
+			/* OK, use the aliases instead */
+			for (varattno = 0; varattno < natts; varattno++)
+			{
+				char	   *label = strVal(list_nth(colaliases, varattno));
+
+				if (label != NULL)
+					namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
+			}
+
+			/* The tuple type is now an anonymous record type */
+			tupdesc->tdtypeid = RECORDOID;
+			tupdesc->tdtypmod = -1;
+		}
+	}
+	else if (functypclass == TYPEFUNC_SCALAR)
+	{
+		/* Base data type, i.e. scalar */
+		char	   *attname;
+
+		/* the alias list is required for base types */
+		if (colaliases == NIL)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("no column alias was provided")));
+
+		/* the alias list length must be 1 */
+		if (list_length(colaliases) != 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATATYPE_MISMATCH),
+					 errmsg("number of aliases does not match number of columns")));
+
+		/* OK, get the column alias */
+		attname = strVal(linitial(colaliases));
+
+		tupdesc = CreateTemplateTupleDesc(1, false);
+		TupleDescInitEntry(tupdesc,
+						   (AttrNumber) 1,
+						   attname,
+						   typeoid,
+						   -1,
+						   0);
+	}
+	else if (functypclass == TYPEFUNC_RECORD)
+	{
+		/* XXX can't support this because typmod wasn't passed in ... */
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("could not determine row description for function returning record")));
+	}
+	else
+	{
+		/* crummy error message, but parser should have caught this */
+		elog(ERROR, "function in FROM has unsupported return type");
+	}
+
+	return tupdesc;
+}
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index d86ce3ef0eb..cb90a5e04c3 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 fdc23b2812e..88ca87fd86d 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 0c11130053e..3dc53ccb166 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 00549897d58..c4d1809948d 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 1c5398a71b0..845a886ba82 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 7acbbe9bc60..6caa7a1c7b5 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 f724963d351..dd308261d9f 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 2f1c8e75130..50495897acd 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;
-- 
GitLab