From 6c5988694218a62c6bc90fc625cbc64f732520cc Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 28 Apr 2002 19:54:29 +0000
Subject: [PATCH] Second try at fixing join alias variables.  Instead of
 attaching miscellaneous lists to join RTEs, attach a list of Vars and
 COALESCE expressions that will replace the join's alias variables during
 planning.  This simplifies flatten_join_alias_vars while still making it easy
 to fix up varno references when transforming the query tree.  Add regression
 test cases for interactions of subqueries with outer joins.

---
 src/backend/nodes/copyfuncs.c           |   8 +-
 src/backend/nodes/equalfuncs.c          |  10 +-
 src/backend/nodes/outfuncs.c            |  22 +-
 src/backend/nodes/readfuncs.c           |  19 +-
 src/backend/optimizer/plan/createplan.c |  68 ++---
 src/backend/optimizer/plan/initsplan.c  |  18 +-
 src/backend/optimizer/plan/planner.c    | 154 +++++++---
 src/backend/optimizer/plan/setrefs.c    |  23 +-
 src/backend/optimizer/util/clauses.c    |  66 +++--
 src/backend/optimizer/util/var.c        | 362 ++----------------------
 src/backend/parser/analyze.c            |  19 +-
 src/backend/parser/parse_clause.c       | 222 ++++++++++-----
 src/backend/parser/parse_relation.c     |  38 ++-
 src/include/catalog/catversion.h        |   4 +-
 src/include/nodes/parsenodes.h          |  17 +-
 src/include/nodes/plannodes.h           |   6 +-
 src/include/optimizer/planmain.h        |   4 +-
 src/include/optimizer/var.h             |   6 +-
 src/include/parser/parse_relation.h     |   7 +-
 src/test/regress/expected/join.out      | 132 +++++++++
 src/test/regress/sql/join.sql           |  72 +++++
 21 files changed, 624 insertions(+), 653 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ac25cd5229..c461f147ef 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.181 2002/04/24 02:48:54 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.182 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -323,7 +323,6 @@ CopyJoinFields(Join *from, Join *newnode)
 {
 	newnode->jointype = from->jointype;
 	Node_Copy(from, newnode, joinqual);
-	newnode->joinrti = from->joinrti;
 	/* subPlan list must point to subplans in the new subtree, not the old */
 	if (from->plan.subPlan != NIL)
 		newnode->plan.subPlan = nconc(newnode->plan.subPlan,
@@ -1475,10 +1474,7 @@ _copyRangeTblEntry(RangeTblEntry *from)
 	newnode->relid = from->relid;
 	Node_Copy(from, newnode, subquery);
 	newnode->jointype = from->jointype;
-	newnode->joincoltypes = listCopy(from->joincoltypes);
-	newnode->joincoltypmods = listCopy(from->joincoltypmods);
-	newnode->joinleftcols = listCopy(from->joinleftcols);
-	newnode->joinrightcols = listCopy(from->joinrightcols);
+	Node_Copy(from, newnode, joinaliasvars);
 	Node_Copy(from, newnode, alias);
 	Node_Copy(from, newnode, eref);
 	newnode->inh = from->inh;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3deb6207c3..31ee46e060 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.129 2002/04/24 02:48:54 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.130 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1680,13 +1680,7 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
 		return false;
 	if (a->jointype != b->jointype)
 		return false;
-	if (!equali(a->joincoltypes, b->joincoltypes))
-		return false;
-	if (!equali(a->joincoltypmods, b->joincoltypmods))
-		return false;
-	if (!equali(a->joinleftcols, b->joinleftcols))
-		return false;
-	if (!equali(a->joinrightcols, b->joinrightcols))
+	if (!equal(a->joinaliasvars, b->joinaliasvars))
 		return false;
 	if (!equal(a->alias, b->alias))
 		return false;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 24238565d5..34264db11e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.156 2002/04/17 20:57:56 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.157 2002/04/28 19:54:28 tgl Exp $
  *
  * NOTES
  *	  Every (plan) node in POSTGRES has an associated "out" routine which
@@ -408,8 +408,6 @@ _outJoin(StringInfo str, Join *node)
 	appendStringInfo(str, " :jointype %d :joinqual ",
 					 (int) node->jointype);
 	_outNode(str, node->joinqual);
-	appendStringInfo(str, " :joinrti %d ",
-					 node->joinrti);
 }
 
 /*
@@ -423,8 +421,6 @@ _outNestLoop(StringInfo str, NestLoop *node)
 	appendStringInfo(str, " :jointype %d :joinqual ",
 					 (int) node->join.jointype);
 	_outNode(str, node->join.joinqual);
-	appendStringInfo(str, " :joinrti %d ",
-					 node->join.joinrti);
 }
 
 /*
@@ -438,8 +434,6 @@ _outMergeJoin(StringInfo str, MergeJoin *node)
 	appendStringInfo(str, " :jointype %d :joinqual ",
 					 (int) node->join.jointype);
 	_outNode(str, node->join.joinqual);
-	appendStringInfo(str, " :joinrti %d ",
-					 node->join.joinrti);
 
 	appendStringInfo(str, " :mergeclauses ");
 	_outNode(str, node->mergeclauses);
@@ -456,8 +450,6 @@ _outHashJoin(StringInfo str, HashJoin *node)
 	appendStringInfo(str, " :jointype %d :joinqual ",
 					 (int) node->join.jointype);
 	_outNode(str, node->join.joinqual);
-	appendStringInfo(str, " :joinrti %d ",
-					 node->join.joinrti);
 
 	appendStringInfo(str, " :hashclauses ");
 	_outNode(str, node->hashclauses);
@@ -982,22 +974,16 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
 	{
 		case RTE_RELATION:
 		case RTE_SPECIAL:
-			appendStringInfo(str, ":relid %u ", node->relid);
+			appendStringInfo(str, ":relid %u", node->relid);
 			break;
 		case RTE_SUBQUERY:
 			appendStringInfo(str, ":subquery ");
 			_outNode(str, node->subquery);
 			break;
 		case RTE_JOIN:
-			appendStringInfo(str, ":jointype %d :joincoltypes ",
+			appendStringInfo(str, ":jointype %d :joinaliasvars ",
 							 (int) node->jointype);
-			_outOidList(str, node->joincoltypes);
-			appendStringInfo(str, " :joincoltypmods ");
-			_outIntList(str, node->joincoltypmods);
-			appendStringInfo(str, " :joinleftcols ");
-			_outIntList(str, node->joinleftcols);
-			appendStringInfo(str, " :joinrightcols ");
-			_outIntList(str, node->joinrightcols);
+			_outNode(str, node->joinaliasvars);
 			break;
 		default:
 			elog(ERROR, "bogus rte kind %d", (int) node->rtekind);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f91ba36e5d..1d4236fc84 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.119 2002/04/11 20:00:00 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.120 2002/04/28 19:54:28 tgl Exp $
  *
  * NOTES
  *	  Most of the read functions for plan nodes are tested. (In fact, they
@@ -421,10 +421,6 @@ _getJoin(Join *node)
 
 	token = pg_strtok(&length); /* skip the :joinqual */
 	node->joinqual = nodeRead(true);	/* get the joinqual */
-
-	token = pg_strtok(&length); /* skip the :joinrti */
-	token = pg_strtok(&length); /* get the joinrti */
-	node->joinrti = atoi(token);
 }
 
 
@@ -1523,17 +1519,8 @@ _readRangeTblEntry(void)
 			token = pg_strtok(&length); /* get jointype */
 			local_node->jointype = (JoinType) atoi(token);
 
-			token = pg_strtok(&length); /* eat :joincoltypes */
-			local_node->joincoltypes = toOidList(nodeRead(true));
-
-			token = pg_strtok(&length); /* eat :joincoltypmods */
-			local_node->joincoltypmods = toIntList(nodeRead(true));
-
-			token = pg_strtok(&length); /* eat :joinleftcols */
-			local_node->joinleftcols = toIntList(nodeRead(true));
-
-			token = pg_strtok(&length); /* eat :joinrightcols */
-			local_node->joinrightcols = toIntList(nodeRead(true));
+			token = pg_strtok(&length); /* eat :joinaliasvars */
+			local_node->joinaliasvars = nodeRead(true);	/* now read it */
 			break;
 
 		default:
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 79d90bf5a3..c20abacca3 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.112 2002/03/12 00:51:45 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.113 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -80,18 +80,18 @@ static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
 static NestLoop *make_nestloop(List *tlist,
 			  List *joinclauses, List *otherclauses,
 			  Plan *lefttree, Plan *righttree,
-			  JoinType jointype, Index joinrti);
+			  JoinType jointype);
 static HashJoin *make_hashjoin(List *tlist,
 			  List *joinclauses, List *otherclauses,
 			  List *hashclauses,
 			  Plan *lefttree, Plan *righttree,
-			  JoinType jointype, Index joinrti);
+			  JoinType jointype);
 static Hash *make_hash(List *tlist, Node *hashkey, Plan *lefttree);
 static MergeJoin *make_mergejoin(List *tlist,
 			   List *joinclauses, List *otherclauses,
 			   List *mergeclauses,
 			   Plan *lefttree, Plan *righttree,
-			   JoinType jointype, Index joinrti);
+			   JoinType jointype);
 
 /*
  * create_plan
@@ -591,7 +591,6 @@ create_nestloop_plan(Query *root,
 					 List *inner_tlist)
 {
 	NestLoop   *join_plan;
-	Index		joinrti = best_path->path.parent->joinrti;
 
 	if (IsA(inner_plan, IndexScan))
 	{
@@ -639,22 +638,19 @@ create_nestloop_plan(Query *root,
 													  root,
 													  outer_tlist,
 													  NIL,
-													  innerrel,
-													  joinrti);
+													  innerrel);
 			innerscan->indxqual = join_references(innerscan->indxqual,
 												  root,
 												  outer_tlist,
 												  NIL,
-												  innerrel,
-												  joinrti);
+												  innerrel);
 			/* fix the inner qpqual too, if it has join clauses */
 			if (NumRelids((Node *) inner_plan->qual) > 1)
 				inner_plan->qual = join_references(inner_plan->qual,
 												   root,
 												   outer_tlist,
 												   NIL,
-												   innerrel,
-												   joinrti);
+												   innerrel);
 		}
 	}
 	else if (IsA(inner_plan, TidScan))
@@ -665,8 +661,7 @@ create_nestloop_plan(Query *root,
 											 root,
 											 outer_tlist,
 											 inner_tlist,
-											 innerscan->scan.scanrelid,
-											 joinrti);
+											 innerscan->scan.scanrelid);
 	}
 	else if (IsA_Join(inner_plan))
 	{
@@ -688,22 +683,19 @@ create_nestloop_plan(Query *root,
 								  root,
 								  outer_tlist,
 								  inner_tlist,
-								  (Index) 0,
-								  joinrti);
+								  (Index) 0);
 	otherclauses = join_references(otherclauses,
 								   root,
 								   outer_tlist,
 								   inner_tlist,
-								   (Index) 0,
-								   joinrti);
+								   (Index) 0);
 
 	join_plan = make_nestloop(tlist,
 							  joinclauses,
 							  otherclauses,
 							  outer_plan,
 							  inner_plan,
-							  best_path->jointype,
-							  joinrti);
+							  best_path->jointype);
 
 	copy_path_costsize(&join_plan->join.plan, &best_path->path);
 
@@ -723,7 +715,6 @@ create_mergejoin_plan(Query *root,
 {
 	List	   *mergeclauses;
 	MergeJoin  *join_plan;
-	Index		joinrti = best_path->jpath.path.parent->joinrti;
 
 	mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
 
@@ -736,8 +727,7 @@ create_mergejoin_plan(Query *root,
 								  root,
 								  outer_tlist,
 								  inner_tlist,
-								  (Index) 0,
-								  joinrti);
+								  (Index) 0);
 
 	/*
 	 * Fix the additional qpquals too.
@@ -746,8 +736,7 @@ create_mergejoin_plan(Query *root,
 								   root,
 								   outer_tlist,
 								   inner_tlist,
-								   (Index) 0,
-								   joinrti);
+								   (Index) 0);
 
 	/*
 	 * Now set the references in the mergeclauses and rearrange them so
@@ -757,8 +746,7 @@ create_mergejoin_plan(Query *root,
 												root,
 												outer_tlist,
 												inner_tlist,
-												(Index) 0,
-												joinrti));
+												(Index) 0));
 
 	/*
 	 * Create explicit sort nodes for the outer and inner join paths if
@@ -824,8 +812,7 @@ create_mergejoin_plan(Query *root,
 							   mergeclauses,
 							   outer_plan,
 							   inner_plan,
-							   best_path->jpath.jointype,
-							   joinrti);
+							   best_path->jpath.jointype);
 
 	copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path);
 
@@ -847,7 +834,6 @@ create_hashjoin_plan(Query *root,
 	HashJoin   *join_plan;
 	Hash	   *hash_plan;
 	Node	   *innerhashkey;
-	Index		joinrti = best_path->jpath.path.parent->joinrti;
 
 	/*
 	 * NOTE: there will always be exactly one hashclause in the list
@@ -866,8 +852,7 @@ create_hashjoin_plan(Query *root,
 								  root,
 								  outer_tlist,
 								  inner_tlist,
-								  (Index) 0,
-								  joinrti);
+								  (Index) 0);
 
 	/*
 	 * Fix the additional qpquals too.
@@ -876,8 +861,7 @@ create_hashjoin_plan(Query *root,
 								   root,
 								   outer_tlist,
 								   inner_tlist,
-								   (Index) 0,
-								   joinrti);
+								   (Index) 0);
 
 	/*
 	 * Now set the references in the hashclauses and rearrange them so
@@ -887,8 +871,7 @@ create_hashjoin_plan(Query *root,
 											   root,
 											   outer_tlist,
 											   inner_tlist,
-											   (Index) 0,
-											   joinrti));
+											   (Index) 0));
 
 	/* Now the righthand op of the sole hashclause is the inner hash key. */
 	innerhashkey = (Node *) get_rightop(lfirst(hashclauses));
@@ -903,8 +886,7 @@ create_hashjoin_plan(Query *root,
 							  hashclauses,
 							  outer_plan,
 							  (Plan *) hash_plan,
-							  best_path->jpath.jointype,
-							  joinrti);
+							  best_path->jpath.jointype);
 
 	copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path);
 
@@ -1363,8 +1345,7 @@ make_nestloop(List *tlist,
 			  List *otherclauses,
 			  Plan *lefttree,
 			  Plan *righttree,
-			  JoinType jointype,
-			  Index joinrti)
+			  JoinType jointype)
 {
 	NestLoop   *node = makeNode(NestLoop);
 	Plan	   *plan = &node->join.plan;
@@ -1377,7 +1358,6 @@ make_nestloop(List *tlist,
 	plan->righttree = righttree;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
-	node->join.joinrti = joinrti;
 
 	return node;
 }
@@ -1389,8 +1369,7 @@ make_hashjoin(List *tlist,
 			  List *hashclauses,
 			  Plan *lefttree,
 			  Plan *righttree,
-			  JoinType jointype,
-			  Index joinrti)
+			  JoinType jointype)
 {
 	HashJoin   *node = makeNode(HashJoin);
 	Plan	   *plan = &node->join.plan;
@@ -1404,7 +1383,6 @@ make_hashjoin(List *tlist,
 	node->hashclauses = hashclauses;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
-	node->join.joinrti = joinrti;
 
 	return node;
 }
@@ -1439,8 +1417,7 @@ make_mergejoin(List *tlist,
 			   List *mergeclauses,
 			   Plan *lefttree,
 			   Plan *righttree,
-			   JoinType jointype,
-			   Index joinrti)
+			   JoinType jointype)
 {
 	MergeJoin  *node = makeNode(MergeJoin);
 	Plan	   *plan = &node->join.plan;
@@ -1454,7 +1431,6 @@ make_mergejoin(List *tlist,
 	node->mergeclauses = mergeclauses;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
-	node->join.joinrti = joinrti;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 39ac5ba886..2410651d68 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.68 2002/04/16 23:08:10 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.69 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -173,16 +173,14 @@ add_vars_to_targetlist(Query *root, List *vars)
 		if (rel->reloptkind == RELOPT_OTHER_JOIN_REL)
 		{
 			/* Var is an alias */
-			Var	   *leftsubvar,
-				   *rightsubvar;
+			Node   *expansion;
+			List   *varsused;
 
-			build_join_alias_subvars(root, var,
-									 &leftsubvar, &rightsubvar);
-
-			rel = find_base_rel(root, leftsubvar->varno);
-			add_var_to_tlist(rel, leftsubvar);
-			rel = find_base_rel(root, rightsubvar->varno);
-			add_var_to_tlist(rel, rightsubvar);
+			expansion = flatten_join_alias_vars((Node *) var,
+												root, true);
+			varsused = pull_var_clause(expansion, false);
+			add_vars_to_targetlist(root, varsused);
+			freeList(varsused);
 		}
 	}
 }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1df2cd2940..5eb17f3b05 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.115 2002/03/12 00:51:47 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.116 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -41,8 +41,10 @@
 #define EXPRKIND_HAVING 2
 
 
-static Node *pull_up_subqueries(Query *parse, Node *jtnode);
+static Node *pull_up_subqueries(Query *parse, Node *jtnode,
+								bool below_outer_join);
 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 Node *preprocess_jointree(Query *parse, Node *jtnode);
 static Node *preprocess_expression(Query *parse, Node *expr, int kind);
@@ -153,7 +155,7 @@ subquery_planner(Query *parse, double tuple_fraction)
 	 * this query.
 	 */
 	parse->jointree = (FromExpr *)
-		pull_up_subqueries(parse, (Node *) parse->jointree);
+		pull_up_subqueries(parse, (Node *) parse->jointree, false);
 
 	/*
 	 * If so, we may have created opportunities to simplify the jointree.
@@ -264,6 +266,9 @@ subquery_planner(Query *parse, double tuple_fraction)
  *		the parent query.  If the subquery has no special features like
  *		grouping/aggregation then we can merge it into the parent's jointree.
  *
+ * below_outer_join is true if this jointree node is within the nullable
+ * side of an outer join.  This restricts what we can do.
+ *
  * A tricky aspect of this code is that if we pull up a subquery we have
  * to replace Vars that reference the subquery's outputs throughout the
  * parent query, including quals attached to jointree nodes above the one
@@ -274,7 +279,7 @@ subquery_planner(Query *parse, double tuple_fraction)
  * copy of the tree; we have to invoke it just on the quals, instead.
  */
 static Node *
-pull_up_subqueries(Query *parse, Node *jtnode)
+pull_up_subqueries(Query *parse, Node *jtnode, bool below_outer_join)
 {
 	if (jtnode == NULL)
 		return NULL;
@@ -288,16 +293,26 @@ pull_up_subqueries(Query *parse, Node *jtnode)
 		 * Is this a subquery RTE, and if so, is the subquery simple
 		 * enough to pull up?  (If not, do nothing at this node.)
 		 *
+		 * If we are inside an outer join, only pull up subqueries whose
+		 * targetlists are nullable --- otherwise substituting their tlist
+		 * entries for upper Var references would do the wrong thing
+		 * (the results wouldn't become NULL when they're supposed to).
+		 * XXX This could be improved by generating pseudo-variables for
+		 * 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.
+		 * it up if there is a reference to its whole tuple result.  Perhaps
+		 * a pseudo-variable is the answer here too.
 		 */
-		if (subquery && is_simple_subquery(subquery) &&
+		if (rte->rtekind == RTE_SUBQUERY && is_simple_subquery(subquery) &&
+			(!below_outer_join || has_nullable_targetlist(subquery)) &&
 			!contain_whole_tuple_var((Node *) parse, varno, 0))
 		{
 			int			rtoffset;
-			Node	   *subjointree;
 			List	   *subtlist;
-			List	   *l;
+			List	   *rt;
 
 			/*
 			 * First, recursively pull up the subquery's subqueries, so
@@ -311,49 +326,61 @@ pull_up_subqueries(Query *parse, Node *jtnode)
 			 * having chunks of structure multiply linked.
 			 */
 			subquery->jointree = (FromExpr *)
-				pull_up_subqueries(subquery, (Node *) subquery->jointree);
+				pull_up_subqueries(subquery, (Node *) subquery->jointree,
+								   below_outer_join);
 
 			/*
-			 * Append the subquery's rangetable to mine (currently, no
-			 * adjustments will be needed in the subquery's rtable).
+			 * Now make a modifiable copy of the subquery that we can
+			 * run OffsetVarNodes on.
+			 */
+			subquery = copyObject(subquery);
+
+			/*
+			 * Adjust varnos in subquery so that we can append its
+			 * rangetable to upper query's.
 			 */
 			rtoffset = length(parse->rtable);
-			parse->rtable = nconc(parse->rtable,
-								  copyObject(subquery->rtable));
-
-			/*
-			 * Make copies of the subquery's jointree and targetlist with
-			 * varnos adjusted to match the merged rangetable.
-			 */
-			subjointree = copyObject(subquery->jointree);
-			OffsetVarNodes(subjointree, rtoffset, 0);
-			subtlist = copyObject(subquery->targetList);
-			OffsetVarNodes((Node *) subtlist, rtoffset, 0);
+			OffsetVarNodes((Node *) subquery, rtoffset, 0);
 
 			/*
 			 * Replace all of the top query's references to the subquery's
 			 * outputs with copies of the adjusted subtlist items, being
 			 * careful not to replace any of the jointree structure.
+			 * (This'd be a lot cleaner if we could use query_tree_mutator.)
 			 */
+			subtlist = subquery->targetList;
 			parse->targetList = (List *)
 				ResolveNew((Node *) parse->targetList,
 						   varno, 0, subtlist, CMD_SELECT, 0);
 			resolvenew_in_jointree((Node *) parse->jointree, varno, subtlist);
+			Assert(parse->setOperations == NULL);
 			parse->havingQual =
 				ResolveNew(parse->havingQual,
 						   varno, 0, subtlist, CMD_SELECT, 0);
 
-			/*
-			 * Pull up any FOR UPDATE markers, too.
-			 */
-			foreach(l, subquery->rowMarks)
+			foreach(rt, parse->rtable)
 			{
-				int			submark = lfirsti(l);
+				RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
 
-				parse->rowMarks = lappendi(parse->rowMarks,
-										   submark + rtoffset);
+				if (rte->rtekind == RTE_JOIN)
+					rte->joinaliasvars = (List *)
+						ResolveNew((Node *) rte->joinaliasvars,
+								   varno, 0, subtlist, CMD_SELECT, 0);
 			}
 
+			/*
+			 * Now append the adjusted rtable entries to upper query.
+			 * (We hold off until after fixing the upper rtable entries;
+			 * no point in running that code on the subquery ones too.)
+			 */
+			parse->rtable = nconc(parse->rtable, subquery->rtable);
+
+			/*
+			 * Pull up any FOR UPDATE markers, too.  (OffsetVarNodes
+			 * already adjusted the marker values, so just nconc the list.)
+			 */
+			parse->rowMarks = nconc(parse->rowMarks, subquery->rowMarks);
+
 			/*
 			 * Miscellaneous housekeeping.
 			 */
@@ -364,7 +391,7 @@ pull_up_subqueries(Query *parse, Node *jtnode)
 			 * Return the adjusted subquery jointree to replace the
 			 * RangeTblRef entry in my jointree.
 			 */
-			return subjointree;
+			return (Node *) subquery->jointree;
 		}
 	}
 	else if (IsA(jtnode, FromExpr))
@@ -373,35 +400,39 @@ pull_up_subqueries(Query *parse, Node *jtnode)
 		List	   *l;
 
 		foreach(l, f->fromlist)
-			lfirst(l) = pull_up_subqueries(parse, lfirst(l));
+			lfirst(l) = pull_up_subqueries(parse, lfirst(l),
+										   below_outer_join);
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		/*
-		 * At the moment, we can't pull up subqueries that are inside the
-		 * nullable side of an outer join, because substituting their
-		 * target list entries for upper Var references wouldn't do the
-		 * right thing (the entries wouldn't go to NULL when they're
-		 * supposed to). Suppressing the pullup is an ugly,
-		 * performance-losing hack, but I see no alternative for now. Find
-		 * a better way to handle this when we redesign query trees ---
-		 * tgl 4/30/01.
-		 */
+		/* Recurse, being careful to tell myself when inside outer join */
 		switch (j->jointype)
 		{
 			case JOIN_INNER:
-				j->larg = pull_up_subqueries(parse, j->larg);
-				j->rarg = pull_up_subqueries(parse, j->rarg);
+				j->larg = pull_up_subqueries(parse, j->larg,
+											 below_outer_join);
+				j->rarg = pull_up_subqueries(parse, j->rarg,
+											 below_outer_join);
 				break;
 			case JOIN_LEFT:
-				j->larg = pull_up_subqueries(parse, j->larg);
+				j->larg = pull_up_subqueries(parse, j->larg,
+											 below_outer_join);
+				j->rarg = pull_up_subqueries(parse, j->rarg,
+											 true);
 				break;
 			case JOIN_FULL:
+				j->larg = pull_up_subqueries(parse, j->larg,
+											 true);
+				j->rarg = pull_up_subqueries(parse, j->rarg,
+											 true);
 				break;
 			case JOIN_RIGHT:
-				j->rarg = pull_up_subqueries(parse, j->rarg);
+				j->larg = pull_up_subqueries(parse, j->larg,
+											 true);
+				j->rarg = pull_up_subqueries(parse, j->rarg,
+											 below_outer_join);
 				break;
 			case JOIN_UNION:
 
@@ -484,6 +515,37 @@ is_simple_subquery(Query *subquery)
 	return true;
 }
 
+/*
+ * has_nullable_targetlist
+ *	  Check a subquery in the range table to see if all the non-junk
+ *	  targetlist items are simple variables (and, hence, will correctly
+ *	  go to NULL when examined above the point of an outer join).
+ *
+ * A possible future extension is to accept strict functions of simple
+ * variables, eg, "x + 1".
+ */
+static bool
+has_nullable_targetlist(Query *subquery)
+{
+	List	   *l;
+
+	foreach(l, subquery->targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+		/* ignore resjunk columns */
+		if (tle->resdom->resjunk)
+			continue;
+
+		/* Okay if tlist item is a simple Var */
+		if (tle->expr && IsA(tle->expr, Var))
+			continue;
+
+		return false;
+	}
+	return true;
+}
+
 /*
  * Helper routine for pull_up_subqueries: do ResolveNew on every expression
  * in the jointree, without changing the jointree structure itself.  Ugly,
@@ -675,7 +737,7 @@ preprocess_expression(Query *parse, Node *expr, int kind)
 		}
 	}
 	if (has_join_rtes)
-		expr = flatten_join_alias_vars(expr, parse, 0);
+		expr = flatten_join_alias_vars(expr, parse, false);
 
 	return expr;
 }
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 2f48821ece..807876e4a7 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.74 2002/03/12 00:51:48 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.75 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,7 +31,6 @@ typedef struct
 	List	   *outer_tlist;
 	List	   *inner_tlist;
 	Index		acceptable_rel;
-	Index		join_rti;
 } join_references_context;
 
 typedef struct
@@ -271,8 +270,7 @@ set_join_references(Query *root, Join *join)
 											root,
 											outer_tlist,
 											inner_tlist,
-											(Index) 0,
-											join->joinrti);
+											(Index) 0);
 }
 
 /*
@@ -367,8 +365,6 @@ set_uppernode_references(Plan *plan, Index subvarno)
  * 'inner_tlist' is the target list of the inner join relation, or NIL
  * 'acceptable_rel' is either zero or the rangetable index of a relation
  *		whose Vars may appear in the clause without provoking an error.
- * 'join_rti' is either zero or the join RTE index of join alias variables
- *		that should be expanded.
  *
  * Returns the new expression tree.  The original clause structure is
  * not modified.
@@ -378,8 +374,7 @@ join_references(List *clauses,
 				Query *root,
 				List *outer_tlist,
 				List *inner_tlist,
-				Index acceptable_rel,
-				Index join_rti)
+				Index acceptable_rel)
 {
 	join_references_context context;
 
@@ -387,7 +382,6 @@ join_references(List *clauses,
 	context.outer_tlist = outer_tlist;
 	context.inner_tlist = inner_tlist;
 	context.acceptable_rel = acceptable_rel;
-	context.join_rti = join_rti;
 	return (List *) join_references_mutator((Node *) clauses, &context);
 }
 
@@ -401,6 +395,7 @@ join_references_mutator(Node *node,
 	{
 		Var		   *var = (Var *) node;
 		Resdom	   *resdom;
+		Node	   *newnode;
 
 		/* First look for the var in the input tlists */
 		resdom = tlist_member((Node *) var, context->outer_tlist);
@@ -423,13 +418,11 @@ join_references_mutator(Node *node,
 		}
 
 		/* Perhaps it's a join alias that can be resolved to input vars? */
-		if (var->varno == context->join_rti)
+		newnode = flatten_join_alias_vars((Node *) var,
+										  context->root,
+										  true);
+		if (!equal(newnode, (Node *) var))
 		{
-			Node   *newnode;
-
-			newnode = flatten_join_alias_vars((Node *) var,
-											  context->root,
-											  context->join_rti);
 			/* Must now resolve the input vars... */
 			newnode = join_references_mutator(newnode, context);
 			return newnode;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index d7f100cb35..1b7cc14e2e 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.96 2002/04/05 00:31:27 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.97 2002/04/28 19:54:28 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -1902,6 +1902,8 @@ query_tree_walker(Query *query,
 				  void *context,
 				  bool visitQueryRTEs)
 {
+	List	   *rt;
+
 	Assert(query != NULL && IsA(query, Query));
 
 	if (walker((Node *) query->targetList, context))
@@ -1912,17 +1914,25 @@ query_tree_walker(Query *query,
 		return true;
 	if (walker(query->havingQual, context))
 		return true;
-	if (visitQueryRTEs)
+	foreach(rt, query->rtable)
 	{
-		List	   *rt;
+		RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
 
-		foreach(rt, query->rtable)
+		switch (rte->rtekind)
 		{
-			RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
-
-			if (rte->subquery)
-				if (walker(rte->subquery, context))
+			case RTE_RELATION:
+			case RTE_SPECIAL:
+				/* nothing to do */
+				break;
+			case RTE_SUBQUERY:
+				if (visitQueryRTEs)
+					if (walker(rte->subquery, context))
+						return true;
+				break;
+			case RTE_JOIN:
+				if (walker(rte->joinaliasvars, context))
 					return true;
+				break;
 		}
 	}
 	return false;
@@ -2281,32 +2291,42 @@ query_tree_mutator(Query *query,
 				   void *context,
 				   bool visitQueryRTEs)
 {
+	List	   *newrt = NIL;
+	List	   *rt;
+
 	Assert(query != NULL && IsA(query, Query));
 
 	MUTATE(query->targetList, query->targetList, List *);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
-	if (visitQueryRTEs)
+	foreach(rt, query->rtable)
 	{
-		List	   *newrt = NIL;
-		List	   *rt;
+		RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+		RangeTblEntry *newrte;
 
-		foreach(rt, query->rtable)
+		switch (rte->rtekind)
 		{
-			RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
-
-			if (rte->subquery)
-			{
-				RangeTblEntry *newrte;
-
+			case RTE_RELATION:
+			case RTE_SPECIAL:
+				/* nothing to do, don't bother to make a copy */
+				break;
+			case RTE_SUBQUERY:
+				if (visitQueryRTEs)
+				{
+					FLATCOPY(newrte, rte, RangeTblEntry);
+					CHECKFLATCOPY(newrte->subquery, rte->subquery, Query);
+					MUTATE(newrte->subquery, newrte->subquery, Query *);
+					rte = newrte;
+				}
+				break;
+			case RTE_JOIN:
 				FLATCOPY(newrte, rte, RangeTblEntry);
-				CHECKFLATCOPY(newrte->subquery, rte->subquery, Query);
-				MUTATE(newrte->subquery, newrte->subquery, Query *);
+				MUTATE(newrte->joinaliasvars, rte->joinaliasvars, List *);
 				rte = newrte;
-			}
-			newrt = lappend(newrt, rte);
+				break;
 		}
-		query->rtable = newrt;
+		newrt = lappend(newrt, rte);
 	}
+	query->rtable = newrt;
 }
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 81aad81c0f..ee861c3557 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -8,18 +8,16 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.35 2002/04/11 20:00:00 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.36 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
-#include "nodes/makefuncs.h"
 #include "nodes/plannodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
-#include "parser/parse_coerce.h"
 
 
 typedef struct
@@ -44,7 +42,7 @@ typedef struct
 typedef struct
 {
 	Query	   *root;
-	int			expandRTI;
+	bool		force;
 } flatten_join_alias_vars_context;
 
 static bool pull_varnos_walker(Node *node,
@@ -56,8 +54,6 @@ static bool pull_var_clause_walker(Node *node,
 					   pull_var_clause_context *context);
 static Node *flatten_join_alias_vars_mutator(Node *node,
 						flatten_join_alias_vars_context *context);
-static Node *flatten_join_alias_var(Var *var, Query *root, int expandRTI);
-static Node *find_jointree_item(Node *jtnode, int rtindex);
 
 
 /*
@@ -314,26 +310,30 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
 
 /*
  * flatten_join_alias_vars
- *	  Whereever possible, replace Vars that reference JOIN outputs with
- *	  references to the original relation variables instead.  This allows
- *	  quals involving such vars to be pushed down.  Vars that cannot be
- *	  simplified to non-join Vars are replaced by COALESCE expressions
- *	  if they have varno = expandRTI, and are left as JOIN RTE references
- *	  otherwise.  (Pass expandRTI = 0 to prevent all COALESCE expansion.)
+ *	  Replace Vars that reference JOIN outputs with references to the original
+ *	  relation variables instead.  This allows quals involving such vars to be
+ *	  pushed down.
  *
- *	  Upper-level vars (with varlevelsup > 0) are ignored; normally there
- *	  should not be any by the time this routine is called.
+ * If force is TRUE then we will reduce all JOIN alias Vars to non-alias Vars
+ * or expressions thereof (there may be COALESCE and/or type conversions
+ * involved).  If force is FALSE we will not expand a Var to a non-Var
+ * expression.  This is a hack to avoid confusing mergejoin planning, which
+ * currently cannot cope with non-Var join items --- we leave the join vars
+ * as Vars till after planning is done, then expand them during setrefs.c.
+ *
+ * Upper-level vars (with varlevelsup > 0) are ignored; normally there
+ * should not be any by the time this routine is called.
  *
  * Does not examine subqueries, therefore must only be used after reduction
  * of sublinks to subplans!
  */
 Node *
-flatten_join_alias_vars(Node *node, Query *root, int expandRTI)
+flatten_join_alias_vars(Node *node, Query *root, bool force)
 {
 	flatten_join_alias_vars_context context;
 
 	context.root = root;
-	context.expandRTI = expandRTI;
+	context.force = force;
 
 	return flatten_join_alias_vars_mutator(node, &context);
 }
@@ -347,326 +347,24 @@ flatten_join_alias_vars_mutator(Node *node,
 	if (IsA(node, Var))
 	{
 		Var	   *var = (Var *) node;
+		RangeTblEntry *rte;
+		Node   *newvar;
 
 		if (var->varlevelsup != 0)
 			return node;		/* no need to copy, really */
-		return flatten_join_alias_var(var, context->root, context->expandRTI);
+		rte = rt_fetch(var->varno, context->root->rtable);
+		if (rte->rtekind != RTE_JOIN)
+			return node;
+		Assert(var->varattno > 0);
+		newvar = (Node *) nth(var->varattno - 1, rte->joinaliasvars);
+		if (IsA(newvar, Var) || context->force)
+		{
+			/* expand it; recurse in case join input is itself a join */
+			return flatten_join_alias_vars_mutator(newvar, context);
+		}
+		/* we don't want to force expansion of this alias Var */
+		return node;
 	}
 	return expression_tree_mutator(node, flatten_join_alias_vars_mutator,
 								   (void *) context);
 }
-
-static Node *
-flatten_join_alias_var(Var *var, Query *root, int expandRTI)
-{
-	Index		varno = var->varno;
-	AttrNumber	varattno = var->varattno;
-	Oid			vartype = var->vartype;
-	int32		vartypmod = var->vartypmod;
-	JoinExpr   *jexpr = NULL;
-
-	/*
-	 * Loop to cope with joins of joins
-	 */
-	for (;;)
-	{
-		RangeTblEntry *rte = rt_fetch(varno, root->rtable);
-		Index		leftrti,
-					rightrti;
-		AttrNumber	leftattno,
-					rightattno;
-		RangeTblEntry *subrte;
-		Oid			subtype;
-		int32		subtypmod;
-
-		if (rte->rtekind != RTE_JOIN)
-			break;				/* reached a non-join RTE */
-		/*
-		 * Find the RT indexes of the left and right children of the
-		 * join node.  We have to search the join tree to do this,
-		 * which is a major pain in the neck --- but keeping RT indexes
-		 * in other RT entries is worse, because it makes modifying
-		 * querytrees difficult.  (Perhaps we can improve on the
-		 * rangetable/jointree datastructure someday.)  One thing we
-		 * can do is avoid repeated searches while tracing a single
-		 * variable down to its baserel.
-		 */
-		if (jexpr == NULL)
-			jexpr = (JoinExpr *)
-				find_jointree_item((Node *) root->jointree, varno);
-		if (jexpr == NULL ||
-			!IsA(jexpr, JoinExpr) ||
-			jexpr->rtindex != varno)
-			elog(ERROR, "flatten_join_alias_var: failed to find JoinExpr");
-		if (IsA(jexpr->larg, RangeTblRef))
-			leftrti = ((RangeTblRef *) jexpr->larg)->rtindex;
-		else if (IsA(jexpr->larg, JoinExpr))
-			leftrti = ((JoinExpr *) jexpr->larg)->rtindex;
-		else
-		{
-			elog(ERROR, "flatten_join_alias_var: unexpected subtree type");
-			leftrti = 0;		/* keep compiler quiet */
-		}
-		if (IsA(jexpr->rarg, RangeTblRef))
-			rightrti = ((RangeTblRef *) jexpr->rarg)->rtindex;
-		else if (IsA(jexpr->rarg, JoinExpr))
-			rightrti = ((JoinExpr *) jexpr->rarg)->rtindex;
-		else
-		{
-			elog(ERROR, "flatten_join_alias_var: unexpected subtree type");
-			rightrti = 0;		/* keep compiler quiet */
-		}
-		/*
-		 * See if the join var is from the left side, the right side,
-		 * or both (ie, it is a USING/NATURAL JOIN merger column).
-		 */
-		Assert(varattno > 0);
-		leftattno = (AttrNumber) nthi(varattno-1, rte->joinleftcols);
-		rightattno = (AttrNumber) nthi(varattno-1, rte->joinrightcols);
-		if (leftattno && rightattno)
-		{
-			/*
-			 * Var is a merge var.  If a left or right join, we can replace
-			 * it by the left or right input var respectively; we only need
-			 * a COALESCE for a full join.  However, beware of the possibility
-			 * that there's been a type promotion to make the input vars
-			 * compatible; do not replace a var by one of a different type!
-			 */
-			if (rte->jointype == JOIN_INNER ||
-				rte->jointype == JOIN_LEFT)
-			{
-				subrte = rt_fetch(leftrti, root->rtable);
-				get_rte_attribute_type(subrte, leftattno,
-									   &subtype, &subtypmod);
-				if (vartype == subtype && vartypmod == subtypmod)
-				{
-					varno = leftrti;
-					varattno = leftattno;
-					jexpr = (JoinExpr *) jexpr->larg;
-					continue;
-				}
-			}
-			if (rte->jointype == JOIN_INNER ||
-				rte->jointype == JOIN_RIGHT)
-			{
-				subrte = rt_fetch(rightrti, root->rtable);
-				get_rte_attribute_type(subrte, rightattno,
-									   &subtype, &subtypmod);
-				if (vartype == subtype && vartypmod == subtypmod)
-				{
-					varno = rightrti;
-					varattno = rightattno;
-					jexpr = (JoinExpr *) jexpr->rarg;
-					continue;
-				}
-			}
-			/*
-			 * This var cannot be substituted directly, only with a COALESCE.
-			 * Do so only if it belongs to the particular join indicated by
-			 * the caller.
-			 */
-			if (varno != expandRTI)
-				break;
-			{
-				Node   *l_var,
-					   *r_var;
-				CaseExpr   *c = makeNode(CaseExpr);
-				CaseWhen   *w = makeNode(CaseWhen);
-				NullTest   *n = makeNode(NullTest);
-
-				subrte = rt_fetch(leftrti, root->rtable);
-				get_rte_attribute_type(subrte, leftattno,
-									   &subtype, &subtypmod);
-				l_var = (Node *) makeVar(leftrti,
-										 leftattno,
-										 subtype,
-										 subtypmod,
-										 0);
-				if (subtype != vartype)
-				{
-					l_var = coerce_type(NULL, l_var, subtype,
-										vartype, vartypmod, false);
-					l_var = coerce_type_typmod(NULL, l_var,
-											   vartype, vartypmod);
-				}
-				else if (subtypmod != vartypmod)
-					l_var = coerce_type_typmod(NULL, l_var,
-											   vartype, vartypmod);
-
-				subrte = rt_fetch(rightrti, root->rtable);
-				get_rte_attribute_type(subrte, rightattno,
-									   &subtype, &subtypmod);
-				r_var = (Node *) makeVar(rightrti,
-										 rightattno,
-										 subtype,
-										 subtypmod,
-										 0);
-				if (subtype != vartype)
-				{
-					r_var = coerce_type(NULL, r_var, subtype,
-										vartype, vartypmod, false);
-					r_var = coerce_type_typmod(NULL, r_var,
-											   vartype, vartypmod);
-				}
-				else if (subtypmod != vartypmod)
-					r_var = coerce_type_typmod(NULL, r_var,
-											   vartype, vartypmod);
-
-				n->arg = l_var;
-				n->nulltesttype = IS_NOT_NULL;
-				w->expr = (Node *) n;
-				w->result = l_var;
-				c->casetype = vartype;
-				c->args = makeList1(w);
-				c->defresult = r_var;
-				return (Node *) c;
-			}
-		}
-		else if (leftattno)
-		{
-			/* Here we do not need to check the type */
-			varno = leftrti;
-			varattno = leftattno;
-			jexpr = (JoinExpr *) jexpr->larg;
-		}
-		else
-		{
-			Assert(rightattno);
-			/* Here we do not need to check the type */
-			varno = rightrti;
-			varattno = rightattno;
-			jexpr = (JoinExpr *) jexpr->rarg;
-		}
-	}
-
-	/*
-	 * When we fall out of the loop, we've reached the base Var.
-	 */
-	return (Node *) makeVar(varno,
-							varattno,
-							vartype,
-							vartypmod,
-							0);
-}
-
-/*
- * Given a join alias Var, construct Vars for the two input vars it directly
- * depends on.  Note that this should *only* be called for merger alias Vars.
- * In practice it is only used for Vars that got past flatten_join_alias_vars.
- */
-void
-build_join_alias_subvars(Query *root, Var *aliasvar,
-						 Var **leftsubvar, Var **rightsubvar)
-{
-	Index		varno = aliasvar->varno;
-	AttrNumber	varattno = aliasvar->varattno;
-	RangeTblEntry *rte;
-	JoinExpr   *jexpr;
-	Index		leftrti,
-				rightrti;
-	AttrNumber	leftattno,
-				rightattno;
-	RangeTblEntry *subrte;
-	Oid			subtype;
-	int32		subtypmod;
-
-	Assert(aliasvar->varlevelsup == 0);
-	rte = rt_fetch(varno, root->rtable);
-	Assert(rte->rtekind == RTE_JOIN);
-
-	/*
-	 * Find the RT indexes of the left and right children of the
-	 * join node.
-	 */
-	jexpr = (JoinExpr *) find_jointree_item((Node *) root->jointree, varno);
-	if (jexpr == NULL ||
-		!IsA(jexpr, JoinExpr) ||
-		jexpr->rtindex != varno)
-		elog(ERROR, "build_join_alias_subvars: failed to find JoinExpr");
-	if (IsA(jexpr->larg, RangeTblRef))
-		leftrti = ((RangeTblRef *) jexpr->larg)->rtindex;
-	else if (IsA(jexpr->larg, JoinExpr))
-		leftrti = ((JoinExpr *) jexpr->larg)->rtindex;
-	else
-	{
-		elog(ERROR, "build_join_alias_subvars: unexpected subtree type");
-		leftrti = 0;			/* keep compiler quiet */
-	}
-	if (IsA(jexpr->rarg, RangeTblRef))
-		rightrti = ((RangeTblRef *) jexpr->rarg)->rtindex;
-	else if (IsA(jexpr->rarg, JoinExpr))
-		rightrti = ((JoinExpr *) jexpr->rarg)->rtindex;
-	else
-	{
-		elog(ERROR, "build_join_alias_subvars: unexpected subtree type");
-		rightrti = 0;			/* keep compiler quiet */
-	}
-
-	Assert(varattno > 0);
-	leftattno = (AttrNumber) nthi(varattno-1, rte->joinleftcols);
-	rightattno = (AttrNumber) nthi(varattno-1, rte->joinrightcols);
-	if (!(leftattno && rightattno))
-		elog(ERROR, "build_join_alias_subvars: non-merger variable");
-
-	subrte = rt_fetch(leftrti, root->rtable);
-	get_rte_attribute_type(subrte, leftattno,
-						   &subtype, &subtypmod);
-	*leftsubvar = makeVar(leftrti,
-						  leftattno,
-						  subtype,
-						  subtypmod,
-						  0);
-
-	subrte = rt_fetch(rightrti, root->rtable);
-	get_rte_attribute_type(subrte, rightattno,
-						   &subtype, &subtypmod);
-	*rightsubvar = makeVar(rightrti,
-						   rightattno,
-						   subtype,
-						   subtypmod,
-						   0);
-}
-
-/*
- * Find jointree item matching the specified RT index
- */
-static Node *
-find_jointree_item(Node *jtnode, int rtindex)
-{
-	if (jtnode == NULL)
-		return NULL;
-	if (IsA(jtnode, RangeTblRef))
-	{
-		if (((RangeTblRef *) jtnode)->rtindex == rtindex)
-			return jtnode;
-	}
-	else if (IsA(jtnode, FromExpr))
-	{
-		FromExpr   *f = (FromExpr *) jtnode;
-		List	   *l;
-
-		foreach(l, f->fromlist)
-		{
-			jtnode = find_jointree_item(lfirst(l), rtindex);
-			if (jtnode)
-				return jtnode;
-		}
-	}
-	else if (IsA(jtnode, JoinExpr))
-	{
-		JoinExpr   *j = (JoinExpr *) jtnode;
-
-		if (j->rtindex == rtindex)
-			return jtnode;
-		jtnode = find_jointree_item(j->larg, rtindex);
-		if (jtnode)
-			return jtnode;
-		jtnode = find_jointree_item(j->rarg, rtindex);
-		if (jtnode)
-			return jtnode;
-	}
-	else
-		elog(ERROR, "find_jointree_item: unexpected node type %d",
-			 nodeTag(jtnode));
-	return NULL;
-}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c0fe929e49..a3acf29453 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.232 2002/04/24 02:22:54 momjian Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.233 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2075,7 +2075,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	Node	   *node;
 	List	   *lefttl,
 			   *dtlist,
-			   *colMods,
+			   *targetvars,
 			   *targetnames,
 			   *sv_namespace,
 			   *sv_rtable;
@@ -2145,14 +2145,12 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	/*
 	 * Generate dummy targetlist for outer query using column names of
 	 * leftmost select and common datatypes of topmost set operation. Also
-	 * make a list of the column names for use in parsing ORDER BY.
-	 *
-	 * XXX colMods is a hack to provide a dummy typmod list below.  We
-	 * should probably keep track of common typmod instead.
+	 * make lists of the dummy vars and their names for use in parsing
+	 * ORDER BY.
 	 */
 	qry->targetList = NIL;
+	targetvars = NIL;
 	targetnames = NIL;
-	colMods = NIL;
 	lefttl = leftmostQuery->targetList;
 	foreach(dtlist, sostmt->colTypes)
 	{
@@ -2174,8 +2172,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 								0);
 		qry->targetList = lappend(qry->targetList,
 								  makeTargetEntry(resdom, expr));
+		targetvars = lappend(targetvars, expr);
 		targetnames = lappend(targetnames, makeString(colName));
-		colMods = lappendi(colMods, -1);
 		lefttl = lnext(lefttl);
 	}
 
@@ -2232,10 +2230,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	jrte = addRangeTableEntryForJoin(NULL,
 									 targetnames,
 									 JOIN_INNER,
-									 sostmt->colTypes,
-									 colMods,
-									 NIL,
-									 NIL,
+									 targetvars,
 									 NULL,
 									 true);
 	jrtr = makeNode(RangeTblRef);
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 452f66284d..6df4a4fd7d 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.89 2002/04/16 23:08:11 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.90 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,7 +39,7 @@
 
 static char *clauseText[] = {"ORDER BY", "GROUP BY", "DISTINCT ON"};
 
-static void extractUniqueColumns(List *common_colnames,
+static void extractRemainingColumns(List *common_colnames,
 					 List *src_colnames, List *src_colvars,
 					 List **res_colnames, List **res_colvars);
 static Node *transformJoinUsingClause(ParseState *pstate,
@@ -51,6 +51,8 @@ static RangeTblRef *transformRangeSubselect(ParseState *pstate,
 						RangeSubselect *r);
 static Node *transformFromClauseItem(ParseState *pstate, Node *n,
 						List **containedRels);
+static Node *buildMergedJoinVar(JoinType jointype,
+								Var *l_colvar, Var *r_colvar);
 static TargetEntry *findTargetlistEntry(ParseState *pstate, Node *node,
 					List *tlist, int clause);
 static List *addTargetToSortList(TargetEntry *tle, List *sortlist,
@@ -194,9 +196,9 @@ interpretInhOption(InhOption inhOpt)
  * Extract all not-in-common columns from column lists of a source table
  */
 static void
-extractUniqueColumns(List *common_colnames,
-					 List *src_colnames, List *src_colvars,
-					 List **res_colnames, List **res_colvars)
+extractRemainingColumns(List *common_colnames,
+						List *src_colnames, List *src_colvars,
+						List **res_colnames, List **res_colvars)
 {
 	List	   *new_colnames = NIL;
 	List	   *new_colvars = NIL;
@@ -496,10 +498,7 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 				   *res_colnames,
 				   *l_colvars,
 				   *r_colvars,
-				   *coltypes,
-				   *coltypmods,
-				   *leftcolnos,
-				   *rightcolnos;
+				   *res_colvars;
 		Index		leftrti,
 					rightrti;
 		RangeTblEntry *rte;
@@ -597,17 +596,14 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 		 * Now transform the join qualifications, if any.
 		 */
 		res_colnames = NIL;
-		coltypes = NIL;
-		coltypmods = NIL;
-		leftcolnos = NIL;
-		rightcolnos = NIL;
+		res_colvars = NIL;
 
 		if (j->using)
 		{
 			/*
 			 * JOIN/USING (or NATURAL JOIN, as transformed above).
 			 * Transform the list into an explicit ON-condition, and
-			 * generate a list of result columns.
+			 * generate a list of merged result columns.
 			 */
 			List	   *ucols = j->using;
 			List	   *l_usingvars = NIL;
@@ -620,14 +616,22 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 			{
 				char	   *u_colname = strVal(lfirst(ucol));
 				List	   *col;
-				Var		   *l_colvar,
-						   *r_colvar;
-				Oid			outcoltype;
-				int32		outcoltypmod;
 				int			ndx;
 				int			l_index = -1;
 				int			r_index = -1;
+				Var		   *l_colvar,
+						   *r_colvar;
 
+				/* Check for USING(foo,foo) */
+				foreach(col, res_colnames)
+				{
+					char	   *res_colname = strVal(lfirst(col));
+
+					if (strcmp(res_colname, u_colname) == 0)
+						elog(ERROR, "USING column name \"%s\" appears more than once", u_colname);
+				}
+
+				/* Find it in left input */
 				ndx = 0;
 				foreach(col, l_colnames)
 				{
@@ -645,6 +649,7 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 					elog(ERROR, "JOIN/USING column \"%s\" not found in left table",
 						 u_colname);
 
+				/* Find it in right input */
 				ndx = 0;
 				foreach(col, r_colnames)
 				{
@@ -667,30 +672,11 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 				r_colvar = nth(r_index, r_colvars);
 				r_usingvars = lappend(r_usingvars, r_colvar);
 
-				res_colnames = lappend(res_colnames,
-									   nth(l_index, l_colnames));
-				/*
-				 * Choose output type if input types are dissimilar.
-				 */
-				outcoltype = l_colvar->vartype;
-				outcoltypmod = l_colvar->vartypmod;
-				if (outcoltype != r_colvar->vartype)
-				{
-					outcoltype =
-						select_common_type(makeListi2(l_colvar->vartype,
-													  r_colvar->vartype),
-										   "JOIN/USING");
-					outcoltypmod = -1; /* ie, unknown */
-				}
-				else if (outcoltypmod != r_colvar->vartypmod)
-				{
-					/* same type, but not same typmod */
-					outcoltypmod = -1; /* ie, unknown */
-				}
-				coltypes = lappendi(coltypes, outcoltype);
-				coltypmods = lappendi(coltypmods, outcoltypmod);
-				leftcolnos = lappendi(leftcolnos, l_index+1);
-				rightcolnos = lappendi(rightcolnos, r_index+1);
+				res_colnames = lappend(res_colnames, lfirst(ucol));
+				res_colvars = lappend(res_colvars,
+									  buildMergedJoinVar(j->jointype,
+														 l_colvar,
+														 r_colvar));
 			}
 
 			j->quals = transformJoinUsingClause(pstate,
@@ -708,34 +694,16 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 		}
 
 		/* Add remaining columns from each side to the output columns */
-		extractUniqueColumns(res_colnames,
-							 l_colnames, l_colvars,
-							 &l_colnames, &l_colvars);
-		extractUniqueColumns(res_colnames,
-							 r_colnames, r_colvars,
-							 &r_colnames, &r_colvars);
+		extractRemainingColumns(res_colnames,
+								l_colnames, l_colvars,
+								&l_colnames, &l_colvars);
+		extractRemainingColumns(res_colnames,
+								r_colnames, r_colvars,
+								&r_colnames, &r_colvars);
 		res_colnames = nconc(res_colnames, l_colnames);
-		while (l_colvars)
-		{
-			Var	   *l_var = (Var *) lfirst(l_colvars);
-
-			coltypes = lappendi(coltypes, l_var->vartype);
-			coltypmods = lappendi(coltypmods, l_var->vartypmod);
-			leftcolnos = lappendi(leftcolnos, l_var->varattno);
-			rightcolnos = lappendi(rightcolnos, 0);
-			l_colvars = lnext(l_colvars);
-		}
+		res_colvars = nconc(res_colvars, l_colvars);
 		res_colnames = nconc(res_colnames, r_colnames);
-		while (r_colvars)
-		{
-			Var	   *r_var = (Var *) lfirst(r_colvars);
-
-			coltypes = lappendi(coltypes, r_var->vartype);
-			coltypmods = lappendi(coltypmods, r_var->vartypmod);
-			leftcolnos = lappendi(leftcolnos, 0);
-			rightcolnos = lappendi(rightcolnos, r_var->varattno);
-			r_colvars = lnext(r_colvars);
-		}
+		res_colvars = nconc(res_colvars, r_colvars);
 
 		/*
 		 * Check alias (AS clause), if any.
@@ -753,11 +721,12 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 		/*
 		 * Now build an RTE for the result of the join
 		 */
-		rte = addRangeTableEntryForJoin(pstate, res_colnames,
+		rte = addRangeTableEntryForJoin(pstate,
+										res_colnames,
 										j->jointype,
-										coltypes, coltypmods,
-										leftcolnos, rightcolnos,
-										j->alias, true);
+										res_colvars,
+										j->alias,
+										true);
 
 		/* assume new rte is at end */
 		j->rtindex = length(pstate->p_rtable);
@@ -777,6 +746,115 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 								 * quiet */
 }
 
+/*
+ * buildMergedJoinVar -
+ *	  generate a suitable replacement expression for a merged join column
+ */
+static Node *
+buildMergedJoinVar(JoinType jointype, Var *l_colvar, Var *r_colvar)
+{
+	Oid			outcoltype;
+	int32		outcoltypmod;
+	Node	   *l_node,
+			   *r_node,
+			   *res_node;
+
+	/*
+	 * Choose output type if input types are dissimilar.
+	 */
+	outcoltype = l_colvar->vartype;
+	outcoltypmod = l_colvar->vartypmod;
+	if (outcoltype != r_colvar->vartype)
+	{
+		outcoltype = select_common_type(makeListi2(l_colvar->vartype,
+												   r_colvar->vartype),
+										"JOIN/USING");
+		outcoltypmod = -1;		/* ie, unknown */
+	}
+	else if (outcoltypmod != r_colvar->vartypmod)
+	{
+		/* same type, but not same typmod */
+		outcoltypmod = -1;		/* ie, unknown */
+	}
+
+	/*
+	 * Insert coercion functions if needed.  Note that a difference in
+	 * typmod can only happen if input has typmod but outcoltypmod is -1.
+	 * In that case we insert a RelabelType to clearly mark that result's
+	 * typmod is not same as input.
+	 */
+	if (l_colvar->vartype != outcoltype)
+		l_node = coerce_type(NULL, (Node *) l_colvar, l_colvar->vartype,
+							 outcoltype, outcoltypmod, false);
+	else if (l_colvar->vartypmod != outcoltypmod)
+		l_node = (Node *) makeRelabelType((Node *) l_colvar,
+										  outcoltype, outcoltypmod);
+	else
+		l_node = (Node *) l_colvar;
+
+	if (r_colvar->vartype != outcoltype)
+		r_node = coerce_type(NULL, (Node *) r_colvar, r_colvar->vartype,
+							 outcoltype, outcoltypmod, false);
+	else if (r_colvar->vartypmod != outcoltypmod)
+		r_node = (Node *) makeRelabelType((Node *) r_colvar,
+										  outcoltype, outcoltypmod);
+	else
+		r_node = (Node *) r_colvar;
+
+	/*
+	 * Choose what to emit
+	 */
+	switch (jointype)
+	{
+		case JOIN_INNER:
+			/*
+			 * We can use either var; prefer non-coerced one if available.
+			 */
+			if (IsA(l_node, Var))
+				res_node = l_node;
+			else if (IsA(r_node, Var))
+				res_node = r_node;
+			else
+				res_node = l_node;
+			break;
+		case JOIN_LEFT:
+			/* Always use left var */
+			res_node = l_node;
+			break;
+		case JOIN_RIGHT:
+			/* Always use right var */
+			res_node = r_node;
+			break;
+		case JOIN_FULL:
+		{
+			/*
+			 * Here we must build a COALESCE expression to ensure that
+			 * the join output is non-null if either input is.
+			 */
+			CaseExpr   *c = makeNode(CaseExpr);
+			CaseWhen   *w = makeNode(CaseWhen);
+			NullTest   *n = makeNode(NullTest);
+
+			n->arg = l_node;
+			n->nulltesttype = IS_NOT_NULL;
+			w->expr = (Node *) n;
+			w->result = l_node;
+			c->casetype = outcoltype;
+			c->args = makeList1(w);
+			c->defresult = r_node;
+			res_node = (Node *) c;
+			break;
+		}
+		default:
+			elog(ERROR, "buildMergedJoinVar: unexpected jointype %d",
+				 (int) jointype);
+			res_node = NULL;	/* keep compiler quiet */
+			break;
+	}
+
+	return res_node;
+}
+
 
 /*
  * transformWhereClause -
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 9b386cb636..b822a2378b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.67 2002/04/02 08:51:51 inoue Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.68 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -681,10 +681,7 @@ RangeTblEntry *
 addRangeTableEntryForJoin(ParseState *pstate,
 						  List *colnames,
 						  JoinType jointype,
-						  List *coltypes,
-						  List *coltypmods,
-						  List *leftcols,
-						  List *rightcols,
+						  List *aliasvars,
 						  Alias *alias,
 						  bool inFromCl)
 {
@@ -696,10 +693,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	rte->relid = InvalidOid;
 	rte->subquery = NULL;
 	rte->jointype = jointype;
-	rte->joincoltypes = coltypes;
-	rte->joincoltypmods = coltypmods;
-	rte->joinleftcols = leftcols;
-	rte->joinrightcols = rightcols;
+	rte->joinaliasvars = aliasvars;
 	rte->alias = alias;
 
 	eref = alias ? (Alias *) copyObject(alias) : makeAlias("unnamed_join", NIL);
@@ -922,13 +916,12 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
 	{
 		/* Join RTE */
 		List	   *aliasp = rte->eref->colnames;
-		List	   *coltypes = rte->joincoltypes;
-		List	   *coltypmods = rte->joincoltypmods;
+		List	   *aliasvars = rte->joinaliasvars;
 
 		varattno = 0;
 		while (aliasp)
 		{
-			Assert(coltypes && coltypmods);
+			Assert(aliasvars);
 			varattno++;
 
 			if (colnames)
@@ -940,21 +933,21 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
 
 			if (colvars)
 			{
+				Node	   *aliasvar = (Node *) lfirst(aliasvars);
 				Var		   *varnode;
 
 				varnode = makeVar(rtindex, varattno,
-								  (Oid) lfirsti(coltypes),
-								  (int32) lfirsti(coltypmods),
+								  exprType(aliasvar),
+								  exprTypmod(aliasvar),
 								  sublevels_up);
 
 				*colvars = lappend(*colvars, varnode);
 			}
 
 			aliasp = lnext(aliasp);
-			coltypes = lnext(coltypes);
-			coltypmods = lnext(coltypmods);
+			aliasvars = lnext(aliasvars);
 		}
-		Assert(coltypes == NIL && coltypmods == NIL);
+		Assert(aliasvars == NIL);
 	}
 	else
 		elog(ERROR, "expandRTE: unsupported RTE kind %d",
@@ -1091,10 +1084,13 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 	}
 	else if (rte->rtekind == RTE_JOIN)
 	{
-		/* Join RTE --- get type info directly from join RTE */
-		Assert(attnum > 0 && attnum <= length(rte->joincoltypes));
-		*vartype = (Oid) nthi(attnum-1, rte->joincoltypes);
-		*vartypmod = nthi(attnum-1, rte->joincoltypmods);
+		/* Join RTE --- get type info from join RTE's alias variable */
+		Node   *aliasvar;
+
+		Assert(attnum > 0 && attnum <= length(rte->joinaliasvars));
+		aliasvar = (Node *) nth(attnum-1, rte->joinaliasvars);
+		*vartype = exprType(aliasvar);
+		*vartypmod = exprTypmod(aliasvar);
 	}
 	else
 		elog(ERROR, "get_rte_attribute_type: unsupported RTE kind %d",
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 6ebe79cc6a..e135782bd5 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.128 2002/04/27 21:24:34 tgl Exp $
+ * $Id: catversion.h,v 1.129 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200204271
+#define CATALOG_VERSION_NO	200204281
 
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60703a06dc..5bd0e89b18 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.174 2002/04/24 02:48:55 momjian Exp $
+ * $Id: parsenodes.h,v 1.175 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -510,17 +510,14 @@ typedef struct RangeTblEntry
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
 	 *
-	 * joincoltypes/joincoltypmods identify the column datatypes of the
-	 * join result.  joinleftcols and joinrightcols identify the source
-	 * columns from the join's inputs: each entry is either a source column
-	 * AttrNumber or zero.  For normal columns exactly one is nonzero,
-	 * but both are nonzero for a column "merged" by USING or NATURAL.
+	 * joinaliasvars is a list of Vars or COALESCE expressions corresponding
+	 * to the columns of the join result.  An alias Var referencing column
+	 * K of the join result can be replaced by the K'th element of
+	 * joinaliasvars --- but to simplify the task of reverse-listing aliases
+	 * correctly, we do not do that until planning time.
 	 */
 	JoinType	jointype;		/* type of join */
-	List	   *joincoltypes;	/* integer list of column type OIDs */
-	List	   *joincoltypmods;	/* integer list of column typmods */
-	List	   *joinleftcols;	/* integer list of left-side column #s */
-	List	   *joinrightcols;	/* integer list of right-side column #s */
+	List	   *joinaliasvars;	/* list of alias-var expansions */
 
 	/*
 	 * Fields valid in all RTEs:
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 66db9cf364..9ba1caca32 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: plannodes.h,v 1.54 2002/03/12 00:52:01 tgl Exp $
+ * $Id: plannodes.h,v 1.55 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -254,7 +254,6 @@ typedef struct SubqueryScan
  * jointype:	rule for joining tuples from left and right subtrees
  * joinqual:	qual conditions that came from JOIN/ON or JOIN/USING
  *				(plan.qual contains conditions that came from WHERE)
- * joinrti:		rtable index of corresponding JOIN RTE, if any (0 if none)
  *
  * When jointype is INNER, joinqual and plan.qual are semantically
  * interchangeable.  For OUTER jointypes, the two are *not* interchangeable;
@@ -263,8 +262,6 @@ typedef struct SubqueryScan
  * (But plan.qual is still applied before actually returning a tuple.)
  * For an outer join, only joinquals are allowed to be used as the merge
  * or hash condition of a merge or hash join.
- *
- * joinrti is for the convenience of setrefs.c; it's not used in execution.
  * ----------------
  */
 typedef struct Join
@@ -272,7 +269,6 @@ typedef struct Join
 	Plan		plan;
 	JoinType	jointype;
 	List	   *joinqual;		/* JOIN quals (in addition to plan.qual) */
-	Index		joinrti;		/* JOIN RTE, if any */
 } Join;
 
 /* ----------------
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 919f3d23de..3956555a2e 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: planmain.h,v 1.55 2002/03/12 00:52:03 tgl Exp $
+ * $Id: planmain.h,v 1.56 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -59,7 +59,7 @@ extern void process_implied_equality(Query *root, Node *item1, Node *item2,
 extern void set_plan_references(Query *root, Plan *plan);
 extern List *join_references(List *clauses, Query *root,
 							 List *outer_tlist, List *inner_tlist,
-							 Index acceptable_rel, Index join_rti);
+							 Index acceptable_rel);
 extern void fix_opids(Node *node);
 
 /*
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index 1153604e48..c9a22b8f55 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: var.h,v 1.18 2002/03/12 00:52:04 tgl Exp $
+ * $Id: var.h,v 1.19 2002/04/28 19:54:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,8 +23,6 @@ extern bool contain_var_reference(Node *node, int varno, int varattno,
 extern bool contain_whole_tuple_var(Node *node, int varno, int levelsup);
 extern bool contain_var_clause(Node *node);
 extern List *pull_var_clause(Node *node, bool includeUpperVars);
-extern Node *flatten_join_alias_vars(Node *node, Query *root, int expandRTI);
-extern void build_join_alias_subvars(Query *root, Var *aliasvar,
-									 Var **leftsubvar, Var **rightsubvar);
+extern Node *flatten_join_alias_vars(Node *node, Query *root, bool force);
 
 #endif   /* VAR_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 038931d9b6..353ce8957c 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_relation.h,v 1.31 2002/03/22 02:56:37 tgl Exp $
+ * $Id: parse_relation.h,v 1.32 2002/04/28 19:54:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,10 +44,7 @@ extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
 extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
 						  List *colnames,
 						  JoinType jointype,
-						  List *coltypes,
-						  List *coltypmods,
-						  List *leftcols,
-						  List *rightcols,
+						  List *aliasvars,
 						  Alias *alias,
 						  bool inFromCl);
 extern void addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index cfe7ded08d..78b0d89685 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1868,6 +1868,138 @@ SELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name);
  dd   |    | 42 |   
 (4 rows)
 
+--
+-- Test interactions of join syntax and subqueries
+--
+-- Basic cases (we expect planner to pull up the subquery here)
+SELECT * FROM
+(SELECT * FROM t2) as s2
+INNER JOIN
+(SELECT * FROM t3) s3
+USING (name);
+ name | n  | n  
+------+----+----
+ aa   | 12 | 13
+ bb   | 22 | 23
+(2 rows)
+
+SELECT * FROM
+(SELECT * FROM t2) as s2
+LEFT JOIN
+(SELECT * FROM t3) s3
+USING (name);
+ name | n  | n  
+------+----+----
+ aa   | 12 | 13
+ bb   | 22 | 23
+ dd   | 42 |   
+(3 rows)
+
+SELECT * FROM
+(SELECT * FROM t2) as s2
+FULL JOIN
+(SELECT * FROM t3) s3
+USING (name);
+ name | n  | n  
+------+----+----
+ aa   | 12 | 13
+ bb   | 22 | 23
+ cc   |    | 33
+ dd   | 42 |   
+(4 rows)
+
+-- Cases with non-nullable expressions in subquery results;
+-- make sure these go to null as expected
+SELECT * FROM
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL INNER JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+ name | s2_n | s2_2 | s3_n | s3_2 
+------+------+------+------+------
+ aa   |   12 |    2 |   13 |    3
+ bb   |   22 |    2 |   23 |    3
+(2 rows)
+
+SELECT * FROM
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL LEFT JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+ name | s2_n | s2_2 | s3_n | s3_2 
+------+------+------+------+------
+ aa   |   12 |    2 |   13 |    3
+ bb   |   22 |    2 |   23 |    3
+ dd   |   42 |    2 |      |     
+(3 rows)
+
+SELECT * FROM
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL FULL JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+ name | s2_n | s2_2 | s3_n | s3_2 
+------+------+------+------+------
+ aa   |   12 |    2 |   13 |    3
+ bb   |   22 |    2 |   23 |    3
+ cc   |      |      |   33 |    3
+ dd   |   42 |    2 |      |     
+(4 rows)
+
+SELECT * FROM
+(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1
+NATURAL INNER JOIN
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL INNER JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+ name | s1_n | s1_1 | s2_n | s2_2 | s3_n | s3_2 
+------+------+------+------+------+------+------
+ aa   |   11 |    1 |   12 |    2 |   13 |    3
+(1 row)
+
+SELECT * FROM
+(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1
+NATURAL FULL JOIN
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL FULL JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+ name | s1_n | s1_1 | s2_n | s2_2 | s3_n | s3_2 
+------+------+------+------+------+------+------
+ aa   |   11 |    1 |   12 |    2 |   13 |    3
+ bb   |      |      |   22 |    2 |   23 |    3
+ cc   |      |      |      |      |   33 |    3
+ dd   |      |      |   42 |    2 |      |     
+(4 rows)
+
+SELECT * FROM
+(SELECT name, n as s1_n FROM t1) as s1
+NATURAL FULL JOIN
+  (SELECT * FROM
+    (SELECT name, n as s2_n FROM t2) as s2
+    NATURAL FULL JOIN
+    (SELECT name, n as s3_n FROM t3) as s3
+  ) ss2;
+ name | s1_n | s2_n | s3_n 
+------+------+------+------
+ aa   |   11 |   12 |   13
+ bb   |      |   22 |   23
+ cc   |      |      |   33
+ dd   |      |   42 |     
+(4 rows)
+
+SELECT * FROM
+(SELECT name, n as s1_n FROM t1) as s1
+NATURAL FULL JOIN
+  (SELECT * FROM
+    (SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+    NATURAL FULL JOIN
+    (SELECT name, n as s3_n FROM t3) as s3
+  ) ss2;
+ name | s1_n | s2_n | s2_2 | s3_n 
+------+------+------+------+------
+ aa   |   11 |   12 |    2 |   13
+ bb   |      |   22 |    2 |   23
+ cc   |      |      |      |   33
+ dd   |      |   42 |    2 |     
+(4 rows)
+
 --
 -- Clean up
 --
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 91e64adfc9..5baf7fd3cf 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -216,6 +216,78 @@ INSERT INTO t3 VALUES ( 'cc', 33 );
 
 SELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name);
 
+--
+-- Test interactions of join syntax and subqueries
+--
+
+-- Basic cases (we expect planner to pull up the subquery here)
+SELECT * FROM
+(SELECT * FROM t2) as s2
+INNER JOIN
+(SELECT * FROM t3) s3
+USING (name);
+
+SELECT * FROM
+(SELECT * FROM t2) as s2
+LEFT JOIN
+(SELECT * FROM t3) s3
+USING (name);
+
+SELECT * FROM
+(SELECT * FROM t2) as s2
+FULL JOIN
+(SELECT * FROM t3) s3
+USING (name);
+
+-- Cases with non-nullable expressions in subquery results;
+-- make sure these go to null as expected
+SELECT * FROM
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL INNER JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+
+SELECT * FROM
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL LEFT JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+
+SELECT * FROM
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL FULL JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+
+SELECT * FROM
+(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1
+NATURAL INNER JOIN
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL INNER JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+
+SELECT * FROM
+(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1
+NATURAL FULL JOIN
+(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+NATURAL FULL JOIN
+(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3;
+
+SELECT * FROM
+(SELECT name, n as s1_n FROM t1) as s1
+NATURAL FULL JOIN
+  (SELECT * FROM
+    (SELECT name, n as s2_n FROM t2) as s2
+    NATURAL FULL JOIN
+    (SELECT name, n as s3_n FROM t3) as s3
+  ) ss2;
+
+SELECT * FROM
+(SELECT name, n as s1_n FROM t1) as s1
+NATURAL FULL JOIN
+  (SELECT * FROM
+    (SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2
+    NATURAL FULL JOIN
+    (SELECT name, n as s3_n FROM t3) as s3
+  ) ss2;
+
 --
 -- Clean up
 --