diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 83be70ca6667e37d45b2936251a5e254a785e94f..a7a3cea9068bc1ca5406fe47ef151e2fdaeb65b9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.170 2008/07/16 16:55:23 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.171 2008/07/18 03:32:51 tgl Exp $ -->
 <!--
  Documentation of the system catalogs, directed toward PostgreSQL developers
  -->
@@ -3680,7 +3680,8 @@
         <literal>i</literal> for <literal>IN</> arguments,
         <literal>o</literal> for <literal>OUT</> arguments,
         <literal>b</literal> for <literal>INOUT</> arguments,
-        <literal>v</literal> for <literal>VARIADIC</> arguments.
+        <literal>v</literal> for <literal>VARIADIC</> arguments,
+        <literal>t</literal> for <literal>TABLE</> arguments.
         If all the arguments are <literal>IN</literal> arguments,
         this field will be null.
         Note that subscripts correspond to positions of
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index acd2b92918ef78f8084a7dcab7d5123212b15f9e..fb77ae43ea453f164e515a86ad5b13454d9508d7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.441 2008/07/16 00:48:53 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.442 2008/07/18 03:32:51 tgl Exp $ -->
 
  <chapter id="functions">
   <title>Functions and Operators</title>
@@ -11563,6 +11563,14 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
     <primary>pg_get_ruledef</primary>
    </indexterm>
 
+   <indexterm>
+    <primary>pg_get_function_arguments</primary>
+   </indexterm>
+
+   <indexterm>
+    <primary>pg_get_function_result</primary>
+   </indexterm>
+
    <indexterm>
     <primary>pg_get_indexdef</primary>
    </indexterm>
@@ -11636,6 +11644,16 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
        <entry>decompile internal form of an expression, assuming that any Vars
        in it refer to the relation indicated by the second parameter</entry>
       </row>
+      <row>
+       <entry><literal><function>pg_get_function_arguments</function>(<parameter>func_oid</parameter>)</literal></entry>
+       <entry><type>text</type></entry>
+       <entry>get argument list for function</entry>
+      </row>
+      <row>
+       <entry><literal><function>pg_get_function_result</function>(<parameter>func_oid</parameter>)</literal></entry>
+       <entry><type>text</type></entry>
+       <entry>get <literal>RETURNS</> clause for function</entry>
+      </row>
       <row>
        <entry><literal><function>pg_get_indexdef</function>(<parameter>index_oid</parameter>)</literal></entry>
        <entry><type>text</type></entry>
@@ -11738,6 +11756,14 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
    the same result as the variant that does not have the parameter at all.
   </para>
 
+  <para>
+   <function>pg_get_function_arguments</function> returns the argument list
+   of a function, in the form it would need to appear in within
+   <command>CREATE FUNCTION</>.
+   <function>pg_get_function_result</function> similarly returns the
+   appropriate <literal>RETURNS</> clause for the function.
+  </para>
+
   <para>
    <function>pg_get_serial_sequence</function> returns the name of the
    sequence associated with a column, or NULL if no sequence is associated
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 42bd6048b68758c4278027b5ba88033136300856..e47142078c0eefa4b376bb718c618b1c0f9932f9 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.132 2008/07/16 01:30:21 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.133 2008/07/18 03:32:51 tgl Exp $ -->
 
 <chapter id="plpgsql">
   <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@@ -157,6 +157,8 @@
      parameters in place of an explicit specification of the return type.
      This does not add any fundamental capability to the language, but
      it is often convenient, especially for returning multiple values.
+     The <literal>RETURNS TABLE</> notation can also be used in place
+     of <literal>RETURNS SETOF</>.
     </para>
 
     <para>
@@ -468,6 +470,23 @@ $$ LANGUAGE plpgsql;
       <literal>RETURNS record</>.
      </para>
 
+     <para>
+      Another way to declare a <application>PL/pgSQL</application> function
+      is with <literal>RETURNS TABLE</>, for example:
+
+<programlisting>
+CREATE FUNCTION extended_sales(p_itemno int) RETURNS TABLE(quantity int, total numeric) AS $$
+BEGIN
+    RETURN QUERY SELECT quantity, quantity * price FROM sales WHERE itemno = p_itemno;
+END;
+$$ LANGUAGE plpgsql;
+</programlisting>
+
+      This is exactly equivalent to declaring one or more <literal>OUT</>
+      parameters and specifying <literal>RETURNS SETOF
+      <replaceable>sometype</></literal>.
+     </para>
+
      <para>
       When the return type of a <application>PL/pgSQL</application>
       function is declared as a polymorphic type (<type>anyelement</type>,
diff --git a/doc/src/sgml/ref/create_function.sgml b/doc/src/sgml/ref/create_function.sgml
index 18b9bf7beeac466b43ffa4fbe8b95e2b3129e51f..b6f9c015c55cf344c0b56685f7025bfaf3370f66 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.79 2008/07/16 01:30:21 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.80 2008/07/18 03:32:52 tgl Exp $
 -->
 
 <refentry id="SQL-CREATEFUNCTION">
@@ -21,7 +21,8 @@ $PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.79 2008/07/16 01:30
 <synopsis>
 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> ]
+    [ RETURNS <replaceable class="parameter">rettype</replaceable>
+      | RETURNS TABLE ( <replaceable class="parameter">colname</replaceable> <replaceable class="parameter">coltype</replaceable> [, ...] ) ]
   { LANGUAGE <replaceable class="parameter">langname</replaceable>
     | IMMUTABLE | STABLE | VOLATILE
     | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
@@ -49,7 +50,7 @@ CREATE [ OR REPLACE ] FUNCTION
    If a schema name is included, then the function is created in the
    specified schema.  Otherwise it is created in the current schema.
    The name of the new function must not match any existing function
-   with the same argument types in the same schema.  However,
+   with the same input argument types in the same schema.  However,
    functions of different argument types can share a name (this is
    called <firstterm>overloading</>).
   </para>
@@ -104,6 +105,9 @@ CREATE [ OR REPLACE ] FUNCTION
        The mode of an argument: <literal>IN</>, <literal>OUT</>,
        <literal>INOUT</>, or <literal>VARIADIC</>.
        If omitted, the default is <literal>IN</>.
+       Only <literal>OUT</> arguments can follow a <literal>VARIADIC</> one.
+       Also, <literal>OUT</> and <literal>INOUT</> arguments cannot be used
+       together with the <literal>RETURNS TABLE</> notation.
       </para>
      </listitem>
     </varlistentry>
@@ -183,6 +187,30 @@ CREATE [ OR REPLACE ] FUNCTION
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><replaceable class="parameter">colname</replaceable></term>
+
+     <listitem>
+      <para>
+       The name of an output column in the <literal>RETURNS TABLE</>
+       syntax.  This is effectively another way of declaring a named
+       <literal>OUT</> parameter, except that <literal>RETURNS TABLE</>
+       also implies <literal>RETURNS SETOF</>.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><replaceable class="parameter">coltype</replaceable></term>
+
+     <listitem>
+      <para>
+       The data type of an output column in the <literal>RETURNS TABLE</>
+       syntax.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><replaceable class="parameter">langname</replaceable></term>
 
@@ -437,7 +465,7 @@ CREATE [ OR REPLACE ] FUNCTION
     <productname>PostgreSQL</productname> allows function
     <firstterm>overloading</firstterm>; that is, the same name can be
     used for several different functions so long as they have distinct
-    argument types.  However, the C names of all functions must be
+    input argument types.  However, the C names of all functions must be
     different, so you must give overloaded C functions different C
     names (for example, use the argument types as part of the C
     names).
@@ -541,6 +569,18 @@ CREATE FUNCTION dup(int) RETURNS dup_result
 
 SELECT * FROM dup(42);
 </programlisting>
+   Another way to return multiple columns is to use a <literal>TABLE</>
+   function:
+<programlisting>
+CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text)
+    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
+    LANGUAGE SQL;
+
+SELECT * FROM dup(42);
+</programlisting>
+   However, a <literal>TABLE</> function is different from the
+   preceding examples, because it actually returns a <emphasis>set</>
+   of records, not just one record.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 55ed719ec64c1701f22fbd8d6308a951349b3ff3..265673343160306bab8026a23f16562817afe032 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.131 2008/07/16 01:30:21 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.132 2008/07/18 03:32:52 tgl Exp $ -->
 
  <sect1 id="xfunc">
   <title>User-Defined Functions</title>
@@ -94,11 +94,12 @@
    </para>
 
    <para>
-    <indexterm><primary>SETOF</><seealso>function</></> Alternatively,
-    an SQL function can be declared to return a set, by specifying the
-    function's return type as <literal>SETOF
-    <replaceable>sometype</></literal>.  In this case all rows of the
-    last query's result are returned.  Further details appear below.
+    Alternatively, an SQL function can be declared to return a set,
+    by specifying the function's return type as <literal>SETOF
+    <replaceable>sometype</></literal>, or equivalently by declaring it as
+    <literal>RETURNS TABLE(<replaceable>columns</>)</literal>.  In this case
+    all rows of the last query's result are returned.  Further details appear
+    below.
    </para>
 
    <para>
@@ -117,7 +118,7 @@
      other SQL commands. (The only exception is that you cannot put
      <command>BEGIN</>, <command>COMMIT</>, <command>ROLLBACK</>, or
      <command>SAVEPOINT</> commands into a <acronym>SQL</acronym> function.)
-     However, the final command 
+     However, the final command
      must be a <command>SELECT</command> that returns whatever is
      specified as the function's return type.  Alternatively, if you
      want to define a SQL function that performs actions but has no
@@ -175,7 +176,7 @@ INSERT INTO $1 VALUES (42);
     <para>
      The simplest possible <acronym>SQL</acronym> function has no arguments and
      simply returns a base type, such as <type>integer</type>:
-     
+
 <screen>
 CREATE FUNCTION one() RETURNS integer AS $$
     SELECT 1 AS result;
@@ -202,7 +203,7 @@ SELECT one();
     </para>
 
     <para>
-     It is almost as easy to define <acronym>SQL</acronym> functions  
+     It is almost as easy to define <acronym>SQL</acronym> functions
      that take base types as arguments.  In the example below, notice
      how we refer to the arguments within the function as <literal>$1</>
      and <literal>$2</>.
@@ -226,7 +227,7 @@ SELECT add_em(1, 2) AS answer;
 
 <programlisting>
 CREATE FUNCTION tf1 (integer, numeric) RETURNS integer AS $$
-    UPDATE bank 
+    UPDATE bank
         SET balance = balance - $2
         WHERE accountno = $1;
     SELECT 1;
@@ -248,7 +249,7 @@ SELECT tf1(17, 100.0);
 
 <programlisting>
 CREATE FUNCTION tf1 (integer, numeric) RETURNS numeric AS $$
-    UPDATE bank 
+    UPDATE bank
         SET balance = balance - $2
         WHERE accountno = $1;
     SELECT balance FROM bank WHERE accountno = $1;
@@ -267,7 +268,7 @@ $$ LANGUAGE SQL;
      types, we must  not  only  specify  which
      argument  we  want (as we did above with <literal>$1</> and <literal>$2</literal>) but
      also the desired attribute (field) of  that  argument.   For  example,
-     suppose that 
+     suppose that
      <type>emp</type> is a table containing employee data, and therefore
      also the name of the composite type of each row of the table.  Here
      is a function <function>double_salary</function> that computes what someone's
@@ -323,7 +324,7 @@ SELECT name, double_salary(ROW(name, salary*1.1, age, cubicle)) AS dream
 
     <para>
      It is also possible to build a function that returns a composite type.
-     This is an example of a function 
+     This is an example of a function
      that returns a single <type>emp</type> row:
 
 <programlisting>
@@ -364,7 +365,7 @@ ERROR:  function declared to return emp returns varchar instead of text at colum
        </para>
       </listitem>
      </itemizedlist>
-    </para>     
+    </para>
 
     <para>
      A different way to define the same function is:
@@ -380,7 +381,7 @@ $$ LANGUAGE SQL;
      in this situation, but it is a handy alternative in some cases
      &mdash; for example, if we need to compute the result by calling
      another function that returns the desired composite value.
-    </para>     
+    </para>
 
     <para>
      We could call this function directly in either of two ways:
@@ -401,7 +402,7 @@ SELECT * FROM new_emp();
 
      The second way is described more fully in <xref
      linkend="xfunc-sql-table-functions">.
-    </para>     
+    </para>
 
     <para>
      When you use a function that returns a composite type,
@@ -429,7 +430,7 @@ LINE 1: SELECT new_emp().name;
 
     <para>
      Another option is to use
-     functional notation for extracting an attribute.  The  simple  way 
+     functional notation for extracting an attribute.  The  simple  way
      to explain this is that we can use the
      notations <literal>attribute(table)</>  and  <literal>table.attribute</>
      interchangeably.
@@ -693,9 +694,14 @@ SELECT *, upper(fooname) FROM getfoo(1) AS t1;
     </para>
    </sect2>
 
-   <sect2>
+   <sect2 id="xfunc-sql-functions-returning-set">
     <title><acronym>SQL</acronym> Functions Returning Sets</title>
 
+    <indexterm>
+     <primary>function</primary>
+     <secondary>with SETOF</secondary>
+    </indexterm>
+
     <para>
      When an SQL function is declared as returning <literal>SETOF
      <replaceable>sometype</></literal>, the function's final
@@ -733,7 +739,7 @@ SELECT * FROM getfoo(1) AS t1;
 
 <programlisting>
 CREATE FUNCTION sum_n_product_with_tab (x int, OUT sum int, OUT product int) RETURNS SETOF record AS $$
-    SELECT x + tab.y, x * tab.y FROM tab;
+    SELECT $1 + tab.y, $1 * tab.y FROM tab;
 $$ LANGUAGE SQL;
 </programlisting>
 
@@ -794,6 +800,41 @@ SELECT name, listchildren(name) FROM nodes;
     </para>
    </sect2>
 
+   <sect2 id="xfunc-sql-functions-returning-table">
+    <title><acronym>SQL</acronym> Functions Returning <literal>TABLE</></title>
+
+    <indexterm>
+     <primary>function</primary>
+     <secondary>RETURNS TABLE</secondary>
+    </indexterm>
+
+    <para>
+     There is another way to declare a function as returning a set,
+     which is to use the syntax
+     <literal>RETURNS TABLE(<replaceable>columns</>)</literal>.
+     This is equivalent to using one or more <literal>OUT</> parameters plus
+     marking the function as returning <literal>SETOF record</> (or
+     <literal>SETOF</> a single output parameter's type, as appropriate).
+     This notation is specified in recent versions of the SQL standard, and
+     thus may be more portable than using <literal>SETOF</>.
+    </para>
+
+    <para>
+     For example, the preceding sum-and-product example could also be
+     done this way:
+
+<programlisting>
+CREATE FUNCTION sum_n_product_with_tab (x int) RETURNS TABLE(sum int, product int) AS $$
+    SELECT $1 + tab.y, $1 * tab.y FROM tab;
+$$ LANGUAGE SQL;
+</programlisting>
+
+     It is not allowed to use explicit <literal>OUT</> or <literal>INOUT</>
+     parameters with the <literal>RETURNS TABLE</> notation &mdash; you must
+     put all the output columns in the <literal>TABLE</> list.
+    </para>
+   </sect2>
+
    <sect2>
     <title>Polymorphic <acronym>SQL</acronym> Functions</title>
 
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 0e2452fa0f6ab7fc3c15ba0c20460c36289cfb6a..b10a2e8ea691b1032153420d74357dcfc5a595c1 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -4,7 +4,7 @@
  *
  * Copyright (c) 2003-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.44 2008/07/16 01:30:21 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/information_schema.sql,v 1.45 2008/07/18 03:32:52 tgl Exp $
  */
 
 /*
@@ -1007,6 +1007,7 @@ CREATE VIEW parameters AS
                 WHEN proargmodes[(ss.x).n] = 'o' THEN 'OUT'
                 WHEN proargmodes[(ss.x).n] = 'b' THEN 'INOUT'
                 WHEN proargmodes[(ss.x).n] = 'v' THEN 'IN'
+                WHEN proargmodes[(ss.x).n] = 't' THEN 'OUT'
              END AS character_data) AS parameter_mode,
            CAST('NO' AS character_data) AS is_result,
            CAST('NO' AS character_data) AS as_locator,
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 37e7ed4343ef2b1fd429376163267f52c07c4de8..e2513eb7dde1df0253103237e7a1be78fc7e6996 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.152 2008/07/16 16:55:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.153 2008/07/18 03:32:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -242,6 +242,7 @@ ProcedureCreate(const char *procedureName,
 						elog(ERROR, "variadic parameter must be last");
 					break;
 				case PROARGMODE_OUT:
+				case PROARGMODE_TABLE:
 					/* okay */
 					break;
 				case PROARGMODE_VARIADIC:
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index d03de8bff17a59dd40afe6b595d9b63b3a72e1b7..2b3723d6445eee3b84e96bf7cacae5ed07078f78 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.97 2008/07/16 16:55:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.98 2008/07/18 03:32:52 tgl Exp $
  *
  * DESCRIPTION
  *	  These routines take the parse tree and pick out the
@@ -228,9 +228,10 @@ examine_parameter_list(List *parameters, Oid languageOid,
 					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 					 errmsg("functions cannot accept set arguments")));
 
-		if (fp->mode != FUNC_PARAM_OUT)
+		/* handle input parameters */
+		if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
 		{
-			/* only OUT parameters can follow a VARIADIC parameter */
+			/* other input parameters can't follow a VARIADIC parameter */
 			if (varCount > 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
@@ -238,9 +239,10 @@ examine_parameter_list(List *parameters, Oid languageOid,
 			inTypes[inCount++] = toid;
 		}
 
+		/* handle output parameters */
 		if (fp->mode != FUNC_PARAM_IN && fp->mode != FUNC_PARAM_VARIADIC)
 		{
-			if (outCount == 0)	/* save first OUT param's type */
+			if (outCount == 0)	/* save first output param's type */
 				*requiredResultType = toid;
 			outCount++;
 		}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 70bbe940afd643610cbbc268bac35d0bd05577fa..39e52099f43f256b06d5469bb17b3cf9252ade3e 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.617 2008/07/16 01:30:22 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.618 2008/07/18 03:32:52 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -112,6 +112,8 @@ static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
 static Node *makeAArrayExpr(List *elements);
 static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args);
+static List *mergeTableFuncParameters(List *func_args, List *columns);
+static TypeName *TableFuncTypeName(List *columns);
 
 %}
 
@@ -253,13 +255,13 @@ static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args)
 				TableFuncElementList opt_type_modifiers
 				prep_type_clause
 				execute_param_clause using_clause returning_clause
-				enum_val_list
+				enum_val_list table_func_column_list
 
 %type <range>	OptTempTableName
 %type <into>	into_clause create_as_target
 
 %type <defelt>	createfunc_opt_item common_func_opt_item
-%type <fun_param> func_arg
+%type <fun_param> func_arg table_func_column
 %type <fun_param_mode> arg_class
 %type <typnam>	func_return func_type
 
@@ -4118,6 +4120,19 @@ CreateFunctionStmt:
 					n->withClause = $9;
 					$$ = (Node *)n;
 				}
+			| CREATE opt_or_replace FUNCTION func_name func_args
+			  RETURNS TABLE '(' table_func_column_list ')' createfunc_opt_list opt_definition
+				{
+					CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
+					n->replace = $2;
+					n->funcname = $4;
+					n->parameters = mergeTableFuncParameters($5, $9);
+					n->returnType = TableFuncTypeName($9);
+					n->returnType->location = @7;
+					n->options = $11;
+					n->withClause = $12;
+					$$ = (Node *)n;
+				}
 			| CREATE opt_or_replace FUNCTION func_name func_args
 			  createfunc_opt_list opt_definition
 				{
@@ -4338,6 +4353,27 @@ opt_definition:
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+table_func_column:	param_name func_type
+				{
+					FunctionParameter *n = makeNode(FunctionParameter);
+					n->name = $1;
+					n->argType = $2;
+					n->mode = FUNC_PARAM_TABLE;
+					$$ = n;
+				}
+		;
+
+table_func_column_list:
+			table_func_column
+				{
+					$$ = list_make1($1);
+				}
+			| table_func_column_list ',' table_func_column
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 /*****************************************************************************
  * ALTER FUNCTION
  *
@@ -9678,7 +9714,7 @@ extractArgTypes(List *parameters)
 	{
 		FunctionParameter *p = (FunctionParameter *) lfirst(i);
 
-		if (p->mode != FUNC_PARAM_OUT)		/* keep if IN, INOUT, VARIADIC */
+		if (p->mode != FUNC_PARAM_OUT && p->mode != FUNC_PARAM_TABLE)
 			result = lappend(result, p->argType);
 	}
 	return result;
@@ -9861,6 +9897,51 @@ parser_init(void)
 	QueryIsRule = FALSE;
 }
 
+/*
+ * Merge the input and output parameters of a table function.
+ */
+static List *
+mergeTableFuncParameters(List *func_args, List *columns)
+{
+	ListCell   *lc;
+
+	/* Explicit OUT and INOUT parameters shouldn't be used in this syntax */
+	foreach(lc, func_args)
+	{
+		FunctionParameter *p = (FunctionParameter *) lfirst(lc);
+
+		if (p->mode != FUNC_PARAM_IN && p->mode != FUNC_PARAM_VARIADIC)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("OUT and INOUT arguments aren't allowed in TABLE functions")));
+	}
+
+	return list_concat(func_args, columns);
+}
+
+/*
+ * Determine return type of a TABLE function.  A single result column
+ * returns setof that column's type; otherwise return setof record.
+ */
+static TypeName *
+TableFuncTypeName(List *columns)
+{
+	TypeName *result;
+
+	if (list_length(columns) == 1)
+	{
+		FunctionParameter *p = (FunctionParameter *) linitial(columns);
+
+		result = (TypeName *) copyObject(p->argType);
+	}
+	else
+		result = SystemTypeName("record");
+
+	result->setof = true;
+
+	return result;
+}
+
 /*
  * Must undefine base_yylex before including scan.c, since we want it
  * to create the function base_yylex not filtered_base_yylex.
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c7f896c524a94f0862b4a4eb03c96da0c1f22d17..b3603c53c16e37168433c797dd32ddae2cab868a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.277 2008/07/16 16:55:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.278 2008/07/18 03:32:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -135,6 +135,8 @@ static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 							int prettyFlags);
 static char *pg_get_expr_worker(text *expr, Oid relid, char *relname,
 				   int prettyFlags);
+static int print_function_arguments(StringInfo buf, HeapTuple proctup,
+						 bool print_table_args);
 static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 			 int prettyFlags);
 static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
@@ -1395,6 +1397,147 @@ pg_get_serial_sequence(PG_FUNCTION_ARGS)
 }
 
 
+/*
+ * pg_get_function_arguments
+ *		Get a nicely-formatted list of arguments for a function.
+ *		This is everything that would go between the parentheses in
+ *		CREATE FUNCTION.
+ */
+Datum
+pg_get_function_arguments(PG_FUNCTION_ARGS)
+{
+	Oid			funcid = PG_GETARG_OID(0);
+	StringInfoData buf;
+	HeapTuple	proctup;
+
+	initStringInfo(&buf);
+
+	proctup = SearchSysCache(PROCOID,
+							 ObjectIdGetDatum(funcid),
+							 0, 0, 0);
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for function %u", funcid);
+
+	(void) print_function_arguments(&buf, proctup, false);
+
+	ReleaseSysCache(proctup);
+
+	PG_RETURN_TEXT_P(string_to_text(buf.data));
+}
+
+/*
+ * pg_get_function_result
+ *		Get a nicely-formatted version of the result type of a function.
+ *		This is what would appear after RETURNS in CREATE FUNCTION.
+ */
+Datum
+pg_get_function_result(PG_FUNCTION_ARGS)
+{
+	Oid			funcid = PG_GETARG_OID(0);
+	StringInfoData buf;
+	HeapTuple	proctup;
+	Form_pg_proc procform;
+	int			ntabargs = 0;
+
+	initStringInfo(&buf);
+
+	proctup = SearchSysCache(PROCOID,
+							 ObjectIdGetDatum(funcid),
+							 0, 0, 0);
+	if (!HeapTupleIsValid(proctup))
+		elog(ERROR, "cache lookup failed for function %u", funcid);
+	procform = (Form_pg_proc) GETSTRUCT(proctup);
+
+	if (procform->proretset)
+	{
+		/* It might be a table function; try to print the arguments */
+		appendStringInfoString(&buf, "TABLE(");
+		ntabargs = print_function_arguments(&buf, proctup, true);
+		if (ntabargs > 0)
+			appendStringInfoString(&buf, ")");
+		else
+			resetStringInfo(&buf);
+	}
+
+	if (ntabargs == 0)
+	{
+		/* Not a table function, so do the normal thing */
+		if (procform->proretset)
+			appendStringInfoString(&buf, "SETOF ");
+		appendStringInfoString(&buf, format_type_be(procform->prorettype));
+	}
+
+	ReleaseSysCache(proctup);
+
+	PG_RETURN_TEXT_P(string_to_text(buf.data));
+}
+
+/*
+ * Common code for pg_get_function_arguments and pg_get_function_result:
+ * append the desired subset of arguments to buf.  We print only TABLE
+ * arguments when print_table_args is true, and all the others when it's false.
+ * Function return value is the number of arguments printed.
+ */
+static int
+print_function_arguments(StringInfo buf, HeapTuple proctup,
+						 bool print_table_args)
+{
+	int			numargs;
+	Oid		   *argtypes;
+	char	  **argnames;
+	char	   *argmodes;
+	int			argsprinted;
+	int			i;
+
+	numargs = get_func_arg_info(proctup,
+								&argtypes, &argnames, &argmodes);
+
+	argsprinted = 0;
+	for (i = 0; i < numargs; i++)
+	{
+		Oid		argtype = argtypes[i];
+		char   *argname = argnames ? argnames[i] : NULL;
+		char	argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+		const char *modename;
+
+		if (print_table_args != (argmode == PROARGMODE_TABLE))
+			continue;
+
+		switch (argmode)
+		{
+			case PROARGMODE_IN:
+				modename = "";
+				break;
+			case PROARGMODE_INOUT:
+				modename = "INOUT ";
+				break;
+			case PROARGMODE_OUT:
+				modename = "OUT ";
+				break;
+			case PROARGMODE_VARIADIC:
+				modename = "VARIADIC ";
+				break;
+			case PROARGMODE_TABLE:
+				modename = "";
+				break;
+			default:
+				elog(ERROR, "invalid parameter mode '%c'", argmode);
+				modename = NULL; /* keep compiler quiet */
+				break;
+		}
+		if (argsprinted)
+			appendStringInfoString(buf, ", ");
+		appendStringInfoString(buf, modename);
+		if (argname && argname[0])
+			appendStringInfo(buf, "%s ", argname);
+		appendStringInfoString(buf, format_type_be(argtype));
+		argsprinted++;
+	}
+
+	return argsprinted;
+}
+
+
 /*
  * deparse_expression			- General utility for deparsing expressions
  *
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 7cba375ee038284382abd1fe3f2ccdef4a52c416..6ff1b90ffcdc5d58a1992094bfe59e76caf249ed 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -7,7 +7,7 @@
  * Copyright (c) 2002-2008, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.40 2008/07/16 01:30:22 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.41 2008/07/18 03:32:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -550,7 +550,7 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			case ANYELEMENTOID:
 			case ANYNONARRAYOID:
 			case ANYENUMOID:
-				if (argmode == PROARGMODE_OUT)
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
 					have_anyelement_result = true;
 				else
 				{
@@ -565,7 +565,7 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 				}
 				break;
 			case ANYARRAYOID:
-				if (argmode == PROARGMODE_OUT)
+				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
 					have_anyarray_result = true;
 				else
 				{
@@ -582,7 +582,7 @@ resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes,
 			default:
 				break;
 		}
-		if (argmode != PROARGMODE_OUT)
+		if (argmode != PROARGMODE_OUT && argmode != PROARGMODE_TABLE)
 			inargno++;
 	}
 
@@ -848,7 +848,8 @@ get_func_result_name(Oid functionId)
 				argmodes[i] == PROARGMODE_VARIADIC)
 				continue;
 			Assert(argmodes[i] == PROARGMODE_OUT ||
-				   argmodes[i] == PROARGMODE_INOUT);
+				   argmodes[i] == PROARGMODE_INOUT ||
+				   argmodes[i] == PROARGMODE_TABLE);
 			if (++numoutargs > 1)
 			{
 				/* multiple out args, so forget it */
@@ -999,7 +1000,8 @@ build_function_result_tupdesc_d(Datum proallargtypes,
 			argmodes[i] == PROARGMODE_VARIADIC)
 			continue;
 		Assert(argmodes[i] == PROARGMODE_OUT ||
-			   argmodes[i] == PROARGMODE_INOUT);
+			   argmodes[i] == PROARGMODE_INOUT ||
+			   argmodes[i] == PROARGMODE_TABLE);
 		outargtypes[numoutargs] = argtypes[i];
 		if (argnames)
 			pname = TextDatumGetCString(argnames[i]);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c4602fd0bce09d66263521a24c0dd1d09d714372..1f09df210996681610ef2c43899a7a32875bfad2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12,7 +12,7 @@
  *	by PostgreSQL
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.495 2008/07/16 16:55:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.496 2008/07/18 03:32:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -165,7 +165,8 @@ static void dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId,
 static void getDependencies(void);
 static void getDomainConstraints(TypeInfo *tinfo);
 static void getTableData(TableInfo *tblinfo, int numTables, bool oids);
-static char *format_function_arguments(FuncInfo *finfo, int nallargs,
+static char *format_function_arguments(FuncInfo *finfo, char *funcargs);
+static char *format_function_arguments_old(FuncInfo *finfo, int nallargs,
 						  char **allargtypes,
 						  char **argmodes,
 						  char **argnames);
@@ -6405,16 +6406,34 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
 /*
  * format_function_arguments: generate function name and argument list
  *
+ * This is used when we can rely on pg_get_function_arguments to format
+ * the argument list.
+ */
+static char *format_function_arguments(FuncInfo *finfo, char *funcargs)
+{
+	PQExpBufferData fn;
+
+	initPQExpBuffer(&fn);
+	appendPQExpBuffer(&fn, "%s(%s)", fmtId(finfo->dobj.name), funcargs);
+	return fn.data;
+}
+
+/*
+ * format_function_arguments_old: generate function name and argument list
+ *
  * The argument type names are qualified if needed.  The function name
  * is never qualified.
  *
+ * This is used only with pre-8.4 servers, so we aren't expecting to see
+ * VARIADIC or TABLE arguments.
+ *
  * Any or all of allargtypes, argmodes, argnames may be NULL.
  */
 static char *
-format_function_arguments(FuncInfo *finfo, int nallargs,
-						  char **allargtypes,
-						  char **argmodes,
-						  char **argnames)
+format_function_arguments_old(FuncInfo *finfo, int nallargs,
+							  char **allargtypes,
+							  char **argmodes,
+							  char **argnames)
 {
 	PQExpBufferData fn;
 	int			j;
@@ -6444,9 +6463,6 @@ format_function_arguments(FuncInfo *finfo, int nallargs,
 				case PROARGMODE_INOUT:
 					argmode = "INOUT ";
 					break;
-				case PROARGMODE_VARIADIC:
-					argmode = "VARIADIC ";
-					break;
 				default:
 					write_msg(NULL, "WARNING: bogus value in proargmodes array\n");
 					argmode = "";
@@ -6475,7 +6491,7 @@ format_function_arguments(FuncInfo *finfo, int nallargs,
 /*
  * format_function_signature: generate function name and argument list
  *
- * This is like format_function_arguments except that only a minimal
+ * This is like format_function_arguments_old except that only a minimal
  * list of input argument types is generated; this is sufficient to
  * reference the function, but not to define it.
  *
@@ -6527,6 +6543,8 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
 	char	   *proretset;
 	char	   *prosrc;
 	char	   *probin;
+	char	   *funcargs;
+	char	   *funcresult;
 	char	   *proallargtypes;
 	char	   *proargmodes;
 	char	   *proargnames;
@@ -6559,7 +6577,24 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
 	selectSourceSchema(finfo->dobj.namespace->dobj.name);
 
 	/* Fetch function-specific details */
-	if (g_fout->remoteVersion >= 80300)
+	if (g_fout->remoteVersion >= 80400)
+	{
+		/*
+		 * In 8.4 and up we rely on pg_get_function_arguments and
+		 * pg_get_function_result instead of examining proallargtypes etc.
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT proretset, prosrc, probin, "
+						  "pg_catalog.pg_get_function_arguments(oid) as funcargs, "
+						  "pg_catalog.pg_get_function_result(oid) as funcresult, "
+						  "provolatile, proisstrict, prosecdef, "
+						  "proconfig, procost, prorows, "
+						  "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) as lanname "
+						  "FROM pg_catalog.pg_proc "
+						  "WHERE oid = '%u'::pg_catalog.oid",
+						  finfo->dobj.catId.oid);
+	}
+	else if (g_fout->remoteVersion >= 80300)
 	{
 		appendPQExpBuffer(query,
 						  "SELECT proretset, prosrc, probin, "
@@ -6659,9 +6694,19 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
 	proretset = PQgetvalue(res, 0, PQfnumber(res, "proretset"));
 	prosrc = PQgetvalue(res, 0, PQfnumber(res, "prosrc"));
 	probin = PQgetvalue(res, 0, PQfnumber(res, "probin"));
-	proallargtypes = PQgetvalue(res, 0, PQfnumber(res, "proallargtypes"));
-	proargmodes = PQgetvalue(res, 0, PQfnumber(res, "proargmodes"));
-	proargnames = PQgetvalue(res, 0, PQfnumber(res, "proargnames"));
+	if (g_fout->remoteVersion >= 80400)
+	{
+		funcargs = PQgetvalue(res, 0, PQfnumber(res, "funcargs"));
+		funcresult = PQgetvalue(res, 0, PQfnumber(res, "funcresult"));
+		proallargtypes = proargmodes = proargnames = NULL;
+	}
+	else
+	{
+		proallargtypes = PQgetvalue(res, 0, PQfnumber(res, "proallargtypes"));
+		proargmodes = PQgetvalue(res, 0, PQfnumber(res, "proargmodes"));
+		proargnames = PQgetvalue(res, 0, PQfnumber(res, "proargnames"));
+		funcargs = funcresult = NULL;
+	}
 	provolatile = PQgetvalue(res, 0, PQfnumber(res, "provolatile"));
 	proisstrict = PQgetvalue(res, 0, PQfnumber(res, "proisstrict"));
 	prosecdef = PQgetvalue(res, 0, PQfnumber(res, "prosecdef"));
@@ -6766,8 +6811,11 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
 		}
 	}
 
-	funcsig = format_function_arguments(finfo, nallargs, allargtypes,
-										argmodes, argnames);
+	if (funcargs)
+		funcsig = format_function_arguments(finfo, funcargs);
+	else
+		funcsig = format_function_arguments_old(finfo, nallargs, allargtypes,
+												argmodes, argnames);
 	funcsig_tag = format_function_signature(finfo, false);
 
 	/*
@@ -6777,13 +6825,17 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
 					  fmtId(finfo->dobj.namespace->dobj.name),
 					  funcsig);
 
-	rettypename = getFormattedTypeName(finfo->prorettype, zeroAsOpaque);
-
 	appendPQExpBuffer(q, "CREATE FUNCTION %s ", funcsig);
-	appendPQExpBuffer(q, "RETURNS %s%s",
-					  (proretset[0] == 't') ? "SETOF " : "",
-					  rettypename);
-	free(rettypename);
+	if (funcresult)
+		appendPQExpBuffer(q, "RETURNS %s", funcresult);
+	else
+	{
+		rettypename = getFormattedTypeName(finfo->prorettype, zeroAsOpaque);
+		appendPQExpBuffer(q, "RETURNS %s%s",
+						  (proretset[0] == 't') ? "SETOF " : "",
+						  rettypename);
+		free(rettypename);
+	}
 
 	appendPQExpBuffer(q, "\n    LANGUAGE %s", fmtId(lanname));
 	if (provolatile[0] != PROVOLATILE_VOLATILE)
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f8f6a657d213f953a8aa4cbcf45fdbfc1d365f5c..0a0819cc02992eb972a663d924697d2be1029e23 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -8,7 +8,7 @@
  *
  * Copyright (c) 2000-2008, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.182 2008/07/16 01:30:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.183 2008/07/18 03:32:52 tgl Exp $
  */
 #include "postgres_fe.h"
 
@@ -189,15 +189,20 @@ describeFunctions(const char *pattern, bool verbose)
 
 	printfPQExpBuffer(&buf,
 					  "SELECT n.nspname as \"%s\",\n"
-					  "  p.proname as \"%s\",\n"
-					  "  CASE WHEN p.proretset THEN 'setof ' ELSE '' END ||\n"
-				  "  pg_catalog.format_type(p.prorettype, NULL) as \"%s\",\n",
+					  "  p.proname as \"%s\",\n",
 					  gettext_noop("Schema"),
-					  gettext_noop("Name"),
-					  gettext_noop("Result data type"));
+					  gettext_noop("Name"));
 
-    if (pset.sversion >= 80100)
+    if (pset.sversion >= 80400)
 		appendPQExpBuffer(&buf,
+						  "  pg_catalog.pg_get_function_result(p.oid) as \"%s\",\n"
+						  "  pg_catalog.pg_get_function_arguments(p.oid) as \"%s\"",
+						  gettext_noop("Result data type"),
+						  gettext_noop("Argument data types"));
+    else if (pset.sversion >= 80100)
+		appendPQExpBuffer(&buf,
+					  "  CASE WHEN p.proretset THEN 'SETOF ' ELSE '' END ||\n"
+				  "  pg_catalog.format_type(p.prorettype, NULL) as \"%s\",\n"
 					  "  CASE WHEN proallargtypes IS NOT NULL THEN\n"
 					  "    pg_catalog.array_to_string(ARRAY(\n"
 					  "      SELECT\n"
@@ -227,10 +232,14 @@ describeFunctions(const char *pattern, bool verbose)
 					  "        pg_catalog.generate_series(0, pg_catalog.array_upper(p.proargtypes, 1)) AS s(i)\n"
 					  "    ), ', ')\n"
 					  "  END AS \"%s\"",
-					  gettext_noop("Argument data types"));
+						  gettext_noop("Result data type"),
+						  gettext_noop("Argument data types"));
 	else
 		appendPQExpBuffer(&buf,
+					  "  CASE WHEN p.proretset THEN 'SETOF ' ELSE '' END ||\n"
+				  "  pg_catalog.format_type(p.prorettype, NULL) as \"%s\",\n"
 					  "  pg_catalog.oidvectortypes(p.proargtypes) as \"%s\"",
+						  gettext_noop("Result data type"),
 						  gettext_noop("Argument data types"));
 
 	if (verbose)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 7581562a48067c5216e7c91817e9d62f4b86b79c..6fadd8b33e56d06a42cde066bed2f336ee50f5d5 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.470 2008/07/16 16:55:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.471 2008/07/18 03:32:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200807161
+#define CATALOG_VERSION_NO	200807171
 
 #endif
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index e6931eca861a1e93735d32016cada6978c7fa5e3..c516af8a164da2e8c652efb33c2715ca81795c78 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.508 2008/07/16 16:55:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.509 2008/07/18 03:32:53 tgl Exp $
  *
  * NOTES
  *	  The script catalog/genbki.sh reads this file and generates .bki
@@ -2291,6 +2291,10 @@ DATA(insert OID = 1716 (  pg_get_expr		   PGNSP PGUID 12 1 0 0 f f t f s 2 25 "2
 DESCR("deparse an encoded expression");
 DATA(insert OID = 1665 (  pg_get_serial_sequence	PGNSP PGUID 12 1 0 0 f f t f s 2 25 "25 25" _null_ _null_ _null_	pg_get_serial_sequence _null_ _null_ _null_ ));
 DESCR("name of sequence for a serial column");
+DATA(insert OID = 2162 (  pg_get_function_arguments	   PGNSP PGUID 12 1 0 0 f f t f s 1 25 "26" _null_ _null_ _null_	pg_get_function_arguments _null_ _null_ _null_ ));
+DESCR("argument list of a function");
+DATA(insert OID = 2165 (  pg_get_function_result	   PGNSP PGUID 12 1 0 0 f f t f s 1 25 "26" _null_ _null_ _null_	pg_get_function_result _null_ _null_ _null_ ));
+DESCR("result type of a function");
 
 DATA(insert OID = 1686 (  pg_get_keywords		PGNSP PGUID 12 10 400 0 f f t t s 0 2249 "" "{25,18,25}" "{o,o,o}" "{word,catcode,catdesc}" pg_get_keywords _null_ _null_ _null_ ));
 DESCR("list of SQL keywords");
@@ -4477,5 +4481,6 @@ DESCR("is txid visible in snapshot?");
 #define PROARGMODE_OUT		'o'
 #define PROARGMODE_INOUT	'b'
 #define PROARGMODE_VARIADIC	'v'
+#define PROARGMODE_TABLE	't'
 
 #endif   /* PG_PROC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 561cc9129dba3027f3362d7c4b9c8995b73cb1d0..e6de4b49c85ae27860e265f094e63a3bdf83a660 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.367 2008/07/16 01:30:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.368 2008/07/18 03:32:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1570,7 +1570,8 @@ typedef enum FunctionParameterMode
 	FUNC_PARAM_IN = 'i',		/* input only */
 	FUNC_PARAM_OUT = 'o',		/* output only */
 	FUNC_PARAM_INOUT = 'b',		/* both */
-	FUNC_PARAM_VARIADIC = 'v'	/* variadic */
+	FUNC_PARAM_VARIADIC = 'v',	/* variadic */
+	FUNC_PARAM_TABLE = 't'		/* table function output column */
 } FunctionParameterMode;
 
 typedef struct FunctionParameter
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 2abbe6678914e32d9360f7f2b2b0481102b4318d..fbe5a119cb9eb5ecc7a3dcad0b238c2c9e5cc00e 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.318 2008/07/03 20:58:47 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.319 2008/07/18 03:32:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -551,6 +551,8 @@ extern Datum pg_get_expr(PG_FUNCTION_ARGS);
 extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS);
 extern Datum pg_get_userbyid(PG_FUNCTION_ARGS);
 extern Datum pg_get_serial_sequence(PG_FUNCTION_ARGS);
+extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS);
+extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
 extern List *deparse_context_for(const char *aliasname, Oid relid);
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 1b2cba38815d17d14190c13069f279eed6fb6e07..b7b4eed97056504853c0816856c7c572519b8a29 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.127 2008/07/16 01:30:23 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.128 2008/07/18 03:32:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -441,7 +441,9 @@ do_compile(FunctionCallInfo fcinfo,
 					argmode == PROARGMODE_INOUT ||
 					argmode == PROARGMODE_VARIADIC)
 					in_arg_varnos[num_in_args++] = argvariable->dno;
-				if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_INOUT)
+				if (argmode == PROARGMODE_OUT ||
+					argmode == PROARGMODE_INOUT ||
+					argmode == PROARGMODE_TABLE)
 					out_arg_variables[num_out_args++] = argvariable;
 
 				/* Add to namespace under the $n name */
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 8e85c6707e2d044817d15a1da2d31e6296cea3b8..c4d6803fcf407a027560bb4c8a081333f9cce1f0 100644
--- a/src/pl/plpython/plpython.c
+++ b/src/pl/plpython/plpython.c
@@ -1,7 +1,7 @@
 /**********************************************************************
  * plpython.c - python as a procedural language for PostgreSQL
  *
- *	$PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.111 2008/07/16 01:30:23 tgl Exp $
+ *	$PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.112 2008/07/18 03:32:53 tgl Exp $
  *
  *********************************************************************
  */
@@ -1271,7 +1271,8 @@ PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key)
 				/* proc->nargs was initialized to 0 above */
 				for (i = 0; i < total; i++)
 				{
-					if (modes[i] != PROARGMODE_OUT)
+					if (modes[i] != PROARGMODE_OUT &&
+						modes[i] != PROARGMODE_TABLE)
 						(proc->nargs)++;
 				}
 			}
@@ -1282,7 +1283,9 @@ PLy_procedure_create(HeapTuple procTup, Oid tgreloid, char *key)
 				HeapTuple	argTypeTup;
 				Form_pg_type argTypeStruct;
 
-				if (modes && modes[i] == PROARGMODE_OUT)
+				if (modes &&
+					(modes[i] == PROARGMODE_OUT ||
+					 modes[i] == PROARGMODE_TABLE))
 					continue;	/* skip OUT arguments */
 
 				Assert(types[i] == procStruct->proargtypes.values[pos]);
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 1e4ef2645fa801e82a5747b7ac0ffdce3fa4ec87..94a485f46b30aad38f57c6106e3b23cbaa1d00d6 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -3634,3 +3634,35 @@ NOTICE:  non-variadic function called
 
 drop function pleast(numeric[]);
 drop function pleast(numeric);
+-- test table functions
+create function tftest(int) returns table(a int, b int) as $$
+begin
+  return query select $1, $1+i from generate_series(1,5) g(i);
+end;
+$$ language plpgsql immutable strict;
+select * from tftest(10);
+ a  | b  
+----+----
+ 10 | 11
+ 10 | 12
+ 10 | 13
+ 10 | 14
+ 10 | 15
+(5 rows)
+
+create or replace function tftest(a1 int) returns table(a int, b int) as $$
+begin
+  a := a1; b := a1 + 1;
+  return next;
+  a := a1 * 10; b := a1 * 10 + 1;
+  return next;
+end;
+$$ language plpgsql immutable strict;
+select * from tftest(10);
+  a  |  b  
+-----+-----
+  10 |  11
+ 100 | 101
+(2 rows)
+
+drop function tftest(int);
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 957bd49f24615baa09c545c49c665d16982287e0..90111553d9b6fec91547e038a31c049b35ce76d2 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -528,3 +528,40 @@ 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 a polymorphic type must have at least one polymorphic argument.
+--
+-- table functions
+--
+CREATE OR REPLACE FUNCTION foo()
+RETURNS TABLE(a int)
+AS $$ SELECT a FROM generate_series(1,5) a(a) $$ LANGUAGE sql;
+SELECT * FROM foo();
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+DROP FUNCTION foo();
+CREATE OR REPLACE FUNCTION foo(int)
+RETURNS TABLE(a int, b int)
+AS $$ SELECT a, b
+         FROM generate_series(1,$1) a(a),
+              generate_series(1,$1) b(b) $$ LANGUAGE sql;
+SELECT * FROM foo(3);
+ a | b 
+---+---
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 2 | 1
+ 2 | 2
+ 2 | 3
+ 3 | 1
+ 3 | 2
+ 3 | 3
+(9 rows)
+
+DROP FUNCTION foo(int);
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 4d45dba2efcbcfdba463467bbd1fdf7093445fb2..9ebe2b5f33248b738eb1688d9d4d6ebcc85114d9 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2925,3 +2925,26 @@ select pleast(10);
 
 drop function pleast(numeric[]);
 drop function pleast(numeric);
+
+-- test table functions
+
+create function tftest(int) returns table(a int, b int) as $$
+begin
+  return query select $1, $1+i from generate_series(1,5) g(i);
+end;
+$$ language plpgsql immutable strict;
+
+select * from tftest(10);
+
+create or replace function tftest(a1 int) returns table(a int, b int) as $$
+begin
+  a := a1; b := a1 + 1;
+  return next;
+  a := a1 * 10; b := a1 * 10 + 1;
+  return next;
+end;
+$$ language plpgsql immutable strict;
+
+select * from tftest(10);
+
+drop function tftest(int);
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
index c56e35ded4e04bb3fd6eb5aa2152f1ba36f7a4a6..435a836c66d9f86bfed444cbd9246853e1f085fd 100644
--- a/src/test/regress/sql/rangefuncs.sql
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -261,3 +261,21 @@ 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;
+
+--
+-- table functions
+--
+
+CREATE OR REPLACE FUNCTION foo()
+RETURNS TABLE(a int)
+AS $$ SELECT a FROM generate_series(1,5) a(a) $$ LANGUAGE sql;
+SELECT * FROM foo();
+DROP FUNCTION foo();
+
+CREATE OR REPLACE FUNCTION foo(int)
+RETURNS TABLE(a int, b int)
+AS $$ SELECT a, b
+         FROM generate_series(1,$1) a(a),
+              generate_series(1,$1) b(b) $$ LANGUAGE sql;
+SELECT * FROM foo(3);
+DROP FUNCTION foo(int);