diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 9c203f5e57f7e50f0ef058dcbb4c6f0222ac243a..a1970c3e88747f359d5cef188990653cd6357374 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/select.sgml,v 1.54 2002/04/23 02:07:16 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/select.sgml,v 1.55 2002/08/04 19:48:09 momjian Exp $
 PostgreSQL documentation
 -->
 
@@ -40,6 +40,12 @@ where <replaceable class="PARAMETER">from_item</replaceable> can be:
 ( <replaceable class="PARAMETER">select</replaceable> )
     [ AS ] <replaceable class="PARAMETER">alias</replaceable> [ ( <replaceable class="PARAMETER">column_alias_list</replaceable> ) ]
 |
+<replaceable class="PARAMETER">table_function_name</replaceable> ( [ <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+    [ AS ] <replaceable class="PARAMETER">alias</replaceable> [ ( <replaceable class="PARAMETER">column_alias_list</replaceable> | <replaceable class="PARAMETER">column_definition_list</replaceable> ) ]
+|
+<replaceable class="PARAMETER">table_function_name</replaceable> ( [ <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+    AS ( <replaceable class="PARAMETER">column_definition_list</replaceable> )
+|
 <replaceable class="PARAMETER">from_item</replaceable> [ NATURAL ] <replaceable class="PARAMETER">join_type</replaceable> <replaceable class="PARAMETER">from_item</replaceable>
     [ ON <replaceable class="PARAMETER">join_condition</replaceable> | USING ( <replaceable class="PARAMETER">join_column_list</replaceable> ) ]
   </synopsis>
@@ -82,7 +88,7 @@ where <replaceable class="PARAMETER">from_item</replaceable> can be:
       <term><replaceable class="PARAMETER">from_item</replaceable></term>
       <listitem>
        <para>
-        A table reference, sub-SELECT, or JOIN clause.  See below for details.
+        A table reference, sub-SELECT, table function, or JOIN clause.  See below for details.
        </para>
       </listitem>
      </varlistentry>
@@ -156,6 +162,23 @@ where <replaceable class="PARAMETER">from_item</replaceable> can be:
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry>
+      <term><replaceable class="PARAMETER">table function</replaceable></term>
+      <listitem>
+       <para>
+	A table function can appear in the FROM clause.  This acts as though
+	its output were created as a temporary table for the duration of
+	this single SELECT command. An alias may also be used. If an alias is
+	written, a column alias list can also be written to provide	substitute names
+	for one or more columns of the table function. If the table function has been
+	defined as returning the RECORD data type, an alias, or the keyword AS, must
+    also be present, followed by a column definition list in the form
+	( <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [, ... ] ).
+	The column definition list must match the actual number and types returned by the function.
+       </para>
+      </listitem>
+     </varlistentry>
      
      <varlistentry>
       <term><replaceable class="PARAMETER">join_type</replaceable></term>
@@ -380,6 +403,19 @@ where <replaceable class="PARAMETER">from_item</replaceable> can be:
     grouping, aggregation, or sorting in a single query.
    </para>
 
+   <para>
+    A FROM item can be a table function (i.e. a function that returns
+    multiple rows and columns).  When a table function is created, it may
+	be defined to return a named scalar or composite data type (an existing
+	scalar data type, or a table or view name), or it may be defined to return
+	a RECORD data type. When a table function is defined to return RECORD, it
+	must be followed in the FROM clause by an alias, or the keyword AS alone,
+	and then by a parenthesized list of column names and types. This provides
+	a query-time composite type definition. The FROM clause composite type
+	must match the actual composite type returned from the function or an
+	ERROR will be generated.
+   </para>
+
    <para>
     Finally, a FROM item can be a JOIN clause, which combines two simpler
     FROM items.  (Use parentheses if necessary to determine the order
@@ -925,6 +961,43 @@ SELECT actors.name
  Warren Beatty
  Westward
  Woody Allen
+</programlisting>
+  </para>
+
+  <para>
+   This example shows how to use a table function, both with and without
+   a column definition list.
+
+<programlisting>
+distributors:
+ did |     name
+-----+--------------
+ 108 | Westward
+ 111 | Walt Disney
+ 112 | Warner Bros.
+ ...
+
+CREATE FUNCTION distributors(int)
+  RETURNS SETOF distributors AS '
+  SELECT * FROM distributors WHERE did = $1;
+  ' LANGUAGE SQL;
+
+SELECT * FROM distributors(111);
+ did |    name
+-----+-------------
+ 111 | Walt Disney
+(1 row)
+
+CREATE FUNCTION distributors_2(int)
+  RETURNS SETOF RECORD AS '
+  SELECT * FROM distributors WHERE did = $1;
+  ' LANGUAGE SQL;
+
+SELECT * FROM distributors_2(111) AS (f1 int, f2 text);
+ f1  |     f2
+-----+-------------
+ 111 | Walt Disney
+(1 row)
 </programlisting>
   </para>
  </refsect1>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 249be8700021127f0e927b9f2d84070de87d52e4..e97f77ce700885dfeec8d6b572134f72e6d076cf 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/common/tupdesc.c,v 1.83 2002/08/02 18:15:04 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/common/tupdesc.c,v 1.84 2002/08/04 19:48:09 momjian Exp $
  *
  * NOTES
  *	  some of the executor utility code such as "ExecTypeFromTL" should be
@@ -24,6 +24,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "nodes/parsenodes.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
@@ -600,46 +601,53 @@ RelationNameGetTupleDesc(char *relname)
 TupleDesc
 TypeGetTupleDesc(Oid typeoid, List *colaliases)
 {
-	Oid			relid = typeidTypeRelid(typeoid);
-	TupleDesc	tupdesc;
+	char		functyptype = typeid_get_typtype(typeoid);
+	TupleDesc	tupdesc = NULL;
 
 	/*
 	 * Build a suitable tupledesc representing the output rows
 	 */
-	if (OidIsValid(relid))
+	if (functyptype == 'c')
 	{
 		/* Composite data type, i.e. a table's row type */
-		Relation	rel;
-		int			natts;
-
-		rel = relation_open(relid, AccessShareLock);
-		tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
-		natts = tupdesc->natts;
-		relation_close(rel, AccessShareLock);
+		Oid			relid = typeidTypeRelid(typeoid);
 
-		/* check to see if we've given column aliases */
-		if(colaliases != NIL)
+		if (OidIsValid(relid))
 		{
-			char	   *label;
-			int			varattno;
+			Relation	rel;
+			int			natts;
 
-			/* does the List length match the number of attributes */
-			if (length(colaliases) != natts)
-				elog(ERROR, "TypeGetTupleDesc: number of aliases does not match number of attributes");
+			rel = relation_open(relid, AccessShareLock);
+			tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+			natts = tupdesc->natts;
+			relation_close(rel, AccessShareLock);
 
-			/* OK, use the aliases instead */
-			for (varattno = 0; varattno < natts; varattno++)
+			/* check to see if we've given column aliases */
+			if(colaliases != NIL)
 			{
-				label = strVal(nth(varattno, colaliases));
-
-				if (label != NULL)
-					namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
-				else
-					MemSet(NameStr(tupdesc->attrs[varattno]->attname), 0, NAMEDATALEN);
+				char	   *label;
+				int			varattno;
+
+				/* does the List length match the number of attributes */
+				if (length(colaliases) != natts)
+					elog(ERROR, "TypeGetTupleDesc: number of aliases does not match number of attributes");
+
+				/* OK, use the aliases instead */
+				for (varattno = 0; varattno < natts; varattno++)
+				{
+					label = strVal(nth(varattno, colaliases));
+
+					if (label != NULL)
+						namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
+					else
+						MemSet(NameStr(tupdesc->attrs[varattno]->attname), 0, NAMEDATALEN);
+				}
 			}
 		}
+		else
+			elog(ERROR, "Invalid return relation specified for function");
 	}
-	else
+	else if (functyptype == 'b')
 	{
 		/* Must be a base data type, i.e. scalar */
 		char	   *attname;
@@ -664,6 +672,11 @@ TypeGetTupleDesc(Oid typeoid, List *colaliases)
 						   0,
 						   false);
 	}
+	else if (functyptype == 'p' && typeoid == RECORDOID)
+		elog(ERROR, "Unable to determine tuple description for function"
+						" returning \"record\"");
+	else
+		elog(ERROR, "Unknown kind of return type specified for function");
 
 	return tupdesc;
 }
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 2624fdaf25dd5baf43068cb5532b59b1b673897c..f2d8e70589ce909782893a51773bcaf18303d52f 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.82 2002/08/02 18:15:05 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.83 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,6 +25,7 @@
 #include "miscadmin.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
@@ -33,7 +34,7 @@
 #include "utils/syscache.h"
 
 
-static void checkretval(Oid rettype, List *queryTreeList);
+static void checkretval(Oid rettype, char fn_typtype, List *queryTreeList);
 Datum fmgr_internal_validator(PG_FUNCTION_ARGS);
 Datum fmgr_c_validator(PG_FUNCTION_ARGS);
 Datum fmgr_sql_validator(PG_FUNCTION_ARGS);
@@ -367,94 +368,113 @@ checkretval(Oid rettype, List *queryTreeList)
 	 */
 	tlistlen = ExecCleanTargetListLength(tlist);
 
-	/*
-	 * For base-type returns, the target list should have exactly one
-	 * entry, and its type should agree with what the user declared. (As
-	 * of Postgres 7.2, we accept binary-compatible types too.)
-	 */
 	typerelid = typeidTypeRelid(rettype);
-	if (typerelid == InvalidOid)
+
+	if (fn_typtype == 'b')
 	{
-		if (tlistlen != 1)
-			elog(ERROR, "function declared to return %s returns multiple columns in final SELECT",
-				 format_type_be(rettype));
+		/*
+		 * For base-type returns, the target list should have exactly one
+		 * entry, and its type should agree with what the user declared. (As
+		 * of Postgres 7.2, we accept binary-compatible types too.)
+		 */
 
-		restype = ((TargetEntry *) lfirst(tlist))->resdom->restype;
-		if (!IsBinaryCompatible(restype, rettype))
-			elog(ERROR, "return type mismatch in function: declared to return %s, returns %s",
-				 format_type_be(rettype), format_type_be(restype));
+		if (typerelid == InvalidOid)
+		{
+			if (tlistlen != 1)
+				elog(ERROR, "function declared to return %s returns multiple columns in final SELECT",
+					 format_type_be(rettype));
 
-		return;
-	}
+			restype = ((TargetEntry *) lfirst(tlist))->resdom->restype;
+			if (!IsBinaryCompatible(restype, rettype))
+				elog(ERROR, "return type mismatch in function: declared to return %s, returns %s",
+					 format_type_be(rettype), format_type_be(restype));
 
-	/*
-	 * 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 *) lfirst(tlist))->resdom->restype;
-		if (IsBinaryCompatible(restype, rettype))
 			return;
-	}
-
-	/*
-	 * By here, the procedure returns a tuple or set of tuples.  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.
-	 */
-	reln = heap_open(typerelid, AccessShareLock);
-	relnatts = reln->rd_rel->relnatts;
-	rellogcols = 0;				/* we'll count nondeleted cols as we go */
-	colindex = 0;
+		}
 
-	foreach(tlistitem, tlist)
+		/*
+		 * 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 *) lfirst(tlist))->resdom->restype;
+			if (IsBinaryCompatible(restype, rettype))
+				return;
+		}
+	}
+	else if (fn_typtype == 'c')
 	{
-		TargetEntry *tle = (TargetEntry *) lfirst(tlistitem);
-		Form_pg_attribute attr;
-		Oid			tletype;
-		Oid			atttype;
+		/*
+		 * By here, the procedure returns a tuple or set of tuples.  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.
+		 */
+		reln = heap_open(typerelid, AccessShareLock);
+		relnatts = reln->rd_rel->relnatts;
+		rellogcols = 0;				/* we'll count nondeleted cols as we go */
+		colindex = 0;
+
+		foreach(tlistitem, tlist)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(tlistitem);
+			Form_pg_attribute attr;
+			Oid			tletype;
+			Oid			atttype;
+
+			if (tle->resdom->resjunk)
+				continue;
+
+			do {
+				colindex++;
+				if (colindex > relnatts)
+					elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
+						 format_type_be(rettype), rellogcols);
+				attr = reln->rd_att->attrs[colindex - 1];
+			} while (attr->attisdropped);
+			rellogcols++;
 
-		if (tle->resdom->resjunk)
-			continue;
+			tletype = exprType(tle->expr);
+			atttype = attr->atttypid;
+			if (!IsBinaryCompatible(tletype, atttype))
+				elog(ERROR, "function declared to return %s returns %s instead of %s at column %d",
+					 format_type_be(rettype),
+					 format_type_be(tletype),
+					 format_type_be(atttype),
+					 rellogcols);
+		}
 
-		do {
+		for (;;)
+		{
 			colindex++;
 			if (colindex > relnatts)
-				elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
-					 format_type_be(rettype), rellogcols);
-			attr = reln->rd_att->attrs[colindex - 1];
-		} while (attr->attisdropped);
-		rellogcols++;
-
-		tletype = exprType(tle->expr);
-		atttype = attr->atttypid;
-		if (!IsBinaryCompatible(tletype, atttype))
-			elog(ERROR, "function declared to return %s returns %s instead of %s at column %d",
-				 format_type_be(rettype),
-				 format_type_be(tletype),
-				 format_type_be(atttype),
-				 rellogcols);
-	}
+				break;
+			if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
+				rellogcols++;
+		}
 
-	for (;;)
-	{
-		colindex++;
-		if (colindex > relnatts)
-			break;
-		if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
-			rellogcols++;
-	}
+		if (tlistlen != rellogcols)
+			elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
+				 format_type_be(rettype), rellogcols);
 
-	if (tlistlen != rellogcols)
-		elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
-			 format_type_be(rettype), rellogcols);
+		heap_close(reln, AccessShareLock);
 
-	heap_close(reln, AccessShareLock);
+		return;
+	}
+	else if (fn_typtype == 'p' && rettype == RECORDOID)
+	{
+		/*
+		 * For RECORD return type, defer this check until we get the
+		 * first tuple.
+		 */
+		return;
+	}
+	else
+		elog(ERROR, "Unknown kind of return type specified for function");
 }
 
 
@@ -553,6 +573,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
 	bool		isnull;
 	Datum		tmp;
 	char	   *prosrc;
+	char		functyptype;
 
 	tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
 	if (!HeapTupleIsValid(tuple))
@@ -569,8 +590,11 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
 
 	prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
 
+	/* check typtype to see if we have a predetermined return type */
+	functyptype = typeid_get_typtype(proc->prorettype);
+
 	querytree_list = pg_parse_and_rewrite(prosrc, proc->proargtypes, proc->pronargs);
-	checkretval(proc->prorettype, querytree_list);
+	checkretval(proc->prorettype, functyptype, querytree_list);
 
 	ReleaseSysCache(tuple);
 	PG_RETURN_BOOL(true);
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index c7b7c398eb27344df9ff7264a841b925c7c4b96d..ab414e1958130e38765e9608cd1ef563fec68038 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.52 2002/06/20 20:29:28 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.53 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -194,7 +194,8 @@ init_sql_fcache(FmgrInfo *finfo)
 	 * get the type length and by-value flag from the type tuple
 	 */
 	fcache->typlen = typeStruct->typlen;
-	if (typeStruct->typrelid == InvalidOid)
+
+	if (typeStruct->typtype == 'b')
 	{
 		/* The return type is not a relation, so just use byval */
 		fcache->typbyval = typeStruct->typbyval;
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index d87f8e6fe887abb7c696ab9454a19e19c90d5980..66c418c8ba62194da915127a988ce39745f64daa 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.3 2002/07/20 05:16:58 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.4 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,6 +31,7 @@
 #include "executor/nodeFunctionscan.h"
 #include "parser/parsetree.h"
 #include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "storage/lmgr.h"
 #include "tcop/pquery.h"
@@ -39,14 +40,11 @@
 #include "utils/tuplestore.h"
 
 static TupleTableSlot *FunctionNext(FunctionScan *node);
-static TupleTableSlot *function_getonetuple(TupleTableSlot *slot,
-											Node *expr,
-											ExprContext *econtext,
-											TupleDesc tupdesc,
-											bool returnsTuple,
+static TupleTableSlot *function_getonetuple(FunctionScanState *scanstate,
 											bool *isNull,
 											ExprDoneCond *isDone);
 static FunctionMode get_functionmode(Node *expr);
+static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
 
 /* ----------------------------------------------------------------
  *						Scan Support
@@ -62,9 +60,6 @@ static TupleTableSlot *
 FunctionNext(FunctionScan *node)
 {
 	TupleTableSlot	   *slot;
-	Node			   *expr;
-	ExprContext		   *econtext;
-	TupleDesc			tupdesc;
 	EState			   *estate;
 	ScanDirection		direction;
 	Tuplestorestate	   *tuplestorestate;
@@ -78,11 +73,8 @@ FunctionNext(FunctionScan *node)
 	scanstate = (FunctionScanState *) node->scan.scanstate;
 	estate = node->scan.plan.state;
 	direction = estate->es_direction;
-	econtext = scanstate->csstate.cstate.cs_ExprContext;
 
 	tuplestorestate = scanstate->tuplestorestate;
-	tupdesc = scanstate->tupdesc;
-	expr = scanstate->funcexpr;
 
 	/*
 	 * If first time through, read all tuples from function and pass them to
@@ -108,10 +100,7 @@ FunctionNext(FunctionScan *node)
 
 			isNull = false;
 			isDone = ExprSingleResult;
-			slot = function_getonetuple(scanstate->csstate.css_ScanTupleSlot,
-										expr, econtext, tupdesc,
-										scanstate->returnsTuple,
-										&isNull, &isDone);
+			slot = function_getonetuple(scanstate, &isNull, &isDone);
 			if (TupIsNull(slot))
 				break;
 
@@ -169,7 +158,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
 	RangeTblEntry	   *rte;
 	Oid					funcrettype;
 	Oid					funcrelid;
-	TupleDesc			tupdesc;
+	char				functyptype;
+	TupleDesc			tupdesc = NULL;
 
 	/*
 	 * FunctionScan should not have any children.
@@ -209,25 +199,36 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
 	rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
 	Assert(rte->rtekind == RTE_FUNCTION);
 	funcrettype = exprType(rte->funcexpr);
-	funcrelid = typeidTypeRelid(funcrettype);
+
+	/*
+	 * Now determine if the function returns a simple or composite type,
+	 * and check/add column aliases.
+	 */
+	functyptype = typeid_get_typtype(funcrettype);
 
 	/*
 	 * Build a suitable tupledesc representing the output rows
 	 */
-	if (OidIsValid(funcrelid))
+	if (functyptype == 'c')
 	{
-		/*
-		 * Composite data type, i.e. a table's row type
-		 * Same as ordinary relation RTE
-		 */
-		Relation	rel;
+		funcrelid = typeidTypeRelid(funcrettype);
+		if (OidIsValid(funcrelid))
+		{
+			/*
+			 * Composite data type, i.e. a table's row type
+			 * Same as ordinary relation RTE
+			 */
+			Relation	rel;
 
-		rel = relation_open(funcrelid, AccessShareLock);
-		tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
-		relation_close(rel, AccessShareLock);
-		scanstate->returnsTuple = true;
+			rel = relation_open(funcrelid, AccessShareLock);
+			tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+			relation_close(rel, AccessShareLock);
+			scanstate->returnsTuple = true;
+		}
+		else
+			elog(ERROR, "Invalid return relation specified for function");
 	}
-	else
+	else if (functyptype == 'b')
 	{
 		/*
 		 * Must be a base data type, i.e. scalar
@@ -244,6 +245,21 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
 						   false);
 		scanstate->returnsTuple = false;
 	}
+	else if (functyptype == 'p' && funcrettype == RECORDOID)
+	{
+		/*
+		 * Must be a pseudo type, i.e. record
+		 */
+		List *coldeflist = rte->coldeflist;
+
+		tupdesc = BuildDescForRelation(coldeflist);
+		scanstate->returnsTuple = true;
+	}
+	else
+		elog(ERROR, "Unknown kind of return type specified for function");
+
+	scanstate->fn_typeid = funcrettype;
+	scanstate->fn_typtype = functyptype;
 	scanstate->tupdesc = tupdesc;
 	ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot,
 						  tupdesc, false);
@@ -404,17 +420,20 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent)
  * Run the underlying function to get the next tuple
  */
 static TupleTableSlot *
-function_getonetuple(TupleTableSlot *slot,
-					 Node *expr,
-					 ExprContext *econtext,
-					 TupleDesc tupdesc,
-					 bool returnsTuple,
+function_getonetuple(FunctionScanState *scanstate,
 					 bool *isNull,
 					 ExprDoneCond *isDone)
 {
-	HeapTuple			tuple;
-	Datum				retDatum;
-	char				nullflag;
+	HeapTuple		tuple;
+	Datum			retDatum;
+	char			nullflag;
+	TupleDesc		tupdesc = scanstate->tupdesc;
+	bool			returnsTuple = scanstate->returnsTuple;
+	Node		   *expr = scanstate->funcexpr;
+	Oid				fn_typeid = scanstate->fn_typeid;
+	char			fn_typtype = scanstate->fn_typtype;
+	ExprContext	   *econtext = scanstate->csstate.cstate.cs_ExprContext;
+	TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
 
 	/*
 	 * get the next Datum from the function
@@ -435,6 +454,16 @@ function_getonetuple(TupleTableSlot *slot,
 			 * function returns pointer to tts??
 			 */
 			slot = (TupleTableSlot *) retDatum;
+
+			/*
+			 * if function return type was RECORD, we need to check to be
+			 * sure the structure from the query matches the actual return
+			 * structure
+			 */
+			if (fn_typtype == 'p' && fn_typeid == RECORDOID)
+				if (tupledesc_mismatch(tupdesc, slot->ttc_tupleDescriptor))
+					elog(ERROR, "Query specified return tuple and actual"
+									" function return tuple do not match");
 		}
 		else
 		{
@@ -467,3 +496,26 @@ get_functionmode(Node *expr)
 	 */
 	return PM_REPEATEDCALL;
 }
+
+static bool
+tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
+{
+	int			i;
+
+	if (tupdesc1->natts != tupdesc2->natts)
+		return true;
+
+	for (i = 0; i < tupdesc1->natts; i++)
+	{
+		Form_pg_attribute attr1 = tupdesc1->attrs[i];
+		Form_pg_attribute attr2 = tupdesc2->attrs[i];
+
+		/*
+		 * We really only care about number of attributes and data type
+		 */
+		if (attr1->atttypid != attr2->atttypid)
+			return true;
+	}
+
+	return false;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d7aefc9acfefda4528e56ac43f818eef9aa271e7..954a372181a927691d0eb18676138915f9f0d8f9 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.199 2002/08/04 04:31:44 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.200 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1482,6 +1482,7 @@ _copyRangeTblEntry(RangeTblEntry *from)
 	newnode->relid = from->relid;
 	Node_Copy(from, newnode, subquery);
 	Node_Copy(from, newnode, funcexpr);
+	Node_Copy(from, newnode, coldeflist);
 	newnode->jointype = from->jointype;
 	Node_Copy(from, newnode, joinaliasvars);
 	Node_Copy(from, newnode, alias);
@@ -1707,6 +1708,7 @@ _copyRangeFunction(RangeFunction *from)
 
 	Node_Copy(from, newnode, funccallnode);
 	Node_Copy(from, newnode, alias);
+	Node_Copy(from, newnode, coldeflist);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9b655640b0dcbf99178fadb2f9ebd1f220b1e697..da7567e7c335e84961420cc41858247c00a4ef6b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.146 2002/08/04 04:31:44 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.147 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1607,6 +1607,8 @@ _equalRangeVar(RangeVar *a, RangeVar *b)
 		return false;
 	if (!equal(a->alias, b->alias))
 		return false;
+	if (!equal(a->coldeflist, b->coldeflist))
+		return false;
 
 	return true;
 }
@@ -1742,6 +1744,8 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
 		return false;
 	if (!equal(a->funcexpr, b->funcexpr))
 		return false;
+	if (!equal(a->coldeflist, b->coldeflist))
+		return false;
 	if (a->jointype != b->jointype)
 		return false;
 	if (!equal(a->joinaliasvars, b->joinaliasvars))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c4dfbec67d0b6335d651eb95b4e63536e09b6144..b992e45a62fa97db1069a0e55b554c1ee518cf32 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.165 2002/07/18 17:14:19 momjian Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.166 2002/08/04 19:48:09 momjian Exp $
  *
  * NOTES
  *	  Every (plan) node in POSTGRES has an associated "out" routine which
@@ -1004,6 +1004,8 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
 		case RTE_FUNCTION:
 			appendStringInfo(str, ":funcexpr ");
 			_outNode(str, node->funcexpr);
+			appendStringInfo(str, ":coldeflist ");
+			_outNode(str, node->coldeflist);
 			break;
 		case RTE_JOIN:
 			appendStringInfo(str, ":jointype %d :joinaliasvars ",
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ba9eb0449ee3d9776e7b60141bc6f4f6b9be29eb..46b2ca2dc85699e9fb8c53224fc073df4cb868b1 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.126 2002/07/18 17:14:19 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.127 2002/08/04 19:48:09 momjian Exp $
  *
  * NOTES
  *	  Most of the read functions for plan nodes are tested. (In fact, they
@@ -1545,6 +1545,10 @@ _readRangeTblEntry(void)
 		case RTE_FUNCTION:
 			token = pg_strtok(&length); /* eat :funcexpr */
 			local_node->funcexpr = nodeRead(true);		/* now read it */
+
+			token = pg_strtok(&length); /* eat :coldeflist */
+			local_node->coldeflist = nodeRead(true);	/* now read it */
+
 			break;
 
 		case RTE_JOIN:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c794dd7d80e1a34afeeb52a2fc9f186d8074ac74..53759b8029658a1de0baf48ac323a499d4a12c00 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.354 2002/08/04 06:51:23 thomas Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.355 2002/08/04 19:48:09 momjian Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -215,7 +215,8 @@ static void doNegateFloat(Value *v);
 				target_list, update_target_list, insert_column_list,
 				insert_target_list, def_list, opt_indirection,
 				group_clause, TriggerFuncArgs, select_limit,
-				opt_select_limit, opclass_item_list, trans_options
+				opt_select_limit, opclass_item_list, trans_options,
+				tableFuncElementList
 
 %type <range>	into_clause, OptTempTableName
 
@@ -256,8 +257,8 @@ static void doNegateFloat(Value *v);
 
 %type <vsetstmt> set_rest
 
-%type <node>	OptTableElement, ConstraintElem
-%type <node>	columnDef
+%type <node>	OptTableElement, ConstraintElem, tableFuncElement
+%type <node>	columnDef, tableFuncColumnDef
 %type <defelt>	def_elem
 %type <node>	def_arg, columnElem, where_clause, insert_column_item,
 				a_expr, b_expr, c_expr, r_expr, AexprConst,
@@ -4448,6 +4449,34 @@ table_ref:	relation_expr
 				{
 					RangeFunction *n = makeNode(RangeFunction);
 					n->funccallnode = $1;
+					n->coldeflist = NIL;
+					$$ = (Node *) n;
+				}
+			| func_table AS '(' tableFuncElementList ')'
+				{
+					RangeFunction *n = makeNode(RangeFunction);
+					n->funccallnode = $1;
+					n->coldeflist = $4;
+					$$ = (Node *) n;
+				}
+			| func_table AS ColId '(' tableFuncElementList ')'
+				{
+					RangeFunction *n = makeNode(RangeFunction);
+					Alias *a = makeNode(Alias);
+					n->funccallnode = $1;
+					a->aliasname = $3;
+					n->alias = a;
+					n->coldeflist = $5;
+					$$ = (Node *) n;
+				}
+			| func_table ColId '(' tableFuncElementList ')'
+				{
+					RangeFunction *n = makeNode(RangeFunction);
+					Alias *a = makeNode(Alias);
+					n->funccallnode = $1;
+					a->aliasname = $2;
+					n->alias = a;
+					n->coldeflist = $4;
 					$$ = (Node *) n;
 				}
 			| func_table alias_clause
@@ -4455,6 +4484,7 @@ table_ref:	relation_expr
 					RangeFunction *n = makeNode(RangeFunction);
 					n->funccallnode = $1;
 					n->alias = $2;
+					n->coldeflist = NIL;
 					$$ = (Node *) n;
 				}
 			| select_with_parens
@@ -4703,6 +4733,39 @@ where_clause:
 		;
 
 
+tableFuncElementList:
+			tableFuncElementList ',' tableFuncElement
+				{
+					if ($3 != NULL)
+						$$ = lappend($1, $3);
+					else
+						$$ = $1;
+				}
+			| tableFuncElement
+				{
+					if ($1 != NULL)
+						$$ = makeList1($1);
+					else
+						$$ = NIL;
+				}
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
+tableFuncElement:
+			tableFuncColumnDef					{ $$ = $1; }
+		;
+
+tableFuncColumnDef:	ColId Typename
+				{
+					ColumnDef *n = makeNode(ColumnDef);
+					n->colname = $1;
+					n->typename = $2;
+					n->constraints = NIL;
+
+					$$ = (Node *)n;
+				}
+		;
+
 /*****************************************************************************
  *
  *	Type syntax
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 81328798eb57529f3efaa1db735273dbe04fb0bb..ae87b056f3e8bd0fc531a81751a1dfba97e2a141 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.94 2002/06/20 20:29:32 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.95 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -515,7 +515,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
 	 * OK, build an RTE for the function.
 	 */
 	rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
-										r->alias, true);
+										r, true);
 
 	/*
 	 * We create a RangeTblRef, but we do not add it to the joinlist or
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 99b639d73e37be865e2590d6644daec224ed60bd..6a4bda5d62bc5b95f135d66ae7b3456a1d563d7b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.71 2002/08/02 18:15:07 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.72 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -699,12 +699,14 @@ RangeTblEntry *
 addRangeTableEntryForFunction(ParseState *pstate,
 							  char *funcname,
 							  Node *funcexpr,
-							  Alias *alias,
+							  RangeFunction *rangefunc,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
 	Oid			funcrettype = exprType(funcexpr);
-	Oid			funcrelid;
+	char		functyptype;
+	Alias	   *alias = rangefunc->alias;
+	List	   *coldeflist = rangefunc->coldeflist;
 	Alias	   *eref;
 	int			numaliases;
 	int			varattno;
@@ -713,6 +715,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	rte->relid = InvalidOid;
 	rte->subquery = NULL;
 	rte->funcexpr = funcexpr;
+	rte->coldeflist = coldeflist;
 	rte->alias = alias;
 
 	eref = alias ? (Alias *) copyObject(alias) : makeAlias(funcname, NIL);
@@ -724,47 +727,56 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	 * Now determine if the function returns a simple or composite type,
 	 * and check/add column aliases.
 	 */
-	funcrelid = typeidTypeRelid(funcrettype);
+	functyptype = typeid_get_typtype(funcrettype);
 
-	if (OidIsValid(funcrelid))
+	if (functyptype == 'c')
 	{
 		/*
-		 * Composite data type, i.e. a table's row type
-		 *
-		 * Get the rel's relcache entry.  This access ensures that we have an
-		 * up-to-date relcache entry for the rel.
+		 * Named composite data type, i.e. a table's row type
 		 */
-		Relation	rel;
-		int			maxattrs;
+		Oid			funcrelid = typeidTypeRelid(funcrettype);
 
-		rel = heap_open(funcrelid, AccessShareLock);
+		if (OidIsValid(funcrelid))
+		{
+			/*
+			 * Get the rel's relcache entry.  This access ensures that we have an
+			 * up-to-date relcache entry for the rel.
+			 */
+			Relation	rel;
+			int			maxattrs;
 
-		/*
-		 * Since the rel is open anyway, let's check that the number of column
-		 * aliases is reasonable.
-		 */
-		maxattrs = RelationGetNumberOfAttributes(rel);
-		if (maxattrs < numaliases)
-			elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
-				 RelationGetRelationName(rel), maxattrs, numaliases);
+			rel = heap_open(funcrelid, AccessShareLock);
 
-		/* fill in alias columns using actual column names */
-		for (varattno = numaliases; varattno < maxattrs; varattno++)
-		{
-			char	   *attrname;
+			/*
+			 * Since the rel is open anyway, let's check that the number of column
+			 * aliases is reasonable.
+			 */
+			maxattrs = RelationGetNumberOfAttributes(rel);
+			if (maxattrs < numaliases)
+				elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
+					 RelationGetRelationName(rel), maxattrs, numaliases);
 
-			attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
-			eref->colnames = lappend(eref->colnames, makeString(attrname));
-		}
+			/* fill in alias columns using actual column names */
+			for (varattno = numaliases; varattno < maxattrs; varattno++)
+			{
+				char	   *attrname;
 
-		/*
-		 * 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.
-		 */
-		heap_close(rel, NoLock);
+				attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
+				eref->colnames = lappend(eref->colnames, makeString(attrname));
+			}
+
+			/*
+			 * 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.
+			 */
+			heap_close(rel, NoLock);
+		}
+		else
+			elog(ERROR, "Invalid return relation specified for function %s",
+				 funcname);
 	}
-	else
+	else if (functyptype == 'b')
 	{
 		/*
 		 * Must be a base data type, i.e. scalar.
@@ -776,6 +788,22 @@ addRangeTableEntryForFunction(ParseState *pstate,
 		if (numaliases == 0)
 			eref->colnames = makeList1(makeString(funcname));
 	}
+	else if (functyptype == 'p' && funcrettype == RECORDOID)
+	{
+		List	   *col;
+
+		foreach(col, coldeflist)
+		{
+			char	   *attrname;
+			ColumnDef  *n = lfirst(col);
+
+			attrname = pstrdup(n->colname);
+			eref->colnames = lappend(eref->colnames, makeString(attrname));
+		}
+	}
+	else
+		elog(ERROR, "Unknown kind of return type specified for function %s",
+			 funcname);
 
 	/*----------
 	 * Flags:
@@ -1051,56 +1079,70 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
 		case RTE_FUNCTION:
 			{
 				/* Function RTE */
-				Oid			funcrettype = exprType(rte->funcexpr);
-				Oid			funcrelid = typeidTypeRelid(funcrettype);
+				Oid	funcrettype = exprType(rte->funcexpr);
+				char functyptype = typeid_get_typtype(funcrettype);
+				List *coldeflist = rte->coldeflist;
 
-				if (OidIsValid(funcrelid))
+				/*
+				 * Build a suitable tupledesc representing the output rows
+				 */
+				if (functyptype == 'c')
 				{
-					/*
-					 * Composite data type, i.e. a table's row type
-					 * Same as ordinary relation RTE
-					 */
-					Relation	rel;
-					int			maxattrs;
-					int			numaliases;
-
-					rel = heap_open(funcrelid, AccessShareLock);
-					maxattrs = RelationGetNumberOfAttributes(rel);
-					numaliases = length(rte->eref->colnames);
-
-					for (varattno = 0; varattno < maxattrs; varattno++)
+					Oid	funcrelid = typeidTypeRelid(funcrettype);
+					if (OidIsValid(funcrelid))
 					{
-						Form_pg_attribute attr = rel->rd_att->attrs[varattno];
 
-						if (attr->attisdropped)
-							continue;
+						/*
+						 * Composite data type, i.e. a table's row type
+						 * Same as ordinary relation RTE
+						 */
+						Relation	rel;
+						int			maxattrs;
+						int			numaliases;
 
-						if (colnames)
-						{
-							char	   *label;
+						rel = heap_open(funcrelid, AccessShareLock);
+						maxattrs = RelationGetNumberOfAttributes(rel);
+						numaliases = length(rte->eref->colnames);
 
-							if (varattno < numaliases)
-								label = strVal(nth(varattno, rte->eref->colnames));
-							else
-								label = NameStr(attr->attname);
-							*colnames = lappend(*colnames, makeString(pstrdup(label)));
-						}
-
-						if (colvars)
+						for (varattno = 0; varattno < maxattrs; varattno++)
 						{
-							Var		   *varnode;
-
-							varnode = makeVar(rtindex, attr->attnum,
-											  attr->atttypid, attr->atttypmod,
-											  sublevels_up);
-
-							*colvars = lappend(*colvars, varnode);
+							Form_pg_attribute attr = rel->rd_att->attrs[varattno];
+
+							if (attr->attisdropped)
+								continue;
+
+							if (colnames)
+							{
+								char	   *label;
+
+								if (varattno < numaliases)
+									label = strVal(nth(varattno, rte->eref->colnames));
+								else
+									label = NameStr(attr->attname);
+								*colnames = lappend(*colnames, makeString(pstrdup(label)));
+							}
+
+							if (colvars)
+							{
+								Var		   *varnode;
+
+								varnode = makeVar(rtindex,
+												attr->attnum,
+												attr->atttypid,
+												attr->atttypmod,
+												sublevels_up);
+
+								*colvars = lappend(*colvars, varnode);
+							}
 						}
-					}
 
-					heap_close(rel, AccessShareLock);
+						heap_close(rel, AccessShareLock);
+					}
+					else
+						elog(ERROR, "Invalid return relation specified"
+									" for function");
 				}
-				else
+				else if (functyptype == 'b')
 				{
 					/*
 					 * Must be a base data type, i.e. scalar
@@ -1120,6 +1162,47 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
 						*colvars = lappend(*colvars, varnode);
 					}
 				}
+				else if (functyptype == 'p' && funcrettype == RECORDOID)
+				{
+					List	   *col;
+					int			attnum = 0;
+
+					foreach(col, coldeflist)
+					{
+						ColumnDef  *colDef = lfirst(col);
+
+						attnum++;
+						if (colnames)
+						{
+							char	   *attrname;
+
+							attrname = pstrdup(colDef->colname);
+							*colnames = lappend(*colnames, makeString(attrname));
+						}
+
+						if (colvars)
+						{
+							Var		   *varnode;
+							HeapTuple	typeTuple;
+							Oid			atttypid;
+
+							typeTuple = typenameType(colDef->typename);
+							atttypid = HeapTupleGetOid(typeTuple);
+							ReleaseSysCache(typeTuple);
+
+							varnode = makeVar(rtindex,
+											attnum,
+											atttypid,
+											-1,
+											sublevels_up);
+
+							*colvars = lappend(*colvars, varnode);
+						}
+					}
+				}
+				else
+					elog(ERROR, "Unknown kind of return type specified"
+								" for function");
 			}
 			break;
 		case RTE_JOIN:
@@ -1308,40 +1391,52 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 		case RTE_FUNCTION:
 			{
 				/* Function RTE */
-				Oid			funcrettype = exprType(rte->funcexpr);
-				Oid			funcrelid = typeidTypeRelid(funcrettype);
+				Oid funcrettype = exprType(rte->funcexpr);
+				char functyptype = typeid_get_typtype(funcrettype);
+				List *coldeflist = rte->coldeflist;
 
-				if (OidIsValid(funcrelid))
+				/*
+				 * Build a suitable tupledesc representing the output rows
+				 */
+				if (functyptype == 'c')
 				{
 					/*
 					 * Composite data type, i.e. a table's row type
 					 * Same as ordinary relation RTE
 					 */
-					HeapTuple			tp;
-					Form_pg_attribute	att_tup;
+					Oid funcrelid = typeidTypeRelid(funcrettype);
 
-					tp = SearchSysCache(ATTNUM,
-										ObjectIdGetDatum(funcrelid),
-										Int16GetDatum(attnum),
-										0, 0);
-					/* this shouldn't happen... */
-					if (!HeapTupleIsValid(tp))
-						elog(ERROR, "Relation %s does not have attribute %d",
-							 get_rel_name(funcrelid), attnum);
-					att_tup = (Form_pg_attribute) GETSTRUCT(tp);
-					/*
-					 * If dropped column, pretend it ain't there.  See notes
-					 * in scanRTEForColumn.
-					 */
-					if (att_tup->attisdropped)
-						elog(ERROR, "Relation \"%s\" has no column \"%s\"",
-							 get_rel_name(funcrelid),
-							 NameStr(att_tup->attname));
-					*vartype = att_tup->atttypid;
-					*vartypmod = att_tup->atttypmod;
-					ReleaseSysCache(tp);
+					if (OidIsValid(funcrelid))
+					{
+						HeapTuple			tp;
+						Form_pg_attribute	att_tup;
+
+						tp = SearchSysCache(ATTNUM,
+											ObjectIdGetDatum(funcrelid),
+											Int16GetDatum(attnum),
+											0, 0);
+						/* this shouldn't happen... */
+						if (!HeapTupleIsValid(tp))
+							elog(ERROR, "Relation %s does not have attribute %d",
+								 get_rel_name(funcrelid), attnum);
+						att_tup = (Form_pg_attribute) GETSTRUCT(tp);
+						/*
+						 * If dropped column, pretend it ain't there.  See notes
+						 * in scanRTEForColumn.
+						 */
+						if (att_tup->attisdropped)
+							elog(ERROR, "Relation \"%s\" has no column \"%s\"",
+								 get_rel_name(funcrelid),
+								 NameStr(att_tup->attname));
+						*vartype = att_tup->atttypid;
+						*vartypmod = att_tup->atttypmod;
+						ReleaseSysCache(tp);
+					}
+					else
+						elog(ERROR, "Invalid return relation specified"
+									" for function");
 				}
-				else
+				else if (functyptype == 'b')
 				{
 					/*
 					 * Must be a base data type, i.e. scalar
@@ -1349,6 +1444,22 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 					*vartype = funcrettype;
 					*vartypmod = -1;
 				}
+				else if (functyptype == 'p' && funcrettype == RECORDOID)
+				{
+					ColumnDef  *colDef = nth(attnum - 1, coldeflist);
+					HeapTuple	typeTuple;
+					Oid			atttypid;
+
+					typeTuple = typenameType(colDef->typename);
+					atttypid = HeapTupleGetOid(typeTuple);
+					ReleaseSysCache(typeTuple);
+
+					*vartype = atttypid;
+					*vartypmod = -1;
+				}
+				else
+					elog(ERROR, "Unknown kind of return type specified"
+								" for function");
 			}
 			break;
 		case RTE_JOIN:
@@ -1576,3 +1687,28 @@ warnAutoRange(ParseState *pstate, RangeVar *relation)
 			 pstate->parentParseState != NULL ? " in subquery" : "",
 			 relation->relname);
 }
+
+char
+typeid_get_typtype(Oid typeid)
+{
+	HeapTuple		typeTuple;
+	Form_pg_type	typeStruct;
+	char			result;
+
+	/*
+	 * determine if the function returns a simple, named composite,
+	 * or anonymous composite type
+	 */
+ 	typeTuple = SearchSysCache(TYPEOID,
+ 							   ObjectIdGetDatum(typeid),
+ 							   0, 0, 0);
+ 	if (!HeapTupleIsValid(typeTuple))
+ 		elog(ERROR, "cache lookup for type %u failed", typeid);
+ 	typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
+ 
+	result = typeStruct->typtype;
+
+ 	ReleaseSysCache(typeTuple);
+
+	return result;
+}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index f3e36226b015a5dcb1981e2abd63435a8568321a..12669ed78ddc9b200bd909169d533e21ffea8758 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.144 2002/08/02 18:15:09 tgl Exp $
+ * $Id: catversion.h,v 1.145 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200208011
+#define CATALOG_VERSION_NO	200208041
 
 #endif
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 1f801611a0559c84447daba09e0c9c98d17a0749..6bc4a2a92aec82dd28183e4a1675b777c7165a47 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_type.h,v 1.125 2002/07/24 19:11:13 petere Exp $
+ * $Id: pg_type.h,v 1.126 2002/08/04 19:48:10 momjian Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -60,10 +60,10 @@ CATALOG(pg_type) BOOTSTRAP
 	bool		typbyval;
 
 	/*
-	 * typtype is 'b' for a basic type and 'c' for a catalog type (ie a
-	 * class). If typtype is 'c', typrelid is the OID of the class' entry
-	 * in pg_class. (Why do we need an entry in pg_type for classes,
-	 * anyway?)
+	 * typtype is 'b' for a basic type, 'c' for a catalog type (ie a
+	 * class), or 'p' for a pseudo type. If typtype is 'c', typrelid is the
+	 * OID of the class' entry in pg_class. (Why do we need an entry in
+	 * pg_type for classes, anyway?)
 	 */
 	char		typtype;
 
@@ -501,6 +501,16 @@ DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b t \054 0 2204 array_in
 DATA(insert OID = 2210 ( _regclass     PGNSP PGUID -1 f b t \054 0 2205 array_in array_out i x f 0 -1 0 _null_ _null_ ));
 DATA(insert OID = 2211 ( _regtype      PGNSP PGUID -1 f b t \054 0 2206 array_in array_out i x f 0 -1 0 _null_ _null_ ));
 
+/*
+ * pseudo-types 
+ *
+ * types with typtype='p' are special types that represent classes of types
+ * that are not easily defined in advance. Currently there is only one pseudo
+ * type -- record. The record type is used to specify that the value is a
+ * tuple, but of unknown structure until runtime. 
+ */
+DATA(insert OID = 2249 ( record        PGNSP PGUID  4 t p t \054 0 0 oidin oidout          i p f 0 -1 0 _null_ _null_ ));
+#define RECORDOID		2249
 
 /*
  * prototypes for functions in pg_type.c
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 2dea171e255e0672e44737c6b631809697d63fcb..ee25cc62c95a44e282e7019ec272fdf0689c37b0 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.70 2002/06/20 20:29:49 momjian Exp $
+ * $Id: execnodes.h,v 1.71 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -509,11 +509,17 @@ typedef struct SubqueryScanState
  *		Function nodes are used to scan the results of a
  *		function appearing in FROM (typically a function returning set).
  *
- *		functionmode			function operating mode:
+ *		functionmode		function operating mode:
  *							- repeated call
  *							- materialize
  *							- return query
+ *		tupdesc				function's return tuple description
  *		tuplestorestate		private state of tuplestore.c
+ *		funcexpr			function expression being evaluated
+ *		returnsTuple		does function return tuples?
+ *		fn_typeid			OID of function return type
+ *		fn_typtype			return Datum type, i.e. 'b'ase,
+ *							'c'atalog, or 'p'seudo
  * ----------------
  */
 typedef enum FunctionMode
@@ -525,12 +531,14 @@ typedef enum FunctionMode
 
 typedef struct FunctionScanState
 {
-	CommonScanState csstate;	/* its first field is NodeTag */
+	CommonScanState csstate;		/* its first field is NodeTag */
 	FunctionMode	functionmode;
 	TupleDesc		tupdesc;
 	void		   *tuplestorestate;
-	Node		   *funcexpr;	/* function expression being evaluated */
-	bool			returnsTuple; /* does function return tuples? */
+	Node		   *funcexpr;
+	bool			returnsTuple;
+	Oid				fn_typeid;
+	char			fn_typtype;
 } FunctionScanState;
 
 /* ----------------------------------------------------------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 07e985b377bb607b971b44714c9088afdc7137ae..765d1ca0514795c7498737b8647b87b9a1447240 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.197 2002/08/04 04:31:44 momjian Exp $
+ * $Id: parsenodes.h,v 1.198 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -400,6 +400,8 @@ typedef struct RangeFunction
 	NodeTag		type;
 	Node	   *funccallnode;	/* untransformed function call tree */
 	Alias	   *alias;			/* table alias & optional column aliases */
+	List	   *coldeflist;		/* list of ColumnDef nodes for runtime
+								 * assignment of RECORD TupleDesc */
 } RangeFunction;
 
 /*
@@ -527,6 +529,8 @@ typedef struct RangeTblEntry
 	 * Fields valid for a function RTE (else NULL):
 	 */
 	Node	   *funcexpr;		/* expression tree for func call */
+	List	   *coldeflist;		/* list of ColumnDef nodes for runtime
+								 * assignment of RECORD TupleDesc */
 
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index a34e2a5619f086cd642fd93a402093cf944319df..7abfd047865200366b801fc4ef116b15e686321e 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_relation.h,v 1.35 2002/08/02 18:15:09 tgl Exp $
+ * $Id: parse_relation.h,v 1.36 2002/08/04 19:48:11 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,7 +44,7 @@ extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
 extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
 													char *funcname,
 													Node *funcexpr,
-													Alias *alias,
+													RangeFunction *rangefunc,
 													bool inFromCl);
 extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
 						  List *colnames,
@@ -61,5 +61,6 @@ extern List *expandRelAttrs(ParseState *pstate, RangeTblEntry *rte);
 extern int	attnameAttNum(Relation rd, const char *attname, bool sysColOK);
 extern Name attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
+extern char typeid_get_typtype(Oid typeid);
 
 #endif   /* PARSE_RELATION_H */
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 711628c2e40380ace2d0807706e15c6c18dc4af1..949393386a4d400fda7195054f09c4c559e7b7f3 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -16,7 +16,7 @@
 SELECT p1.oid, p1.typname
 FROM pg_type as p1
 WHERE (p1.typlen <= 0 AND p1.typlen != -1) OR
-    (p1.typtype != 'b' AND p1.typtype != 'c') OR
+    (p1.typtype != 'b' AND p1.typtype != 'c' AND p1.typtype != 'p') OR
     NOT p1.typisdefined OR
     (p1.typalign != 'c' AND p1.typalign != 's' AND
      p1.typalign != 'i' AND p1.typalign != 'd') OR
@@ -60,7 +60,7 @@ WHERE (p1.typtype = 'c' AND p1.typrelid = 0) OR
 -- NOTE: as of 7.3, this check finds SET, smgr, and unknown.
 SELECT p1.oid, p1.typname
 FROM pg_type as p1
-WHERE p1.typtype != 'c' AND p1.typname NOT LIKE '\\_%' AND NOT EXISTS
+WHERE p1.typtype = 'b' AND p1.typname NOT LIKE '\\_%' AND NOT EXISTS
     (SELECT 1 FROM pg_type as p2
      WHERE p2.typname = ('_' || p1.typname)::name AND
            p2.typelem = p1.oid);
diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql
index 87bcad1979308fb78d164d4f275a73b182eea1f7..58019a15d25aa4d382b9707a28128fbfd1855e46 100644
--- a/src/test/regress/sql/type_sanity.sql
+++ b/src/test/regress/sql/type_sanity.sql
@@ -19,7 +19,7 @@
 SELECT p1.oid, p1.typname
 FROM pg_type as p1
 WHERE (p1.typlen <= 0 AND p1.typlen != -1) OR
-    (p1.typtype != 'b' AND p1.typtype != 'c') OR
+    (p1.typtype != 'b' AND p1.typtype != 'c' AND p1.typtype != 'p') OR
     NOT p1.typisdefined OR
     (p1.typalign != 'c' AND p1.typalign != 's' AND
      p1.typalign != 'i' AND p1.typalign != 'd') OR
@@ -55,7 +55,7 @@ WHERE (p1.typtype = 'c' AND p1.typrelid = 0) OR
 
 SELECT p1.oid, p1.typname
 FROM pg_type as p1
-WHERE p1.typtype != 'c' AND p1.typname NOT LIKE '\\_%' AND NOT EXISTS
+WHERE p1.typtype = 'b' AND p1.typname NOT LIKE '\\_%' AND NOT EXISTS
     (SELECT 1 FROM pg_type as p2
      WHERE p2.typname = ('_' || p1.typname)::name AND
            p2.typelem = p1.oid);