diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index b05f760ade2fe6cd5b6da3132691766206b3c66f..6aea0811bb2aefe398ef5ba4a40c0374be5d797e 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.167 2004/03/24 22:40:28 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.168 2004/04/02 19:06:57 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -2550,16 +2550,6 @@ expression_tree_walker(Node *node,
 					return true;
 			}
 			break;
-		case T_RangeVar:
-			/*
-			 * Give a useful complaint if someone uses a bare relation name
-			 * in an expression (see comments in transformColumnRef()).
-			 */
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("relation reference \"%s\" cannot be used in an expression",
-							((RangeVar *) node)->relname)));
-			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
@@ -3031,16 +3021,6 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
-		case T_RangeVar:
-			/*
-			 * Give a useful complaint if someone uses a bare relation name
-			 * in an expression (see comments in transformColumnRef()).
-			 */
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("relation reference \"%s\" cannot be used in an expression",
-							((RangeVar *) node)->relname)));
-			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) nodeTag(node));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 83daae9b62b7506d8222d4d91d7b6909556ffde2..acee2b300e419e01c162bd3bbd105c92860a5156 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.167 2004/03/24 22:40:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.168 2004/04/02 19:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,6 +40,8 @@ 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);
 
@@ -932,34 +934,29 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 {
 	int			numnames = length(cref->fields);
 	Node	   *node;
-	RangeVar   *rv;
 	int			levels_up;
 
 	/*----------
 	 * The allowed syntaxes are:
 	 *
 	 * A		First try to resolve as unqualified column name;
-	 *			if no luck, try to resolve as unqual. table name (A.*).
-	 * A.B		A is an unqual. table name; B is either a
+	 *			if no luck, try to resolve as unqualified table name (A.*).
+	 * A.B		A is an unqualified table name; B is either a
 	 *			column or function name (trying column name first).
 	 * A.B.C	schema A, table B, col or func name C.
 	 * A.B.C.D	catalog A, schema B, table C, col or func D.
-	 * A.*		A is an unqual. table name; means whole-row value.
+	 * A.*		A is an unqualified table name; means whole-row value.
 	 * A.B.*	whole-row value of table B in schema A.
 	 * A.B.C.*	whole-row value of table C in schema B in catalog A.
 	 *
 	 * We do not need to cope with bare "*"; that will only be accepted by
 	 * the grammar at the top level of a SELECT list, and transformTargetList
-	 * will take care of it before it ever gets here.
+	 * will take care of it before it ever gets here.  Also, "A.*" etc will
+	 * be expanded by transformTargetList if they appear at SELECT top level,
+	 * so here we are only going to see them as function or operator inputs.
 	 *
 	 * Currently, if a catalog name is given then it must equal the current
 	 * database name; we check it here and then discard it.
-	 *
-	 * For whole-row references, the result is an untransformed RangeVar,
-	 * which will work as the argument to a function call, but not in any
-	 * other context at present.  (We could instead coerce to a whole-row Var,
-	 * but that will fail for subselect and join RTEs, because there is no
-	 * pg_type entry for their rowtypes.)
 	 *----------
 	 */
 	switch (numnames)
@@ -1001,16 +998,12 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					if (cref->indirection == NIL &&
 						refnameRangeTblEntry(pstate, NULL, name,
 											 &levels_up) != NULL)
-					{
-						rv = makeNode(RangeVar);
-						rv->relname = name;
-						rv->inhOpt = INH_DEFAULT;
-						node = (Node *) rv;
-					}
+						node = transformWholeRowRef(pstate, NULL, name);
 					else
 						ereport(ERROR,
 								(errcode(ERRCODE_UNDEFINED_COLUMN),
-							errmsg("column \"%s\" does not exist", name)));
+								 errmsg("column \"%s\" does not exist",
+										name)));
 				}
 				break;
 			}
@@ -1022,10 +1015,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 				/* Whole-row reference? */
 				if (strcmp(name2, "*") == 0)
 				{
-					rv = makeNode(RangeVar);
-					rv->relname = name1;
-					rv->inhOpt = INH_DEFAULT;
-					node = (Node *) rv;
+					node = transformWholeRowRef(pstate, NULL, name1);
 					break;
 				}
 
@@ -1038,12 +1028,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 					 * try it as a function call.  Here, we will create an
 					 * implicit RTE for tables not already entered.
 					 */
-					rv = makeNode(RangeVar);
-					rv->relname = name1;
-					rv->inhOpt = INH_DEFAULT;
+					node = transformWholeRowRef(pstate, NULL, name1);
 					node = ParseFuncOrColumn(pstate,
 											 makeList1(makeString(name2)),
-											 makeList1(rv),
+											 makeList1(node),
 											 false, false, true);
 				}
 				break;
@@ -1057,11 +1045,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 				/* Whole-row reference? */
 				if (strcmp(name3, "*") == 0)
 				{
-					rv = makeNode(RangeVar);
-					rv->schemaname = name1;
-					rv->relname = name2;
-					rv->inhOpt = INH_DEFAULT;
-					node = (Node *) rv;
+					node = transformWholeRowRef(pstate, name1, name2);
 					break;
 				}
 
@@ -1070,13 +1054,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 				if (node == NULL)
 				{
 					/* Try it as a function call */
-					rv = makeNode(RangeVar);
-					rv->schemaname = name1;
-					rv->relname = name2;
-					rv->inhOpt = INH_DEFAULT;
+					node = transformWholeRowRef(pstate, name1, name2);
 					node = ParseFuncOrColumn(pstate,
 											 makeList1(makeString(name3)),
-											 makeList1(rv),
+											 makeList1(node),
 											 false, false, true);
 				}
 				break;
@@ -1100,11 +1081,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 				/* Whole-row reference? */
 				if (strcmp(name4, "*") == 0)
 				{
-					rv = makeNode(RangeVar);
-					rv->schemaname = name2;
-					rv->relname = name3;
-					rv->inhOpt = INH_DEFAULT;
-					node = (Node *) rv;
+					node = transformWholeRowRef(pstate, name2, name3);
 					break;
 				}
 
@@ -1113,13 +1090,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 				if (node == NULL)
 				{
 					/* Try it as a function call */
-					rv = makeNode(RangeVar);
-					rv->schemaname = name2;
-					rv->relname = name3;
-					rv->inhOpt = INH_DEFAULT;
+					node = transformWholeRowRef(pstate, name2, name3);
 					node = ParseFuncOrColumn(pstate,
 											 makeList1(makeString(name4)),
-											 makeList1(rv),
+											 makeList1(node),
 											 false, false, true);
 				}
 				break;
@@ -1136,6 +1110,99 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 	return transformIndirection(pstate, node, cref->indirection);
 }
 
+/*
+ * 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
+ * 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)
+{
+	Node	   *result;
+	RangeTblEntry *rte;
+	int			vnum;
+	int			sublevels_up;
+	Oid			toid;
+
+	/* Look up the referenced RTE, creating it if needed */
+
+	rte = refnameRangeTblEntry(pstate, schemaname, relname,
+							   &sublevels_up);
+
+	if (rte == NULL)
+		rte = addImplicitRTE(pstate, makeRangeVar(schemaname, relname));
+
+	vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
+
+	/* Build the appropriate referencing node */
+
+	switch (rte->rtekind)
+	{
+		case RTE_RELATION:
+			/* relation: the rowtype is a named composite type */
+			toid = get_rel_type_id(rte->relid);
+			if (!OidIsValid(toid))
+				elog(ERROR, "could not find type OID for relation %u",
+					 rte->relid);
+			result = (Node *) makeVar(vnum,
+									  InvalidAttrNumber,
+									  toid,
+									  -1,
+									  sublevels_up);
+			break;
+		case RTE_FUNCTION:
+			toid = exprType(rte->funcexpr);
+			if (toid == RECORDOID || get_typtype(toid) == 'c')
+			{
+				/* func returns composite; same as relation case */
+				result = (Node *) makeVar(vnum,
+										  InvalidAttrNumber,
+										  toid,
+										  -1,
+										  sublevels_up);
+			}
+			else
+			{
+				/*
+				 * func returns scalar; instead of making a whole-row Var,
+				 * just reference the function's scalar output.  (XXX this
+				 * seems a tad inconsistent, especially if "f.*" was
+				 * explicitly written ...)
+				 */
+				result = (Node *) makeVar(vnum,
+										  1,
+										  toid,
+										  -1,
+										  sublevels_up);
+			}
+			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.
+			 */
+			result = (Node *) makeVar(vnum,
+									  InvalidAttrNumber,
+									  RECORDOID,
+									  -1,
+									  sublevels_up);
+			break;
+	}
+
+	return result;
+}
+
 /*
  *	exprType -
  *	  returns the Oid of the type of the expression. (Used for typechecking.)
@@ -1294,19 +1361,6 @@ exprType(Node *expr)
 		case T_SetToDefault:
 			type = ((SetToDefault *) expr)->typeId;
 			break;
-		case T_RangeVar:
-
-			/*
-			 * If someone uses a bare relation name in an expression, we
-			 * will likely first notice a problem here (see comments in
-			 * transformColumnRef()).  Issue an appropriate error message.
-			 */
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("relation reference \"%s\" cannot be used in an expression",
-							((RangeVar *) expr)->relname)));
-			type = InvalidOid;	/* keep compiler quiet */
-			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
 			type = InvalidOid;	/* keep compiler quiet */
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 1677493abbcea97c925275b8662adfedc8e08644..deab6c2ad34e0950f87b21ea2699c84e9b020ac1 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.166 2004/04/01 21:28:44 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.167 2004/04/02 19:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,14 +32,14 @@
 #include "utils/syscache.h"
 
 
-static Node *ParseComplexProjection(char *funcname, Node *first_arg);
+static Node *ParseComplexProjection(ParseState *pstate, char *funcname,
+									Node *first_arg);
 static Oid **argtype_inherit(int nargs, Oid *argtypes);
 
 static int	find_inheritors(Oid relid, Oid **supervec);
 static Oid **gen_cross_product(InhPaths *arginh, int nargs);
 static FieldSelect *setup_field_select(Node *input, char *attname, Oid relid);
-static void unknown_attribute(const char *schemaname, const char *relname,
-				  const char *attname);
+static void unknown_attribute(ParseState *pstate, Node *relref, char *attname);
 
 
 /*
@@ -48,7 +48,9 @@ static void unknown_attribute(const char *schemaname, const char *relname,
  *	For historical reasons, Postgres tries to treat the notations tab.col
  *	and col(tab) as equivalent: if a single-argument function call has an
  *	argument of complex type and the (unqualified) function name matches
- *	any attribute of the type, we take it as a column projection.
+ *	any attribute of the type, we take it as a column projection.  Conversely
+ *	a function of a single complex-type argument can be written like a
+ *	column reference, allowing functions to act like computed columns.
  *
  *	Hence, both cases come through here.  The is_column parameter tells us
  *	which syntactic construct is actually being dealt with, but this is
@@ -57,9 +59,7 @@ static void unknown_attribute(const char *schemaname, const char *relname,
  *	a single argument (the putative table), unqualified function name
  *	equal to the column name, and no aggregate decoration.
  *
- *	In the function-call case, the argument expressions have been transformed
- *	already.  In the column case, we may get either a transformed expression
- *	or a RangeVar node as argument.
+ *	The argument expressions (in fargs) must have been transformed already.
  */
 Node *
 ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
@@ -96,44 +96,32 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	}
 
 	/*
-	 * check for column projection: if function has one argument, and that
+	 * Check for column projection: if function has one argument, and that
 	 * argument is of complex type, and function name is not qualified,
 	 * then the "function call" could be a projection.	We also check that
 	 * there wasn't any aggregate decoration.
 	 */
 	if (nargs == 1 && !agg_star && !agg_distinct && length(funcname) == 1)
 	{
-		char	   *cname = strVal(lfirst(funcname));
+		Oid			argtype = exprType(first_arg);
 
-		/* Is it a not-yet-transformed RangeVar node? */
-		if (IsA(first_arg, RangeVar))
+		if (argtype == RECORDOID || ISCOMPLEX(argtype))
 		{
-			/* First arg is a relation. This could be a projection. */
-			retval = qualifiedNameToVar(pstate,
-									((RangeVar *) first_arg)->schemaname,
-										((RangeVar *) first_arg)->relname,
-										cname,
-										true);
+			retval = ParseComplexProjection(pstate,
+											strVal(lfirst(funcname)),
+											first_arg);
 			if (retval)
 				return retval;
-		}
-		else if (ISCOMPLEX(exprType(first_arg)))
-		{
 			/*
-			 * Attempt to handle projection of a complex argument. If
-			 * ParseComplexProjection can't handle the projection, we have
-			 * to keep going.
+			 * If ParseComplexProjection doesn't recognize it as a projection,
+			 * just press on.
 			 */
-			retval = ParseComplexProjection(cname, first_arg);
-			if (retval)
-				return retval;
 		}
 	}
 
 	/*
 	 * Okay, it's not a column projection, so it must really be a
-	 * function. Extract arg type info and transform RangeVar arguments
-	 * into varnodes of the appropriate form.
+	 * function. Extract arg type info in preparation for function lookup.
 	 */
 	MemSet(actual_arg_types, 0, FUNC_MAX_ARGS * sizeof(Oid));
 
@@ -141,96 +129,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 	foreach(i, fargs)
 	{
 		Node	   *arg = lfirst(i);
-		Oid			toid;
-
-		if (IsA(arg, RangeVar))
-		{
-			char	   *schemaname;
-			char	   *relname;
-			RangeTblEntry *rte;
-			int			vnum;
-			int			sublevels_up;
-
-			/*
-			 * a relation: look it up in the range table, or add if needed
-			 */
-			schemaname = ((RangeVar *) arg)->schemaname;
-			relname = ((RangeVar *) arg)->relname;
 
-			rte = refnameRangeTblEntry(pstate, schemaname, relname,
-									   &sublevels_up);
-
-			if (rte == NULL)
-				rte = addImplicitRTE(pstate, (RangeVar *) arg);
-
-			vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
-
-			/*
-			 * The parameter to be passed to the function is the whole
-			 * tuple from the relation.  We build a special VarNode to
-			 * reflect this -- it has varno set to the correct range table
-			 * entry, but has varattno == 0 to signal that the whole tuple
-			 * is the argument.
-			 */
-			switch (rte->rtekind)
-			{
-				case RTE_RELATION:
-					toid = get_rel_type_id(rte->relid);
-					if (!OidIsValid(toid))
-						elog(ERROR, "could not find type OID for relation %u",
-							 rte->relid);
-					/* replace RangeVar in the arg list */
-					lfirst(i) = makeVar(vnum,
-										InvalidAttrNumber,
-										toid,
-										-1,
-										sublevels_up);
-					break;
-				case RTE_FUNCTION:
-					toid = exprType(rte->funcexpr);
-					if (get_typtype(toid) == 'c')
-					{
-						/* func returns composite; same as relation case */
-						lfirst(i) = makeVar(vnum,
-											InvalidAttrNumber,
-											toid,
-											-1,
-											sublevels_up);
-					}
-					else
-					{
-						/* func returns scalar; use attno 1 instead */
-						lfirst(i) = makeVar(vnum,
-											1,
-											toid,
-											-1,
-											sublevels_up);
-					}
-					break;
-				default:
-
-					/*
-					 * RTE is a join or subselect; must fail for lack of a
-					 * named tuple type
-					 *
-					 * XXX FIXME
-					 */
-					if (is_column)
-						unknown_attribute(schemaname, relname,
-										  strVal(lfirst(funcname)));
-					else
-						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot pass result of subquery or join \"%s\" to a function",
-										relname)));
-					toid = InvalidOid;	/* keep compiler quiet */
-					break;
-			}
-		}
-		else
-			toid = exprType(arg);
-
-		actual_arg_types[argn++] = toid;
+		actual_arg_types[argn++] = exprType(arg);
 	}
 
 	/*
@@ -281,25 +181,9 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
 		 */
 		if (is_column)
 		{
-			char	   *colname = strVal(lfirst(funcname));
-			Oid			relTypeId;
-
 			Assert(nargs == 1);
-			if (IsA(first_arg, RangeVar))
-				unknown_attribute(((RangeVar *) first_arg)->schemaname,
-								  ((RangeVar *) first_arg)->relname,
-								  colname);
-			relTypeId = exprType(first_arg);
-			if (!ISCOMPLEX(relTypeId))
-				ereport(ERROR,
-						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-						 errmsg("attribute notation .%s applied to type %s, which is not a complex type",
-								colname, format_type_be(relTypeId))));
-			else
-				ereport(ERROR,
-						(errcode(ERRCODE_UNDEFINED_COLUMN),
-					  errmsg("attribute \"%s\" not found in data type %s",
-							 colname, format_type_be(relTypeId))));
+			Assert(length(funcname) == 1);
+			unknown_attribute(pstate, first_arg, strVal(lfirst(funcname)));
 		}
 
 		/*
@@ -1284,80 +1168,92 @@ setup_field_select(Node *input, char *attname, Oid relid)
  *	  handles function calls with a single argument that is of complex type.
  *	  If the function call is actually a column projection, return a suitably
  *	  transformed expression tree.	If not, return NULL.
- *
- * NB: argument is expected to be transformed already, ie, not a RangeVar.
  */
 static Node *
-ParseComplexProjection(char *funcname, Node *first_arg)
+ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg)
 {
-	Oid			argtype = exprType(first_arg);
+	Oid			argtype;
 	Oid			argrelid;
 	AttrNumber	attnum;
-	FieldSelect *fselect;
-
-	argrelid = typeidTypeRelid(argtype);
-	if (!argrelid)
-		return NULL;			/* probably should not happen */
-	attnum = get_attnum(argrelid, funcname);
-	if (attnum == InvalidAttrNumber)
-		return NULL;			/* funcname does not match any column */
 
 	/*
-	 * Check for special cases where we don't want to return a
-	 * FieldSelect.
+	 * Special case for whole-row Vars so that we can resolve (foo.*).bar
+	 * even when foo is a reference to a subselect, join, or RECORD function.
+	 * A bonus is that we avoid generating an unnecessary FieldSelect; our
+	 * result can omit the whole-row Var and just be a Var for the selected
+	 * field.
 	 */
-	switch (nodeTag(first_arg))
+	if (IsA(first_arg, Var) &&
+		((Var *) first_arg)->varattno == InvalidAttrNumber)
 	{
-		case T_Var:
-			{
-				Var		   *var = (Var *) first_arg;
+		RangeTblEntry *rte;
 
-				/*
-				 * If the Var is a whole-row tuple, we can just replace it
-				 * with a simple Var reference.
-				 */
-				if (var->varattno == InvalidAttrNumber)
-				{
-					Oid			vartype;
-					int32		vartypmod;
+		rte = GetRTEByRangeTablePosn(pstate,
+									 ((Var *) first_arg)->varno,
+									 ((Var *) first_arg)->varlevelsup);
+		/* Return a Var if funcname matches a column, else NULL */
+		return scanRTEForColumn(pstate, rte, funcname);
+	}
 
-					get_atttypetypmod(argrelid, attnum,
-									  &vartype, &vartypmod);
+	/*
+	 * Else do it the hard way.  Note that if the arg is of RECORD type,
+	 * we will never recognize a column name, and always assume the item
+	 * must be a function.
+	 */
+	argtype = exprType(first_arg);
+	argrelid = typeidTypeRelid(argtype);
+	if (!argrelid)
+		return NULL;			/* can only happen if RECORD */
 
-					return (Node *) makeVar(var->varno,
-											attnum,
-											vartype,
-											vartypmod,
-											var->varlevelsup);
-				}
-				break;
-			}
-		default:
-			break;
-	}
+	attnum = get_attnum(argrelid, funcname);
+	if (attnum == InvalidAttrNumber)
+		return NULL;			/* funcname does not match any column */
 
-	/* Else generate a FieldSelect expression */
-	fselect = setup_field_select(first_arg, funcname, argrelid);
-	return (Node *) fselect;
+	/* Success, so generate a FieldSelect expression */
+	return (Node *) setup_field_select(first_arg, funcname, argrelid);
 }
 
 /*
- * Simple helper routine for delivering "column does not exist" error message
+ * helper routine for delivering "column does not exist" error message
  */
 static void
-unknown_attribute(const char *schemaname, const char *relname,
-				  const char *attname)
+unknown_attribute(ParseState *pstate, Node *relref, char *attname)
 {
-	if (schemaname)
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_COLUMN),
-				 errmsg("column %s.%s.%s does not exist",
-						schemaname, relname, attname)));
-	else
+	RangeTblEntry *rte;
+
+	if (IsA(relref, Var))
+	{
+		/* Reference the RTE by alias not by actual table name */
+		rte = GetRTEByRangeTablePosn(pstate,
+									 ((Var *) relref)->varno,
+									 ((Var *) relref)->varlevelsup);
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_COLUMN),
 				 errmsg("column %s.%s does not exist",
-						relname, attname)));
+						rte->eref->aliasname, attname)));
+	}
+	else
+	{
+		/* Have to do it by reference to the type of the expression */
+		Oid			relTypeId = exprType(relref);
+
+		if (ISCOMPLEX(relTypeId))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" not found in data type %s",
+							attname, format_type_be(relTypeId))));
+		else if (relTypeId == RECORDOID)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("could not identify column \"%s\" in record data type",
+							attname)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("column notation .%s applied to type %s, "
+							"which is not a composite type",
+							attname, format_type_be(relTypeId))));
+	}
 }
 
 /*
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 3e314bea963417675f3d3044d3c69aaac422c70d..4df92ee310cdd9bdc7cb8e95f30fa0dac3ac63af 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.92 2004/01/14 23:01:55 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.93 2004/04/02 19:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -41,8 +41,6 @@ static Node *scanNameSpaceForRelid(ParseState *pstate, Node *nsnode,
 					  Oid relid);
 static void scanNameSpaceForConflict(ParseState *pstate, Node *nsnode,
 						 RangeTblEntry *rte1, const char *aliasname1);
-static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
-				 char *colname);
 static bool isForUpdate(ParseState *pstate, char *refname);
 static bool get_rte_attribute_is_dropped(RangeTblEntry *rte,
 							 AttrNumber attnum);
@@ -424,6 +422,24 @@ RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
 	return 0;					/* keep compiler quiet */
 }
 
+/*
+ * Given an RT index and nesting depth, find the corresponding RTE.
+ * This is the inverse of RTERangeTablePosn.
+ */
+RangeTblEntry *
+GetRTEByRangeTablePosn(ParseState *pstate,
+					   int varno,
+					   int sublevels_up)
+{
+	while (sublevels_up-- > 0)
+	{
+		pstate = pstate->parentParseState;
+		Assert(pstate != NULL);
+	}
+	Assert(varno > 0 && varno <= length(pstate->p_rtable));
+	return rt_fetch(varno, pstate->p_rtable);
+}
+
 /*
  * scanRTEForColumn
  *	  Search the column names of a single RTE for the given name.
@@ -439,7 +455,7 @@ RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
  * expression can only appear in a FROM clause, and any table named in
  * FROM will be marked as requiring read access from the beginning.
  */
-static Node *
+Node *
 scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
 {
 	Node	   *result = NULL;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 6bed23f906467afbd9f3330f63a07ccd41a99280..c9c44ac23896cad5c1ed0095d726d2d616f47e33 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.115 2004/02/13 01:08:20 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.116 2004/04/02 19:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -60,14 +60,6 @@ transformTargetEntry(ParseState *pstate,
 	if (expr == NULL)
 		expr = transformExpr(pstate, node);
 
-	if (IsA(expr, RangeVar))
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("relation reference \"%s\" cannot be used as a select-list entry",
-						((RangeVar *) expr)->relname),
-				 errhint("Write \"%s\".* to denote all the columns of the relation.",
-						 ((RangeVar *) expr)->relname)));
-
 	type_id = exprType(expr);
 	type_mod = exprTypmod(expr);
 
@@ -243,21 +235,12 @@ markTargetListOrigins(ParseState *pstate, List *targetlist)
 static void
 markTargetListOrigin(ParseState *pstate, Resdom *res, Var *var)
 {
-	Index		levelsup;
 	RangeTblEntry *rte;
 	AttrNumber	attnum;
 
 	if (var == NULL || !IsA(var, Var))
 		return;
-	levelsup = var->varlevelsup;
-	while (levelsup-- > 0)
-	{
-		pstate = pstate->parentParseState;
-		Assert(pstate != NULL);
-	}
-	Assert(var->varno > 0 &&
-		   (int) var->varno <= length(pstate->p_rtable));
-	rte = rt_fetch(var->varno, pstate->p_rtable);
+	rte = GetRTEByRangeTablePosn(pstate, var->varno, var->varlevelsup);
 	attnum = var->varattno;
 
 	switch (rte->rtekind)
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index ca209c93028ef879af54a12e3fb4498e76dae6e0..0fa75d0297ddf56d1f6d7674e414dfd4762814ce 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.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/parser/parse_relation.h,v 1.42 2003/11/29 22:41:09 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.43 2004/04/02 19:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,6 +27,11 @@ extern void checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
 extern int RTERangeTablePosn(ParseState *pstate,
 				  RangeTblEntry *rte,
 				  int *sublevels_up);
+extern RangeTblEntry *GetRTEByRangeTablePosn(ParseState *pstate,
+											 int varno,
+											 int sublevels_up);
+extern Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
+							  char *colname);
 extern Node *colnameToVar(ParseState *pstate, char *colname);
 extern Node *qualifiedNameToVar(ParseState *pstate,
 				   char *schemaname,