diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9e306a99282dab58a1b39fe31a0f1afb85f30c87..1dde8b59a3ced5669d5cc4f5a3711eda7ee48de6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.200 2004/05/10 21:08:28 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.201 2004/05/10 22:44:42 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -7822,13 +7822,15 @@ SELECT col1 FROM tab1
   </para>
 
 <synopsis>
-(<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) IN (<replaceable>subquery</replaceable>)
+<replaceable>row_constructor</replaceable> IN (<replaceable>subquery</replaceable>)
 </synopsis>
 
   <para>
-   The right-hand side of this form of <token>IN</token> is a parenthesized
+   The left-hand side of this form of <token>IN</token> is a row constructor,
+   as described in <xref linkend="sql-syntax-row-constructors">.
+   The right-hand side is a parenthesized
    subquery, which must return exactly as many columns as there are
-   expressions in the left-hand list.  The left-hand expressions are
+   expressions in the left-hand row.  The left-hand expressions are
    evaluated and compared row-wise to each row of the subquery result.
    The result of <token>IN</token> is <quote>true</> if any equal subquery row is found.
    The result is <quote>false</> if no equal row is found (including the special
@@ -7876,13 +7878,15 @@ SELECT col1 FROM tab1
   </para>
 
 <synopsis>
-(<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) NOT IN (<replaceable>subquery</replaceable>)
+<replaceable>row_constructor</replaceable> NOT IN (<replaceable>subquery</replaceable>)
 </synopsis>
 
   <para>
-   The right-hand side of this form of <token>NOT IN</token> is a parenthesized
+   The left-hand side of this form of <token>NOT IN</token> is a row constructor,
+   as described in <xref linkend="sql-syntax-row-constructors">.
+   The right-hand side is a parenthesized
    subquery, which must return exactly as many columns as there are
-   expressions in the left-hand list.  The left-hand expressions are
+   expressions in the left-hand row.  The left-hand expressions are
    evaluated and compared row-wise to each row of the subquery result.
    The result of <token>NOT IN</token> is <quote>true</> if only unequal subquery rows
    are found (including the special case where the subquery returns no rows).
@@ -7938,14 +7942,16 @@ SELECT col1 FROM tab1
   </para>
 
 <synopsis>
-(<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) <replaceable>operator</> ANY (<replaceable>subquery</replaceable>)
-(<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) <replaceable>operator</> SOME (<replaceable>subquery</replaceable>)
+<replaceable>row_constructor</replaceable> <replaceable>operator</> ANY (<replaceable>subquery</replaceable>)
+<replaceable>row_constructor</replaceable> <replaceable>operator</> SOME (<replaceable>subquery</replaceable>)
 </synopsis>
 
   <para>
-   The right-hand side of this form of <token>ANY</token> is a parenthesized
+   The left-hand side of this form of <token>ANY</token> is a row constructor,
+   as described in <xref linkend="sql-syntax-row-constructors">.
+   The right-hand side is a parenthesized
    subquery, which must return exactly as many columns as there are
-   expressions in the left-hand list.  The left-hand expressions are
+   expressions in the left-hand row.  The left-hand expressions are
    evaluated and compared row-wise to each row of the subquery result,
    using the given <replaceable>operator</replaceable>.  Presently,
    only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
@@ -8003,13 +8009,15 @@ SELECT col1 FROM tab1
   </para>
 
 <synopsis>
-(<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) <replaceable>operator</replaceable> ALL (<replaceable>subquery</replaceable>)
+<replaceable>row_constructor</replaceable> <replaceable>operator</replaceable> ALL (<replaceable>subquery</replaceable>)
 </synopsis>
 
   <para>
-   The right-hand side of this form of <token>ALL</token> is a parenthesized
+   The left-hand side of this form of <token>ALL</token> is a row constructor,
+   as described in <xref linkend="sql-syntax-row-constructors">.
+   The right-hand side is a parenthesized
    subquery, which must return exactly as many columns as there are
-   expressions in the left-hand list.  The left-hand expressions are
+   expressions in the left-hand row.  The left-hand expressions are
    evaluated and compared row-wise to each row of the subquery result,
    using the given <replaceable>operator</replaceable>.  Presently,
    only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
@@ -8041,16 +8049,17 @@ SELECT col1 FROM tab1
    </indexterm>
 
 <synopsis>
-(<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) <replaceable>operator</replaceable> (<replaceable>subquery</replaceable>)
+<replaceable>row_constructor</replaceable> <replaceable>operator</replaceable> (<replaceable>subquery</replaceable>)
 </synopsis>
 
   <para>
-   The left-hand side is a list of scalar expressions.  The right-hand side is
-   a parenthesized subquery, which must return exactly as many columns as there
-   are expressions on the left-hand side.  Furthermore, the subquery cannot
-   return more than one row.  (If it returns zero rows, the result is taken to
-   be null.)  The left-hand side is evaluated and compared row-wise to the
-   single subquery result row.
+   The left-hand side is a row constructor,
+   as described in <xref linkend="sql-syntax-row-constructors">.
+   The right-hand side is a parenthesized subquery, which must return exactly
+   as many columns as there are expressions in the left-hand row. Furthermore,
+   the subquery cannot return more than one row.  (If it returns zero rows,
+   the result is taken to be null.)  The left-hand side is evaluated and
+   compared row-wise to the single subquery result row.
    Presently, only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
    in row-wise comparisons.
    The result is <quote>true</> if the two rows are equal or unequal, respectively.
@@ -8223,13 +8232,14 @@ AND
    <title>Row-wise Comparison</title>
 
 <synopsis>
-(<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>) <replaceable>operator</replaceable> (<replaceable>expression</replaceable> <optional>, <replaceable>expression</replaceable> ...</optional>)
+<replaceable>row_constructor</replaceable> <replaceable>operator</replaceable> <replaceable>row_constructor</replaceable>
 </synopsis>
 
   <para>
-   Each side is a list of scalar expressions; the two lists must be
-   of the same length.  Each side is evaluated and they are compared
-   row-wise.
+   Each side is a row constructor,
+   as described in <xref linkend="sql-syntax-row-constructors">.
+   The two row values must have the same number of fields.
+   Each side is evaluated and they are compared row-wise.
    Presently, only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
    in row-wise comparisons.
    The result is <quote>true</> if the two rows are equal or unequal, respectively.
@@ -8242,6 +8252,29 @@ AND
    are unequal if any corresponding members are non-null and unequal;
    otherwise the result of the row comparison is unknown (null).
   </para>
+
+<synopsis>
+<replaceable>row_constructor</replaceable> IS DISTINCT FROM <replaceable>row_constructor</replaceable>
+</synopsis>
+
+  <para>
+   This construct is similar to a <literal>&lt;&gt;</literal> row comparison,
+   but it does not yield null for null inputs.  Instead, any null value is
+   considered unequal to (distinct from) any non-null value, and any two
+   nulls are considered equal (not distinct).  Thus the result will always
+   be either true or false, never null.
+  </para>
+
+<synopsis>
+<replaceable>row_constructor</replaceable> IS NULL
+<replaceable>row_constructor</replaceable> IS NOT NULL
+</synopsis>
+
+  <para>
+   These constructs test a row value for null or not null.  A row value
+   is considered not null if it has at least one field that is not null.
+  </para>
+
   </sect2>
  </sect1>
 
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index c6093b846377f5babb6e6d965e9d82ab054c8490..24a01891d31ae3a00c8baa411f3bedcfb968b1b3 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.90 2004/03/12 00:25:40 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/syntax.sgml,v 1.91 2004/05/10 22:44:43 tgl Exp $
 -->
 
 <chapter id="sql-syntax">
@@ -920,6 +920,12 @@ SELECT 3 OPERATOR(pg_catalog.+) 4;
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      A row constructor.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       Another value expression in parentheses, useful to group
@@ -1428,6 +1434,79 @@ SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%');
 
   </sect2>
 
+  <sect2 id="sql-syntax-row-constructors">
+   <title>Row Constructors</title>
+
+   <indexterm>
+    <primary>row</primary>
+    <secondary>constructor</secondary>
+   </indexterm>
+
+   <para>
+    A row constructor is an expression that builds a row value from values
+    for its member fields.  A row constructor consists of the key word
+    <literal>ROW</literal>, a left parenthesis <literal>(</>, zero or more
+    expressions (separated by commas) for the row field values, and finally
+    a right parenthesis <literal>)</>.  For example,
+<programlisting>
+SELECT myfunc(ROW(1,2.5,'this is a test'));
+</programlisting>
+    The key word <literal>ROW</> is optional when there is more than one
+    expression in the list.
+   </para>
+
+   <para>
+    By default, the value created by a <literal>ROW</> expression is of
+    an anonymous record type.  If necessary, it can be cast to a named
+    composite type --- either the rowtype of a table, or a composite type
+    created with <command>CREATE TYPE AS</>.  An explicit cast may be needed
+    to avoid ambiguity.  For example:
+<programlisting>
+CREATE TABLE mytable(f1 int, f2 float, f3 text);
+CREATE FUNCTION getf1(mytable) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;
+-- No cast needed since only one getf1() exists
+SELECT getf1(ROW(1,2.5,'this is a test'));
+ getf1
+-------
+     1
+(1 row)
+
+CREATE TYPE myrowtype AS (f1 int, f2 text, f3 numeric);
+CREATE FUNCTION getf1(myrowtype) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;
+-- Now we need a cast to indicate which function to call:
+SELECT getf1(ROW(1,2.5,'this is a test'));
+ERROR:  function getf1(record) is not unique
+SELECT getf1(ROW(1,2.5,'this is a test')::mytable);
+ getf1
+-------
+     1
+(1 row)
+
+SELECT getf1(CAST(ROW(11,'this is a test',2.5) AS myrowtype));
+ getf1
+-------
+    11
+(1 row)
+</programlisting>
+  </para>
+
+  <para>
+   Row constructors have only limited uses, other than creating an argument
+   value for a user-defined function that accepts a rowtype parameter, as
+   illustrated above.
+   It is possible to compare two row values or test a row with
+   <literal>IS NULL</> or <literal>IS NOT NULL</>, for example
+<programlisting>
+SELECT ROW(1,2.5,'this is a test') = ROW(1, 3, 'not the same');
+SELECT ROW(a, b, c) IS NOT NULL FROM table;
+</programlisting>
+   For more detail see <xref linkend="functions-comparisons">.
+   Row constructors can also be used in connection with subqueries,
+   as discussed in <xref linkend="functions-subquery">.
+  </para>
+
+  </sect2>
+
   <sect2 id="syntax-express-eval">
    <title>Expression Evaluation Rules</title>
 
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 4b06aefd362eb9413260df3f628c4fc80e66c0dd..a3c24a1c4c1ff3e6e6f5f32d1293dfd75ed54f37 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.81 2004/04/01 21:28:43 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.82 2004/05/10 22:44:43 tgl Exp $
 -->
 
  <sect1 id="xfunc">
@@ -240,10 +240,11 @@ SELECT clean_emp();
     <title><acronym>SQL</acronym> Functions on Composite Types</title>
 
     <para>
-     When  specifying  functions with arguments of composite
+     When writing  functions with arguments of composite
      types, we must  not  only  specify  which
      argument  we  want (as we did above with <literal>$1</> and <literal>$2</literal>) but
-     also the attributes of  that  argument.   For  example, suppose that
+     also the desired attribute (field) of  that  argument.   For  example,
+     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
@@ -252,16 +253,16 @@ SELECT clean_emp();
 <screen>
 CREATE TABLE emp (
     name        text,
-    salary      integer,
+    salary      numeric,
     age         integer,
     cubicle     point
 );
 
-CREATE FUNCTION double_salary(emp) RETURNS integer AS '
+CREATE FUNCTION double_salary(emp) RETURNS numeric AS '
     SELECT $1.salary * 2 AS salary;
 ' LANGUAGE SQL;
 
-SELECT name, double_salary(emp) AS dream
+SELECT name, double_salary(emp.*) AS dream
     FROM emp
     WHERE emp.cubicle ~= point '(2,1)';
 
@@ -274,15 +275,27 @@ SELECT name, double_salary(emp) AS dream
     <para>
      Notice the use of the syntax <literal>$1.salary</literal>
      to select one field of the argument row value.  Also notice
-     how the calling <command>SELECT</> command uses a table name to denote
-     the entire current row of that table as a composite value.  The table
-     row can alternatively be referenced like this:
+     how the calling <command>SELECT</> command uses <literal>*</>
+     to select
+     the entire current row of a table as a composite value.  The table
+     row can alternatively be referenced using just the table name,
+     like this:
 <screen>
-SELECT name, double_salary(emp.*) AS dream
+SELECT name, double_salary(emp) AS dream
     FROM emp
     WHERE emp.cubicle ~= point '(2,1)';
 </screen>
-     which emphasizes its row nature.
+     but this usage is deprecated since it's easy to get confused.
+    </para>
+
+    <para>
+     Sometimes it is handy to construct a composite argument value
+     on-the-fly.  This can be done with the <literal>ROW</> construct.
+     For example, we could adjust the data being passed to the function:
+<screen>
+SELECT name, double_salary(row(name, salary*1.1, age, cubicle)) AS dream
+    FROM emp;
+</screen>
     </para>
 
     <para>
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index b27e86122bc871255c997441d99e85276d3c21fb..d44f580be024486d921c42476f75d873d1fc3945 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.158 2004/04/01 21:28:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.159 2004/05/10 22:44:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,6 +42,7 @@
 #include "executor/execdebug.h"
 #include "executor/functions.h"
 #include "executor/nodeSubplan.h"
+#include "funcapi.h"
 #include "miscadmin.h"
 #include "optimizer/planmain.h"
 #include "parser/parse_expr.h"
@@ -93,6 +94,9 @@ static Datum ExecEvalCaseTestExpr(ExprState *exprstate,
 static Datum ExecEvalArray(ArrayExprState *astate,
 						   ExprContext *econtext,
 						   bool *isNull, ExprDoneCond *isDone);
+static Datum ExecEvalRow(RowExprState *rstate,
+						 ExprContext *econtext,
+						 bool *isNull, ExprDoneCond *isDone);
 static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr,
 							  ExprContext *econtext,
 							  bool *isNull, ExprDoneCond *isDone);
@@ -2101,6 +2105,54 @@ ExecEvalArray(ArrayExprState *astate, ExprContext *econtext,
 	return PointerGetDatum(result);
 }
 
+/* ----------------------------------------------------------------
+ *		ExecEvalRow - ROW() expressions
+ * ----------------------------------------------------------------
+ */
+static Datum
+ExecEvalRow(RowExprState *rstate,
+			ExprContext *econtext,
+			bool *isNull, ExprDoneCond *isDone)
+{
+	HeapTuple	tuple;
+	Datum	   *values;
+	char	   *nulls;
+	int			nargs;
+	List	   *arg;
+	int			i;
+
+	/* Set default values for result flags: non-null, not a set result */
+	*isNull = false;
+	if (isDone)
+		*isDone = ExprSingleResult;
+
+	/* Allocate workspace */
+	nargs = length(rstate->args);
+	if (nargs == 0)				/* avoid palloc(0) if no fields */
+		nargs = 1;
+	values = (Datum *) palloc(nargs * sizeof(Datum));
+	nulls = (char *) palloc(nargs * sizeof(char));
+
+	/* Evaluate field values */
+	i = 0;
+	foreach(arg, rstate->args)
+	{
+		ExprState  *e = (ExprState *) lfirst(arg);
+		bool		eisnull;
+
+		values[i] = ExecEvalExpr(e, econtext, &eisnull, NULL);
+		nulls[i] = eisnull ? 'n' : ' ';
+		i++;
+	}
+
+	tuple = heap_formtuple(rstate->tupdesc, values, nulls);
+
+	pfree(values);
+	pfree(nulls);
+
+	return HeapTupleGetDatum(tuple);
+}
+
 /* ----------------------------------------------------------------
  *		ExecEvalCoalesce
  * ----------------------------------------------------------------
@@ -2822,6 +2874,39 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				state = (ExprState *) astate;
 			}
 			break;
+		case T_RowExpr:
+			{
+				RowExpr	   *rowexpr = (RowExpr *) node;
+				RowExprState *rstate = makeNode(RowExprState);
+				List	   *outlist;
+				List	   *inlist;
+
+				rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRow;
+				outlist = NIL;
+				foreach(inlist, rowexpr->args)
+				{
+					Expr	   *e = (Expr *) lfirst(inlist);
+					ExprState  *estate;
+
+					estate = ExecInitExpr(e, parent);
+					outlist = lappend(outlist, estate);
+				}
+				rstate->args = outlist;
+				/* Build tupdesc to describe result tuples */
+				if (rowexpr->row_typeid == RECORDOID)
+				{
+					/* generic record, use runtime type assignment */
+					rstate->tupdesc = ExecTypeFromExprList(rowexpr->args);
+					rstate->tupdesc = BlessTupleDesc(rstate->tupdesc);
+				}
+				else
+				{
+					/* it's been cast to a named type, use that */
+					rstate->tupdesc = lookup_rowtype_tupdesc(rowexpr->row_typeid, -1);
+				}
+				state = (ExprState *) rstate;
+			}
+			break;
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index faf910b736f9828f43d90c37dab839ee02bf9e46..725b8fea0ffcc9cacc3bae759ff6c260bf69b389 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.76 2004/04/01 21:28:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.77 2004/05/10 22:44:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -111,6 +111,7 @@
 #include "access/heapam.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
+#include "parser/parse_expr.h"
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
@@ -118,6 +119,7 @@
 static TupleDesc ExecTypeFromTLInternal(List *targetList,
 										bool hasoid, bool skipjunk);
 
+
 /* ----------------------------------------------------------------
  *				  tuple table create/delete functions
  * ----------------------------------------------------------------
@@ -595,6 +597,38 @@ ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk)
 	return typeInfo;
 }
 
+/*
+ * ExecTypeFromExprList - build a tuple descriptor from a list of Exprs
+ *
+ * Here we must make up an arbitrary set of field names.
+ */
+TupleDesc
+ExecTypeFromExprList(List *exprList)
+{
+	TupleDesc	 typeInfo;
+	List		*l;
+	int			 cur_resno = 1;
+	char		fldname[NAMEDATALEN];
+
+	typeInfo = CreateTemplateTupleDesc(length(exprList), false);
+
+	foreach(l, exprList)
+	{
+		Node	*e = lfirst(l);
+
+		sprintf(fldname, "f%d", cur_resno);
+
+		TupleDescInitEntry(typeInfo,
+						   cur_resno++,
+						   fldname,
+						   exprType(e),
+						   exprTypmod(e),
+						   0);
+	}
+
+	return typeInfo;
+}
+
 /*
  * BlessTupleDesc - make a completed tuple descriptor useful for SRFs
  *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 1466be98cbb4d65efe71af0c1a4409a181bda26f..91fb3b08343ca38dcb61ea8f145598c84f20196e 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
- *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.280 2004/05/05 04:48:45 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.281 2004/05/10 22:44:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -998,6 +998,21 @@ _copyArrayExpr(ArrayExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyRowExpr
+ */
+static RowExpr *
+_copyRowExpr(RowExpr *from)
+{
+	RowExpr  *newnode = makeNode(RowExpr);
+
+	COPY_NODE_FIELD(args);
+	COPY_SCALAR_FIELD(row_typeid);
+	COPY_SCALAR_FIELD(row_format);
+
+	return newnode;
+}
+
 /*
  * _copyCoalesceExpr
  */
@@ -2674,6 +2689,9 @@ copyObject(void *from)
 		case T_ArrayExpr:
 			retval = _copyArrayExpr(from);
 			break;
+		case T_RowExpr:
+			retval = _copyRowExpr(from);
+			break;
 		case T_CoalesceExpr:
 			retval = _copyCoalesceExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index eab30d122c2b1cf7f2cac86a3ecd4d8fcbf94ff1..19ffbb1be708ba647a1424536bf012163261f095 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.219 2004/05/05 04:48:45 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.220 2004/05/10 22:44:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -423,6 +423,24 @@ _equalArrayExpr(ArrayExpr *a, ArrayExpr *b)
 	return true;
 }
 
+static bool
+_equalRowExpr(RowExpr *a, RowExpr *b)
+{
+	COMPARE_NODE_FIELD(args);
+	COMPARE_SCALAR_FIELD(row_typeid);
+
+	/*
+	 * Special-case COERCE_DONTCARE, so that planner can build coercion
+	 * nodes that are equal() to both explicit and implicit coercions.
+	 */
+	if (a->row_format != b->row_format &&
+		a->row_format != COERCE_DONTCARE &&
+		b->row_format != COERCE_DONTCARE)
+		return false;
+
+	return true;
+}
+
 static bool
 _equalCoalesceExpr(CoalesceExpr *a, CoalesceExpr *b)
 {
@@ -1748,6 +1766,9 @@ equal(void *a, void *b)
 		case T_ArrayExpr:
 			retval = _equalArrayExpr(a, b);
 			break;
+		case T_RowExpr:
+			retval = _equalRowExpr(a, b);
+			break;
 		case T_CoalesceExpr:
 			retval = _equalCoalesceExpr(a, b);
 			break;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 230910aca0f98b364605e5e670af37d3a9a8665b..8842bd4aa3b4e4728981c7bbd448c377d4866f0e 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -9,12 +9,13 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.42 2003/11/29 19:51:49 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/makefuncs.c,v 1.43 2004/05/10 22:44:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "utils/lsyscache.h"
 
@@ -170,6 +171,17 @@ makeNullConst(Oid consttype)
 					 typByVal);
 }
 
+/*
+ * makeBoolConst -
+ *	  creates a Const node representing a boolean value (can be NULL too)
+ */
+Node *
+makeBoolConst(bool value, bool isnull)
+{
+	/* note that pg_type.h hardwires size of bool as 1 ... duplicate it */
+	return (Node *) makeConst(BOOLOID, 1, BoolGetDatum(value), isnull, true);
+}
+
 /*
  * makeBoolExpr -
  *	  creates a BoolExpr node
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9139a9bec54b004e15f67b1107814e97dac075fc..e919a851940a8f241b9d45ceeac34080416e9807 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.235 2004/05/08 21:21:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.236 2004/05/10 22:44:44 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -828,6 +828,16 @@ _outArrayExpr(StringInfo str, ArrayExpr *node)
 	WRITE_BOOL_FIELD(multidims);
 }
 
+static void
+_outRowExpr(StringInfo str, RowExpr *node)
+{
+	WRITE_NODE_TYPE("ROW");
+
+	WRITE_NODE_FIELD(args);
+	WRITE_OID_FIELD(row_typeid);
+	WRITE_ENUM_FIELD(row_format, CoercionForm);
+}
+
 static void
 _outCoalesceExpr(StringInfo str, CoalesceExpr *node)
 {
@@ -1719,6 +1729,9 @@ _outNode(StringInfo str, void *obj)
 			case T_ArrayExpr:
 				_outArrayExpr(str, obj);
 				break;
+			case T_RowExpr:
+				_outRowExpr(str, obj);
+				break;
 			case T_CoalesceExpr:
 				_outCoalesceExpr(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ebd3c636c29db03287b068330eb5dd2e0dc890eb..253b5a3eafa3c5ae8826b9c757f22124075e948c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.168 2004/05/08 21:21:18 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.169 2004/05/10 22:44:44 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -628,6 +628,21 @@ _readArrayExpr(void)
 	READ_DONE();
 }
 
+/*
+ * _readRowExpr
+ */
+static RowExpr *
+_readRowExpr(void)
+{
+	READ_LOCALS(RowExpr);
+
+	READ_NODE_FIELD(args);
+	READ_OID_FIELD(row_typeid);
+	READ_ENUM_FIELD(row_format, CoercionForm);
+
+	READ_DONE();
+}
+
 /*
  * _readCoalesceExpr
  */
@@ -978,6 +993,8 @@ parseNodeString(void)
 		return_value = _readCaseTestExpr();
 	else if (MATCH("ARRAY", 5))
 		return_value = _readArrayExpr();
+	else if (MATCH("ROW", 3))
+		return_value = _readRowExpr();
 	else if (MATCH("COALESCE", 8))
 		return_value = _readCoalesceExpr();
 	else if (MATCH("NULLIFEXPR", 10))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 5d92fc4c5d193ec66b62e5728e4e5cca3eacc337..217f06a6b2bc207b57e8f817394d7ed2fcb710fc 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.113 2004/04/25 18:23:56 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.114 2004/05/10 22:44:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -57,9 +57,10 @@ static void compare_tlist_datatypes(List *tlist, List *colTypes,
 						bool *differentTypes);
 static bool qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
 					  bool *differentTypes);
-static void subquery_push_qual(Query *subquery, Index rti, Node *qual);
+static void subquery_push_qual(Query *subquery,
+							   RangeTblEntry *rte, Index rti, Node *qual);
 static void recurse_push_qual(Node *setOp, Query *topquery,
-				  Index rti, Node *qual);
+							  RangeTblEntry *rte, Index rti, Node *qual);
 
 
 /*
@@ -375,7 +376,7 @@ set_subquery_pathlist(Query *root, RelOptInfo *rel,
 			if (qual_is_pushdown_safe(subquery, rti, clause, differentTypes))
 			{
 				/* Push it down */
-				subquery_push_qual(subquery, rti, clause);
+				subquery_push_qual(subquery, rte, rti, clause);
 			}
 			else
 			{
@@ -778,12 +779,12 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
  * subquery_push_qual - push down a qual that we have determined is safe
  */
 static void
-subquery_push_qual(Query *subquery, Index rti, Node *qual)
+subquery_push_qual(Query *subquery, RangeTblEntry *rte, Index rti, Node *qual)
 {
 	if (subquery->setOperations != NULL)
 	{
 		/* Recurse to push it separately to each component query */
-		recurse_push_qual(subquery->setOperations, subquery, rti, qual);
+		recurse_push_qual(subquery->setOperations, subquery, rte, rti, qual);
 	}
 	else
 	{
@@ -797,7 +798,7 @@ subquery_push_qual(Query *subquery, Index rti, Node *qual)
 		 * This step also ensures that when we are pushing into a setop tree,
 		 * each component query gets its own copy of the qual.
 		 */
-		qual = ResolveNew(qual, rti, 0,
+		qual = ResolveNew(qual, rti, 0, rte,
 						  subquery->targetList,
 						  CMD_SELECT, 0);
 		subquery->havingQual = make_and_qual(subquery->havingQual,
@@ -816,23 +817,23 @@ subquery_push_qual(Query *subquery, Index rti, Node *qual)
  */
 static void
 recurse_push_qual(Node *setOp, Query *topquery,
-				  Index rti, Node *qual)
+				  RangeTblEntry *rte, Index rti, Node *qual)
 {
 	if (IsA(setOp, RangeTblRef))
 	{
 		RangeTblRef *rtr = (RangeTblRef *) setOp;
-		RangeTblEntry *rte = rt_fetch(rtr->rtindex, topquery->rtable);
-		Query	   *subquery = rte->subquery;
+		RangeTblEntry *subrte = rt_fetch(rtr->rtindex, topquery->rtable);
+		Query	   *subquery = subrte->subquery;
 
 		Assert(subquery != NULL);
-		subquery_push_qual(subquery, rti, qual);
+		subquery_push_qual(subquery, rte, rti, qual);
 	}
 	else if (IsA(setOp, SetOperationStmt))
 	{
 		SetOperationStmt *op = (SetOperationStmt *) setOp;
 
-		recurse_push_qual(op->larg, topquery, rti, qual);
-		recurse_push_qual(op->rarg, topquery, rti, qual);
+		recurse_push_qual(op->larg, topquery, rte, rti, qual);
+		recurse_push_qual(op->rarg, topquery, rte, rti, qual);
 	}
 	else
 	{
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 2cd445961e932d341b42f816a06ebfc22c6ff457..96ce94c8fcbc0bc7c8f6b98666c32f94cd4bdd0d 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.64 2004/01/05 16:44:40 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/path/clausesel.c,v 1.65 2004/05/10 22:44:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,11 +27,6 @@
 #include "utils/selfuncs.h"
 
 
-/* note that pg_type.h hardwires size of bool as 1 ... duplicate it */
-#define MAKEBOOLCONST(val,isnull) \
-	((Node *) makeConst(BOOLOID, 1, (Datum) (val), (isnull), true))
-
-
 /*
  * Data structure for accumulating info about possible range-query
  * clause pairs in clauselist_selectivity.
@@ -486,7 +481,7 @@ clause_selectivity(Query *root,
 				s1 = restriction_selectivity(root,
 											 BooleanEqualOperator,
 											 makeList2(var,
-													   MAKEBOOLCONST(true,
+													   makeBoolConst(true,
 																 false)),
 											 varRelid);
 			}
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 033aaba494e0436cd432d615558dc2f3f52675e4..0ddf1ccd67f2b8dbd1188b5e5d44fd7e0ce66d96 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -16,7 +16,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.16 2004/01/10 18:13:53 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.17 2004/05/10 22:44:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,7 +45,8 @@ typedef struct reduce_outer_joins_state
 
 static bool is_simple_subquery(Query *subquery);
 static bool has_nullable_targetlist(Query *subquery);
-static void resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist);
+static void resolvenew_in_jointree(Node *jtnode, int varno,
+								   RangeTblEntry *rte, List *subtlist);
 static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode);
 static void reduce_outer_joins_pass2(Node *jtnode,
 						 reduce_outer_joins_state *state,
@@ -154,14 +155,10 @@ pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join)
 		 * such expressions; we'd have to figure out how to get the pseudo-
 		 * variables evaluated at the right place in the modified plan
 		 * tree. Fix it someday.
-		 *
-		 * Note: even if the subquery itself is simple enough, we can't pull
-		 * it up if there is a reference to its whole tuple result.
-		 * Perhaps a pseudo-variable is the answer here too.
 		 */
-		if (rte->rtekind == RTE_SUBQUERY && is_simple_subquery(subquery) &&
-			(!below_outer_join || has_nullable_targetlist(subquery)) &&
-			!contain_whole_tuple_var((Node *) parse, varno, 0))
+		if (rte->rtekind == RTE_SUBQUERY &&
+			is_simple_subquery(subquery) &&
+			(!below_outer_join || has_nullable_targetlist(subquery)))
 		{
 			int			rtoffset;
 			List	   *subtlist;
@@ -206,8 +203,7 @@ pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join)
 			 * the one above.
 			 */
 			if (is_simple_subquery(subquery) &&
-				(!below_outer_join || has_nullable_targetlist(subquery)) &&
-				!contain_whole_tuple_var((Node *) parse, varno, 0))
+				(!below_outer_join || has_nullable_targetlist(subquery)))
 			{
 				/* good to go */
 			}
@@ -247,24 +243,25 @@ pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join)
 			subtlist = subquery->targetList;
 			parse->targetList = (List *)
 				ResolveNew((Node *) parse->targetList,
-						   varno, 0, subtlist, CMD_SELECT, 0);
-			resolvenew_in_jointree((Node *) parse->jointree, varno, subtlist);
+						   varno, 0, rte, subtlist, CMD_SELECT, 0);
+			resolvenew_in_jointree((Node *) parse->jointree, varno,
+								   rte, subtlist);
 			Assert(parse->setOperations == NULL);
 			parse->havingQual =
 				ResolveNew(parse->havingQual,
-						   varno, 0, subtlist, CMD_SELECT, 0);
+						   varno, 0, rte, subtlist, CMD_SELECT, 0);
 			parse->in_info_list = (List *)
 				ResolveNew((Node *) parse->in_info_list,
-						   varno, 0, subtlist, CMD_SELECT, 0);
+						   varno, 0, rte, subtlist, CMD_SELECT, 0);
 
 			foreach(rt, parse->rtable)
 			{
-				RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+				RangeTblEntry *otherrte = (RangeTblEntry *) lfirst(rt);
 
-				if (rte->rtekind == RTE_JOIN)
-					rte->joinaliasvars = (List *)
-						ResolveNew((Node *) rte->joinaliasvars,
-								   varno, 0, subtlist, CMD_SELECT, 0);
+				if (otherrte->rtekind == RTE_JOIN)
+					otherrte->joinaliasvars = (List *)
+						ResolveNew((Node *) otherrte->joinaliasvars,
+								   varno, 0, rte, subtlist, CMD_SELECT, 0);
 			}
 
 			/*
@@ -479,7 +476,8 @@ has_nullable_targetlist(Query *subquery)
  * but there's no other way...
  */
 static void
-resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist)
+resolvenew_in_jointree(Node *jtnode, int varno,
+					   RangeTblEntry *rte, List *subtlist)
 {
 	if (jtnode == NULL)
 		return;
@@ -493,18 +491,18 @@ resolvenew_in_jointree(Node *jtnode, int varno, List *subtlist)
 		List	   *l;
 
 		foreach(l, f->fromlist)
-			resolvenew_in_jointree(lfirst(l), varno, subtlist);
+			resolvenew_in_jointree(lfirst(l), varno, rte, subtlist);
 		f->quals = ResolveNew(f->quals,
-							  varno, 0, subtlist, CMD_SELECT, 0);
+							  varno, 0, rte, subtlist, CMD_SELECT, 0);
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		resolvenew_in_jointree(j->larg, varno, subtlist);
-		resolvenew_in_jointree(j->rarg, varno, subtlist);
+		resolvenew_in_jointree(j->larg, varno, rte, subtlist);
+		resolvenew_in_jointree(j->rarg, varno, rte, subtlist);
 		j->quals = ResolveNew(j->quals,
-							  varno, 0, subtlist, CMD_SELECT, 0);
+							  varno, 0, rte, subtlist, CMD_SELECT, 0);
 
 		/*
 		 * We don't bother to update the colvars list, since it won't be
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index f653640ee8ca7203e3e22bda40079057e0d8c800..4dabbf50dac8a2b2a7d1d9e7c28776412381a074 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.169 2004/04/02 23:14:08 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.170 2004/05/10 22:44:45 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -41,10 +41,6 @@
 #include "utils/syscache.h"
 
 
-/* note that pg_type.h hardwires size of bool as 1 ... duplicate it */
-#define MAKEBOOLCONST(val,isnull) \
-	((Node *) makeConst(BOOLOID, 1, (Datum) (val), (isnull), true))
-
 typedef struct
 {
 	int			nargs;
@@ -281,7 +277,7 @@ Expr *
 make_ands_explicit(List *andclauses)
 {
 	if (andclauses == NIL)
-		return (Expr *) MAKEBOOLCONST(true, false);
+		return (Expr *) makeBoolConst(true, false);
 	else if (lnext(andclauses) == NIL)
 		return (Expr *) lfirst(andclauses);
 	else
@@ -484,6 +480,8 @@ expression_returns_set_walker(Node *node, void *context)
 		return false;
 	if (IsA(node, ArrayExpr))
 		return false;
+	if (IsA(node, RowExpr))
+		return false;
 	if (IsA(node, CoalesceExpr))
 		return false;
 	if (IsA(node, NullIfExpr))
@@ -778,6 +776,8 @@ contain_nonstrict_functions_walker(Node *node, void *context)
 	if (IsA(node, CaseWhen))
 		return true;
 	/* NB: ArrayExpr might someday be nonstrict */
+	if (IsA(node, RowExpr))
+		return true;
 	if (IsA(node, CoalesceExpr))
 		return true;
 	if (IsA(node, NullIfExpr))
@@ -1030,6 +1030,8 @@ set_coercionform_dontcare_walker(Node *node, void *context)
 		((FuncExpr *) node)->funcformat = COERCE_DONTCARE;
 	if (IsA(node, RelabelType))
 		((RelabelType *) node)->relabelformat = COERCE_DONTCARE;
+	if (IsA(node, RowExpr))
+		((RowExpr *) node)->row_format = COERCE_DONTCARE;
 	if (IsA(node, CoerceToDomain))
 		((CoerceToDomain *) node)->coercionformat = COERCE_DONTCARE;
 	return expression_tree_walker(node, set_coercionform_dontcare_walker,
@@ -1197,11 +1199,11 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		{
 			/* all nulls? then not distinct */
 			if (all_null_input)
-				return MAKEBOOLCONST(false, false);
+				return makeBoolConst(false, false);
 
 			/* one null? then distinct */
 			if (has_null_input)
-				return MAKEBOOLCONST(true, false);
+				return makeBoolConst(true, false);
 
 			/* otherwise try to evaluate the '=' operator */
 			/* (NOT okay to try to inline it, though!) */
@@ -1272,12 +1274,12 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 					newargs = simplify_or_arguments(args,
 													&haveNull, &forceTrue);
 					if (forceTrue)
-						return MAKEBOOLCONST(true, false);
+						return makeBoolConst(true, false);
 					if (haveNull)
-						newargs = lappend(newargs, MAKEBOOLCONST(false, true));
+						newargs = lappend(newargs, makeBoolConst(false, true));
 					/* If all the inputs are FALSE, result is FALSE */
 					if (newargs == NIL)
-						return MAKEBOOLCONST(false, false);
+						return makeBoolConst(false, false);
 					/* If only one nonconst-or-NULL input, it's the result */
 					if (lnext(newargs) == NIL)
 						return (Node *) lfirst(newargs);
@@ -1293,12 +1295,12 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 					newargs = simplify_and_arguments(args,
 													 &haveNull, &forceFalse);
 					if (forceFalse)
-						return MAKEBOOLCONST(false, false);
+						return makeBoolConst(false, false);
 					if (haveNull)
-						newargs = lappend(newargs, MAKEBOOLCONST(false, true));
+						newargs = lappend(newargs, makeBoolConst(false, true));
 					/* If all the inputs are TRUE, result is TRUE */
 					if (newargs == NIL)
-						return MAKEBOOLCONST(true, false);
+						return makeBoolConst(true, false);
 					/* If only one nonconst-or-NULL input, it's the result */
 					if (lnext(newargs) == NIL)
 						return (Node *) lfirst(newargs);
@@ -1313,9 +1315,9 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 
 					/* NOT NULL => NULL */
 					if (const_input->constisnull)
-						return MAKEBOOLCONST(false, true);
+						return makeBoolConst(false, true);
 					/* otherwise pretty easy */
-					return MAKEBOOLCONST(!DatumGetBool(const_input->constvalue),
+					return makeBoolConst(!DatumGetBool(const_input->constvalue),
 										 false);
 				}
 				else if (not_clause((Node *) lfirst(args)))
@@ -1387,7 +1389,6 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 	}
 	if (IsA(node, CaseExpr))
 	{
-
 		/*----------
 		 * CASE expressions can be simplified if there are constant
 		 * condition clauses:
@@ -1546,22 +1547,38 @@ eval_const_expressions_mutator(Node *node, List *active_fns)
 		 * We can optimize field selection from a whole-row Var into a
 		 * simple Var.	(This case won't be generated directly by the
 		 * parser, because ParseComplexProjection short-circuits it. But
-		 * it can arise while simplifying functions.)  If the argument
-		 * isn't a whole-row Var, just fall through to do generic
-		 * processing.
+		 * it can arise while simplifying functions.)  Also, we can
+		 * optimize field selection from a RowExpr construct.
 		 */
 		FieldSelect *fselect = (FieldSelect *) node;
-		Var		   *argvar = (Var *) fselect->arg;
+		FieldSelect *newfselect;
+		Node	   *arg;
 
-		if (argvar && IsA(argvar, Var) &&
-			argvar->varattno == InvalidAttrNumber)
+		arg = eval_const_expressions_mutator((Node *) fselect->arg,
+											 active_fns);
+		if (arg && IsA(arg, Var) &&
+			((Var *) arg)->varattno == InvalidAttrNumber)
 		{
-			return (Node *) makeVar(argvar->varno,
+			return (Node *) makeVar(((Var *) arg)->varno,
 									fselect->fieldnum,
 									fselect->resulttype,
 									fselect->resulttypmod,
-									argvar->varlevelsup);
+									((Var *) arg)->varlevelsup);
 		}
+		if (arg && IsA(arg, RowExpr))
+		{
+			RowExpr	*rowexpr = (RowExpr *) arg;
+
+			if (fselect->fieldnum > 0 &&
+				fselect->fieldnum <= length(rowexpr->args))
+				return (Node *) nth(fselect->fieldnum - 1, rowexpr->args);
+		}
+		newfselect = makeNode(FieldSelect);
+		newfselect->arg = (Expr *) arg;
+		newfselect->fieldnum = fselect->fieldnum;
+		newfselect->resulttype = fselect->resulttype;
+		newfselect->resulttypmod = fselect->resulttypmod;
+		return (Node *) newfselect;
 	}
 
 	/*
@@ -1759,7 +1776,6 @@ evaluate_function(Oid funcid, Oid result_type, List *args,
 	bool		has_null_input = false;
 	List	   *arg;
 	FuncExpr   *newexpr;
-	char		result_typtype;
 
 	/*
 	 * Can't simplify if it returns a set.
@@ -1796,15 +1812,6 @@ evaluate_function(Oid funcid, Oid result_type, List *args,
 		has_nonconst_input)
 		return NULL;
 
-	/*
-	 * Can't simplify functions returning composite types (mainly because
-	 * datumCopy() doesn't cope; FIXME someday when we have a saner
-	 * representation for whole-tuple results).
-	 */
-	result_typtype = get_typtype(funcform->prorettype);
-	if (result_typtype == 'c')
-		return NULL;
-
 	/*
 	 * OK, looks like we can simplify this operator/function.
 	 *
@@ -1850,7 +1857,6 @@ inline_function(Oid funcid, Oid result_type, List *args,
 				HeapTuple func_tuple, List *active_fns)
 {
 	Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
-	char		result_typtype;
 	bool		polymorphic = false;
 	Oid			argtypes[FUNC_MAX_ARGS];
 	char	   *src;
@@ -1877,21 +1883,6 @@ inline_function(Oid funcid, Oid result_type, List *args,
 		funcform->pronargs != length(args))
 		return NULL;
 
-	/*
-	 * Forget it if declared return type is not base, domain, or
-	 * polymorphic
-	 */
-	result_typtype = get_typtype(funcform->prorettype);
-	if (result_typtype != 'b' &&
-		result_typtype != 'd')
-	{
-		if (funcform->prorettype == ANYARRAYOID ||
-			funcform->prorettype == ANYELEMENTOID)
-			polymorphic = true;
-		else
-			return NULL;
-	}
-
 	/* Check for recursive function, and give up trying to expand if so */
 	if (oidMember(funcid, active_fns))
 		return NULL;
@@ -1912,6 +1903,10 @@ inline_function(Oid funcid, Oid result_type, List *args,
 		}
 	}
 
+	if (funcform->prorettype == ANYARRAYOID ||
+		funcform->prorettype == ANYELEMENTOID)
+		polymorphic = true;
+
 	/*
 	 * Setup error traceback support for ereport().  This is so that we
 	 * can finger the function that bad information came from.
@@ -2483,6 +2478,8 @@ expression_tree_walker(Node *node,
 			break;
 		case T_ArrayExpr:
 			return walker(((ArrayExpr *) node)->elements, context);
+		case T_RowExpr:
+			return walker(((RowExpr *) node)->args, context);
 		case T_CoalesceExpr:
 			return walker(((CoalesceExpr *) node)->args, context);
 		case T_NullIfExpr:
@@ -2889,6 +2886,16 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_RowExpr:
+			{
+				RowExpr	   *rowexpr = (RowExpr *) node;
+				RowExpr	   *newnode;
+
+				FLATCOPY(newnode, rowexpr, RowExpr);
+				MUTATE(newnode->args, rowexpr->args, List *);
+				return (Node *) newnode;
+			}
+			break;
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index dac90809de19db4bf41440a74b7df461046cc587..47b1fdbf70efc3c023370edb3fd201042b73f347 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.55 2003/11/29 19:51:51 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.56 2004/05/10 22:44:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -191,19 +191,6 @@ contain_var_reference_walker(Node *node,
 }
 
 
-/*
- *		contain_whole_tuple_var
- *
- *		Detect whether a parsetree contains any references to the whole
- *		tuple of a given rtable entry (ie, a Var with varattno = 0).
- */
-bool
-contain_whole_tuple_var(Node *node, int varno, int levelsup)
-{
-	return contain_var_reference(node, varno, InvalidAttrNumber, levelsup);
-}
-
-
 /*
  * contain_var_clause
  *	  Recursively scan a clause to discover whether it contains any Var nodes
@@ -486,7 +473,10 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
  * flatten_join_alias_vars
  *	  Replace Vars that reference JOIN outputs with references to the original
  *	  relation variables instead.  This allows quals involving such vars to be
- *	  pushed down.
+ *	  pushed down.  Whole-row Vars that reference JOIN relations are expanded
+ *	  into RowExpr constructs that name the individual output Vars.  This
+ *	  is necessary since we will not scan the JOIN as a base relation, which
+ *	  is the only way that the executor can directly handle whole-row Vars.
  *
  * NOTE: this is used on not-yet-planned expressions.  We do not expect it
  * to be applied directly to a Query node.
@@ -520,8 +510,39 @@ flatten_join_alias_vars_mutator(Node *node,
 		rte = rt_fetch(var->varno, context->root->rtable);
 		if (rte->rtekind != RTE_JOIN)
 			return node;
+		if (var->varattno == InvalidAttrNumber)
+		{
+			/* Must expand whole-row reference */
+			RowExpr *rowexpr;
+			List	*fields = NIL;
+			List    *l;
+
+			foreach(l, rte->joinaliasvars)
+			{
+				newvar = (Node *) lfirst(l);
+				/*
+				 * If we are expanding an alias carried down from an upper
+				 * query, must adjust its varlevelsup fields.
+				 */
+				if (context->sublevels_up != 0)
+				{
+					newvar = copyObject(newvar);
+					IncrementVarSublevelsUp(newvar, context->sublevels_up, 0);
+				}
+				/* Recurse in case join input is itself a join */
+				newvar = flatten_join_alias_vars_mutator(newvar, context);
+				fields = lappend(fields, newvar);
+			}
+			rowexpr = makeNode(RowExpr);
+			rowexpr->args = fields;
+			rowexpr->row_typeid = var->vartype;
+			rowexpr->row_format = COERCE_IMPLICIT_CAST;
+
+			return (Node *) rowexpr;
+		}
+
+		/* Expand join alias reference */
 		Assert(var->varattno > 0);
-		/* Okay, must expand it */
 		newvar = (Node *) nth(var->varattno - 1, rte->joinaliasvars);
 
 		/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 64825893bc10b5a40f20da93a1018251236ff461..b9becf91dbb908f52d0cfc966a217d2a1c8dc7a5 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.453 2004/05/05 04:48:46 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.454 2004/05/10 22:44:45 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -80,11 +80,9 @@ static Node *makeStringConst(char *str, TypeName *typename);
 static Node *makeIntConst(int val);
 static Node *makeFloatConst(char *str);
 static Node *makeAConst(Value *v);
-static Node *makeRowExpr(List *opr, List *largs, List *rargs);
-static Node *makeDistinctExpr(List *largs, List *rargs);
-static Node *makeRowNullTest(NullTestType test, List *args);
+static Node *makeRowNullTest(NullTestType test, RowExpr *row);
 static DefElem *makeDefElem(char *name, Node *arg);
-static A_Const *makeBoolConst(bool state);
+static A_Const *makeBoolAConst(bool state);
 static FuncCall *makeOverlaps(List *largs, List *rargs);
 static List *extractArgTypes(List *parameters);
 static SelectStmt *findLeftmostSelect(SelectStmt *node);
@@ -277,9 +275,9 @@ static void doNegateFloat(Value *v);
 %type <node>	columnDef
 %type <defelt>	def_elem
 %type <node>	def_arg columnElem where_clause insert_column_item
-				a_expr b_expr c_expr r_expr AexprConst
+				a_expr b_expr c_expr AexprConst
 				in_expr having_clause func_table array_expr
-%type <list>	row row_descriptor type_list array_expr_list
+%type <list>	row type_list array_expr_list
 %type <node>	case_expr case_arg when_clause case_default
 %type <list>	when_clause_list
 %type <ival>	sub_type
@@ -5710,163 +5708,6 @@ opt_interval:
  *
  *****************************************************************************/
 
-/* Expressions using row descriptors
- * Define row_descriptor to allow yacc to break the reduce/reduce conflict
- * with singleton expressions. Use SQL99's ROW keyword to allow rows of
- * one element.
- */
-r_expr:  row IN_P select_with_parens
-				{
-					SubLink *n = makeNode(SubLink);
-					n->subLinkType = ANY_SUBLINK;
-					n->lefthand = $1;
-					n->operName = makeList1(makeString("="));
-					n->subselect = $3;
-					$$ = (Node *)n;
-				}
-			| row NOT IN_P select_with_parens
-				{
-					/* Make an IN node */
-					SubLink *n = makeNode(SubLink);
-					n->subLinkType = ANY_SUBLINK;
-					n->lefthand = $1;
-					n->operName = makeList1(makeString("="));
-					n->subselect = $4;
-					/* Stick a NOT on top */
-					$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL, (Node *) n);
-				}
-			| row subquery_Op sub_type select_with_parens
-			%prec Op
-				{
-					SubLink *n = makeNode(SubLink);
-					n->subLinkType = $3;
-					n->lefthand = $1;
-					n->operName = $2;
-					n->subselect = $4;
-					$$ = (Node *)n;
-				}
-			| row subquery_Op select_with_parens
-			%prec Op
-				{
-					SubLink *n = makeNode(SubLink);
-					n->subLinkType = MULTIEXPR_SUBLINK;
-					n->lefthand = $1;
-					n->operName = $2;
-					n->subselect = $3;
-					$$ = (Node *)n;
-				}
-			| row subquery_Op row
-			%prec Op
-				{
-					$$ = makeRowExpr($2, $1, $3);
-				}
-			| row IS NULL_P
-				{
-					$$ = makeRowNullTest(IS_NULL, $1);
-				}
-			| row IS NOT NULL_P
-				{
-					$$ = makeRowNullTest(IS_NOT_NULL, $1);
-				}
-			| row OVERLAPS row
-				{
-					$$ = (Node *)makeOverlaps($1, $3);
-				}
-			| row IS DISTINCT FROM row
-			%prec IS
-				{
-					/* IS DISTINCT FROM has the following rules for non-array types:
-					 * a) the row lengths must be equal
-					 * b) if both rows are zero-length, then they are not distinct
-					 * c) if any element is distinct, the rows are distinct
-					 * The rules for an element being distinct:
-					 * a) if the elements are both NULL, then they are not distinct
-					 * b) if the elements compare to be equal, then they are not distinct
-					 * c) otherwise, they are distinct
-					 */
-					List *largs = $1;
-					List *rargs = $5;
-					/* lengths don't match? then complain */
-					if (length(largs) != length(rargs))
-					{
-						ereport(ERROR,
-								(errcode(ERRCODE_SYNTAX_ERROR),
-								 errmsg("unequal number of entries in row expression")));
-					}
-					/* both are zero-length rows? then they are not distinct */
-					else if (length(largs) <= 0)
-					{
-						$$ = (Node *)makeBoolConst(FALSE);
-					}
-					/* otherwise, we need to compare each element */
-					else
-					{
-						$$ = (Node *)makeDistinctExpr(largs, rargs);
-					}
-				}
-		;
-
-/* Explicit row production.
- * SQL99 allows an optional ROW keyword, so we can now do single-element productions
- * without conflicting with the parenthesized a_expr production.
- */
-row:  ROW '(' row_descriptor ')'					{ $$ = $3; }
-			| ROW '(' a_expr ')'					{ $$ = makeList1($3); }
-			| ROW '(' ')'							{ $$ = NULL; }
-			| '(' row_descriptor ')'				{ $$ = $2; }
-		;
-
-row_descriptor:  expr_list ',' a_expr				{ $$ = lappend($1, $3); }
-		;
-
-sub_type:	ANY										{ $$ = ANY_SUBLINK; }
-			| SOME									{ $$ = ANY_SUBLINK; }
-			| ALL									{ $$ = ALL_SUBLINK; }
-		;
-
-all_Op:		Op										{ $$ = $1; }
-			| MathOp								{ $$ = $1; }
-		;
-
-MathOp:		 '+'									{ $$ = "+"; }
-			| '-'									{ $$ = "-"; }
-			| '*'									{ $$ = "*"; }
-			| '/'									{ $$ = "/"; }
-			| '%'									{ $$ = "%"; }
-			| '^'									{ $$ = "^"; }
-			| '<'									{ $$ = "<"; }
-			| '>'									{ $$ = ">"; }
-			| '='									{ $$ = "="; }
-		;
-
-qual_Op:	Op
-					{ $$ = makeList1(makeString($1)); }
-			| OPERATOR '(' any_operator ')'			{ $$ = $3; }
-		;
-
-qual_all_Op:
-			all_Op
-					{ $$ = makeList1(makeString($1)); }
-			| OPERATOR '(' any_operator ')'			{ $$ = $3; }
-		;
-
-subquery_Op:
-			all_Op { $$ = makeList1(makeString($1)); }
-			| OPERATOR '(' any_operator ')'			{ $$ = $3; }
-			| LIKE { $$ = makeList1(makeString("~~")); }
-			| NOT LIKE { $$ = makeList1(makeString("!~~")); }
-			| ILIKE { $$ = makeList1(makeString("~~*")); }
-			| NOT ILIKE { $$ = makeList1(makeString("!~~*")); }
-/* cannot put SIMILAR TO here, because SIMILAR TO is a hack.
- * the regular expression is preprocessed by a function (similar_escape),
- * and the ~ operator for posix regular expressions is used. 
- *        x SIMILAR TO y     ->    x ~ similar_escape(y)
- * this transformation is made on the fly by the parser upwards.
- * however the SubLink structure which handles any/some/all stuff
- * is not ready for such a thing.
- */
-			;
-
 /*
  * General expressions
  * This is the heart of the expression syntax.
@@ -6046,31 +5887,55 @@ a_expr:		c_expr									{ $$ = $1; }
 			 */
 			| a_expr ISNULL
 				{
-					NullTest *n = makeNode(NullTest);
-					n->arg = (Expr *) $1;
-					n->nulltesttype = IS_NULL;
-					$$ = (Node *)n;
+					if (IsA($1, RowExpr))
+						$$ = makeRowNullTest(IS_NULL, (RowExpr *) $1);
+					else
+					{
+						NullTest *n = makeNode(NullTest);
+						n->arg = (Expr *) $1;
+						n->nulltesttype = IS_NULL;
+						$$ = (Node *)n;
+					}
 				}
 			| a_expr IS NULL_P
 				{
-					NullTest *n = makeNode(NullTest);
-					n->arg = (Expr *) $1;
-					n->nulltesttype = IS_NULL;
-					$$ = (Node *)n;
+					if (IsA($1, RowExpr))
+						$$ = makeRowNullTest(IS_NULL, (RowExpr *) $1);
+					else
+					{
+						NullTest *n = makeNode(NullTest);
+						n->arg = (Expr *) $1;
+						n->nulltesttype = IS_NULL;
+						$$ = (Node *)n;
+					}
 				}
 			| a_expr NOTNULL
 				{
-					NullTest *n = makeNode(NullTest);
-					n->arg = (Expr *) $1;
-					n->nulltesttype = IS_NOT_NULL;
-					$$ = (Node *)n;
+					if (IsA($1, RowExpr))
+						$$ = makeRowNullTest(IS_NOT_NULL, (RowExpr *) $1);
+					else
+					{
+						NullTest *n = makeNode(NullTest);
+						n->arg = (Expr *) $1;
+						n->nulltesttype = IS_NOT_NULL;
+						$$ = (Node *)n;
+					}
 				}
 			| a_expr IS NOT NULL_P
 				{
-					NullTest *n = makeNode(NullTest);
-					n->arg = (Expr *) $1;
-					n->nulltesttype = IS_NOT_NULL;
-					$$ = (Node *)n;
+					if (IsA($1, RowExpr))
+						$$ = makeRowNullTest(IS_NOT_NULL, (RowExpr *) $1);
+					else
+					{
+						NullTest *n = makeNode(NullTest);
+						n->arg = (Expr *) $1;
+						n->nulltesttype = IS_NOT_NULL;
+						$$ = (Node *)n;
+					}
+				}
+			| row OVERLAPS row
+				{
+					$$ = (Node *)makeOverlaps($1, $3);
 				}
 			| a_expr IS TRUE_P
 				{
@@ -6115,7 +5980,9 @@ a_expr:		c_expr									{ $$ = $1; }
 					$$ = (Node *)b;
 				}
 			| a_expr IS DISTINCT FROM a_expr			%prec IS
-				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5); }
+				{
+					$$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5);
+				}
 			| a_expr IS OF '(' type_list ')'			%prec IS
 				{
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "=", $1, (Node *) $5);
@@ -6143,7 +6010,10 @@ a_expr:		c_expr									{ $$ = $1; }
 					{
 							SubLink *n = (SubLink *)$3;
 							n->subLinkType = ANY_SUBLINK;
-							n->lefthand = makeList1($1);
+							if (IsA($1, RowExpr))
+								n->lefthand = ((RowExpr *) $1)->args;
+							else
+								n->lefthand = makeList1($1);
 							n->operName = makeList1(makeString("="));
 							$$ = (Node *)n;
 					}
@@ -6171,7 +6041,10 @@ a_expr:		c_expr									{ $$ = $1; }
 						/* Make an IN node */
 						SubLink *n = (SubLink *)$4;
 						n->subLinkType = ANY_SUBLINK;
-						n->lefthand = makeList1($1);
+						if (IsA($1, RowExpr))
+							n->lefthand = ((RowExpr *) $1)->args;
+						else
+							n->lefthand = makeList1($1);
 						n->operName = makeList1(makeString("="));
 						/* Stick a NOT on top */
 						$$ = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL, (Node *) n);
@@ -6196,7 +6069,10 @@ a_expr:		c_expr									{ $$ = $1; }
 				{
 					SubLink *n = makeNode(SubLink);
 					n->subLinkType = $3;
-					n->lefthand = makeList1($1);
+					if (IsA($1, RowExpr))
+						n->lefthand = ((RowExpr *) $1)->args;
+					else
+						n->lefthand = makeList1($1);
 					n->operName = $2;
 					n->subselect = $4;
 					$$ = (Node *)n;
@@ -6223,8 +6099,6 @@ a_expr:		c_expr									{ $$ = $1; }
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("UNIQUE predicate is not yet implemented")));
 				}
-			| r_expr
-				{ $$ = $1; }
 		;
 
 /*
@@ -6277,7 +6151,9 @@ b_expr:		c_expr
 			| b_expr qual_Op					%prec POSTFIXOP
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, NULL); }
 			| b_expr IS DISTINCT FROM b_expr	%prec IS
-				{ $$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5); }
+				{
+					$$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5);
+				}
 			| b_expr IS OF '(' type_list ')'	%prec IS
 				{
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "=", $1, (Node *) $5);
@@ -6819,12 +6695,86 @@ c_expr:		columnref								{ $$ = (Node *) $1; }
 				}
 			| ARRAY array_expr
 				{	$$ = $2;	}
+			| row
+				{
+					RowExpr *r = makeNode(RowExpr);
+					r->args = $1;
+					r->row_typeid = InvalidOid;	/* not analyzed yet */
+					$$ = (Node *)r;
+				}
 		;
 
 /*
  * Supporting nonterminals for expressions.
  */
 
+/* Explicit row production.
+ *
+ * SQL99 allows an optional ROW keyword, so we can now do single-element rows
+ * without conflicting with the parenthesized a_expr production.  Without the
+ * ROW keyword, there must be more than one a_expr inside the parens.
+ */
+row:		ROW '(' expr_list ')'					{ $$ = $3; }
+			| ROW '(' ')'							{ $$ = NIL; }
+			| '(' expr_list ',' a_expr ')'			{ $$ = lappend($2, $4); }
+		;
+
+sub_type:	ANY										{ $$ = ANY_SUBLINK; }
+			| SOME									{ $$ = ANY_SUBLINK; }
+			| ALL									{ $$ = ALL_SUBLINK; }
+		;
+
+all_Op:		Op										{ $$ = $1; }
+			| MathOp								{ $$ = $1; }
+		;
+
+MathOp:		 '+'									{ $$ = "+"; }
+			| '-'									{ $$ = "-"; }
+			| '*'									{ $$ = "*"; }
+			| '/'									{ $$ = "/"; }
+			| '%'									{ $$ = "%"; }
+			| '^'									{ $$ = "^"; }
+			| '<'									{ $$ = "<"; }
+			| '>'									{ $$ = ">"; }
+			| '='									{ $$ = "="; }
+		;
+
+qual_Op:	Op
+					{ $$ = makeList1(makeString($1)); }
+			| OPERATOR '(' any_operator ')'
+					{ $$ = $3; }
+		;
+
+qual_all_Op:
+			all_Op
+					{ $$ = makeList1(makeString($1)); }
+			| OPERATOR '(' any_operator ')'
+					{ $$ = $3; }
+		;
+
+subquery_Op:
+			all_Op
+					{ $$ = makeList1(makeString($1)); }
+			| OPERATOR '(' any_operator ')'
+					{ $$ = $3; }
+			| LIKE
+					{ $$ = makeList1(makeString("~~")); }
+			| NOT LIKE
+					{ $$ = makeList1(makeString("!~~")); }
+			| ILIKE
+					{ $$ = makeList1(makeString("~~*")); }
+			| NOT ILIKE
+					{ $$ = makeList1(makeString("!~~*")); }
+/* cannot put SIMILAR TO here, because SIMILAR TO is a hack.
+ * the regular expression is preprocessed by a function (similar_escape),
+ * and the ~ operator for posix regular expressions is used. 
+ *        x SIMILAR TO y     ->    x ~ similar_escape(y)
+ * this transformation is made on the fly by the parser upwards.
+ * however the SubLink structure which handles any/some/all stuff
+ * is not ready for such a thing.
+ */
+			;
+
 opt_indirection:
 			opt_indirection '[' a_expr ']'
 				{
@@ -7358,11 +7308,11 @@ AexprConst: Iconst
 				}
 			| TRUE_P
 				{
-					$$ = (Node *)makeBoolConst(TRUE);
+					$$ = (Node *)makeBoolAConst(TRUE);
 				}
 			| FALSE_P
 				{
-					$$ = (Node *)makeBoolConst(FALSE);
+					$$ = (Node *)makeBoolAConst(FALSE);
 				}
 			| NULL_P
 				{
@@ -7892,11 +7842,11 @@ makeDefElem(char *name, Node *arg)
 	return f;
 }
 
-/* makeBoolConst()
+/* makeBoolAConst()
  * Create an A_Const node and initialize to a boolean constant.
  */
 static A_Const *
-makeBoolConst(bool state)
+makeBoolAConst(bool state)
 {
 	A_Const *n = makeNode(A_Const);
 	n->val.type = T_String;
@@ -7905,119 +7855,41 @@ makeBoolConst(bool state)
 	return n;
 }
 
-/* makeRowExpr()
- * Generate separate operator nodes for a single row descriptor expression.
- * Perhaps this should go deeper in the parser someday...
- * - thomas 1997-12-22
+/* makeRowNullTest()
+ * Generate separate operator nodes for a single row descriptor test.
+ *
+ * Eventually this should be eliminated in favor of making the NullTest
+ * node type capable of handling it directly.
  */
 static Node *
-makeRowExpr(List *opr, List *largs, List *rargs)
+makeRowNullTest(NullTestType test, RowExpr *row)
 {
-	Node *expr = NULL;
-	Node *larg, *rarg;
-	char *oprname;
+	Node	*result = NULL;
+	List	*arg;
 
-	if (length(largs) != length(rargs))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("unequal number of entries in row expression")));
-
-	if (lnext(largs) != NIL)
-		expr = makeRowExpr(opr, lnext(largs), lnext(rargs));
-
-	larg = lfirst(largs);
-	rarg = lfirst(rargs);
+	foreach(arg, row->args)
+	{
+		NullTest *n;
 
-	oprname = strVal(llast(opr));
+		n = makeNode(NullTest);
+		n->arg = (Expr *) lfirst(arg);
+		n->nulltesttype = test;
 
-	if ((strcmp(oprname, "=") == 0) ||
-		(strcmp(oprname, "<") == 0) ||
-		(strcmp(oprname, "<=") == 0) ||
-		(strcmp(oprname, ">") == 0) ||
-		(strcmp(oprname, ">=") == 0))
-	{
-		if (expr == NULL)
-			expr = (Node *) makeA_Expr(AEXPR_OP, opr, larg, rarg);
-		else
-			expr = (Node *) makeA_Expr(AEXPR_AND, NIL, expr,
-									   (Node *) makeA_Expr(AEXPR_OP, opr,
-														   larg, rarg));
-	}
-	else if (strcmp(oprname, "<>") == 0)
-	{
-		if (expr == NULL)
-			expr = (Node *) makeA_Expr(AEXPR_OP, opr, larg, rarg);
+		if (result == NULL)
+			result = (Node *) n;
+		else if (test == IS_NOT_NULL)
+			result = (Node *) makeA_Expr(AEXPR_OR, NIL, result, (Node *)n);
 		else
-			expr = (Node *) makeA_Expr(AEXPR_OR, NIL, expr,
-									   (Node *) makeA_Expr(AEXPR_OP, opr,
-														   larg, rarg));
+			result = (Node *) makeA_Expr(AEXPR_AND, NIL, result, (Node *)n);
 	}
-	else
+
+	if (result == NULL)
 	{
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("operator %s is not supported for row expressions",
-						oprname)));
+		/* zero-length rows?  Generate constant TRUE or FALSE */
+		result = (Node *) makeBoolAConst(test == IS_NULL);
 	}
 
-	return expr;
-}
-
-/* makeDistinctExpr()
- * Generate separate operator nodes for a single row descriptor expression.
- * Same comments as for makeRowExpr().
- */
-static Node *
-makeDistinctExpr(List *largs, List *rargs)
-{
-	Node *expr = NULL;
-	Node *larg, *rarg;
-
-	if (length(largs) != length(rargs))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("unequal number of entries in row expression")));
-
-	if (lnext(largs) != NIL)
-		expr = makeDistinctExpr(lnext(largs), lnext(rargs));
-
-	larg = lfirst(largs);
-	rarg = lfirst(rargs);
-
-	if (expr == NULL)
-		expr = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", larg, rarg);
-	else
-		expr = (Node *) makeA_Expr(AEXPR_OR, NIL, expr,
-								   (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=",
-															 larg, rarg));
-
-	return expr;
-}
-
-/* makeRowNullTest()
- * Generate separate operator nodes for a single row descriptor test.
- */
-static Node *
-makeRowNullTest(NullTestType test, List *args)
-{
-	Node *expr = NULL;
-	NullTest *n;
-
-	if (lnext(args) != NIL)
-		expr = makeRowNullTest(test, lnext(args));
-
-	n = makeNode(NullTest);
-	n->arg = (Expr *) lfirst(args);
-	n->nulltesttype = test;
-
-	if (expr == NULL)
-		expr = (Node *) n;
-	else if (test == IS_NOT_NULL)
-		expr = (Node *) makeA_Expr(AEXPR_OR, NIL, expr, (Node *)n);
-	else
-		expr = (Node *) makeA_Expr(AEXPR_AND, NIL, expr, (Node *)n);
-
-	return expr;
+	return result;
 }
 
 /* makeOverlaps()
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index a5bff31c89fd684c7d8b5c643063f3f17f98dfaf..c7a8c3c83bbd27413a33755eebe0feff19a7daff 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.114 2004/03/15 01:13:40 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.115 2004/05/10 22:44:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,19 +19,26 @@
 #include "nodes/makefuncs.h"
 #include "nodes/params.h"
 #include "optimizer/clauses.h"
+#include "parser/parsetree.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_func.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 static Node *coerce_type_typmod(Node *node,
 				   Oid targetTypeId, int32 targetTypMod,
 				   CoercionForm cformat, bool isExplicit);
+static Node *coerce_record_to_complex(ParseState *pstate, Node *node,
+									  Oid targetTypeId,
+									  CoercionContext ccontext,
+									  CoercionForm cformat);
 
 
 /*
@@ -279,6 +286,13 @@ coerce_type(ParseState *pstate, Node *node,
 		}
 		return result;
 	}
+	if (inputTypeId == RECORDOID &&
+		ISCOMPLEX(targetTypeId))
+	{
+		/* Coerce a RECORD to a specific complex type */
+		return coerce_record_to_complex(pstate, node, targetTypeId,
+										ccontext, cformat);
+	}
 	if (typeInheritsFrom(inputTypeId, targetTypeId))
 	{
 		/*
@@ -359,6 +373,14 @@ can_coerce_type(int nargs, Oid *input_typeids, Oid *target_typeids,
 								  &funcId))
 			continue;
 
+		/*
+		 * If input is RECORD and target is a composite type, assume
+		 * we can coerce (may need tighter checking here)
+		 */
+		if (inputTypeId == RECORDOID &&
+			ISCOMPLEX(targetTypeId))
+			continue;
+
 		/*
 		 * If input is a class type that inherits from target, accept
 		 */
@@ -506,6 +528,103 @@ coerce_type_typmod(Node *node, Oid targetTypeId, int32 targetTypMod,
 	return node;
 }
 
+/*
+ * coerce_record_to_complex
+ *		Coerce a RECORD to a specific composite type.
+ *
+ * Currently we only support this for inputs that are RowExprs or whole-row
+ * Vars.
+ */
+static Node *
+coerce_record_to_complex(ParseState *pstate, Node *node,
+						 Oid targetTypeId,
+						 CoercionContext ccontext,
+						 CoercionForm cformat)
+{
+	RowExpr	   *rowexpr;
+	TupleDesc	tupdesc;
+	List	   *args = NIL;
+	List	   *newargs;
+	int			i;
+	List	   *arg;
+
+	if (node && IsA(node, RowExpr))
+	{
+		args = ((RowExpr *) node)->args;
+	}
+	else if (node && IsA(node, Var) &&
+			 ((Var *) node)->varattno == InvalidAttrNumber)
+	{
+		RangeTblEntry *rte;
+		AttrNumber nfields;
+		AttrNumber nf;
+
+		rte = GetRTEByRangeTablePosn(pstate,
+									 ((Var *) node)->varno,
+									 ((Var *) node)->varlevelsup);
+		nfields = length(rte->eref->colnames);
+		for (nf = 1; nf <= nfields; nf++)
+		{
+			Oid		vartype;
+			int32	vartypmod;
+
+			get_rte_attribute_type(rte, nf, &vartype, &vartypmod);
+			args = lappend(args,
+						   makeVar(((Var *) node)->varno,
+								   nf,
+								   vartype,
+								   vartypmod,
+								   ((Var *) node)->varlevelsup));
+		}
+	}
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_CANNOT_COERCE),
+				 errmsg("cannot cast type %s to %s",
+						format_type_be(RECORDOID),
+						format_type_be(targetTypeId))));
+
+	tupdesc = lookup_rowtype_tupdesc(targetTypeId, -1);
+	if (length(args) != tupdesc->natts)
+		ereport(ERROR,
+				(errcode(ERRCODE_CANNOT_COERCE),
+				 errmsg("cannot cast type %s to %s",
+						format_type_be(RECORDOID),
+						format_type_be(targetTypeId)),
+				 errdetail("Input has wrong number of columns.")));
+	newargs = NIL;
+	i = 0;
+	foreach(arg, args)
+	{
+		Node   *expr = (Node *) lfirst(arg);
+		Oid		exprtype = exprType(expr);
+
+		expr = coerce_to_target_type(pstate,
+									 expr, exprtype,
+									 tupdesc->attrs[i]->atttypid,
+									 tupdesc->attrs[i]->atttypmod,
+									 ccontext,
+									 COERCE_IMPLICIT_CAST);
+		if (expr == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_CANNOT_COERCE),
+					 errmsg("cannot cast type %s to %s",
+							format_type_be(RECORDOID),
+							format_type_be(targetTypeId)),
+					 errdetail("Cannot cast type %s to %s in column %d.",
+							   format_type_be(exprtype),
+							   format_type_be(tupdesc->attrs[i]->atttypid),
+							   i + 1)));
+		newargs = lappend(newargs, expr);
+		i++;
+	}
+
+	rowexpr = makeNode(RowExpr);
+	rowexpr->args = newargs;
+	rowexpr->row_typeid = targetTypeId;
+	rowexpr->row_format = cformat;
+	return (Node *) rowexpr;
+}
 
 /* coerce_to_boolean()
  *		Coerce an argument of a construct that requires boolean input
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2747ec3ed4386a878b9e27a839908a7e52d33426..d4a8cdcc8b64999933be2a1bb078a861927bdf75 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.169 2004/04/18 18:12:58 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.170 2004/05/10 22:44:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,13 +37,19 @@
 
 bool		Transform_null_equals = false;
 
-static Node *typecast_expression(ParseState *pstate, Node *expr,
-					TypeName *typename);
 static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref);
 static Node *transformWholeRowRef(ParseState *pstate, char *schemaname,
 								  char *relname);
 static Node *transformIndirection(ParseState *pstate, Node *basenode,
 					 List *indirection);
+static Node *typecast_expression(ParseState *pstate, Node *expr,
+					TypeName *typename);
+static Node *make_row_op(ParseState *pstate, List *opname,
+						 Node *ltree, Node *rtree);
+static Node *make_row_distinct_op(ParseState *pstate, List *opname,
+								  Node *ltree, Node *rtree);
+static Expr *make_distinct_op(ParseState *pstate, List *opname,
+							  Node *ltree, Node *rtree);
 
 
 /*
@@ -202,6 +208,9 @@ transformExpr(ParseState *pstate, Node *expr)
 				{
 					case AEXPR_OP:
 						{
+							Node	   *lexpr = a->lexpr;
+							Node	   *rexpr = a->rexpr;
+
 							/*
 							 * Special-case "foo = NULL" and "NULL = foo"
 							 * for compatibility with standards-broken
@@ -211,27 +220,51 @@ transformExpr(ParseState *pstate, Node *expr)
 							if (Transform_null_equals &&
 								length(a->name) == 1 &&
 							 strcmp(strVal(lfirst(a->name)), "=") == 0 &&
-								(exprIsNullConstant(a->lexpr) ||
-								 exprIsNullConstant(a->rexpr)))
+								(exprIsNullConstant(lexpr) ||
+								 exprIsNullConstant(rexpr)))
 							{
 								NullTest   *n = makeNode(NullTest);
 
 								n->nulltesttype = IS_NULL;
 
-								if (exprIsNullConstant(a->lexpr))
-									n->arg = (Expr *) a->rexpr;
+								if (exprIsNullConstant(lexpr))
+									n->arg = (Expr *) rexpr;
 								else
-									n->arg = (Expr *) a->lexpr;
+									n->arg = (Expr *) lexpr;
 
 								result = transformExpr(pstate,
 													   (Node *) n);
 							}
+							else if (lexpr && IsA(lexpr, RowExpr) &&
+									 rexpr && IsA(rexpr, SubLink) &&
+									 ((SubLink *) rexpr)->subLinkType == EXPR_SUBLINK)
+							{
+								/*
+								 * Convert "row op subselect" into a
+								 * MULTIEXPR sublink.  Formerly the grammar
+								 * did this, but now that a row construct is
+								 * allowed anywhere in expressions, it's
+								 * easier to do it here.
+								 */
+								SubLink	   *s = (SubLink *) rexpr;
+
+								s->subLinkType = MULTIEXPR_SUBLINK;
+								s->lefthand = ((RowExpr *) lexpr)->args;
+								s->operName = a->name;
+								result = transformExpr(pstate, (Node *) s);
+							}
+							else if (lexpr && IsA(lexpr, RowExpr) &&
+									 rexpr && IsA(rexpr, RowExpr))
+							{
+								/* "row op row" */
+								result = make_row_op(pstate, a->name,
+													 lexpr, rexpr);
+							}
 							else
 							{
-								Node	   *lexpr = transformExpr(pstate,
-															   a->lexpr);
-								Node	   *rexpr = transformExpr(pstate,
-															   a->rexpr);
+								/* Ordinary scalar operator */
+								lexpr = transformExpr(pstate, lexpr);
+								rexpr = transformExpr(pstate, rexpr);
 
 								result = (Node *) make_op(pstate,
 														  a->name,
@@ -311,25 +344,27 @@ transformExpr(ParseState *pstate, Node *expr)
 						break;
 					case AEXPR_DISTINCT:
 						{
-							Node	   *lexpr = transformExpr(pstate,
-															  a->lexpr);
-							Node	   *rexpr = transformExpr(pstate,
-															  a->rexpr);
+							Node	   *lexpr = a->lexpr;
+							Node	   *rexpr = a->rexpr;
 
-							result = (Node *) make_op(pstate,
-													  a->name,
-													  lexpr,
-													  rexpr);
-							if (((OpExpr *) result)->opresulttype != BOOLOID)
-								ereport(ERROR,
-									 (errcode(ERRCODE_DATATYPE_MISMATCH),
-									  errmsg("IS DISTINCT FROM requires = operator to yield boolean")));
+							if (lexpr && IsA(lexpr, RowExpr) &&
+								rexpr && IsA(rexpr, RowExpr))
+							{
+								/* "row op row" */
+								result = make_row_distinct_op(pstate, a->name,
+															  lexpr, rexpr);
+							}
+							else
+							{
+								/* Ordinary scalar operator */
+								lexpr = transformExpr(pstate, lexpr);
+								rexpr = transformExpr(pstate, rexpr);
 
-							/*
-							 * We rely on DistinctExpr and OpExpr being
-							 * same struct
-							 */
-							NodeSetTag(result, T_DistinctExpr);
+								result = (Node *) make_distinct_op(pstate,
+																   a->name,
+																   lexpr,
+																   rexpr);
+							}
 						}
 						break;
 					case AEXPR_NULLIF:
@@ -787,6 +822,32 @@ transformExpr(ParseState *pstate, Node *expr)
 				break;
 			}
 
+		case T_RowExpr:
+			{
+				RowExpr	   *r = (RowExpr *) expr;
+				RowExpr	   *newr = makeNode(RowExpr);
+				List	   *newargs = NIL;
+				List	   *arg;
+
+				/* Transform the field expressions */
+				foreach(arg, r->args)
+				{
+					Node	   *e = (Node *) lfirst(arg);
+					Node	   *newe;
+
+					newe = transformExpr(pstate, e);
+					newargs = lappend(newargs, newe);
+				}
+				newr->args = newargs;
+
+				/* Barring later casting, we consider the type RECORD */
+				newr->row_typeid = RECORDOID;
+				newr->row_format = COERCE_IMPLICIT_CAST;
+
+				result = (Node *) newr;
+				break;
+			}
+
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *c = (CoalesceExpr *) expr;
@@ -1113,14 +1174,11 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 /*
  * Construct a whole-row reference to represent the notation "relation.*".
  *
- * In simple cases, this will be a Var with varno set to the correct range
+ * A whole-row reference is a Var with varno set to the correct range
  * table entry, and varattno == 0 to signal that it references the whole
  * tuple.  (Use of zero here is unclean, since it could easily be confused
  * with error cases, but it's not worth changing now.)  The vartype indicates
  * a rowtype; either a named composite type, or RECORD.
- *
- * We also need the ability to build a row-constructor expression, but the
- * infrastructure for that doesn't exist just yet.
  */
 static Node *
 transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname)
@@ -1185,12 +1243,10 @@ transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname)
 			break;
 		default:
 			/*
-			 * RTE is a join or subselect.  For the moment we represent this
-			 * as a whole-row Var of RECORD type, but this will not actually
-			 * work; need a row-constructor expression instead.
-			 *
-			 * XXX after fixing, be sure that unknown_attribute still
-			 * does the right thing.
+			 * RTE is a join or subselect.  We represent this as a whole-row
+			 * Var of RECORD type.  (Note that in most cases the Var will
+			 * be expanded to a RowExpr during planning, but that is not
+			 * our concern here.)
 			 */
 			result = (Node *) makeVar(vnum,
 									  InvalidAttrNumber,
@@ -1266,8 +1322,8 @@ exprType(Node *expr)
 					if (sublink->subLinkType == EXPR_SUBLINK)
 						type = tent->resdom->restype;
 					else
-/* ARRAY_SUBLINK */
 					{
+						/* ARRAY_SUBLINK */
 						type = get_array_type(tent->resdom->restype);
 						if (!OidIsValid(type))
 							ereport(ERROR,
@@ -1305,8 +1361,8 @@ exprType(Node *expr)
 					if (subplan->subLinkType == EXPR_SUBLINK)
 						type = tent->resdom->restype;
 					else
-/* ARRAY_SUBLINK */
 					{
+						/* ARRAY_SUBLINK */
 						type = get_array_type(tent->resdom->restype);
 						if (!OidIsValid(type))
 							ereport(ERROR,
@@ -1340,6 +1396,9 @@ exprType(Node *expr)
 		case T_ArrayExpr:
 			type = ((ArrayExpr *) expr)->array_typeid;
 			break;
+		case T_RowExpr:
+			type = ((RowExpr *) expr)->row_typeid;
+			break;
 		case T_CoalesceExpr:
 			type = ((CoalesceExpr *) expr)->coalescetype;
 			break;
@@ -1573,3 +1632,166 @@ typecast_expression(ParseState *pstate, Node *expr, TypeName *typename)
 
 	return expr;
 }
+
+/*
+ * Transform a "row op row" construct
+ */
+static Node *
+make_row_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree)
+{
+	Node	   *result = NULL;
+	RowExpr	   *lrow,
+			   *rrow;
+	List	   *largs,
+			   *rargs;
+	List	   *largl,
+			   *rargl;
+	char	   *oprname;
+	BoolExprType boolop;
+
+	/* Inputs are untransformed RowExprs */
+	lrow = (RowExpr *) transformExpr(pstate, ltree);
+	rrow = (RowExpr *) transformExpr(pstate, rtree);
+	Assert(IsA(lrow, RowExpr));
+	Assert(IsA(rrow, RowExpr));
+	largs = lrow->args;
+	rargs = rrow->args;
+
+	if (length(largs) != length(rargs))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unequal number of entries in row expression")));
+
+	/*
+	 * XXX it's really wrong to generate a simple AND combination for < <=
+	 * > >=.  We probably need to invent a new runtime node type to handle
+	 * those correctly.  For the moment, though, keep on doing this ...
+	 */
+	oprname = strVal(llast(opname));
+
+	if ((strcmp(oprname, "=") == 0) ||
+		(strcmp(oprname, "<") == 0) ||
+		(strcmp(oprname, "<=") == 0) ||
+		(strcmp(oprname, ">") == 0) ||
+		(strcmp(oprname, ">=") == 0))
+	{
+		boolop = AND_EXPR;
+	}
+	else if (strcmp(oprname, "<>") == 0)
+	{
+		boolop = OR_EXPR;
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("operator %s is not supported for row expressions",
+						oprname)));
+		boolop = 0;			/* keep compiler quiet */
+	}
+
+	/* XXX use forboth */
+	rargl = rargs;
+	foreach(largl, largs)
+	{
+		Node	*larg = (Node *) lfirst(largl);
+		Node	*rarg = (Node *) lfirst(rargl);
+		Node	*cmp;
+
+		rargl = lnext(rargl);
+		cmp = (Node *) make_op(pstate, opname, larg, rarg);
+		cmp = coerce_to_boolean(pstate, cmp, "row comparison");
+		if (result == NULL)
+			result = cmp;
+		else
+			result = (Node *) makeBoolExpr(boolop,
+										   makeList2(result, cmp));
+	}
+
+	if (result == NULL)
+	{
+		/* zero-length rows?  Generate constant TRUE or FALSE */
+		if (boolop == AND_EXPR)
+			result = makeBoolConst(true, false);
+		else
+			result = makeBoolConst(false, false);
+	}
+
+	return result;
+}
+
+/*
+ * Transform a "row IS DISTINCT FROM row" construct
+ */
+static Node *
+make_row_distinct_op(ParseState *pstate, List *opname,
+					 Node *ltree, Node *rtree)
+{
+	Node	   *result = NULL;
+	RowExpr	   *lrow,
+			   *rrow;
+	List	   *largs,
+			   *rargs;
+	List	   *largl,
+			   *rargl;
+
+	/* Inputs are untransformed RowExprs */
+	lrow = (RowExpr *) transformExpr(pstate, ltree);
+	rrow = (RowExpr *) transformExpr(pstate, rtree);
+	Assert(IsA(lrow, RowExpr));
+	Assert(IsA(rrow, RowExpr));
+	largs = lrow->args;
+	rargs = rrow->args;
+
+	if (length(largs) != length(rargs))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unequal number of entries in row expression")));
+
+	/* XXX use forboth */
+	rargl = rargs;
+	foreach(largl, largs)
+	{
+		Node	*larg = (Node *) lfirst(largl);
+		Node	*rarg = (Node *) lfirst(rargl);
+		Node	*cmp;
+
+		rargl = lnext(rargl);
+		cmp = (Node *) make_distinct_op(pstate, opname, larg, rarg);
+		if (result == NULL)
+			result = cmp;
+		else
+			result = (Node *) makeBoolExpr(OR_EXPR,
+										   makeList2(result, cmp));
+	}
+
+	if (result == NULL)
+	{
+		/* zero-length rows?  Generate constant FALSE */
+		result = makeBoolConst(false, false);
+	}
+
+	return result;
+}
+
+/*
+ * make the node for an IS DISTINCT FROM operator
+ */
+static Expr *
+make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree)
+{
+	Expr	*result;
+
+	result = make_op(pstate, opname, ltree, rtree);
+	if (((OpExpr *) result)->opresulttype != BOOLOID)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("IS DISTINCT FROM requires = operator to yield boolean")));
+	/*
+	 * We rely on DistinctExpr and OpExpr being
+	 * same struct
+	 */
+	NodeSetTag(result, T_DistinctExpr);
+
+	return result;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index c9c44ac23896cad5c1ed0095d726d2d616f47e33..fbee22b37d3b2ef67b9c7bc5d11561580998a401 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.116 2004/04/02 19:06:58 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.117 2004/05/10 22:44:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -643,6 +643,10 @@ FigureColnameInternal(Node *node, char **name)
 			/* make ARRAY[] act like a function */
 			*name = "array";
 			return 2;
+		case T_RowExpr:
+			/* make ROW() act like a function */
+			*name = "row";
+			return 2;
 		case T_CoalesceExpr:
 			/* make coalesce() act like a regular function */
 			*name = "coalesce";
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d83e8ac45805bec8661681b51c9cefc552a45b20..3b898b973d8ec9eb52f5c7faded630ee84af7495 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.134 2004/04/01 21:28:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.135 2004/05/10 22:44:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -212,6 +212,8 @@ rewriteRuleAction(Query *parsetree,
 		sub_action = (Query *) ResolveNew((Node *) sub_action,
 										  new_varno,
 										  0,
+										  rt_fetch(new_varno,
+												   sub_action->rtable),
 										  parsetree->targetList,
 										  event,
 										  current_varno);
@@ -947,6 +949,7 @@ CopyAndAddInvertedQual(Query *parsetree,
 		new_qual = ResolveNew(new_qual,
 							  PRS2_NEW_VARNO,
 							  0,
+							  rt_fetch(rt_index, parsetree->rtable),
 							  parsetree->targetList,
 							  event,
 							  rt_index);
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 5210b99fd7e3410839e4bf6eb20ed19a35f183a6..45b60715966e8e9931dc121fc407d4c20a38da55 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.81 2003/11/29 19:51:55 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteManip.c,v 1.82 2004/05/10 22:44:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -850,6 +850,10 @@ AddInvertedQual(Query *parsetree, Node *qual)
  * If not, we either change the unmatched Var's varno to update_varno
  * (when event == CMD_UPDATE) or replace it with a constant NULL.
  *
+ * The caller must also provide target_rte, the RTE describing the target
+ * relation.  This is needed to handle whole-row Vars referencing the target.
+ * We expand such Vars into RowExpr constructs.
+ *
  * Note: the business with inserted_sublink is needed to update hasSubLinks
  * in subqueries when the replacement adds a subquery inside a subquery.
  * Messy, isn't it?  We do not need to do similar pushups for hasAggs,
@@ -861,12 +865,52 @@ typedef struct
 {
 	int			target_varno;
 	int			sublevels_up;
+	RangeTblEntry *target_rte;
 	List	   *targetlist;
 	int			event;
 	int			update_varno;
 	bool		inserted_sublink;
 } ResolveNew_context;
 
+static Node *
+resolve_one_var(Var *var, ResolveNew_context *context)
+{
+	TargetEntry *tle;
+
+	tle = get_tle_by_resno(context->targetlist, var->varattno);
+
+	if (tle == NULL)
+	{
+		/* Failed to find column in insert/update tlist */
+		if (context->event == CMD_UPDATE)
+		{
+			/* For update, just change unmatched var's varno */
+			var = (Var *) copyObject(var);
+			var->varno = context->update_varno;
+			var->varnoold = context->update_varno;
+			return (Node *) var;
+		}
+		else
+		{
+			/* Otherwise replace unmatched var with a null */
+			return (Node *) makeNullConst(var->vartype);
+		}
+	}
+	else
+	{
+		/* Make a copy of the tlist item to return */
+		Node	   *n = copyObject(tle->expr);
+
+		/* Adjust varlevelsup if tlist item is from higher query */
+		if (var->varlevelsup > 0)
+			IncrementVarSublevelsUp(n, var->varlevelsup, 0);
+		/* Report it if we are adding a sublink to query */
+		if (!context->inserted_sublink)
+			context->inserted_sublink = checkExprHasSubLink(n);
+		return n;
+	}
+}
+
 static Node *
 ResolveNew_mutator(Node *node, ResolveNew_context *context)
 {
@@ -881,45 +925,41 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
 		if (this_varno == context->target_varno &&
 			this_varlevelsup == context->sublevels_up)
 		{
-			TargetEntry *tle;
-
-			/* band-aid: don't do the wrong thing with a whole-tuple Var */
 			if (var->varattno == InvalidAttrNumber)
-				ereport(ERROR,
-						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-						 errmsg("cannot handle whole-row reference")));
-
-			tle = get_tle_by_resno(context->targetlist, var->varattno);
-
-			if (tle == NULL)
 			{
-				if (context->event == CMD_UPDATE)
+				/* Must expand whole-tuple reference into RowExpr */
+				RangeTblEntry *rte = context->target_rte;
+				RowExpr *rowexpr;
+				List	*fields = NIL;
+				AttrNumber nfields = length(rte->eref->colnames);
+				AttrNumber nf;
+
+				for (nf = 1; nf <= nfields; nf++)
 				{
-					/* For update, just change unmatched var's varno */
-					var = (Var *) copyObject(node);
-					var->varno = context->update_varno;
-					var->varnoold = context->update_varno;
-					return (Node *) var;
+					Oid		vartype;
+					int32	vartypmod;
+					Var	   *newvar;
+
+					get_rte_attribute_type(rte, nf, &vartype, &vartypmod);
+					newvar = makeVar(this_varno,
+									 nf,
+									 vartype,
+									 vartypmod,
+									 this_varlevelsup);
+					fields = lappend(fields,
+									 resolve_one_var(newvar, context));
 				}
-				else
-				{
-					/* Otherwise replace unmatched var with a null */
-					return (Node *) makeNullConst(var->vartype);
-				}
-			}
-			else
-			{
-				/* Make a copy of the tlist item to return */
-				Node	   *n = copyObject(tle->expr);
-
-				/* Adjust varlevelsup if tlist item is from higher query */
-				if (this_varlevelsup > 0)
-					IncrementVarSublevelsUp(n, this_varlevelsup, 0);
-				/* Report it if we are adding a sublink to query */
-				if (!context->inserted_sublink)
-					context->inserted_sublink = checkExprHasSubLink(n);
-				return n;
+
+				rowexpr = makeNode(RowExpr);
+				rowexpr->args = fields;
+				rowexpr->row_typeid = var->vartype;
+				rowexpr->row_format = COERCE_IMPLICIT_CAST;
+
+				return (Node *) rowexpr;
 			}
+
+			/* Normal case for scalar variable */
+			return resolve_one_var(var, context);
 		}
 		/* otherwise fall through to copy the var normally */
 	}
@@ -948,12 +988,14 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
 
 Node *
 ResolveNew(Node *node, int target_varno, int sublevels_up,
+		   RangeTblEntry *target_rte,
 		   List *targetlist, int event, int update_varno)
 {
 	ResolveNew_context context;
 
 	context.target_varno = target_varno;
 	context.sublevels_up = sublevels_up;
+	context.target_rte = target_rte;
 	context.targetlist = targetlist;
 	context.event = event;
 	context.update_varno = update_varno;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9cb90444a878a21edb1c2c66a6693ec9a8c27f0c..95e457c261fb2ae25db0b91e2e4865c58584b611 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
  *				back to source text
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.165 2004/05/07 03:19:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.166 2004/05/10 22:44:46 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -2432,6 +2432,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 
 		case T_ArrayRef:
 		case T_ArrayExpr:
+		case T_RowExpr:
 		case T_CoalesceExpr:
 		case T_NullIfExpr:
 		case T_Aggref:
@@ -2528,6 +2529,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 				case T_BoolExpr:		/* lower precedence */
 				case T_ArrayRef:		/* other separators */
 				case T_ArrayExpr:		/* other separators */
+				case T_RowExpr:			/* other separators */
 				case T_CoalesceExpr:	/* own parentheses */
 				case T_NullIfExpr:		/* other separators */
 				case T_Aggref:			/* own parentheses */
@@ -2574,6 +2576,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
 					}
 				case T_ArrayRef:		/* other separators */
 				case T_ArrayExpr:		/* other separators */
+				case T_RowExpr:			/* other separators */
 				case T_CoalesceExpr:	/* own parentheses */
 				case T_NullIfExpr:		/* other separators */
 				case T_Aggref:			/* own parentheses */
@@ -2942,11 +2945,9 @@ get_rule_expr(Node *node, deparse_context *context,
 				 * arg.fieldname, but most cases where FieldSelect is used
 				 * are *not* simple.  So, always use parenthesized syntax.
 				 */
-				if (!PRETTY_PAREN(context))
-					appendStringInfoChar(buf, '(');
+				appendStringInfoChar(buf, '(');
 				get_rule_expr_paren((Node *) fselect->arg, context, true, node);
-				if (!PRETTY_PAREN(context))
-					appendStringInfoChar(buf, ')');
+				appendStringInfoChar(buf, ')');
 				appendStringInfo(buf, ".%s", quote_identifier(fieldname));
 			}
 			break;
@@ -3051,6 +3052,33 @@ get_rule_expr(Node *node, deparse_context *context,
 			}
 			break;
 
+		case T_RowExpr:
+			{
+				RowExpr	   *rowexpr = (RowExpr *) node;
+				List	   *arg;
+				char	   *sep;
+
+				/*
+				 * SQL99 allows "ROW" to be omitted when length(args) > 1,
+				 * but for simplicity we always print it.
+				 */
+				appendStringInfo(buf, "ROW(");
+				sep = "";
+				foreach(arg, rowexpr->args)
+				{
+					Node	   *e = (Node *) lfirst(arg);
+
+					appendStringInfo(buf, sep);
+					get_rule_expr(e, context, true);
+					sep = ", ";
+				}
+				appendStringInfo(buf, ")");
+				if (rowexpr->row_format == COERCE_EXPLICIT_CAST)
+					appendStringInfo(buf, "::%s",
+									 format_type_with_typemod(rowexpr->row_typeid, -1));
+			}
+			break;
+
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 46a8a2d06f30473cdb0c4d28ec94e440d1655030..0e957db82ca3c2b50d7a86eae61abea897dd7f2f 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.228 2004/05/08 21:21:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.229 2004/05/10 22:44:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200405081
+#define CATALOG_VERSION_NO	200405101
 
 #endif
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index a3088ca7f628483ff645d73daf7669a4ddcca9af..52873e864692cb507c8381f7bc5a0799a8487d82 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.109 2004/04/01 21:28:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.110 2004/05/10 22:44:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -175,6 +175,7 @@ extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
 					  TupleDesc tupType);
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
+extern TupleDesc ExecTypeFromExprList(List *exprList);
 extern void UpdateChangedParamSet(PlanState *node, Bitmapset *newchg);
 
 typedef struct TupOutputState
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8a0fbf7be0f38473edd2c16d92bfee615a906f6c..325bf8768002185c6f90a9e79f8f381d36f9cfb4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.115 2004/04/01 21:28:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.116 2004/05/10 22:44:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -613,6 +613,17 @@ typedef struct ArrayExprState
 	char		elemalign;		/* typalign of the element type */
 } ArrayExprState;
 
+/* ----------------
+ *		RowExprState node
+ * ----------------
+ */
+typedef struct RowExprState
+{
+	ExprState	xprstate;
+	List	   *args;			/* the arguments */
+	TupleDesc	tupdesc;		/* descriptor for result tuples */
+} RowExprState;
+
 /* ----------------
  *		CoalesceExprState node
  * ----------------
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 7562e1a82b6c7345d917ecd02283f33b119251a1..085e76d03bf58aa12d4726702b3faa0bc48f5e13 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.48 2003/11/29 22:41:06 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/makefuncs.h,v 1.49 2004/05/10 22:44:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,6 +45,8 @@ extern Const *makeConst(Oid consttype,
 
 extern Const *makeNullConst(Oid consttype);
 
+extern Node *makeBoolConst(bool value, bool isnull);
+
 extern Expr *makeBoolExpr(BoolExprType boolop, List *args);
 
 extern Alias *makeAlias(const char *aliasname, List *colnames);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a776607ec660ab56fe7025c6adda76ae3efcb556..20c1b08f5c5c60b05e8cb17e56832f2a0eec3be1 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.153 2004/05/05 04:48:47 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.154 2004/05/10 22:44:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -115,6 +115,7 @@ typedef enum NodeTag
 	T_CaseWhen,
 	T_CaseTestExpr,
 	T_ArrayExpr,
+	T_RowExpr,
 	T_CoalesceExpr,
 	T_NullIfExpr,
 	T_NullTest,
@@ -145,6 +146,7 @@ typedef enum NodeTag
 	T_CaseExprState,
 	T_CaseWhenState,
 	T_ArrayExprState,
+	T_RowExprState,
 	T_CoalesceExprState,
 	T_CoerceToDomainState,
 	T_DomainConstraintState,
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 567310fa1c3bae831fdb1c338ac71484bcc7eb13..d5819eab7bba18bfdd953d99cef5fef4e41df904 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.97 2004/04/01 21:28:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.98 2004/05/10 22:44:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -632,6 +632,23 @@ typedef struct ArrayExpr
 	bool		multidims;		/* true if elements are sub-arrays */
 } ArrayExpr;
 
+/*
+ * RowExpr - a ROW() expression
+ */
+typedef struct RowExpr
+{
+	Expr		xpr;
+	List	   *args;			/* the fields */
+	Oid			row_typeid;		/* RECORDOID or a composite type's ID */
+	/*
+	 * Note: we deliberately do NOT store a typmod.  Although a typmod
+	 * will be associated with specific RECORD types at runtime, it will
+	 * differ for different backends, and so cannot safely be stored in
+	 * stored parsetrees.  We must assume typmod -1 for a RowExpr node.
+	 */
+	CoercionForm row_format;	/* how to display this node */
+} RowExpr;
+
 /*
  * CoalesceExpr - a COALESCE expression
  */
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index 087489a0a75ab0ebf44c59dc78ae98e93007ab8b..0f0920d0639d62cf43e7a688b8e205407e613454 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/var.h,v 1.29 2003/11/29 22:41:07 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/var.h,v 1.30 2004/05/10 22:44:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,7 +20,6 @@
 extern Relids pull_varnos(Node *node);
 extern bool contain_var_reference(Node *node, int varno, int varattno,
 					  int levelsup);
-extern bool contain_whole_tuple_var(Node *node, int varno, int levelsup);
 extern bool contain_var_clause(Node *node);
 extern bool contain_vars_of_level(Node *node, int levelsup);
 extern bool contain_vars_above_level(Node *node, int levelsup);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index d2c694bb4b2b3e80fc404e2c78d47e51f0ecc4cb..ce4702de35ba49dd29fca8543617c850b186b985 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/rewrite/rewriteManip.h,v 1.34 2003/11/29 22:41:11 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/rewrite/rewriteManip.h,v 1.35 2004/05/10 22:44:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,6 +38,7 @@ extern bool checkExprHasAggs(Node *node);
 extern bool checkExprHasSubLink(Node *node);
 
 extern Node *ResolveNew(Node *node, int target_varno, int sublevels_up,
-		   List *targetlist, int event, int update_varno);
+						RangeTblEntry *target_rte,
+						List *targetlist, int event, int update_varno);
 
 #endif   /* REWRITEMANIP_H */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b71a71bbaac23e42a11a5622610adba87920353a..3a90b4648518aa2b25d43297869d0000964a92ac 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.99 2004/04/01 21:28:46 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.100 2004/05/10 22:44:49 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -3752,6 +3752,16 @@ exec_simple_check_node(Node *node)
 				return TRUE;
 			}
 
+		case T_RowExpr:
+			{
+				RowExpr  *expr = (RowExpr *) node;
+
+				if (!exec_simple_check_node((Node *) expr->args))
+					return FALSE;
+
+				return TRUE;
+			}
+
 		case T_CoalesceExpr:
 			{
 				CoalesceExpr *expr = (CoalesceExpr *) node;
diff --git a/src/test/regress/input/misc.source b/src/test/regress/input/misc.source
index ebf13626c7fabf3f74deb4696e19218cad48af13..cfaaea95131169ff79fdd2084ef431ed37dd0470 100644
--- a/src/test/regress/input/misc.source
+++ b/src/test/regress/input/misc.source
@@ -218,6 +218,17 @@ SELECT hobbies_by_name('basketball');
 
 SELECT name, overpaid(emp.*) FROM emp;
 
+--
+-- Try a few cases with SQL-spec row constructor expressions
+--
+SELECT * FROM equipment(ROW('skywalking', 'mer'));
+
+SELECT name(equipment(ROW('skywalking', 'mer')));
+
+SELECT *, name(equipment(h.*)) FROM hobbies_r h;
+
+SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
+
 --
 -- check that old-style C functions work properly with TOASTed values
 --
diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source
index 52ecabbd90856d4038f2ac2c4cd348e5c3cc7a53..2237e0c59661a9866ddc4b828b8c6837f315b393 100644
--- a/src/test/regress/output/constraints.source
+++ b/src/test/regress/output/constraints.source
@@ -45,9 +45,9 @@ SELECT '' AS four, * FROM DEFAULTEXPR_TBL;
 -- syntax errors
 --  test for extraneous comma
 CREATE TABLE error_tbl (i int DEFAULT (100, ));
-ERROR:  syntax error at or near "," at character 43
+ERROR:  syntax error at or near ")" at character 45
 LINE 1: CREATE TABLE error_tbl (i int DEFAULT (100, ));
-                                                  ^
+                                                    ^
 --  this will fail because gram.y uses b_expr not a_expr for defaults,
 --  to avoid a shift/reduce conflict that arises from NOT NULL being
 --  part of the column definition syntax:
diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source
index 3173f718c6c8268a141055801efde138d0c1ec95..0c1ed5deaf4d583b4ed2633b418a2f204556eb5b 100644
--- a/src/test/regress/output/misc.source
+++ b/src/test/regress/output/misc.source
@@ -686,6 +686,45 @@ SELECT name, overpaid(emp.*) FROM emp;
  linda  | f
 (6 rows)
 
+--
+-- Try a few cases with SQL-spec row constructor expressions
+--
+SELECT * FROM equipment(ROW('skywalking', 'mer'));
+ name |   hobby    
+------+------------
+ guts | skywalking
+(1 row)
+
+SELECT name(equipment(ROW('skywalking', 'mer')));
+ name 
+------
+ guts
+(1 row)
+
+SELECT *, name(equipment(h.*)) FROM hobbies_r h;
+    name     | person |     name      
+-------------+--------+---------------
+ posthacking | mike   | advil
+ posthacking | mike   | peet's coffee
+ posthacking | jeff   | advil
+ posthacking | jeff   | peet's coffee
+ basketball  | joe    | hightops
+ basketball  | sally  | hightops
+ skywalking  |        | guts
+(7 rows)
+
+SELECT *, (equipment(CAST((h.*) AS hobbies_r))).name FROM hobbies_r h;
+    name     | person |     name      
+-------------+--------+---------------
+ posthacking | mike   | advil
+ posthacking | mike   | peet's coffee
+ posthacking | jeff   | advil
+ posthacking | jeff   | peet's coffee
+ basketball  | joe    | hightops
+ basketball  | sally  | hightops
+ skywalking  |        | guts
+(7 rows)
+
 --
 -- check that old-style C functions work properly with TOASTed values
 --