diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 319a9c83d82ee5ad7de8ec4f075179f4d5a41f5c..e230ba598a00fae5a6a56d5269f2a33e9a6361c1 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.70 2002/03/06 06:09:33 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.71 2002/03/12 00:51:35 tgl Exp $
  *
  */
 
@@ -15,12 +15,15 @@
 #include "executor/instrument.h"
 #include "lib/stringinfo.h"
 #include "nodes/print.h"
+#include "optimizer/clauses.h"
 #include "optimizer/planner.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "tcop/pquery.h"
+#include "utils/builtins.h"
 #include "utils/relcache.h"
 
+
 typedef struct ExplainState
 {
 	/* options */
@@ -32,6 +35,14 @@ typedef struct ExplainState
 
 static StringInfo Explain_PlanToString(Plan *plan, ExplainState *es);
 static void ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest);
+static void show_scan_qual(List *qual, bool is_or_qual, const char *qlabel,
+						   int scanrelid,
+						   StringInfo str, int indent, ExplainState *es);
+static void show_upper_qual(List *qual, const char *qlabel,
+							const char *outer_name, int outer_varno, Plan *outer_plan,
+							const char *inner_name, int inner_varno, Plan *inner_plan,
+							StringInfo str, int indent, ExplainState *es);
+static Node *make_ors_ands_explicit(List *orclauses);
 
 /* Convert a null string pointer into "<>" */
 #define stringStringInfo(s) (((s) == NULL) ? "<>" : (s))
@@ -40,7 +51,6 @@ static void ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDes
 /*
  * ExplainQuery -
  *	  print out the execution plan for a given query
- *
  */
 void
 ExplainQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
@@ -81,7 +91,6 @@ ExplainQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
 /*
  * ExplainOneQuery -
  *	  print out the execution plan for one query
- *
  */
 static void
 ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
@@ -176,9 +185,6 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
 	pfree(es);
 }
 
-/*****************************************************************************
- *
- *****************************************************************************/
 
 /*
  * explain_outNode -
@@ -341,6 +347,90 @@ explain_outNode(StringInfo str, Plan *plan, int indent, ExplainState *es)
 	}
 	appendStringInfo(str, "\n");
 
+	/* quals */
+	switch (nodeTag(plan))
+	{
+		case T_IndexScan:
+			show_scan_qual(((IndexScan *) plan)->indxqualorig, true,
+						   "indxqual",
+						   ((Scan *) plan)->scanrelid,
+						   str, indent, es);
+			show_scan_qual(plan->qual, false, "qual",
+						   ((Scan *) plan)->scanrelid,
+						   str, indent, es);
+			break;
+		case T_SeqScan:
+		case T_TidScan:
+			show_scan_qual(plan->qual, false, "qual",
+						   ((Scan *) plan)->scanrelid,
+						   str, indent, es);
+			break;
+		case T_NestLoop:
+			show_upper_qual(((NestLoop *) plan)->join.joinqual, "joinqual",
+							"outer", OUTER, outerPlan(plan),
+							"inner", INNER, innerPlan(plan),
+							str, indent, es);
+			show_upper_qual(plan->qual, "qual",
+							"outer", OUTER, outerPlan(plan),
+							"inner", INNER, innerPlan(plan),
+							str, indent, es);
+			break;
+		case T_MergeJoin:
+			show_upper_qual(((MergeJoin *) plan)->mergeclauses, "merge",
+							"outer", OUTER, outerPlan(plan),
+							"inner", INNER, innerPlan(plan),
+							str, indent, es);
+			show_upper_qual(((MergeJoin *) plan)->join.joinqual, "joinqual",
+							"outer", OUTER, outerPlan(plan),
+							"inner", INNER, innerPlan(plan),
+							str, indent, es);
+			show_upper_qual(plan->qual, "qual",
+							"outer", OUTER, outerPlan(plan),
+							"inner", INNER, innerPlan(plan),
+							str, indent, es);
+			break;
+		case T_HashJoin:
+			show_upper_qual(((HashJoin *) plan)->hashclauses, "hash",
+							"outer", OUTER, outerPlan(plan),
+							"inner", INNER, innerPlan(plan),
+							str, indent, es);
+			show_upper_qual(((HashJoin *) plan)->join.joinqual, "joinqual",
+							"outer", OUTER, outerPlan(plan),
+							"inner", INNER, innerPlan(plan),
+							str, indent, es);
+			show_upper_qual(plan->qual, "qual",
+							"outer", OUTER, outerPlan(plan),
+							"inner", INNER, innerPlan(plan),
+							str, indent, es);
+			break;
+		case T_SubqueryScan:
+			show_upper_qual(plan->qual, "qual",
+							"subplan", 1, ((SubqueryScan *) plan)->subplan,
+							"", 0, NULL,
+							str, indent, es);
+			break;
+		case T_Agg:
+		case T_Group:
+			show_upper_qual(plan->qual, "qual",
+							"subplan", 0, outerPlan(plan),
+							"", 0, NULL,
+							str, indent, es);
+			break;
+		case T_Result:
+			show_upper_qual((List *) ((Result *) plan)->resconstantqual,
+							"constqual",
+							"subplan", OUTER, outerPlan(plan),
+							"", 0, NULL,
+							str, indent, es);
+			show_upper_qual(plan->qual, "qual",
+							"subplan", OUTER, outerPlan(plan),
+							"", 0, NULL,
+							str, indent, es);
+			break;
+		default:
+			break;
+	}
+
 	/* initPlan-s */
 	if (plan->initPlan)
 	{
@@ -448,3 +538,121 @@ Explain_PlanToString(Plan *plan, ExplainState *es)
 		explain_outNode(str, plan, 0, es);
 	return str;
 }
+
+/*
+ * Show a qualifier expression for a scan plan node
+ */
+static void
+show_scan_qual(List *qual, bool is_or_qual, const char *qlabel,
+			   int scanrelid,
+			   StringInfo str, int indent, ExplainState *es)
+{
+	RangeTblEntry *rte;
+	List	   *context;
+	Node	   *node;
+	char	   *exprstr;
+	int			i;
+
+	/* No work if empty qual */
+	if (qual == NIL)
+		return;
+	if (is_or_qual)
+	{
+		if (lfirst(qual) == NIL && lnext(qual) == NIL)
+			return;
+	}
+
+	/* Generate deparse context */
+	Assert(scanrelid > 0 && scanrelid <= length(es->rtable));
+	rte = rt_fetch(scanrelid, es->rtable);
+
+	/* Assume it's on a real relation */
+	Assert(rte->relname);
+
+	context = deparse_context_for(rte->relname, rte->relid);
+
+	/* Fix qual --- indexqual requires different processing */
+	if (is_or_qual)
+		node = make_ors_ands_explicit(qual);
+	else
+		node = (Node *) make_ands_explicit(qual);
+
+	/* Deparse the expression */
+	exprstr = deparse_expression(node, context, false);
+
+	/* And add to str */
+	for (i = 0; i < indent; i++)
+		appendStringInfo(str, "  ");
+	appendStringInfo(str, "  %s: %s\n", qlabel, exprstr);
+}
+
+/*
+ * Show a qualifier expression for an upper-level plan node
+ */
+static void
+show_upper_qual(List *qual, const char *qlabel,
+				const char *outer_name, int outer_varno, Plan *outer_plan,
+				const char *inner_name, int inner_varno, Plan *inner_plan,
+				StringInfo str, int indent, ExplainState *es)
+{
+	List	   *context;
+	Node	   *outercontext;
+	Node	   *innercontext;
+	Node	   *node;
+	char	   *exprstr;
+	int			i;
+
+	/* No work if empty qual */
+	if (qual == NIL)
+		return;
+
+	/* Generate deparse context */
+	if (outer_plan)
+		outercontext = deparse_context_for_subplan(outer_name,
+												   outer_plan->targetlist,
+												   es->rtable);
+	else
+		outercontext = NULL;
+	if (inner_plan)
+		innercontext = deparse_context_for_subplan(inner_name,
+												   inner_plan->targetlist,
+												   es->rtable);
+	else
+		innercontext = NULL;
+	context = deparse_context_for_plan(outer_varno, outercontext,
+									   inner_varno, innercontext);
+
+	/* Deparse the expression */
+	node = (Node *) make_ands_explicit(qual);
+	exprstr = deparse_expression(node, context, (inner_plan != NULL));
+
+	/* And add to str */
+	for (i = 0; i < indent; i++)
+		appendStringInfo(str, "  ");
+	appendStringInfo(str, "  %s: %s\n", qlabel, exprstr);
+}
+
+/*
+ * Indexscan qual lists have an implicit OR-of-ANDs structure.  Make it
+ * explicit so deparsing works properly.
+ */
+static Node *
+make_ors_ands_explicit(List *orclauses)
+{
+	if (orclauses == NIL)
+		return NULL;			/* probably can't happen */
+	else if (lnext(orclauses) == NIL)
+		return (Node *) make_ands_explicit(lfirst(orclauses));
+	else
+	{
+		List   *args = NIL;
+		List   *orptr;
+
+		foreach(orptr, orclauses)
+		{
+			args = lappend(args, make_ands_explicit(lfirst(orptr)));
+		}
+
+		return (Node *) make_orclause(args);
+	}
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ae9ac430456dc1c4ee3af3ac55d47ebb4411eb9c..97eeb35ea38303ff68ed809bde2b9399e6fd3605 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.168 2002/03/08 04:37:16 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.169 2002/03/12 00:51:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -323,6 +323,7 @@ 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,
@@ -970,8 +971,7 @@ _copyJoinExpr(JoinExpr *from)
 	Node_Copy(from, newnode, using);
 	Node_Copy(from, newnode, quals);
 	Node_Copy(from, newnode, alias);
-	Node_Copy(from, newnode, colnames);
-	Node_Copy(from, newnode, colvars);
+	newnode->rtindex = from->rtindex;
 
 	return newnode;
 }
@@ -1081,16 +1081,13 @@ _copyArrayRef(ArrayRef *from)
  *		_copyRelOptInfo
  * ----------------
  */
-/*
- *	when you change this, also make sure to fix up xfunc_copyRelOptInfo in
- *	planner/path/xfunc.c accordingly!!!
- *		-- JMH, 8/2/93
- */
 static RelOptInfo *
 _copyRelOptInfo(RelOptInfo *from)
 {
 	RelOptInfo *newnode = makeNode(RelOptInfo);
 
+	newnode->reloptkind = from->reloptkind;
+
 	newnode->relids = listCopy(from->relids);
 
 	newnode->rows = from->rows;
@@ -1109,6 +1106,9 @@ _copyRelOptInfo(RelOptInfo *from)
 	newnode->tuples = from->tuples;
 	Node_Copy(from, newnode, subplan);
 
+	newnode->joinrti = from->joinrti;
+	newnode->joinrteids = listCopy(from->joinrteids);
+
 	Node_Copy(from, newnode, baserestrictinfo);
 	newnode->baserestrictcost = from->baserestrictcost;
 	newnode->outerjoinset = listCopy(from->outerjoinset);
@@ -1487,10 +1487,16 @@ _copyRangeTblEntry(RangeTblEntry *from)
 {
 	RangeTblEntry *newnode = makeNode(RangeTblEntry);
 
+	newnode->rtekind = from->rtekind;
 	if (from->relname)
 		newnode->relname = pstrdup(from->relname);
 	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, 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 b9269c5a834a92949898d63c079350537b1b3f28..760554dbb63f40d119a2ee1dd96694bc44e8b78b 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.116 2002/03/08 04:37:16 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.117 2002/03/12 00:51:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -335,9 +335,7 @@ _equalJoinExpr(JoinExpr *a, JoinExpr *b)
 		return false;
 	if (!equal(a->alias, b->alias))
 		return false;
-	if (!equal(a->colnames, b->colnames))
-		return false;
-	if (!equal(a->colvars, b->colvars))
+	if (a->rtindex != b->rtindex)
 		return false;
 
 	return true;
@@ -1639,12 +1637,24 @@ _equalTargetEntry(TargetEntry *a, TargetEntry *b)
 static bool
 _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
 {
+	if (a->rtekind != b->rtekind)
+		return false;
 	if (!equalstr(a->relname, b->relname))
 		return false;
 	if (a->relid != b->relid)
 		return false;
 	if (!equal(a->subquery, b->subquery))
 		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))
+		return false;
 	if (!equal(a->alias, b->alias))
 		return false;
 	if (!equal(a->eref, b->eref))
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d3b737ed21306a2ee3e57503582c6901988b9a87..3699fc38ffb6e9e0b57d0768c5d2db88eca5f33a 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.148 2002/03/06 06:09:49 momjian Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.149 2002/03/12 00:51:39 tgl Exp $
  *
  * NOTES
  *	  Every (plan) node in POSTGRES has an associated "out" routine which
@@ -410,6 +410,8 @@ _outJoin(StringInfo str, Join *node)
 	appendStringInfo(str, " :jointype %d :joinqual ",
 					 (int) node->jointype);
 	_outNode(str, node->joinqual);
+	appendStringInfo(str, " :joinrti %d ",
+					 node->joinrti);
 }
 
 /*
@@ -423,6 +425,8 @@ _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);
 }
 
 /*
@@ -436,6 +440,8 @@ _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);
@@ -452,6 +458,8 @@ _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);
@@ -939,10 +947,7 @@ _outJoinExpr(StringInfo str, JoinExpr *node)
 	_outNode(str, node->quals);
 	appendStringInfo(str, " :alias ");
 	_outNode(str, node->alias);
-	appendStringInfo(str, " :colnames ");
-	_outNode(str, node->colnames);
-	appendStringInfo(str, " :colvars ");
-	_outNode(str, node->colvars);
+	appendStringInfo(str, " :rtindex %d ", node->rtindex);
 }
 
 /*
@@ -961,12 +966,21 @@ _outTargetEntry(StringInfo str, TargetEntry *node)
 static void
 _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
 {
-	appendStringInfo(str, " RTE :relname ");
+	appendStringInfo(str, " RTE :rtekind %d :relname ",
+					 (int) node->rtekind);
 	_outToken(str, node->relname);
-	appendStringInfo(str, " :relid %u ",
+	appendStringInfo(str, " :relid %u :subquery ",
 					 node->relid);
-	appendStringInfo(str, " :subquery ");
 	_outNode(str, node->subquery);
+	appendStringInfo(str, " :jointype %d :joincoltypes ",
+					 (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);
 	appendStringInfo(str, " :alias ");
 	_outNode(str, node->alias);
 	appendStringInfo(str, " :eref ");
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 7cdd0c73c39aa751aa0bfd670bac3b81d9203c73..c0794123b3e54552172481f644437390424df788 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.115 2002/03/01 06:01:18 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.116 2002/03/12 00:51:39 tgl Exp $
  *
  * NOTES
  *	  Most of the read functions for plan nodes are tested. (In fact, they
@@ -421,6 +421,10 @@ _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);
 }
 
 
@@ -1343,7 +1347,7 @@ _readJoinExpr(void)
 	local_node->jointype = (JoinType) atoi(token);
 
 	token = pg_strtok(&length); /* eat :isNatural */
-	token = pg_strtok(&length); /* get :isNatural */
+	token = pg_strtok(&length); /* get isNatural */
 	local_node->isNatural = strtobool(token);
 
 	token = pg_strtok(&length); /* eat :larg */
@@ -1361,11 +1365,9 @@ _readJoinExpr(void)
 	token = pg_strtok(&length); /* eat :alias */
 	local_node->alias = nodeRead(true); /* now read it */
 
-	token = pg_strtok(&length); /* eat :colnames */
-	local_node->colnames = nodeRead(true);		/* now read it */
-
-	token = pg_strtok(&length); /* eat :colvars */
-	local_node->colvars = nodeRead(true);		/* now read it */
+	token = pg_strtok(&length); /* eat :rtindex */
+	token = pg_strtok(&length); /* get rtindex */
+	local_node->rtindex = atoi(token);
 
 	return local_node;
 }
@@ -1424,6 +1426,10 @@ _readRangeTblEntry(void)
 
 	local_node = makeNode(RangeTblEntry);
 
+	token = pg_strtok(&length); /* eat :rtekind */
+	token = pg_strtok(&length); /* get :rtekind */
+	local_node->rtekind = (RTEKind) atoi(token);
+
 	token = pg_strtok(&length); /* eat :relname */
 	token = pg_strtok(&length); /* get :relname */
 	local_node->relname = nullable_string(token, length);
@@ -1435,6 +1441,22 @@ _readRangeTblEntry(void)
 	token = pg_strtok(&length); /* eat :subquery */
 	local_node->subquery = nodeRead(true);		/* now read it */
 
+	token = pg_strtok(&length); /* eat :jointype */
+	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 :alias */
 	local_node->alias = nodeRead(true); /* now read it */
 
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index e215a6cd3669e6e400aa23eef3049ab170931562..2bc0bb8a137b5daab11e50b048ca2fe8842fbac4 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -42,7 +42,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.82 2002/03/01 20:50:20 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/costsize.c,v 1.83 2002/03/12 00:51:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -597,7 +597,7 @@ cost_mergejoin(Path *path, Query *root,
 
 	leftvar = get_leftop(firstclause->clause);
 	Assert(IsA(leftvar, Var));
-	if (intMember(leftvar->varno, outer_path->parent->relids))
+	if (VARISRELMEMBER(leftvar->varno, outer_path->parent))
 	{
 		/* left side of clause is outer */
 		outerscansel = firstclause->left_mergescansel;
@@ -748,7 +748,7 @@ cost_hashjoin(Path *path, Query *root,
 	 * a large query, we cache the bucketsize estimate in the RestrictInfo
 	 * node to avoid repeated lookups of statistics.
 	 */
-	if (intMember(right->varno, inner_path->parent->relids))
+	if (VARISRELMEMBER(right->varno, inner_path->parent))
 	{
 		/* righthand side is inner */
 		innerbucketsize = restrictinfo->right_bucketsize;
@@ -761,7 +761,7 @@ cost_hashjoin(Path *path, Query *root,
 	}
 	else
 	{
-		Assert(intMember(left->varno, inner_path->parent->relids));
+		Assert(VARISRELMEMBER(left->varno, inner_path->parent));
 		/* lefthand side is inner */
 		innerbucketsize = restrictinfo->left_bucketsize;
 		if (innerbucketsize < 0)
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 4d60569e7ef6fc01ab9e5fb283c56dc5df7c35a8..11bc8a9f7d31bebfa56cb2dced84ccaea88c9799 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.67 2001/11/11 19:18:54 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.68 2002/03/12 00:51:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,6 +24,7 @@
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
 
+
 static void sort_inner_and_outer(Query *root, RelOptInfo *joinrel,
 					 RelOptInfo *outerrel, RelOptInfo *innerrel,
 					 List *restrictlist, List *mergeclause_list,
@@ -720,8 +721,6 @@ hash_inner_and_outer(Query *root,
 					 List *restrictlist,
 					 JoinType jointype)
 {
-	Relids		outerrelids = outerrel->relids;
-	Relids		innerrelids = innerrel->relids;
 	bool		isouterjoin;
 	List	   *i;
 
@@ -773,13 +772,13 @@ hash_inner_and_outer(Query *root,
 		/*
 		 * Check if clause is usable with these input rels.
 		 */
-		if (intMember(left->varno, outerrelids) &&
-			intMember(right->varno, innerrelids))
+		if (VARISRELMEMBER(left->varno, outerrel) &&
+			VARISRELMEMBER(right->varno, innerrel))
 		{
 			/* righthand side is inner */
 		}
-		else if (intMember(left->varno, innerrelids) &&
-				 intMember(right->varno, outerrelids))
+		else if (VARISRELMEMBER(left->varno, innerrel) &&
+				 VARISRELMEMBER(right->varno, outerrel))
 		{
 			/* lefthand side is inner */
 		}
@@ -901,8 +900,6 @@ select_mergejoin_clauses(RelOptInfo *joinrel,
 						 JoinType jointype)
 {
 	List	   *result_list = NIL;
-	Relids		outerrelids = outerrel->relids;
-	Relids		innerrelids = innerrel->relids;
 	bool		isouterjoin = IS_OUTER_JOIN(jointype);
 	List	   *i;
 
@@ -952,10 +949,10 @@ select_mergejoin_clauses(RelOptInfo *joinrel,
 		left = get_leftop(clause);
 		right = get_rightop(clause);
 
-		if ((intMember(left->varno, outerrelids) &&
-			 intMember(right->varno, innerrelids)) ||
-			(intMember(left->varno, innerrelids) &&
-			 intMember(right->varno, outerrelids)))
+		if ((VARISRELMEMBER(left->varno, outerrel) &&
+			 VARISRELMEMBER(right->varno, innerrel)) ||
+			(VARISRELMEMBER(left->varno, innerrel) &&
+			 VARISRELMEMBER(right->varno, outerrel)))
 			result_list = lcons(restrictinfo, result_list);
 	}
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 745a1eb0b5b5e50dda2378932db76fb47c0107d9..d2d511e75e7ca25bdf554043715b354bf4f06e88 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.55 2001/10/25 05:49:32 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.56 2002/03/12 00:51:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -340,7 +340,7 @@ make_jointree_rel(Query *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		return build_base_rel(root, varno);
+		return find_base_rel(root, varno);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index f91e25cdb4fc986056efa91f0e86c314d90e8f05..60e05ca340f3e79adcaa6970484e7f3bcf307be6 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.36 2001/11/11 20:33:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.37 2002/03/12 00:51:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -854,7 +854,8 @@ make_pathkeys_for_mergeclauses(Query *root,
 		cache_mergeclause_pathkeys(root, restrictinfo);
 
 		key = (Node *) get_leftop(restrictinfo->clause);
-		if (IsA(key, Var) &&intMember(((Var *) key)->varno, rel->relids))
+		if (IsA(key, Var) &&
+			VARISRELMEMBER(((Var *) key)->varno, rel))
 		{
 			/* Rel is left side of mergeclause */
 			pathkey = restrictinfo->left_pathkey;
@@ -862,7 +863,8 @@ make_pathkeys_for_mergeclauses(Query *root,
 		else
 		{
 			key = (Node *) get_rightop(restrictinfo->clause);
-			if (IsA(key, Var) &&intMember(((Var *) key)->varno, rel->relids))
+			if (IsA(key, Var) &&
+				VARISRELMEMBER(((Var *) key)->varno, rel))
 			{
 				/* Rel is right side of mergeclause */
 				pathkey = restrictinfo->right_pathkey;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 88c5499b3024d0b274c4c61fd301e645dcd5c394..79d90bf5a3ccaa6d0bd0e21240774fc34345638c 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.111 2001/10/28 06:25:44 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.112 2002/03/12 00:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -43,7 +43,8 @@ static TidScan *create_tidscan_plan(TidPath *best_path, List *tlist,
 					List *scan_clauses);
 static SubqueryScan *create_subqueryscan_plan(Path *best_path,
 						 List *tlist, List *scan_clauses);
-static NestLoop *create_nestloop_plan(NestPath *best_path, List *tlist,
+static NestLoop *create_nestloop_plan(Query *root,
+					 NestPath *best_path, List *tlist,
 					 List *joinclauses, List *otherclauses,
 					 Plan *outer_plan, List *outer_tlist,
 					 Plan *inner_plan, List *inner_tlist);
@@ -52,7 +53,8 @@ static MergeJoin *create_mergejoin_plan(Query *root,
 					  List *joinclauses, List *otherclauses,
 					  Plan *outer_plan, List *outer_tlist,
 					  Plan *inner_plan, List *inner_tlist);
-static HashJoin *create_hashjoin_plan(HashPath *best_path, List *tlist,
+static HashJoin *create_hashjoin_plan(Query *root,
+					 HashPath *best_path, List *tlist,
 					 List *joinclauses, List *otherclauses,
 					 Plan *outer_plan, List *outer_tlist,
 					 Plan *inner_plan, List *inner_tlist);
@@ -78,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);
+			  JoinType jointype, Index joinrti);
 static HashJoin *make_hashjoin(List *tlist,
 			  List *joinclauses, List *otherclauses,
 			  List *hashclauses,
 			  Plan *lefttree, Plan *righttree,
-			  JoinType jointype);
+			  JoinType jointype, Index joinrti);
 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);
+			   JoinType jointype, Index joinrti);
 
 /*
  * create_plan
@@ -259,7 +261,8 @@ create_join_plan(Query *root, JoinPath *best_path)
 												  inner_tlist);
 			break;
 		case T_HashJoin:
-			plan = (Join *) create_hashjoin_plan((HashPath *) best_path,
+			plan = (Join *) create_hashjoin_plan(root,
+												 (HashPath *) best_path,
 												 join_tlist,
 												 joinclauses,
 												 otherclauses,
@@ -269,7 +272,8 @@ create_join_plan(Query *root, JoinPath *best_path)
 												 inner_tlist);
 			break;
 		case T_NestLoop:
-			plan = (Join *) create_nestloop_plan((NestPath *) best_path,
+			plan = (Join *) create_nestloop_plan(root,
+												 (NestPath *) best_path,
 												 join_tlist,
 												 joinclauses,
 												 otherclauses,
@@ -576,7 +580,8 @@ create_subqueryscan_plan(Path *best_path, List *tlist, List *scan_clauses)
  *****************************************************************************/
 
 static NestLoop *
-create_nestloop_plan(NestPath *best_path,
+create_nestloop_plan(Query *root,
+					 NestPath *best_path,
 					 List *tlist,
 					 List *joinclauses,
 					 List *otherclauses,
@@ -586,6 +591,7 @@ create_nestloop_plan(NestPath *best_path,
 					 List *inner_tlist)
 {
 	NestLoop   *join_plan;
+	Index		joinrti = best_path->path.parent->joinrti;
 
 	if (IsA(inner_plan, IndexScan))
 	{
@@ -630,19 +636,25 @@ create_nestloop_plan(NestPath *best_path,
 
 			/* only refs to outer vars get changed in the inner indexqual */
 			innerscan->indxqualorig = join_references(indxqualorig,
+													  root,
 													  outer_tlist,
 													  NIL,
-													  innerrel);
+													  innerrel,
+													  joinrti);
 			innerscan->indxqual = join_references(innerscan->indxqual,
+												  root,
 												  outer_tlist,
 												  NIL,
-												  innerrel);
+												  innerrel,
+												  joinrti);
 			/* 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);
+												   innerrel,
+												   joinrti);
 		}
 	}
 	else if (IsA(inner_plan, TidScan))
@@ -650,9 +662,11 @@ create_nestloop_plan(NestPath *best_path,
 		TidScan    *innerscan = (TidScan *) inner_plan;
 
 		innerscan->tideval = join_references(innerscan->tideval,
+											 root,
 											 outer_tlist,
 											 inner_tlist,
-											 innerscan->scan.scanrelid);
+											 innerscan->scan.scanrelid,
+											 joinrti);
 	}
 	else if (IsA_Join(inner_plan))
 	{
@@ -671,20 +685,25 @@ create_nestloop_plan(NestPath *best_path,
 	 * Set quals to contain INNER/OUTER var references.
 	 */
 	joinclauses = join_references(joinclauses,
+								  root,
 								  outer_tlist,
 								  inner_tlist,
-								  (Index) 0);
+								  (Index) 0,
+								  joinrti);
 	otherclauses = join_references(otherclauses,
+								   root,
 								   outer_tlist,
 								   inner_tlist,
-								   (Index) 0);
+								   (Index) 0,
+								   joinrti);
 
 	join_plan = make_nestloop(tlist,
 							  joinclauses,
 							  otherclauses,
 							  outer_plan,
 							  inner_plan,
-							  best_path->jointype);
+							  best_path->jointype,
+							  joinrti);
 
 	copy_path_costsize(&join_plan->join.plan, &best_path->path);
 
@@ -704,6 +723,7 @@ 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);
 
@@ -713,26 +733,32 @@ create_mergejoin_plan(Query *root,
 	 * clauses to contain INNER/OUTER var references.
 	 */
 	joinclauses = join_references(set_difference(joinclauses, mergeclauses),
+								  root,
 								  outer_tlist,
 								  inner_tlist,
-								  (Index) 0);
+								  (Index) 0,
+								  joinrti);
 
 	/*
 	 * Fix the additional qpquals too.
 	 */
 	otherclauses = join_references(otherclauses,
+								   root,
 								   outer_tlist,
 								   inner_tlist,
-								   (Index) 0);
+								   (Index) 0,
+								   joinrti);
 
 	/*
 	 * Now set the references in the mergeclauses and rearrange them so
 	 * that the outer variable is always on the left.
 	 */
 	mergeclauses = switch_outer(join_references(mergeclauses,
+												root,
 												outer_tlist,
 												inner_tlist,
-												(Index) 0));
+												(Index) 0,
+												joinrti));
 
 	/*
 	 * Create explicit sort nodes for the outer and inner join paths if
@@ -798,7 +824,8 @@ create_mergejoin_plan(Query *root,
 							   mergeclauses,
 							   outer_plan,
 							   inner_plan,
-							   best_path->jpath.jointype);
+							   best_path->jpath.jointype,
+							   joinrti);
 
 	copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path);
 
@@ -806,7 +833,8 @@ create_mergejoin_plan(Query *root,
 }
 
 static HashJoin *
-create_hashjoin_plan(HashPath *best_path,
+create_hashjoin_plan(Query *root,
+					 HashPath *best_path,
 					 List *tlist,
 					 List *joinclauses,
 					 List *otherclauses,
@@ -819,6 +847,7 @@ create_hashjoin_plan(HashPath *best_path,
 	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
@@ -834,26 +863,32 @@ create_hashjoin_plan(HashPath *best_path,
 	 * clauses to contain INNER/OUTER var references.
 	 */
 	joinclauses = join_references(set_difference(joinclauses, hashclauses),
+								  root,
 								  outer_tlist,
 								  inner_tlist,
-								  (Index) 0);
+								  (Index) 0,
+								  joinrti);
 
 	/*
 	 * Fix the additional qpquals too.
 	 */
 	otherclauses = join_references(otherclauses,
+								   root,
 								   outer_tlist,
 								   inner_tlist,
-								   (Index) 0);
+								   (Index) 0,
+								   joinrti);
 
 	/*
 	 * Now set the references in the hashclauses and rearrange them so
 	 * that the outer variable is always on the left.
 	 */
 	hashclauses = switch_outer(join_references(hashclauses,
+											   root,
 											   outer_tlist,
 											   inner_tlist,
-											   (Index) 0));
+											   (Index) 0,
+											   joinrti));
 
 	/* Now the righthand op of the sole hashclause is the inner hash key. */
 	innerhashkey = (Node *) get_rightop(lfirst(hashclauses));
@@ -868,7 +903,8 @@ create_hashjoin_plan(HashPath *best_path,
 							  hashclauses,
 							  outer_plan,
 							  (Plan *) hash_plan,
-							  best_path->jpath.jointype);
+							  best_path->jpath.jointype,
+							  joinrti);
 
 	copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path);
 
@@ -1327,7 +1363,8 @@ make_nestloop(List *tlist,
 			  List *otherclauses,
 			  Plan *lefttree,
 			  Plan *righttree,
-			  JoinType jointype)
+			  JoinType jointype,
+			  Index joinrti)
 {
 	NestLoop   *node = makeNode(NestLoop);
 	Plan	   *plan = &node->join.plan;
@@ -1340,6 +1377,7 @@ make_nestloop(List *tlist,
 	plan->righttree = righttree;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
+	node->join.joinrti = joinrti;
 
 	return node;
 }
@@ -1351,7 +1389,8 @@ make_hashjoin(List *tlist,
 			  List *hashclauses,
 			  Plan *lefttree,
 			  Plan *righttree,
-			  JoinType jointype)
+			  JoinType jointype,
+			  Index joinrti)
 {
 	HashJoin   *node = makeNode(HashJoin);
 	Plan	   *plan = &node->join.plan;
@@ -1365,6 +1404,7 @@ make_hashjoin(List *tlist,
 	node->hashclauses = hashclauses;
 	node->join.jointype = jointype;
 	node->join.joinqual = joinclauses;
+	node->join.joinrti = joinrti;
 
 	return node;
 }
@@ -1399,7 +1439,8 @@ make_mergejoin(List *tlist,
 			   List *mergeclauses,
 			   Plan *lefttree,
 			   Plan *righttree,
-			   JoinType jointype)
+			   JoinType jointype,
+			   Index joinrti)
 {
 	MergeJoin  *node = makeNode(MergeJoin);
 	Plan	   *plan = &node->join.plan;
@@ -1413,6 +1454,7 @@ 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 76677b4059caf5b1cb13723c7e5b21e645ec52b4..2c9acc73b7ff4a30d5a3c3fb1d1baba6dab94d7c 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.66 2002/03/01 06:01:19 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.67 2002/03/12 00:51:45 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,67 +53,29 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
 
 /*****************************************************************************
  *
- *	 TARGET LISTS
+ *	 JOIN TREES
  *
  *****************************************************************************/
 
 /*
- * build_base_rel_tlists
- *	  Creates rel nodes for every relation mentioned in the target list
- *	  'tlist' (if a node hasn't already been created) and adds them to
- *	  root->base_rel_list.	Creates targetlist entries for each var seen
- *	  in 'tlist' and adds them to the tlist of the appropriate rel node.
- */
-void
-build_base_rel_tlists(Query *root, List *tlist)
-{
-	List	   *tlist_vars = pull_var_clause((Node *) tlist, false);
-
-	add_vars_to_targetlist(root, tlist_vars);
-	freeList(tlist_vars);
-}
-
-/*
- * add_vars_to_targetlist
- *	  For each variable appearing in the list, add it to the relation's
- *	  targetlist if not already present.  Corresponding base rel nodes
- *	  will be created if not already present.
- */
-static void
-add_vars_to_targetlist(Query *root, List *vars)
-{
-	List	   *temp;
-
-	foreach(temp, vars)
-	{
-		Var		   *var = (Var *) lfirst(temp);
-		RelOptInfo *rel = build_base_rel(root, var->varno);
-
-		add_var_to_tlist(rel, var);
-	}
-}
-
-/*----------
- * add_missing_rels_to_query
+ * add_base_rels_to_query
+ *
+ *	  Scan the query's jointree and create baserel RelOptInfos for all
+ *	  the base relations (ie, table and subquery RTEs) appearing in the
+ *	  jointree.  Also, create otherrel RelOptInfos for join RTEs.
  *
- *	  If we have a relation listed in the join tree that does not appear
- *	  in the target list nor qualifications, we must add it to the base
- *	  relation list so that it can be processed.  For instance,
- *			select count(*) from foo;
- *	  would fail to scan foo if this routine were not called.  More subtly,
- *			select f.x from foo f, foo f2
- *	  is a join of f and f2.  Note that if we have
- *			select foo.x from foo f
- *	  this also gets turned into a join (between foo as foo and foo as f).
+ * The return value is a list of all the baserel indexes (but not join RTE
+ * indexes) included in the scanned jointree.  This is actually just an
+ * internal convenience for marking join otherrels properly; no outside
+ * caller uses the result.
  *
- *	  Returns a list of all the base relations (RelOptInfo nodes) that appear
- *	  in the join tree.  This list can be used for cross-checking in the
- *	  reverse direction, ie, that we have a join tree entry for every
- *	  relation used in the query.
- *----------
+ * At the end of this process, there should be one baserel RelOptInfo for
+ * every non-join RTE that is used in the query.  Therefore, this routine
+ * is the only place that should call build_base_rel.  But build_other_rel
+ * will be used again later to build rels for inheritance children.
  */
 List *
-add_missing_rels_to_query(Query *root, Node *jtnode)
+add_base_rels_to_query(Query *root, Node *jtnode)
 {
 	List	   *result = NIL;
 
@@ -123,10 +85,8 @@ add_missing_rels_to_query(Query *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		/* This call to build_base_rel does the primary work... */
-		RelOptInfo *rel = build_base_rel(root, varno);
-
-		result = makeList1(rel);
+		build_base_rel(root, varno);
+		result = makeListi1(varno);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -136,24 +96,98 @@ add_missing_rels_to_query(Query *root, Node *jtnode)
 		foreach(l, f->fromlist)
 		{
 			result = nconc(result,
-						   add_missing_rels_to_query(root, lfirst(l)));
+						   add_base_rels_to_query(root, lfirst(l)));
 		}
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
+		RelOptInfo *jrel;
 
-		result = add_missing_rels_to_query(root, j->larg);
+		result = add_base_rels_to_query(root, j->larg);
 		result = nconc(result,
-					   add_missing_rels_to_query(root, j->rarg));
+					   add_base_rels_to_query(root, j->rarg));
+		/* the join's own rtindex is NOT added to result */
+		jrel = build_other_rel(root, j->rtindex);
+		/*
+		 * Mark the join's otherrel with outerjoinset = list of baserel ids
+		 * included in the join.  Note we must copy here because result list
+		 * is destructively modified by nconcs at higher levels.
+		 */
+		jrel->outerjoinset = listCopy(result);
+		/*
+		 * Safety check: join RTEs should not be SELECT FOR UPDATE targets
+		 */
+		if (intMember(j->rtindex, root->rowMarks))
+			elog(ERROR, "SELECT FOR UPDATE cannot be applied to a join");
 	}
 	else
-		elog(ERROR, "add_missing_rels_to_query: unexpected node type %d",
+		elog(ERROR, "add_base_rels_to_query: unexpected node type %d",
 			 nodeTag(jtnode));
 	return result;
 }
 
 
+/*****************************************************************************
+ *
+ *	 TARGET LISTS
+ *
+ *****************************************************************************/
+
+/*
+ * build_base_rel_tlists
+ *	  Creates targetlist entries for each var seen in 'tlist' and adds
+ *	  them to the tlist of the appropriate rel node.
+ */
+void
+build_base_rel_tlists(Query *root, List *tlist)
+{
+	List	   *tlist_vars = pull_var_clause((Node *) tlist, false);
+
+	add_vars_to_targetlist(root, tlist_vars);
+	freeList(tlist_vars);
+}
+
+/*
+ * add_vars_to_targetlist
+ *	  For each variable appearing in the list, add it to the owning
+ *	  relation's targetlist if not already present.
+ *
+ * Note that join alias variables will be attached to the otherrel for
+ * the join RTE.  They will later be transferred to the tlist of
+ * the corresponding joinrel.  We will also cause entries to be made
+ * for the Vars that the alias will eventually depend on.
+ */
+static void
+add_vars_to_targetlist(Query *root, List *vars)
+{
+	List	   *temp;
+
+	foreach(temp, vars)
+	{
+		Var		   *var = (Var *) lfirst(temp);
+		RelOptInfo *rel = find_base_rel(root, var->varno);
+
+		add_var_to_tlist(rel, var);
+
+		if (rel->reloptkind == RELOPT_OTHER_JOIN_REL)
+		{
+			/* Var is an alias */
+			Var	   *leftsubvar,
+				   *rightsubvar;
+
+			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);
+		}
+	}
+}
+
+
 /*****************************************************************************
  *
  *	  QUALIFICATIONS
@@ -165,10 +199,9 @@ add_missing_rels_to_query(Query *root, Node *jtnode)
  * distribute_quals_to_rels
  *	  Recursively scan the query's join tree for WHERE and JOIN/ON qual
  *	  clauses, and add these to the appropriate RestrictInfo and JoinInfo
- *	  lists belonging to base RelOptInfos.	New base rel entries are created
- *	  as needed.  Also, base RelOptInfos are marked with outerjoinset
- *	  information, to aid in proper positioning of qual clauses that appear
- *	  above outer joins.
+ *	  lists belonging to base RelOptInfos.  Also, base RelOptInfos are marked
+ *	  with outerjoinset information, to aid in proper positioning of qual
+ *	  clauses that appear above outer joins.
  *
  * NOTE: when dealing with inner joins, it is appropriate to let a qual clause
  * be evaluated at the lowest level where all the variables it mentions are
@@ -181,7 +214,7 @@ add_missing_rels_to_query(Query *root, Node *jtnode)
  * a rel, thereby forcing them up the join tree to the right level.
  *
  * To ease the calculation of these values, distribute_quals_to_rels() returns
- * the list of Relids involved in its own level of join.  This is just an
+ * the list of base Relids involved in its own level of join.  This is just an
  * internal convenience; no outside callers pay attention to the result.
  */
 Relids
@@ -302,7 +335,7 @@ mark_baserels_for_outer_join(Query *root, Relids rels, Relids outerrels)
 	foreach(relid, rels)
 	{
 		int			relno = lfirsti(relid);
-		RelOptInfo *rel = build_base_rel(root, relno);
+		RelOptInfo *rel = find_base_rel(root, relno);
 
 		/*
 		 * Since we do this bottom-up, any outer-rels previously marked
@@ -382,11 +415,41 @@ distribute_qual_to_rels(Query *root, Node *clause,
 	clause_get_relids_vars(clause, &relids, &vars);
 
 	/*
-	 * Cross-check: clause should contain no relids not within its scope.
-	 * Otherwise the parser messed up.
+	 * The clause might contain some join alias vars; if so, we want to
+	 * remove the join otherrelids from relids and add the referent joins'
+	 * scope lists instead (thus ensuring that the clause can be evaluated
+	 * no lower than that join node).  We rely here on the marking done
+	 * earlier by add_base_rels_to_query.
+	 *
+	 * We can combine this step with a cross-check that the clause contains
+	 * no relids not within its scope.  If the first crosscheck succeeds,
+	 * the clause contains no aliases and we needn't look more closely.
 	 */
 	if (!is_subseti(relids, qualscope))
-		elog(ERROR, "JOIN qualification may not refer to other relations");
+	{
+		Relids		newrelids = NIL;
+		List	   *relid;
+
+		foreach(relid, relids)
+		{
+			RelOptInfo *rel = find_other_rel(root, lfirsti(relid));
+
+			if (rel && rel->outerjoinset)
+			{
+				/* this relid is for a join RTE */
+				newrelids = set_unioni(newrelids, rel->outerjoinset);
+			}
+			else
+			{
+				/* this relid is for a true baserel */
+				newrelids = lappendi(newrelids, lfirsti(relid));
+			}
+		}
+		relids = newrelids;
+		/* Now repeat the crosscheck */
+		if (!is_subseti(relids, qualscope))
+			elog(ERROR, "JOIN qualification may not refer to other relations");
+	}
 
 	/*
 	 * If the clause is variable-free, we force it to be evaluated at its
@@ -439,7 +502,7 @@ distribute_qual_to_rels(Query *root, Node *clause,
 		can_be_equijoin = true;
 		foreach(relid, relids)
 		{
-			RelOptInfo *rel = build_base_rel(root, lfirsti(relid));
+			RelOptInfo *rel = find_base_rel(root, lfirsti(relid));
 
 			if (rel->outerjoinset &&
 				!is_subseti(rel->outerjoinset, relids))
@@ -475,7 +538,7 @@ distribute_qual_to_rels(Query *root, Node *clause,
 		 * There is only one relation participating in 'clause', so
 		 * 'clause' is a restriction clause for that relation.
 		 */
-		RelOptInfo *rel = build_base_rel(root, lfirsti(relids));
+		RelOptInfo *rel = find_base_rel(root, lfirsti(relids));
 
 		/*
 		 * Check for a "mergejoinable" clause even though it's not a join
@@ -595,7 +658,7 @@ add_join_info_to_rels(Query *root, RestrictInfo *restrictinfo,
 		 * Find or make the joininfo node for this combination of rels,
 		 * and add the restrictinfo node to it.
 		 */
-		joininfo = find_joininfo_node(build_base_rel(root, cur_relid),
+		joininfo = find_joininfo_node(find_base_rel(root, cur_relid),
 									  unjoined_relids);
 		joininfo->jinfo_restrictinfo = lappend(joininfo->jinfo_restrictinfo,
 											   restrictinfo);
@@ -640,9 +703,6 @@ process_implied_equality(Query *root, Node *item1, Node *item2,
 	 * If both vars belong to same rel, we need to look at that rel's
 	 * baserestrictinfo list.  If different rels, each will have a
 	 * joininfo node for the other, and we can scan either list.
-	 *
-	 * All baserel entries should already exist at this point, so use
-	 * find_base_rel not build_base_rel.
 	 */
 	rel1 = find_base_rel(root, irel1);
 	if (irel1 == irel2)
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 57c9b963c734f57c39762024a4ca4912ec2f6543..ee1d18907121634221d977516191fb752095c5ef 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.67 2001/10/25 05:49:33 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.68 2002/03/12 00:51:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -176,48 +176,32 @@ subplanner(Query *root,
 		   List *flat_tlist,
 		   double tuple_fraction)
 {
-	List	   *joined_rels;
-	List	   *brel;
 	RelOptInfo *final_rel;
 	Plan	   *resultplan;
 	Path	   *cheapestpath;
 	Path	   *presortedpath;
 
-	/*
-	 * Examine the targetlist and qualifications, adding entries to
-	 * base_rel_list as relation references are found (e.g., in the
-	 * qualification, the targetlist, etc.).  Restrict and join clauses
-	 * are added to appropriate lists belonging to the mentioned
-	 * relations.  We also build lists of equijoined keys for pathkey
-	 * construction.
-	 */
+	/* init lists to empty */
 	root->base_rel_list = NIL;
 	root->other_rel_list = NIL;
 	root->join_rel_list = NIL;
 	root->equi_key_list = NIL;
 
-	build_base_rel_tlists(root, flat_tlist);
-
-	(void) distribute_quals_to_rels(root, (Node *) root->jointree);
-
 	/*
-	 * Make sure we have RelOptInfo nodes for all relations to be joined.
+	 * Construct RelOptInfo nodes for all base relations in query.
 	 */
-	joined_rels = add_missing_rels_to_query(root, (Node *) root->jointree);
+	(void) add_base_rels_to_query(root, (Node *) root->jointree);
 
 	/*
-	 * Check that the join tree includes all the base relations used in
-	 * the query --- otherwise, the parser or rewriter messed up.
+	 * Examine the targetlist and qualifications, adding entries to
+	 * baserel targetlists for all referenced Vars.  Restrict and join
+	 * clauses are added to appropriate lists belonging to the mentioned
+	 * relations.  We also build lists of equijoined keys for pathkey
+	 * construction.
 	 */
-	foreach(brel, root->base_rel_list)
-	{
-		RelOptInfo *baserel = (RelOptInfo *) lfirst(brel);
-		int			relid = lfirsti(baserel->relids);
+	build_base_rel_tlists(root, flat_tlist);
 
-		if (!ptrMember(baserel, joined_rels))
-			elog(ERROR, "Internal error: no jointree entry for rel %s (%d)",
-				 rt_fetch(relid, root->rtable)->eref->relname, relid);
-	}
+	(void) distribute_quals_to_rels(root, (Node *) root->jointree);
 
 	/*
 	 * Use the completed lists of equijoined keys to deduce any implied
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index eb92c3d3af95771bc110f9e6956ae7610352d642..1df2cd2940226741aab5967a758855b4e53dbe6a 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.114 2001/12/10 22:54:12 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.115 2002/03/12 00:51:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -99,7 +99,7 @@ planner(Query *parse)
 	result_plan->nParamExec = length(PlannerParamVar);
 
 	/* final cleanup of the plan */
-	set_plan_references(result_plan);
+	set_plan_references(parse, result_plan);
 
 	/* restore state for outer planner, if any */
 	PlannerQueryLevel = save_PlannerQueryLevel;
@@ -616,6 +616,9 @@ preprocess_jointree(Query *parse, Node *jtnode)
 static Node *
 preprocess_expression(Query *parse, Node *expr, int kind)
 {
+	bool		has_join_rtes;
+	List	   *rt;
+
 	/*
 	 * Simplify constant expressions.
 	 *
@@ -651,6 +654,29 @@ preprocess_expression(Query *parse, Node *expr, int kind)
 	if (PlannerQueryLevel > 1)
 		expr = SS_replace_correlation_vars(expr);
 
+	/*
+	 * If the query has any join RTEs, try to replace join alias variables
+	 * with base-relation variables, to allow quals to be pushed down.
+	 * We must do this after sublink processing, since it does not recurse
+	 * into sublinks.
+	 *
+	 * The flattening pass is expensive enough that it seems worthwhile to
+	 * scan the rangetable to see if we can avoid it.
+	 */
+	has_join_rtes = false;
+	foreach(rt, parse->rtable)
+	{
+		RangeTblEntry *rte = lfirst(rt);
+
+		if (rte->rtekind == RTE_JOIN)
+		{
+			has_join_rtes = true;
+			break;
+		}
+	}
+	if (has_join_rtes)
+		expr = flatten_join_alias_vars(expr, parse, 0);
+
 	return expr;
 }
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 167901acdf0ad3379a18c910d9797ccb12468314..2f48821ece6867d7275c2497d8647776c84b3431 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,25 +9,29 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.73 2001/11/05 17:46:26 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.74 2002/03/12 00:51:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
-#include <sys/types.h>
-
 #include "postgres.h"
 
+#include <sys/types.h>
+
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planmain.h"
 #include "optimizer/tlist.h"
+#include "optimizer/var.h"
+
 
 typedef struct
 {
+	Query	   *root;
 	List	   *outer_tlist;
 	List	   *inner_tlist;
 	Index		acceptable_rel;
+	Index		join_rti;
 } join_references_context;
 
 typedef struct
@@ -38,7 +42,7 @@ typedef struct
 } replace_vars_with_subplan_refs_context;
 
 static void fix_expr_references(Plan *plan, Node *node);
-static void set_join_references(Join *join);
+static void set_join_references(Query *root, Join *join);
 static void set_uppernode_references(Plan *plan, Index subvarno);
 static Node *join_references_mutator(Node *node,
 						join_references_context *context);
@@ -71,7 +75,7 @@ static bool fix_opids_walker(Node *node, void *context);
  * Returns nothing of interest, but modifies internal fields of nodes.
  */
 void
-set_plan_references(Plan *plan)
+set_plan_references(Query *root, Plan *plan)
 {
 	List	   *pl;
 
@@ -115,16 +119,16 @@ set_plan_references(Plan *plan)
 			fix_expr_references(plan, (Node *) plan->targetlist);
 			fix_expr_references(plan, (Node *) plan->qual);
 			/* Recurse into subplan too */
-			set_plan_references(((SubqueryScan *) plan)->subplan);
+			set_plan_references(root, ((SubqueryScan *) plan)->subplan);
 			break;
 		case T_NestLoop:
-			set_join_references((Join *) plan);
+			set_join_references(root, (Join *) plan);
 			fix_expr_references(plan, (Node *) plan->targetlist);
 			fix_expr_references(plan, (Node *) plan->qual);
 			fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
 			break;
 		case T_MergeJoin:
-			set_join_references((Join *) plan);
+			set_join_references(root, (Join *) plan);
 			fix_expr_references(plan, (Node *) plan->targetlist);
 			fix_expr_references(plan, (Node *) plan->qual);
 			fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
@@ -132,7 +136,7 @@ set_plan_references(Plan *plan)
 							(Node *) ((MergeJoin *) plan)->mergeclauses);
 			break;
 		case T_HashJoin:
-			set_join_references((Join *) plan);
+			set_join_references(root, (Join *) plan);
 			fix_expr_references(plan, (Node *) plan->targetlist);
 			fix_expr_references(plan, (Node *) plan->qual);
 			fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
@@ -186,7 +190,7 @@ set_plan_references(Plan *plan)
 			 * recurse into subplans.
 			 */
 			foreach(pl, ((Append *) plan)->appendplans)
-				set_plan_references((Plan *) lfirst(pl));
+				set_plan_references(root, (Plan *) lfirst(pl));
 			break;
 		default:
 			elog(ERROR, "set_plan_references: unknown plan type %d",
@@ -203,21 +207,21 @@ set_plan_references(Plan *plan)
 	 * plan's var nodes against the already-modified nodes of the
 	 * subplans.
 	 */
-	set_plan_references(plan->lefttree);
-	set_plan_references(plan->righttree);
+	set_plan_references(root, plan->lefttree);
+	set_plan_references(root, plan->righttree);
 	foreach(pl, plan->initPlan)
 	{
 		SubPlan    *sp = (SubPlan *) lfirst(pl);
 
 		Assert(IsA(sp, SubPlan));
-		set_plan_references(sp->plan);
+		set_plan_references(root, sp->plan);
 	}
 	foreach(pl, plan->subPlan)
 	{
 		SubPlan    *sp = (SubPlan *) lfirst(pl);
 
 		Assert(IsA(sp, SubPlan));
-		set_plan_references(sp->plan);
+		set_plan_references(root, sp->plan);
 	}
 }
 
@@ -256,7 +260,7 @@ fix_expr_references(Plan *plan, Node *node)
  * 'join' is a join plan node
  */
 static void
-set_join_references(Join *join)
+set_join_references(Query *root, Join *join)
 {
 	Plan	   *outer = join->plan.lefttree;
 	Plan	   *inner = join->plan.righttree;
@@ -264,9 +268,11 @@ set_join_references(Join *join)
 	List	   *inner_tlist = ((inner == NULL) ? NIL : inner->targetlist);
 
 	join->plan.targetlist = join_references(join->plan.targetlist,
+											root,
 											outer_tlist,
 											inner_tlist,
-											(Index) 0);
+											(Index) 0,
+											join->joinrti);
 }
 
 /*
@@ -343,7 +349,8 @@ set_uppernode_references(Plan *plan, Index subvarno)
  *	   Creates a new set of targetlist entries or join qual clauses by
  *	   changing the varno/varattno values of variables in the clauses
  *	   to reference target list values from the outer and inner join
- *	   relation target lists.
+ *	   relation target lists.  Also, any join alias variables in the
+ *	   clauses are expanded into references to their component variables.
  *
  * This is used in two different scenarios: a normal join clause, where
  * all the Vars in the clause *must* be replaced by OUTER or INNER references;
@@ -360,21 +367,27 @@ 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.
  */
 List *
 join_references(List *clauses,
+				Query *root,
 				List *outer_tlist,
 				List *inner_tlist,
-				Index acceptable_rel)
+				Index acceptable_rel,
+				Index join_rti)
 {
 	join_references_context context;
 
+	context.root = root;
 	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);
 }
 
@@ -387,12 +400,14 @@ join_references_mutator(Node *node,
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
-		Var		   *newvar = (Var *) copyObject(var);
 		Resdom	   *resdom;
 
+		/* First look for the var in the input tlists */
 		resdom = tlist_member((Node *) var, context->outer_tlist);
 		if (resdom)
 		{
+			Var	   *newvar = (Var *) copyObject(var);
+
 			newvar->varno = OUTER;
 			newvar->varattno = resdom->resno;
 			return (Node *) newvar;
@@ -400,18 +415,33 @@ join_references_mutator(Node *node,
 		resdom = tlist_member((Node *) var, context->inner_tlist);
 		if (resdom)
 		{
+			Var	   *newvar = (Var *) copyObject(var);
+
 			newvar->varno = INNER;
 			newvar->varattno = resdom->resno;
 			return (Node *) newvar;
 		}
 
+		/* Perhaps it's a join alias that can be resolved to input vars? */
+		if (var->varno == context->join_rti)
+		{
+			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;
+		}
+
 		/*
-		 * Var not in either tlist --- either raise an error, or return
-		 * the Var unmodified.
+		 * No referent found for Var --- either raise an error, or return
+		 * the Var unmodified if it's for acceptable_rel.
 		 */
 		if (var->varno != context->acceptable_rel)
 			elog(ERROR, "join_references: variable not in subplan target lists");
-		return (Node *) newvar;
+		return (Node *) copyObject(var);
 	}
 	return expression_tree_mutator(node,
 								   join_references_mutator,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 64d8b78f066b5b9e89414bd321c933a14237eb00..b2f18780fcced782cb394930d14cfb31da1b8ab2 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.71 2002/03/05 05:10:24 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.72 2002/03/12 00:51:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -673,7 +673,7 @@ List *
 expand_inherted_rtentry(Query *parse, Index rti, bool dup_parent)
 {
 	RangeTblEntry *rte = rt_fetch(rti, parse->rtable);
-	Oid			parentOID = rte->relid;
+	Oid			parentOID;
 	List	   *inhOIDs;
 	List	   *inhRTIs;
 	List	   *l;
@@ -681,10 +681,11 @@ expand_inherted_rtentry(Query *parse, Index rti, bool dup_parent)
 	/* Does RT entry allow inheritance? */
 	if (!rte->inh)
 		return NIL;
-	Assert(parentOID != InvalidOid && rte->subquery == NULL);
+	Assert(rte->rtekind == RTE_RELATION);
 	/* Always clear the parent's inh flag, see above comments */
 	rte->inh = false;
 	/* Fast path for common case of childless table */
+	parentOID = rte->relid;
 	if (!has_subclass(parentOID))
 		return NIL;
 	/* Scan for all members of inheritance set */
@@ -811,6 +812,19 @@ adjust_inherited_attrs_mutator(Node *node,
 			rtr->rtindex = context->new_rt_index;
 		return (Node *) rtr;
 	}
+	if (IsA(node, JoinExpr))
+	{
+		/* Copy the JoinExpr node with correct mutation of subnodes */
+		JoinExpr *j;
+
+		j = (JoinExpr *) expression_tree_mutator(node,
+												 adjust_inherited_attrs_mutator,
+												 (void *) context);
+		/* now fix JoinExpr's rtindex */
+		if (j->rtindex == context->old_rt_index)
+			j->rtindex = context->new_rt_index;
+		return (Node *) j;
+	}
 
 	/*
 	 * We have to process RestrictInfo nodes specially: we do NOT want to
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 8a4bcf4d9c450bf02c3c94bcc0b15d5075cdc360..6585fb1905da64b6395815c8ef3abc1a3a12a76a 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.93 2002/01/03 18:01:59 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.94 2002/03/12 00:51:50 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -1808,12 +1808,8 @@ expression_tree_walker(Node *node,
 					return true;
 				if (walker(join->quals, context))
 					return true;
-				if (walker((Node *) join->colvars, context))
-					return true;
-
 				/*
-				 * alias clause, using list, colnames list are deemed
-				 * uninteresting.
+				 * alias clause, using list are deemed uninteresting.
 				 */
 			}
 			break;
@@ -2186,8 +2182,7 @@ expression_tree_mutator(Node *node,
 				MUTATE(newnode->larg, join->larg, Node *);
 				MUTATE(newnode->rarg, join->rarg, Node *);
 				MUTATE(newnode->quals, join->quals, Node *);
-				MUTATE(newnode->colvars, join->colvars, List *);
-				/* We do not mutate alias, using, or colnames by default */
+				/* We do not mutate alias or using by default */
 				return (Node *) newnode;
 			}
 			break;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 457826f5d316ff7484a096c3ffbf8b3d6fee03fd..0fad16fbdd082bbb4bef7a3fadf61bd27f5e8414 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.35 2001/10/25 05:49:34 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.36 2002/03/12 00:51:51 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,26 +40,26 @@ static void subbuild_joinrel_joinlist(RelOptInfo *joinrel,
 
 /*
  * build_base_rel
- *	  Returns relation entry corresponding to 'relid', creating a new one
- *	  if necessary.  This is for base relations.
+ *	  Construct a new base relation RelOptInfo, and put it in the query's
+ *	  base_rel_list.
  */
-RelOptInfo *
+void
 build_base_rel(Query *root, int relid)
 {
 	List	   *rels;
 	RelOptInfo *rel;
 
-	/* Already made? */
+	/* Rel should not exist already */
 	foreach(rels, root->base_rel_list)
 	{
 		rel = (RelOptInfo *) lfirst(rels);
 
 		/* length(rel->relids) == 1 for all members of base_rel_list */
 		if (lfirsti(rel->relids) == relid)
-			return rel;
+			elog(ERROR, "build_base_rel: rel already exists");
 	}
 
-	/* It should not exist as an "other" rel */
+	/* It should not exist as an "other" rel, either */
 	foreach(rels, root->other_rel_list)
 	{
 		rel = (RelOptInfo *) lfirst(rels);
@@ -73,14 +73,12 @@ build_base_rel(Query *root, int relid)
 
 	/* and add it to the list */
 	root->base_rel_list = lcons(rel, root->base_rel_list);
-
-	return rel;
 }
 
 /*
  * build_other_rel
  *	  Returns relation entry corresponding to 'relid', creating a new one
- *	  if necessary.  This is for 'other' relations, which are just like
+ *	  if necessary.  This is for 'other' relations, which are much like
  *	  base relations except that they live in a different list.
  */
 RelOptInfo *
@@ -111,6 +109,10 @@ build_other_rel(Query *root, int relid)
 	/* No existing RelOptInfo for this other rel, so make a new one */
 	rel = make_base_rel(root, relid);
 
+	/* if it's not a join rel, must be a child rel */
+	if (rel->reloptkind == RELOPT_BASEREL)
+		rel->reloptkind = RELOPT_OTHER_CHILD_REL;
+
 	/* and add it to the list */
 	root->other_rel_list = lcons(rel, root->other_rel_list);
 
@@ -127,8 +129,9 @@ static RelOptInfo *
 make_base_rel(Query *root, int relid)
 {
 	RelOptInfo *rel = makeNode(RelOptInfo);
-	Oid			relationObjectId;
+	RangeTblEntry *rte = rt_fetch(relid, root->rtable);
 
+	rel->reloptkind = RELOPT_BASEREL;
 	rel->relids = makeListi1(relid);
 	rel->rows = 0;
 	rel->width = 0;
@@ -142,29 +145,40 @@ make_base_rel(Query *root, int relid)
 	rel->pages = 0;
 	rel->tuples = 0;
 	rel->subplan = NULL;
+	rel->joinrti = 0;
+	rel->joinrteids = NIL;
 	rel->baserestrictinfo = NIL;
 	rel->baserestrictcost = 0;
 	rel->outerjoinset = NIL;
 	rel->joininfo = NIL;
 	rel->innerjoin = NIL;
 
-	/* Check rtable to see if it's a plain relation or a subquery */
-	relationObjectId = getrelid(relid, root->rtable);
-
-	if (relationObjectId != InvalidOid)
-	{
-		/* Plain relation --- retrieve statistics from the system catalogs */
-		bool		indexed;
-
-		get_relation_info(relationObjectId,
-						  &indexed, &rel->pages, &rel->tuples);
-		if (indexed)
-			rel->indexlist = find_secondary_indexes(relationObjectId);
-	}
-	else
+	/* Check type of rtable entry */
+	switch (rte->rtekind)
 	{
-		/* subquery --- mark it as such for later processing */
-		rel->issubquery = true;
+		case RTE_RELATION:
+		{
+			/* Table --- retrieve statistics from the system catalogs */
+			bool		indexed;
+
+			get_relation_info(rte->relid,
+							  &indexed, &rel->pages, &rel->tuples);
+			if (indexed)
+				rel->indexlist = find_secondary_indexes(rte->relid);
+			break;
+		}
+		case RTE_SUBQUERY:
+			/* Subquery --- mark it as such for later processing */
+			rel->issubquery = true;
+			break;
+		case RTE_JOIN:
+			/* Join --- must be an otherrel */
+			rel->reloptkind = RELOPT_OTHER_JOIN_REL;
+			break;
+		default:
+			elog(ERROR, "make_base_rel: unsupported RTE kind %d",
+				 (int) rte->rtekind);
+			break;
 	}
 
 	return rel;
@@ -203,6 +217,47 @@ find_base_rel(Query *root, int relid)
 	return NULL;				/* keep compiler quiet */
 }
 
+/*
+ * find_other_rel
+ *	  Find an otherrel entry, if one exists for the given relid.
+ *	  Return NULL if no entry.
+ */
+RelOptInfo *
+find_other_rel(Query *root, int relid)
+{
+	List	   *rels;
+
+	foreach(rels, root->other_rel_list)
+	{
+		RelOptInfo *rel = (RelOptInfo *) lfirst(rels);
+
+		if (lfirsti(rel->relids) == relid)
+			return rel;
+	}
+	return NULL;
+}
+
+/*
+ * find_other_rel_for_join
+ *	  Look for an otherrel for a join RTE matching the given baserel set.
+ *	  Return NULL if no entry.
+ */
+RelOptInfo *
+find_other_rel_for_join(Query *root, List *relids)
+{
+	List	   *rels;
+
+	foreach(rels, root->other_rel_list)
+	{
+		RelOptInfo *rel = (RelOptInfo *) lfirst(rels);
+
+		if (rel->reloptkind == RELOPT_OTHER_JOIN_REL
+			&& sameseti(relids, rel->outerjoinset))
+			return rel;
+	}
+	return NULL;
+}
+
 /*
  * find_join_rel
  *	  Returns relation entry corresponding to 'relids' (a list of RT indexes),
@@ -252,6 +307,7 @@ build_join_rel(Query *root,
 {
 	List	   *joinrelids;
 	RelOptInfo *joinrel;
+	RelOptInfo *joinrterel;
 	List	   *restrictlist;
 	List	   *new_outer_tlist;
 	List	   *new_inner_tlist;
@@ -286,6 +342,7 @@ build_join_rel(Query *root,
 	 * Nope, so make one.
 	 */
 	joinrel = makeNode(RelOptInfo);
+	joinrel->reloptkind = RELOPT_JOINREL;
 	joinrel->relids = joinrelids;
 	joinrel->rows = 0;
 	joinrel->width = 0;
@@ -299,30 +356,61 @@ build_join_rel(Query *root,
 	joinrel->pages = 0;
 	joinrel->tuples = 0;
 	joinrel->subplan = NULL;
+	joinrel->joinrti = 0;
+	joinrel->joinrteids = nconc(listCopy(outer_rel->joinrteids),
+								inner_rel->joinrteids);
 	joinrel->baserestrictinfo = NIL;
 	joinrel->baserestrictcost = 0;
 	joinrel->outerjoinset = NIL;
 	joinrel->joininfo = NIL;
 	joinrel->innerjoin = NIL;
 
+	/* Is there a join RTE matching this join? */
+	joinrterel = find_other_rel_for_join(root, joinrelids);
+	if (joinrterel)
+	{
+		/* Yes, remember its RT index */
+		joinrel->joinrti = lfirsti(joinrterel->relids);
+		joinrel->joinrteids = lconsi(joinrel->joinrti, joinrel->joinrteids);
+	}
+
 	/*
 	 * Create a new tlist by removing irrelevant elements from both tlists
 	 * of the outer and inner join relations and then merging the results
 	 * together.
 	 *
+	 * XXX right now we don't remove any irrelevant elements, we just
+	 * append the two tlists together.  Someday consider pruning vars from the
+	 * join's targetlist if they are needed only to evaluate restriction
+	 * clauses of this join, and will never be accessed at higher levels of
+	 * the plantree.
+	 *
 	 * NOTE: the tlist order for a join rel will depend on which pair of
 	 * outer and inner rels we first try to build it from.	But the
 	 * contents should be the same regardless.
-	 *
-	 * XXX someday: consider pruning vars from the join's targetlist if they
-	 * are needed only to evaluate restriction clauses of this join, and
-	 * will never be accessed at higher levels of the plantree.
 	 */
 	new_outer_tlist = new_join_tlist(outer_rel->targetlist, 1);
 	new_inner_tlist = new_join_tlist(inner_rel->targetlist,
 									 length(new_outer_tlist) + 1);
 	joinrel->targetlist = nconc(new_outer_tlist, new_inner_tlist);
 
+	/*
+	 * If there are any alias variables attached to the matching join RTE,
+	 * attach them to the tlist too, so that they will be evaluated for use
+	 * at higher plan levels.
+	 */
+	if (joinrterel)
+	{
+		List   *jrtetl;
+
+		foreach(jrtetl, joinrterel->targetlist)
+		{
+			TargetEntry *jrtete = lfirst(jrtetl);
+
+			add_var_to_tlist(joinrel, (Var *) jrtete->expr);
+		}
+	}
+
 	/*
 	 * Construct restrict and join clause lists for the new joinrel. (The
 	 * caller might or might not need the restrictlist, but I need it
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 2521ffb2b6c7ed962332b9e411ead79e8d2aec17..568e024d20ba20add8bf7c44912870ee270adcda 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -8,15 +8,18 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.33 2001/10/25 05:49:34 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.34 2002/03/12 00:51:51 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
@@ -38,6 +41,12 @@ typedef struct
 	bool		includeUpperVars;
 } pull_var_clause_context;
 
+typedef struct
+{
+	Query	   *root;
+	int			expandRTI;
+} flatten_join_alias_vars_context;
+
 static bool pull_varnos_walker(Node *node,
 				   pull_varnos_context *context);
 static bool contain_var_reference_walker(Node *node,
@@ -45,6 +54,10 @@ static bool contain_var_reference_walker(Node *node,
 static bool contain_var_clause_walker(Node *node, void *context);
 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);
 
 
 /*
@@ -297,3 +310,363 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
 	return expression_tree_walker(node, pull_var_clause_walker,
 								  (void *) 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.)
+ *
+ *	  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_context context;
+
+	context.root = root;
+	context.expandRTI = expandRTI;
+
+	return flatten_join_alias_vars_mutator(node, &context);
+}
+
+static Node *
+flatten_join_alias_vars_mutator(Node *node,
+								flatten_join_alias_vars_context *context)
+{
+	if (node == NULL)
+		return NULL;
+	if (IsA(node, Var))
+	{
+		Var	   *var = (Var *) node;
+
+		if (var->varlevelsup != 0)
+			return node;		/* no need to copy, really */
+		return flatten_join_alias_var(var, context->root, context->expandRTI);
+	}
+	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);
+					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);
+					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 5fb298e1c27a64bac5c22ba084cedb1c70db6f92..98f5030f78b756706dfd3a66d1afb21b33573fc6 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.219 2002/03/10 06:02:23 momjian Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.220 2002/03/12 00:51:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2045,10 +2045,12 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	Node	   *node;
 	List	   *lefttl,
 			   *dtlist,
-			   *targetvars,
+			   *colMods,
 			   *targetnames,
-			   *sv_namespace;
-	JoinExpr   *jnode;
+			   *sv_namespace,
+			   *sv_rtable;
+	RangeTblEntry *jrte;
+	RangeTblRef *jrtr;
 	int			tllen;
 
 	qry->commandType = CMD_SELECT;
@@ -2115,12 +2117,14 @@ 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 lists of the dummy vars and their names for use in parsing
-	 * ORDER BY.
+	 * 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.
 	 */
 	qry->targetList = NIL;
-	targetvars = NIL;
 	targetnames = NIL;
+	colMods = NIL;
 	lefttl = leftmostQuery->targetList;
 	foreach(dtlist, sostmt->colTypes)
 	{
@@ -2135,15 +2139,15 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 							-1,
 							colName,
 							false);
-		expr = (Node *) makeVar(leftmostRTI,
+		expr = (Node *) makeVar(1,
 								leftResdom->resno,
 								colType,
 								-1,
 								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);
 	}
 
@@ -2190,7 +2194,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	/*
 	 * As a first step towards supporting sort clauses that are
 	 * expressions using the output columns, generate a namespace entry
-	 * that makes the output columns visible.  A JoinExpr node is handy
+	 * that makes the output columns visible.  A Join RTE node is handy
 	 * for this, since we can easily control the Vars generated upon
 	 * matches.
 	 *
@@ -2198,12 +2202,23 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	 * "ORDER BY upper(foo)" will draw the right error message rather than
 	 * "foo not found".
 	 */
-	jnode = makeNode(JoinExpr);
-	jnode->colnames = targetnames;
-	jnode->colvars = targetvars;
+	jrte = addRangeTableEntryForJoin(NULL,
+									 targetnames,
+									 JOIN_INNER,
+									 sostmt->colTypes,
+									 colMods,
+									 NIL,
+									 NIL,
+									 NULL,
+									 true);
+	jrtr = makeNode(RangeTblRef);
+	jrtr->rtindex = 1;
+
+	sv_rtable = pstate->p_rtable;
+	pstate->p_rtable = makeList1(jrte);
 
 	sv_namespace = pstate->p_namespace;
-	pstate->p_namespace = makeList1(jnode);
+	pstate->p_namespace = makeList1(jrtr);
 
 	/*
 	 * For now, we don't support resjunk sort clauses on the output of a
@@ -2218,6 +2233,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 										  qry->targetList);
 
 	pstate->p_namespace = sv_namespace;
+	pstate->p_rtable = sv_rtable;
 
 	if (tllen != length(qry->targetList))
 		elog(ERROR, "ORDER BY on a UNION/INTERSECT/EXCEPT result must be on one of the result columns");
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 61904439e7d5c3273f17798e1727aea73956f367..2f1eda4a9bf1502bdaf1b331f5fd754295792209 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.83 2001/10/25 05:49:37 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.84 2002/03/12 00:51:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -503,7 +503,13 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 				   *res_colnames,
 				   *l_colvars,
 				   *r_colvars,
-				   *res_colvars;
+				   *coltypes,
+				   *coltypmods,
+				   *leftcolnos,
+				   *rightcolnos;
+		Index		leftrti,
+					rightrti;
+		RangeTblEntry *rte;
 
 		/*
 		 * Recursively process the left and right subtrees
@@ -525,39 +531,32 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 
 		/*
 		 * Extract column name and var lists from both subtrees
+		 *
+		 * Note: expandRTE returns new lists, safe for me to modify
 		 */
-		if (IsA(j->larg, JoinExpr))
-		{
-			/* Make a copy of the subtree's lists so we can modify! */
-			l_colnames = copyObject(((JoinExpr *) j->larg)->colnames);
-			l_colvars = copyObject(((JoinExpr *) j->larg)->colvars);
-		}
+		if (IsA(j->larg, RangeTblRef))
+			leftrti = ((RangeTblRef *) j->larg)->rtindex;
+		else if (IsA(j->larg, JoinExpr))
+			leftrti = ((JoinExpr *) j->larg)->rtindex;
 		else
 		{
-			RangeTblEntry *rte;
-
-			Assert(IsA(j->larg, RangeTblRef));
-			rte = rt_fetch(((RangeTblRef *) j->larg)->rtindex,
-						   pstate->p_rtable);
-			expandRTE(pstate, rte, &l_colnames, &l_colvars);
-			/* expandRTE returns new lists, so no need for copyObject */
-		}
-		if (IsA(j->rarg, JoinExpr))
-		{
-			/* Make a copy of the subtree's lists so we can modify! */
-			r_colnames = copyObject(((JoinExpr *) j->rarg)->colnames);
-			r_colvars = copyObject(((JoinExpr *) j->rarg)->colvars);
+			elog(ERROR, "transformFromClauseItem: unexpected subtree type");
+			leftrti = 0;		/* keep compiler quiet */
 		}
+		rte = rt_fetch(leftrti, pstate->p_rtable);
+		expandRTE(pstate, rte, &l_colnames, &l_colvars);
+
+		if (IsA(j->rarg, RangeTblRef))
+			rightrti = ((RangeTblRef *) j->rarg)->rtindex;
+		else if (IsA(j->rarg, JoinExpr))
+			rightrti = ((JoinExpr *) j->rarg)->rtindex;
 		else
 		{
-			RangeTblEntry *rte;
-
-			Assert(IsA(j->rarg, RangeTblRef));
-			rte = rt_fetch(((RangeTblRef *) j->rarg)->rtindex,
-						   pstate->p_rtable);
-			expandRTE(pstate, rte, &r_colnames, &r_colvars);
-			/* expandRTE returns new lists, so no need for copyObject */
+			elog(ERROR, "transformFromClauseItem: unexpected subtree type");
+			rightrti = 0;		/* keep compiler quiet */
 		}
+		rte = rt_fetch(rightrti, pstate->p_rtable);
+		expandRTE(pstate, rte, &r_colnames, &r_colvars);
 
 		/*
 		 * Natural join does not explicitly specify columns; must generate
@@ -604,7 +603,10 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 		 * Now transform the join qualifications, if any.
 		 */
 		res_colnames = NIL;
-		res_colvars = NIL;
+		coltypes = NIL;
+		coltypmods = NIL;
+		leftcolnos = NIL;
+		rightcolnos = NIL;
 
 		if (j->using)
 		{
@@ -624,9 +626,10 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 			{
 				char	   *u_colname = strVal(lfirst(ucol));
 				List	   *col;
-				Node	   *l_colvar,
-						   *r_colvar,
-						   *colvar;
+				Var		   *l_colvar,
+						   *r_colvar;
+				Oid			outcoltype;
+				int32		outcoltypmod;
 				int			ndx;
 				int			l_index = -1;
 				int			r_index = -1;
@@ -672,34 +675,28 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 
 				res_colnames = lappend(res_colnames,
 									   nth(l_index, l_colnames));
-				switch (j->jointype)
+				/*
+				 * Choose output type if input types are dissimilar.
+				 */
+				outcoltype = l_colvar->vartype;
+				outcoltypmod = l_colvar->vartypmod;
+				if (outcoltype != r_colvar->vartype)
 				{
-					case JOIN_INNER:
-					case JOIN_LEFT:
-						colvar = l_colvar;
-						break;
-					case JOIN_RIGHT:
-						colvar = r_colvar;
-						break;
-					default:
-						{
-							/* Need COALESCE(l_colvar, r_colvar) */
-							CaseExpr   *c = makeNode(CaseExpr);
-							CaseWhen   *w = makeNode(CaseWhen);
-							NullTest   *n = makeNode(NullTest);
-
-							n->arg = l_colvar;
-							n->nulltesttype = IS_NOT_NULL;
-							w->expr = (Node *) n;
-							w->result = l_colvar;
-							c->args = makeList1(w);
-							c->defresult = r_colvar;
-							colvar = transformExpr(pstate, (Node *) c,
-												   EXPR_COLUMN_FIRST);
-							break;
-						}
+					outcoltype =
+						select_common_type(makeListi2(l_colvar->vartype,
+													  r_colvar->vartype),
+										   "JOIN/USING");
+					outcoltypmod = -1; /* ie, unknown */
 				}
-				res_colvars = lappend(res_colvars, colvar);
+				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);
 			}
 
 			j->quals = transformJoinUsingClause(pstate,
@@ -724,30 +721,53 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 							 r_colnames, r_colvars,
 							 &r_colnames, &r_colvars);
 		res_colnames = nconc(res_colnames, l_colnames);
-		res_colvars = nconc(res_colvars, l_colvars);
+		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_colnames = nconc(res_colnames, r_colnames);
-		res_colvars = nconc(res_colvars, r_colvars);
+		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);
+		}
 
 		/*
-		 * Process alias (AS clause), if any.
+		 * Check alias (AS clause), if any.
 		 */
 		if (j->alias)
 		{
-			/*
-			 * If a column alias list is specified, substitute the alias
-			 * names into my output-column list
-			 */
 			if (j->alias->attrs != NIL)
 			{
-				if (length(j->alias->attrs) != length(res_colnames))
-					elog(ERROR, "Column alias list for \"%s\" has wrong number of entries (need %d)",
-						 j->alias->relname, length(res_colnames));
-				res_colnames = j->alias->attrs;
+				if (length(j->alias->attrs) > length(res_colnames))
+					elog(ERROR, "Column alias list for \"%s\" has too many entries",
+						 j->alias->relname);
 			}
 		}
 
-		j->colnames = res_colnames;
-		j->colvars = res_colvars;
+		/*
+		 * Now build an RTE for the result of the join
+		 */
+		rte = addRangeTableEntryForJoin(pstate, res_colnames,
+										j->jointype,
+										coltypes, coltypmods,
+										leftcolnos, rightcolnos,
+										j->alias, true);
+
+		/* assume new rte is at end */
+		j->rtindex = length(pstate->p_rtable);
+		Assert(rte == rt_fetch(j->rtindex, pstate->p_rtable));
 
 		return (Node *) j;
 	}
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f740d632cc1e3ed5b2dd839aad9204e02a69be16..9c32fac23147507055320efb015e0be91acf2315 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.107 2002/03/07 16:35:36 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.108 2002/03/12 00:51:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -646,7 +646,7 @@ transformIdent(ParseState *pstate, Ident *ident, int precedence)
 	 * appear
 	 */
 	if (ident->indirection == NIL &&
-	 refnameRangeOrJoinEntry(pstate, ident->name, &sublevels_up) != NULL)
+		refnameRangeTblEntry(pstate, ident->name, &sublevels_up) != NULL)
 	{
 		ident->isRel = TRUE;
 		result = (Node *) ident;
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 766a5daad55b8fcaa5780896c20324535ec984f9..ed39d6c1036c8e02034e81311994ba690779fd8d 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.116 2002/02/19 20:11:15 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.117 2002/03/12 00:51:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -316,7 +316,6 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 		{
 			RangeTblEntry *rte;
 			int			vnum;
-			Node	   *rteorjoin;
 			int			sublevels_up;
 
 			/*
@@ -324,49 +323,11 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 			 */
 			refname = ((Ident *) arg)->name;
 
-			rteorjoin = refnameRangeOrJoinEntry(pstate, refname,
-												&sublevels_up);
+			rte = refnameRangeTblEntry(pstate, refname,
+									   &sublevels_up);
 
-			if (rteorjoin == NULL)
+			if (rte == NULL)
 				rte = addImplicitRTE(pstate, refname);
-			else if (IsA(rteorjoin, RangeTblEntry))
-				rte = (RangeTblEntry *) rteorjoin;
-			else if (IsA(rteorjoin, JoinExpr))
-			{
-				/*
-				 * The relation name refers to a join.	We can't support
-				 * functions on join tuples (since we don't have a named
-				 * type for the join tuples), so error out.
-				 */
-				if (nargs == 1)
-				{
-					/*
-					 * We have f(x) or more likely x.f where x is a join
-					 * and f is not one of the attribute names of the join
-					 * (else we'd have recognized it above).  Give an
-					 * appropriately vague error message.  Would be nicer
-					 * to know which syntax was used...
-					 */
-					elog(ERROR, "No such attribute or function %s.%s",
-						 refname, funcname);
-				}
-				else
-				{
-					/*
-					 * There are multiple arguments, so it must be a
-					 * function call.
-					 */
-					elog(ERROR, "Cannot pass result of join %s to a function",
-						 refname);
-				}
-				rte = NULL;		/* keep compiler quiet */
-			}
-			else
-			{
-				elog(ERROR, "ParseFuncOrColumn: unexpected node type %d",
-					 nodeTag(rteorjoin));
-				rte = NULL;		/* keep compiler quiet */
-			}
 
 			vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
 
@@ -379,11 +340,11 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 			 * sizeof(Pointer) to signal that the runtime representation
 			 * will be a pointer not an Oid.
 			 */
-			if (rte->relname == NULL)
+			if (rte->rtekind != RTE_RELATION)
 			{
 				/*
-				 * RTE is a subselect; must fail for lack of a specific
-				 * type
+				 * RTE is a join or subselect; must fail for lack of a
+				 * named tuple type
 				 */
 				if (nargs == 1)
 				{
@@ -397,7 +358,7 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 				}
 				else
 				{
-					elog(ERROR, "Cannot pass result of sub-select %s to a function",
+					elog(ERROR, "Cannot pass result of sub-select or join %s to a function",
 						 refname);
 				}
 			}
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index a43dcb13af223a3afae05f91b5f607e2f6ad72b5..be825c26f9ec6667c708bbdea5e9ac7aee9ad5e9 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -8,21 +8,22 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.58 2002/03/06 06:09:54 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.59 2002/03/12 00:51:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
+#include "postgres.h"
+
 #include <ctype.h>
 #include <errno.h>
 #include <float.h>
 
-#include "postgres.h"
-
 #include "access/heapam.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
 #include "fmgr.h"
 #include "nodes/makefuncs.h"
+#include "parser/parsetree.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_node.h"
@@ -165,51 +166,11 @@ make_var(ParseState *pstate, RangeTblEntry *rte, int attrno)
 {
 	int			vnum,
 				sublevels_up;
-	Oid			vartypeid = 0;
-	int32		type_mod = 0;
+	Oid			vartypeid;
+	int32		type_mod;
 
 	vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
-
-	if (rte->relid != InvalidOid)
-	{
-		/* Plain relation RTE --- get the attribute's type info */
-		HeapTuple	tp;
-		Form_pg_attribute att_tup;
-
-		tp = SearchSysCache(ATTNUM,
-							ObjectIdGetDatum(rte->relid),
-							Int16GetDatum(attrno),
-							0, 0);
-		/* this shouldn't happen... */
-		if (!HeapTupleIsValid(tp))
-			elog(ERROR, "Relation %s does not have attribute %d",
-				 rte->relname, attrno);
-		att_tup = (Form_pg_attribute) GETSTRUCT(tp);
-		vartypeid = att_tup->atttypid;
-		type_mod = att_tup->atttypmod;
-		ReleaseSysCache(tp);
-	}
-	else
-	{
-		/* Subselect RTE --- get type info from subselect's tlist */
-		List	   *tlistitem;
-
-		foreach(tlistitem, rte->subquery->targetList)
-		{
-			TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
-
-			if (te->resdom->resjunk || te->resdom->resno != attrno)
-				continue;
-			vartypeid = te->resdom->restype;
-			type_mod = te->resdom->restypmod;
-			break;
-		}
-		/* falling off end of list shouldn't happen... */
-		if (tlistitem == NIL)
-			elog(ERROR, "Subquery %s does not have attribute %d",
-				 rte->eref->relname, attrno);
-	}
-
+	get_rte_attribute_type(rte, attrno, &vartypeid, &type_mod);
 	return makeVar(vnum, attrno, vartypeid, type_mod, sublevels_up);
 }
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 9440914a776cd43336ff928b3e1d90d55ac5db9d..1609c89ce07b796a78b41e5d7e3bd19a4643b56b 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.62 2002/03/06 06:09:55 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.63 2002/03/12 00:51:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -36,38 +36,41 @@ static Node *scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
 						char *refname);
 static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
 				 char *colname);
-static Node *scanJoinForColumn(JoinExpr *join, char *colname,
-				  int sublevels_up);
 static bool isForUpdate(ParseState *pstate, char *relname);
-static List *expandNamesVars(ParseState *pstate, List *names, List *vars);
 static int	specialAttNum(char *a);
 static void warnAutoRange(ParseState *pstate, char *refname);
 
 
 /*
- * refnameRangeOrJoinEntry
- *	  Given a refname, look to see if it matches any RTE or join table.
- *	  If so, return a pointer to the RangeTblEntry or JoinExpr.
+ * refnameRangeTblEntry
+ *	  Given a refname, look to see if it matches any RTE.
+ *	  If so, return a pointer to the RangeTblEntry.
  *	  Optionally get its nesting depth (0 = current).	If sublevels_up
  *	  is NULL, only consider items at the current nesting level.
  */
-Node *
-refnameRangeOrJoinEntry(ParseState *pstate,
-						char *refname,
-						int *sublevels_up)
+RangeTblEntry *
+refnameRangeTblEntry(ParseState *pstate,
+					 char *refname,
+					 int *sublevels_up)
 {
 	if (sublevels_up)
 		*sublevels_up = 0;
 
 	while (pstate != NULL)
 	{
-		Node	   *rte;
+		Node	   *nsnode;
 
-		rte = scanNameSpaceForRefname(pstate,
-									  (Node *) pstate->p_namespace,
-									  refname);
-		if (rte)
-			return rte;
+		nsnode = scanNameSpaceForRefname(pstate,
+										 (Node *) pstate->p_namespace,
+										 refname);
+		if (nsnode)
+		{
+			/* should get an RTE or JoinExpr */
+			if (IsA(nsnode, RangeTblEntry))
+				return (RangeTblEntry *) nsnode;
+			Assert(IsA(nsnode, JoinExpr));
+			return rt_fetch(((JoinExpr *) nsnode)->rtindex, pstate->p_rtable);
+		}
 
 		pstate = pstate->parentParseState;
 		if (sublevels_up)
@@ -247,6 +250,12 @@ RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
  *
  * Side effect: if we find a match, mark the RTE as requiring read access.
  * See comments in setTargetTable().
+ *
+ * NOTE: if the RTE is for a join, marking it as requiring read access does
+ * nothing.  It might seem that we need to propagate the mark to all the
+ * contained RTEs, but that is not necessary.  This is so because a join
+ * expression can only appear in a FROM clause, and any table named in
+ * FROM will be marked checkForRead from the beginning.
  */
 static Node *
 scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
@@ -279,10 +288,9 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
 		return result;
 
 	/*
-	 * If the RTE represents a table (not a sub-select), consider system
-	 * column names.
+	 * If the RTE represents a real table, consider system column names.
 	 */
-	if (rte->relid != InvalidOid)
+	if (rte->rtekind == RTE_RELATION)
 	{
 		/* quick check to see if name could be a system column */
 		attnum = specialAttNum(colname);
@@ -303,44 +311,6 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
 	return result;
 }
 
-/*
- * scanJoinForColumn
- *	  Search the column names of a single join table for the given name.
- *	  If found, return an appropriate Var node or expression, else return NULL.
- *	  If the name proves ambiguous within this jointable, raise error.
- *
- * NOTE: unlike scanRTEForColumn, there's no need to worry about forcing
- * checkForRead true for the referenced tables.  This is so because a join
- * expression can only appear in a FROM clause, and any table named in
- * FROM will be marked checkForRead from the beginning.
- */
-static Node *
-scanJoinForColumn(JoinExpr *join, char *colname, int sublevels_up)
-{
-	Node	   *result = NULL;
-	int			attnum = 0;
-	List	   *c;
-
-	foreach(c, join->colnames)
-	{
-		attnum++;
-		if (strcmp(strVal(lfirst(c)), colname) == 0)
-		{
-			if (result)
-				elog(ERROR, "Column reference \"%s\" is ambiguous", colname);
-			result = copyObject(nth(attnum - 1, join->colvars));
-
-			/*
-			 * If referencing an uplevel join item, we must adjust
-			 * sublevels settings in the copied expression.
-			 */
-			if (sublevels_up > 0)
-				IncrementVarSublevelsUp(result, sublevels_up, 0);
-		}
-	}
-	return result;
-}
-
 /*
  * colnameToVar
  *	  Search for an unqualified column name.
@@ -382,9 +352,13 @@ colnameToVar(ParseState *pstate, char *colname)
 			}
 			else if (IsA(nsnode, JoinExpr))
 			{
-				JoinExpr   *j = (JoinExpr *) nsnode;
+				int			varno = ((JoinExpr *) nsnode)->rtindex;
+				RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
 
-				newresult = scanJoinForColumn(j, colname, levels_up);
+				/* joins are always inFromCl, so no need to check */
+
+				/* use orig_pstate here to get the right sublevels_up */
+				newresult = scanRTEForColumn(orig_pstate, rte, colname);
 			}
 			else
 				elog(ERROR, "colnameToVar: unexpected node type %d",
@@ -412,41 +386,26 @@ colnameToVar(ParseState *pstate, char *colname)
 /*
  * qualifiedNameToVar
  *	  Search for a qualified column name (refname + column name).
- *	  If found, return the appropriate Var node (or expression).
+ *	  If found, return the appropriate Var node.
  *	  If not found, return NULL.  If the name proves ambiguous, raise error.
  */
 Node *
 qualifiedNameToVar(ParseState *pstate, char *refname, char *colname,
 				   bool implicitRTEOK)
 {
-	Node	   *result;
-	Node	   *rteorjoin;
+	RangeTblEntry *rte;
 	int			sublevels_up;
 
-	rteorjoin = refnameRangeOrJoinEntry(pstate, refname, &sublevels_up);
+	rte = refnameRangeTblEntry(pstate, refname, &sublevels_up);
 
-	if (rteorjoin == NULL)
+	if (rte == NULL)
 	{
 		if (!implicitRTEOK)
 			return NULL;
-		rteorjoin = (Node *) addImplicitRTE(pstate, refname);
-		sublevels_up = 0;
+		rte = addImplicitRTE(pstate, refname);
 	}
 
-	if (IsA(rteorjoin, RangeTblEntry))
-		result = scanRTEForColumn(pstate, (RangeTblEntry *) rteorjoin,
-								  colname);
-	else if (IsA(rteorjoin, JoinExpr))
-		result = scanJoinForColumn((JoinExpr *) rteorjoin,
-								   colname, sublevels_up);
-	else
-	{
-		elog(ERROR, "qualifiedNameToVar: unexpected node type %d",
-			 nodeTag(rteorjoin));
-		result = NULL;			/* keep compiler quiet */
-	}
-
-	return result;
+	return scanRTEForColumn(pstate, rte, colname);
 }
 
 /*
@@ -474,9 +433,9 @@ addRangeTableEntry(ParseState *pstate,
 	int			numaliases;
 	int			varattno;
 
+	rte->rtekind = RTE_RELATION;
 	rte->relname = relname;
 	rte->alias = alias;
-	rte->subquery = NULL;
 
 	/*
 	 * Get the rel's OID.  This access also ensures that we have an
@@ -563,6 +522,7 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	int			varattno;
 	List	   *tlistitem;
 
+	rte->rtekind = RTE_SUBQUERY;
 	rte->relname = NULL;
 	rte->relid = InvalidOid;
 	rte->subquery = subquery;
@@ -621,6 +581,76 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	return rte;
 }
 
+/*
+ * Add an entry for a join to the pstate's range table (p_rtable).
+ *
+ * This is much like addRangeTableEntry() except that it makes a join RTE.
+ */
+RangeTblEntry *
+addRangeTableEntryForJoin(ParseState *pstate,
+						  List *colnames,
+						  JoinType jointype,
+						  List *coltypes,
+						  List *coltypmods,
+						  List *leftcols,
+						  List *rightcols,
+						  Attr *alias,
+						  bool inFromCl)
+{
+	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	Attr	   *eref;
+	int			numaliases;
+
+	rte->rtekind = RTE_JOIN;
+	rte->relname = NULL;
+	rte->relid = InvalidOid;
+	rte->subquery = NULL;
+	rte->jointype = jointype;
+	rte->joincoltypes = coltypes;
+	rte->joincoltypmods = coltypmods;
+	rte->joinleftcols = leftcols;
+	rte->joinrightcols = rightcols;
+	rte->alias = alias;
+
+	eref = alias ? (Attr *) copyObject(alias) : makeAttr("unnamed_join", NULL);
+	numaliases = length(eref->attrs);
+
+	/* fill in any unspecified alias columns */
+	if (numaliases < length(colnames))
+	{
+		while (numaliases-- > 0)
+			colnames = lnext(colnames);
+		eref->attrs = nconc(eref->attrs, colnames);
+	}
+
+	rte->eref = eref;
+
+	/*----------
+	 * Flags:
+	 * - this RTE should be expanded to include descendant tables,
+	 * - this RTE is in the FROM clause,
+	 * - this RTE should be checked for read/write access rights.
+	 *
+	 * Joins are never checked for access rights.
+	 *----------
+	 */
+	rte->inh = false;			/* never true for joins */
+	rte->inFromCl = inFromCl;
+	rte->checkForRead = false;
+	rte->checkForWrite = false;
+
+	rte->checkAsUser = InvalidOid;
+
+	/*
+	 * Add completed RTE to pstate's range table list, but not to join
+	 * list nor namespace --- caller must do that if appropriate.
+	 */
+	if (pstate != NULL)
+		pstate->p_rtable = lappend(pstate->p_rtable, rte);
+
+	return rte;
+}
+
 /*
  * Has the specified relname been selected FOR UPDATE?
  */
@@ -720,15 +750,16 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
 	/* Need the RT index of the entry for creating Vars */
 	rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up);
 
-	if (rte->relname)
+	if (rte->rtekind == RTE_RELATION)
 	{
 		/* Ordinary relation RTE */
 		Relation	rel;
 		int			maxattrs;
+		int			numaliases;
 
 		rel = heap_openr(rte->relname, AccessShareLock);
-
 		maxattrs = RelationGetNumberOfAttributes(rel);
+		numaliases = length(rte->eref->attrs);
 
 		for (varattno = 0; varattno < maxattrs; varattno++)
 		{
@@ -743,7 +774,7 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
 			{
 				char	   *label;
 
-				if (varattno < length(rte->eref->attrs))
+				if (varattno < numaliases)
 					label = strVal(nth(varattno, rte->eref->attrs));
 				else
 					label = NameStr(attr->attname);
@@ -764,7 +795,7 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
 
 		heap_close(rel, AccessShareLock);
 	}
-	else
+	else if (rte->rtekind == RTE_SUBQUERY)
 	{
 		/* Subquery RTE */
 		List	   *aliasp = rte->eref->attrs;
@@ -802,56 +833,63 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
 			}
 		}
 	}
-}
+	else if (rte->rtekind == RTE_JOIN)
+	{
+		/* Join RTE */
+		List	   *aliasp = rte->eref->attrs;
+		List	   *coltypes = rte->joincoltypes;
+		List	   *coltypmods = rte->joincoltypmods;
 
-/*
- * expandRelAttrs -
- *	  makes a list of TargetEntry nodes for the attributes of the rel
- */
-List *
-expandRelAttrs(ParseState *pstate, RangeTblEntry *rte)
-{
-	List	   *name_list,
-			   *var_list;
+		varattno = 0;
+		while (aliasp)
+		{
+			Assert(coltypes && coltypmods);
+			varattno++;
 
-	expandRTE(pstate, rte, &name_list, &var_list);
+			if (colnames)
+			{
+				char	   *label = strVal(lfirst(aliasp));
 
-	return expandNamesVars(pstate, name_list, var_list);
-}
+				*colnames = lappend(*colnames, makeString(pstrdup(label)));
+			}
 
-/*
- * expandJoinAttrs -
- *	  makes a list of TargetEntry nodes for the attributes of the join
- */
-List *
-expandJoinAttrs(ParseState *pstate, JoinExpr *join, int sublevels_up)
-{
-	List	   *vars;
+			if (colvars)
+			{
+				Var		   *varnode;
 
-	vars = copyObject(join->colvars);
+				varnode = makeVar(rtindex, varattno,
+								  (Oid) lfirsti(coltypes),
+								  (int32) lfirsti(coltypmods),
+								  sublevels_up);
 
-	/*
-	 * If referencing an uplevel join item, we must adjust sublevels
-	 * settings in the copied expression.
-	 */
-	if (sublevels_up > 0)
-		IncrementVarSublevelsUp((Node *) vars, sublevels_up, 0);
+				*colvars = lappend(*colvars, varnode);
+			}
 
-	return expandNamesVars(pstate,
-						   copyObject(join->colnames),
-						   vars);
+			aliasp = lnext(aliasp);
+			coltypes = lnext(coltypes);
+			coltypmods = lnext(coltypmods);
+		}
+		Assert(coltypes == NIL && coltypmods == NIL);
+	}
+	else
+		elog(ERROR, "expandRTE: unsupported RTE kind %d",
+			 (int) rte->rtekind);
 }
 
 /*
- * expandNamesVars -
- *		Workhorse for "*" expansion: produce a list of targetentries
- *		given lists of column names (as String nodes) and var references.
+ * expandRelAttrs -
+ *	  Workhorse for "*" expansion: produce a list of targetentries
+ *	  for the attributes of the rte
  */
-static List *
-expandNamesVars(ParseState *pstate, List *names, List *vars)
+List *
+expandRelAttrs(ParseState *pstate, RangeTblEntry *rte)
 {
+	List	   *names,
+			   *vars;
 	List	   *te_list = NIL;
 
+	expandRTE(pstate, rte, &names, &vars);
+
 	while (names)
 	{
 		char	   *label = strVal(lfirst(names));
@@ -875,22 +913,16 @@ expandNamesVars(ParseState *pstate, List *names, List *vars)
 	return te_list;
 }
 
-/* ----------
+/*
  * get_rte_attribute_name
  *		Get an attribute name from a RangeTblEntry
  *
  * This is unlike get_attname() because we use aliases if available.
- * In particular, it will work on an RTE for a subselect, whereas
+ * In particular, it will work on an RTE for a subselect or join, whereas
  * get_attname() only works on real relations.
  *
  * "*" is returned if the given attnum is InvalidAttrNumber --- this case
  * occurs when a Var represents a whole tuple of a relation.
- *
- * XXX Actually, this is completely bogus, because refnames of RTEs are
- * not guaranteed unique, and may not even have scope across the whole
- * query.  Cleanest fix would be to add refname/attname to Var nodes and
- * just print those, rather than indulging in this hack.
- * ----------
  */
 char *
 get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
@@ -901,7 +933,8 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 		return "*";
 
 	/*
-	 * If there is an alias, use it
+	 * If there is an alias, use it.  (This path should always be taken
+	 * for non-relation RTEs.)
 	 */
 	if (attnum > 0 && attnum <= length(rte->eref->attrs))
 		return strVal(nth(attnum - 1, rte->eref->attrs));
@@ -909,9 +942,9 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	/*
 	 * Can get here for a system attribute (which never has an alias), or
 	 * if alias name list is too short (which probably can't happen
-	 * anymore).  Neither of these cases is valid for a subselect RTE.
+	 * anymore).  Neither of these cases is valid for a non-relation RTE.
 	 */
-	if (rte->relid == InvalidOid)
+	if (rte->rtekind != RTE_RELATION)
 		elog(ERROR, "Invalid attnum %d for rangetable entry %s",
 			 attnum, rte->eref->relname);
 
@@ -925,6 +958,64 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	return attname;
 }
 
+/*
+ * get_rte_attribute_type
+ *		Get attribute type information from a RangeTblEntry
+ */
+void
+get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
+					   Oid *vartype, int32 *vartypmod)
+{
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/* Plain relation RTE --- get the attribute's type info */
+		HeapTuple	tp;
+		Form_pg_attribute att_tup;
+
+		tp = SearchSysCache(ATTNUM,
+							ObjectIdGetDatum(rte->relid),
+							Int16GetDatum(attnum),
+							0, 0);
+		/* this shouldn't happen... */
+		if (!HeapTupleIsValid(tp))
+			elog(ERROR, "Relation %s does not have attribute %d",
+				 rte->relname, attnum);
+		att_tup = (Form_pg_attribute) GETSTRUCT(tp);
+		*vartype = att_tup->atttypid;
+		*vartypmod = att_tup->atttypmod;
+		ReleaseSysCache(tp);
+	}
+	else if (rte->rtekind == RTE_SUBQUERY)
+	{
+		/* Subselect RTE --- get type info from subselect's tlist */
+		List	   *tlistitem;
+
+		foreach(tlistitem, rte->subquery->targetList)
+		{
+			TargetEntry *te = (TargetEntry *) lfirst(tlistitem);
+
+			if (te->resdom->resjunk || te->resdom->resno != attnum)
+				continue;
+			*vartype = te->resdom->restype;
+			*vartypmod = te->resdom->restypmod;
+			return;
+		}
+		/* falling off end of list shouldn't happen... */
+		elog(ERROR, "Subquery %s does not have attribute %d",
+			 rte->eref->relname, 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);
+	}
+	else
+		elog(ERROR, "get_rte_attribute_type: unsupported RTE kind %d",
+			 (int) rte->rtekind);
+}
+
 /*
  *	given relation and att name, return id of variable
  *
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index bb398a7068f48fbd058d0a7bb580af0b91ac55ae..f5791298f310c6d9197fe032fa6e702b01f9816d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -8,12 +8,13 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.76 2001/11/05 17:46:26 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.77 2002/03/12 00:51:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
+
 #include "nodes/makefuncs.h"
 #include "parser/parsetree.h"
 #include "parser/parse_coerce.h"
@@ -118,30 +119,16 @@ transformTargetList(ParseState *pstate, List *targetlist)
 				 * Target item is relation.*, expand that table (eg.
 				 * SELECT emp.*, dname FROM emp, dept)
 				 */
-				Node	   *rteorjoin;
+				RangeTblEntry *rte;
 				int			sublevels_up;
 
-				rteorjoin = refnameRangeOrJoinEntry(pstate, att->relname,
-													&sublevels_up);
-
-				if (rteorjoin == NULL)
-				{
-					rteorjoin = (Node *) addImplicitRTE(pstate, att->relname);
-					sublevels_up = 0;
-				}
+				rte = refnameRangeTblEntry(pstate, att->relname,
+										   &sublevels_up);
+				if (rte == NULL)
+					rte = addImplicitRTE(pstate, att->relname);
 
-				if (IsA(rteorjoin, RangeTblEntry))
-					p_target = nconc(p_target,
-									 expandRelAttrs(pstate,
-										   (RangeTblEntry *) rteorjoin));
-				else if (IsA(rteorjoin, JoinExpr))
-					p_target = nconc(p_target,
-									 expandJoinAttrs(pstate,
-												  (JoinExpr *) rteorjoin,
-													 sublevels_up));
-				else
-					elog(ERROR, "transformTargetList: unexpected node type %d",
-						 nodeTag(rteorjoin));
+				p_target = nconc(p_target,
+								 expandRelAttrs(pstate, rte));
 			}
 			else
 			{
@@ -405,34 +392,29 @@ ExpandAllTables(ParseState *pstate)
 	foreach(ns, pstate->p_namespace)
 	{
 		Node	   *n = (Node *) lfirst(ns);
+		RangeTblEntry *rte;
 
 		if (IsA(n, RangeTblRef))
-		{
-			RangeTblEntry *rte;
-
 			rte = rt_fetch(((RangeTblRef *) n)->rtindex,
 						   pstate->p_rtable);
-
-			/*
-			 * Ignore added-on relations that were not listed in the FROM
-			 * clause.
-			 */
-			if (!rte->inFromCl)
-				continue;
-
-			target = nconc(target, expandRelAttrs(pstate, rte));
-		}
 		else if (IsA(n, JoinExpr))
-		{
-			/* A newfangled join expression */
-			JoinExpr   *j = (JoinExpr *) n;
-
-			/* Currently, a join expr could only have come from FROM. */
-			target = nconc(target, expandJoinAttrs(pstate, j, 0));
-		}
+			rte = rt_fetch(((JoinExpr *) n)->rtindex,
+						   pstate->p_rtable);
 		else
+		{
 			elog(ERROR, "ExpandAllTables: unexpected node (internal error)"
 				 "\n\t%s", nodeToString(n));
+			rte = NULL;			/* keep compiler quiet */
+		}
+
+		/*
+		 * Ignore added-on relations that were not listed in the FROM
+		 * clause.
+		 */
+		if (!rte->inFromCl)
+			continue;
+
+		target = nconc(target, expandRelAttrs(pstate, rte));
 	}
 
 	/* Check for SELECT *; */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index f3e2306a00bb7d96e965af29ed17a65037e08869..e118654e4040c8933df27d57ff7af1600ba189a2 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.98 2001/10/25 05:49:41 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.99 2002/03/12 00:51:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -289,6 +289,7 @@ ApplyRetrieveRule(Query *parsetree,
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
 
+	rte->rtekind = RTE_SUBQUERY;
 	rte->relname = NULL;
 	rte->relid = InvalidOid;
 	rte->subquery = rule_action;
@@ -354,17 +355,17 @@ markQueryForUpdate(Query *qry, bool skipOldNew)
 			(rti == PRS2_OLD_VARNO || rti == PRS2_NEW_VARNO))
 			continue;
 
-		if (rte->subquery)
-		{
-			/* FOR UPDATE of subquery is propagated to subquery's rels */
-			markQueryForUpdate(rte->subquery, false);
-		}
-		else
+		if (rte->rtekind == RTE_RELATION)
 		{
 			if (!intMember(rti, qry->rowMarks))
 				qry->rowMarks = lappendi(qry->rowMarks, rti);
 			rte->checkForWrite = true;
 		}
+		else if (rte->rtekind == RTE_SUBQUERY)
+		{
+			/* FOR UPDATE of subquery is propagated to subquery's rels */
+			markQueryForUpdate(rte->subquery, false);
+		}
 	}
 }
 
@@ -440,12 +441,18 @@ fireRIRrules(Query *parsetree)
 		 * to do to this level of the query, but we must recurse into the
 		 * subquery to expand any rule references in it.
 		 */
-		if (rte->subquery)
+		if (rte->rtekind == RTE_SUBQUERY)
 		{
 			rte->subquery = fireRIRrules(rte->subquery);
 			continue;
 		}
 
+		/*
+		 * Joins and other non-relation RTEs can be ignored completely.
+		 */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
 		/*
 		 * If the table is not referenced in the query, then we ignore it.
 		 * This prevents infinite expansion loop due to new rtable entries
@@ -756,6 +763,7 @@ RewriteQuery(Query *parsetree, bool *instead_flag, List **qual_products)
 	result_relation = parsetree->resultRelation;
 	Assert(result_relation != 0);
 	rt_entry = rt_fetch(result_relation, parsetree->rtable);
+	Assert(rt_entry->rtekind == RTE_RELATION);
 
 	/*
 	 * This may well be the first access to the result relation during the
@@ -945,7 +953,7 @@ QueryRewrite(Query *parsetree)
 			RangeTblEntry *rte = rt_fetch(query->resultRelation,
 										  query->rtable);
 
-			if (rte->subquery)
+			if (rte->rtekind == RTE_SUBQUERY)
 			{
 				switch (query->commandType)
 				{
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 49f67c91c7bcfea105016861217ee91fed09870d..af413cab938d701500e1b496707800738adbab81 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.61 2001/11/05 17:46:27 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.62 2002/03/12 00:51:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,8 +101,8 @@ checkExprHasSubLink_walker(Node *node, void *context)
  *
  * Find all Var nodes in the given tree with varlevelsup == sublevels_up,
  * and increment their varno fields (rangetable indexes) by 'offset'.
- * The varnoold fields are adjusted similarly.	Also, RangeTblRef nodes
- * in join trees and setOp trees are adjusted.
+ * The varnoold fields are adjusted similarly.	Also, RangeTblRef and
+ * JoinExpr nodes in join trees and setOp trees are adjusted.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
  * nodes in-place.	The given expression tree should have been copied
@@ -140,6 +140,14 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
 		/* the subquery itself is visited separately */
 		return false;
 	}
+	if (IsA(node, JoinExpr))
+	{
+		JoinExpr *j = (JoinExpr *) node;
+
+		if (context->sublevels_up == 0)
+			j->rtindex += context->offset;
+		/* fall through to examine children */
+	}
 	if (IsA(node, Query))
 	{
 		/* Recurse into subselects */
@@ -200,7 +208,7 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
  * Find all Var nodes in the given tree belonging to a specific relation
  * (identified by sublevels_up and rt_index), and change their varno fields
  * to 'new_index'.	The varnoold fields are changed too.  Also, RangeTblRef
- * nodes in join trees and setOp trees are adjusted.
+ * and JoinExpr nodes in join trees and setOp trees are adjusted.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
  * nodes in-place.	The given expression tree should have been copied
@@ -241,6 +249,15 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 		/* the subquery itself is visited separately */
 		return false;
 	}
+	if (IsA(node, JoinExpr))
+	{
+		JoinExpr *j = (JoinExpr *) node;
+
+		if (context->sublevels_up == 0 &&
+			j->rtindex == context->rt_index)
+			j->rtindex = context->new_index;
+		/* fall through to examine children */
+	}
 	if (IsA(node, Query))
 	{
 		/* Recurse into subselects */
@@ -410,6 +427,15 @@ rangeTableEntry_used_walker(Node *node,
 		/* the subquery itself is visited separately */
 		return false;
 	}
+	if (IsA(node, JoinExpr))
+	{
+		JoinExpr *j = (JoinExpr *) node;
+
+		if (j->rtindex == context->rt_index &&
+			context->sublevels_up == 0)
+			return true;
+		/* fall through to examine children */
+	}
 	if (IsA(node, Query))
 	{
 		/* Recurse into subselects */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 169bcd23e29be6d605c8b8c635b90aa2d5c5e473..d47bf3bb945c9f658c7e4ff0022ffade1b4abd41 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
  *				back to source text
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.92 2002/03/06 19:58:26 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.93 2002/03/12 00:51:59 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -73,17 +73,22 @@ typedef struct
 
 /*
  * Each level of query context around a subtree needs a level of Var namespace.
- * The rangetable is the list of actual RTEs, and the namespace indicates
- * which parts of the rangetable are accessible (and under what aliases)
- * in the expression currently being looked at.  A Var having varlevelsup=N
- * refers to the N'th item (counting from 0) in the current context's
- * namespaces list.
+ * A Var having varlevelsup=N refers to the N'th item (counting from 0) in
+ * the current context's namespaces list.
+ *
+ * The rangetable is the list of actual RTEs from the query tree.
+ *
+ * For deparsing plan trees, we allow two special RTE entries that are not
+ * part of the rtable list (mainly because they don't have consecutively
+ * allocated varnos).
  */
 typedef struct
 {
 	List	   *rtable;			/* List of RangeTblEntry nodes */
-	List	   *namespace;		/* List of joinlist items (RangeTblRef and
-								 * JoinExpr nodes) */
+	int			outer_varno;	/* varno for outer_rte */
+	RangeTblEntry *outer_rte;	/* special RangeTblEntry, or NULL */
+	int			inner_varno;	/* varno for inner_rte */
+	RangeTblEntry *inner_rte;	/* special RangeTblEntry, or NULL */
 } deparse_namespace;
 
 
@@ -122,12 +127,6 @@ static void get_rule_sortgroupclause(SortClause *srt, List *tlist,
 						 deparse_context *context);
 static void get_names_for_var(Var *var, deparse_context *context,
 				  char **refname, char **attname);
-static bool get_alias_for_case(CaseExpr *caseexpr, deparse_context *context,
-				   char **refname, char **attname);
-static bool find_alias_in_namespace(Node *nsnode, Node *expr,
-						List *rangetable, int levelsup,
-						char **refname, char **attname);
-static bool phony_equal(Node *expr1, Node *expr2, int levelsup);
 static void get_rule_expr(Node *node, deparse_context *context);
 static void get_func_expr(Expr *expr, deparse_context *context);
 static Node *strip_type_coercion(Node *expr, Oid resultType);
@@ -644,7 +643,7 @@ deparse_expression(Node *expr, List *dpcontext, bool forceprefix)
  *
  * Given the name and OID of a relation, build deparsing context for an
  * expression referencing only that relation (as varno 1, varlevelsup 0).
- * This is presently sufficient for the external uses of deparse_expression.
+ * This is sufficient for many uses of deparse_expression.
  * ----------
  */
 List *
@@ -652,30 +651,119 @@ deparse_context_for(char *relname, Oid relid)
 {
 	deparse_namespace *dpns;
 	RangeTblEntry *rte;
-	RangeTblRef *rtr;
 
 	dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
 
 	/* Build a minimal RTE for the rel */
 	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
 	rte->relname = relname;
 	rte->relid = relid;
 	rte->eref = makeNode(Attr);
 	rte->eref->relname = relname;
 	rte->inh = false;
 	rte->inFromCl = true;
+
 	/* Build one-element rtable */
 	dpns->rtable = makeList1(rte);
+	dpns->outer_varno = dpns->inner_varno = 0;
+	dpns->outer_rte = dpns->inner_rte = NULL;
 
-	/* Build a namespace list referencing this RTE only */
-	rtr = makeNode(RangeTblRef);
-	rtr->rtindex = 1;
-	dpns->namespace = makeList1(rtr);
+	/* Return a one-deep namespace stack */
+	return makeList1(dpns);
+}
+
+/*
+ * deparse_context_for_plan		- Build deparse context for a plan node
+ *
+ * We assume we are dealing with an upper-level plan node having either
+ * one or two referenceable children (pass innercontext = NULL if only one).
+ * The passed-in Nodes should be made using deparse_context_for_subplan.
+ * The resulting context will work for deparsing quals, tlists, etc of the
+ * plan node.
+ */
+List *
+deparse_context_for_plan(int outer_varno, Node *outercontext,
+						 int inner_varno, Node *innercontext)
+{
+	deparse_namespace *dpns;
+
+	dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
+
+	dpns->rtable = NIL;
+	dpns->outer_varno = outer_varno;
+	dpns->outer_rte = (RangeTblEntry *) outercontext;
+	dpns->inner_varno = inner_varno;
+	dpns->inner_rte = (RangeTblEntry *) innercontext;
 
 	/* Return a one-deep namespace stack */
 	return makeList1(dpns);
 }
 
+/*
+ * deparse_context_for_subplan	- Build deparse context for a plan node
+ *
+ * Helper routine to build one of the inputs for deparse_context_for_plan.
+ * Pass the tlist of the subplan node, plus the query rangetable.
+ *
+ * The returned node is actually a RangeTblEntry, but we declare it as just
+ * Node to discourage callers from assuming anything.
+ */
+Node *
+deparse_context_for_subplan(const char *name, List *tlist,
+							List *rtable)
+{
+	RangeTblEntry *rte = makeNode(RangeTblEntry);
+	List	   *attrs = NIL;
+	int			nattrs = 0;
+	int			rtablelength = length(rtable);
+	List	   *tl;
+	char		buf[32];
+
+	foreach(tl, tlist)
+	{
+		TargetEntry *tle = lfirst(tl);
+		Resdom *resdom = tle->resdom;
+
+		nattrs++;
+		Assert(resdom->resno == nattrs);
+		if (resdom->resname)
+		{
+			attrs = lappend(attrs, makeString(resdom->resname));
+			continue;
+		}
+		if (tle->expr && IsA(tle->expr, Var))
+		{
+			Var	   *var = (Var *) tle->expr;
+
+			/* varno/varattno won't be any good, but varnoold might be */
+			if (var->varnoold > 0 && var->varnoold <= rtablelength)
+			{
+				RangeTblEntry *varrte = rt_fetch(var->varnoold, rtable);
+				char *varname;
+
+				varname = get_rte_attribute_name(varrte, var->varoattno);
+				attrs = lappend(attrs, makeString(varname));
+				continue;
+			}
+		}
+		/* Fallback if can't get name */
+		snprintf(buf, sizeof(buf), "?column%d?", resdom->resno);
+		attrs = lappend(attrs, makeString(pstrdup(buf)));
+	}
+
+	rte->rtekind = RTE_SPECIAL;	/* XXX */
+	rte->relname = pstrdup(name);
+	rte->relid = InvalidOid;
+	rte->eref = makeNode(Attr);
+	rte->eref->relname = rte->relname;
+	rte->eref->attrs = attrs;
+	rte->inh = false;
+	rte->inFromCl = true;
+
+	return (Node *) rte;
+}
+
 /* ----------
  * make_ruledef			- reconstruct the CREATE RULE command
  *				  for a given pg_rewrite tuple
@@ -789,7 +877,8 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
 		context.namespaces = makeList1(&dpns);
 		context.varprefix = (length(query->rtable) != 1);
 		dpns.rtable = query->rtable;
-		dpns.namespace = query->jointree ? query->jointree->fromlist : NIL;
+		dpns.outer_varno = dpns.inner_varno = 0;
+		dpns.outer_rte = dpns.inner_rte = NULL;
 
 		get_rule_expr(qual, &context);
 	}
@@ -912,7 +1001,8 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace)
 	context.varprefix = (parentnamespace != NIL ||
 						 length(query->rtable) != 1);
 	dpns.rtable = query->rtable;
-	dpns.namespace = query->jointree ? query->jointree->fromlist : NIL;
+	dpns.outer_varno = dpns.inner_varno = 0;
+	dpns.outer_rte = dpns.inner_rte = NULL;
 
 	switch (query->commandType)
 	{
@@ -1382,12 +1472,11 @@ get_utility_query_def(Query *query, deparse_context *context)
 /*
  * Get the relation refname and attname for a (possibly nonlocal) Var.
  *
+ * refname will be returned as NULL if the Var references an unnamed join.
+ * In this case the Var *must* be displayed without any qualification.
+ *
  * attname will be returned as NULL if the Var represents a whole tuple
  * of the relation.
- *
- * This is trickier than it ought to be because of the possibility of aliases
- * and limited scope of refnames.  We have to try to return the correct alias
- * with respect to the current namespace given by the context.
  */
 static void
 get_names_for_var(Var *var, deparse_context *context,
@@ -1406,262 +1495,31 @@ get_names_for_var(Var *var, deparse_context *context,
 			 var->varlevelsup);
 	dpns = (deparse_namespace *) lfirst(nslist);
 
-	/* Scan namespace to see if we can find an alias for the var */
-	if (find_alias_in_namespace((Node *) dpns->namespace, (Node *) var,
-								dpns->rtable, var->varlevelsup,
-								refname, attname))
-		return;
+	/* Find the relevant RTE */
+	if (var->varno >= 1 && var->varno <= length(dpns->rtable))
+		rte = rt_fetch(var->varno, dpns->rtable);
+	else if (var->varno == dpns->outer_varno)
+		rte = dpns->outer_rte;
+	else if (var->varno == dpns->inner_varno)
+		rte = dpns->inner_rte;
+	else
+		rte = NULL;
+	if (rte == NULL)
+		elog(ERROR, "get_names_for_var: bogus varno %d",
+			 var->varno);
+
+	/* Emit results */
+	if (rte->rtekind == RTE_JOIN && rte->alias == NULL)
+		*refname = NULL;
+	else
+		*refname = rte->eref->relname;
 
-	/*
-	 * Otherwise, fall back on the rangetable entry.  This should happen
-	 * only for uses of special RTEs like *NEW* and *OLD*, which won't get
-	 * placed in our namespace.
-	 */
-	rte = rt_fetch(var->varno, dpns->rtable);
-	*refname = rte->eref->relname;
 	if (var->varattno == InvalidAttrNumber)
 		*attname = NULL;
 	else
 		*attname = get_rte_attribute_name(rte, var->varattno);
 }
 
-/*
- * Check to see if a CASE expression matches a FULL JOIN's output expression.
- * If so, return the refname and alias it should be expressed as.
- */
-static bool
-get_alias_for_case(CaseExpr *caseexpr, deparse_context *context,
-				   char **refname, char **attname)
-{
-	List	   *nslist;
-	int			sup;
-
-	/*
-	 * This could be done more efficiently if we first groveled through
-	 * the CASE to find varlevelsup values, but it's probably not worth
-	 * the trouble.  All this code will go away someday anyway ...
-	 */
-
-	sup = 0;
-	foreach(nslist, context->namespaces)
-	{
-		deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
-
-		if (find_alias_in_namespace((Node *) dpns->namespace,
-									(Node *) caseexpr,
-									dpns->rtable, sup,
-									refname, attname))
-			return true;
-		sup++;
-	}
-	return false;
-}
-
-/*
- * Recursively scan a namespace (same representation as a jointree) to see
- * if we can find an alias for the given expression.  If so, return the
- * correct alias refname and attname.  The expression may be either a plain
- * Var or a CASE expression (which may be a FULL JOIN reference).
- */
-static bool
-find_alias_in_namespace(Node *nsnode, Node *expr,
-						List *rangetable, int levelsup,
-						char **refname, char **attname)
-{
-	if (nsnode == NULL)
-		return false;
-	if (IsA(nsnode, RangeTblRef))
-	{
-		if (IsA(expr, Var))
-		{
-			Var		   *var = (Var *) expr;
-			int			rtindex = ((RangeTblRef *) nsnode)->rtindex;
-
-			if (var->varno == rtindex && var->varlevelsup == levelsup)
-			{
-				RangeTblEntry *rte = rt_fetch(rtindex, rangetable);
-
-				*refname = rte->eref->relname;
-				if (var->varattno == InvalidAttrNumber)
-					*attname = NULL;
-				else
-					*attname = get_rte_attribute_name(rte, var->varattno);
-				return true;
-			}
-		}
-	}
-	else if (IsA(nsnode, JoinExpr))
-	{
-		JoinExpr   *j = (JoinExpr *) nsnode;
-
-		if (j->alias)
-		{
-			List	   *vlist;
-			List	   *nlist;
-
-			/*
-			 * Does the expr match any of the output columns of the join?
-			 *
-			 * We can't just use equal() here, because the given expr may
-			 * have nonzero levelsup, whereas the saved expression in the
-			 * JoinExpr should have zero levelsup.
-			 */
-			nlist = j->colnames;
-			foreach(vlist, j->colvars)
-			{
-				if (phony_equal(lfirst(vlist), expr, levelsup))
-				{
-					*refname = j->alias->relname;
-					*attname = strVal(lfirst(nlist));
-					return true;
-				}
-				nlist = lnext(nlist);
-			}
-
-			/*
-			 * Tables within an aliased join are invisible from outside
-			 * the join, according to the scope rules of SQL92 (the join
-			 * is considered a subquery).  So, stop here.
-			 */
-			return false;
-		}
-		if (find_alias_in_namespace(j->larg, expr,
-									rangetable, levelsup,
-									refname, attname))
-			return true;
-		if (find_alias_in_namespace(j->rarg, expr,
-									rangetable, levelsup,
-									refname, attname))
-			return true;
-	}
-	else if (IsA(nsnode, List))
-	{
-		List	   *l;
-
-		foreach(l, (List *) nsnode)
-		{
-			if (find_alias_in_namespace(lfirst(l), expr,
-										rangetable, levelsup,
-										refname, attname))
-				return true;
-		}
-	}
-	else
-		elog(ERROR, "find_alias_in_namespace: unexpected node type %d",
-			 nodeTag(nsnode));
-	return false;
-}
-
-/*
- * Check for equality of two expressions, with the proviso that all Vars in
- * expr1 should have varlevelsup = 0, while all Vars in expr2 should have
- * varlevelsup = levelsup.
- *
- * In reality we only need to support equality checks on Vars and the type
- * of CASE expression that is used for FULL JOIN outputs, so not all node
- * types need be handled here.
- *
- * Otherwise, this code is a straight ripoff from equalfuncs.c.
- */
-static bool
-phony_equal(Node *expr1, Node *expr2, int levelsup)
-{
-	if (expr1 == NULL || expr2 == NULL)
-		return (expr1 == expr2);
-	if (nodeTag(expr1) != nodeTag(expr2))
-		return false;
-	if (IsA(expr1, Var))
-	{
-		Var		   *a = (Var *) expr1;
-		Var		   *b = (Var *) expr2;
-
-		if (a->varno != b->varno)
-			return false;
-		if (a->varattno != b->varattno)
-			return false;
-		if (a->vartype != b->vartype)
-			return false;
-		if (a->vartypmod != b->vartypmod)
-			return false;
-		if (a->varlevelsup != 0 || b->varlevelsup != levelsup)
-			return false;
-		if (a->varnoold != b->varnoold)
-			return false;
-		if (a->varoattno != b->varoattno)
-			return false;
-		return true;
-	}
-	if (IsA(expr1, CaseExpr))
-	{
-		CaseExpr   *a = (CaseExpr *) expr1;
-		CaseExpr   *b = (CaseExpr *) expr2;
-
-		if (a->casetype != b->casetype)
-			return false;
-		if (!phony_equal(a->arg, b->arg, levelsup))
-			return false;
-		if (!phony_equal((Node *) a->args, (Node *) b->args, levelsup))
-			return false;
-		if (!phony_equal(a->defresult, b->defresult, levelsup))
-			return false;
-		return true;
-	}
-	if (IsA(expr1, CaseWhen))
-	{
-		CaseWhen   *a = (CaseWhen *) expr1;
-		CaseWhen   *b = (CaseWhen *) expr2;
-
-		if (!phony_equal(a->expr, b->expr, levelsup))
-			return false;
-		if (!phony_equal(a->result, b->result, levelsup))
-			return false;
-		return true;
-	}
-	if (IsA(expr1, Expr))
-	{
-		Expr	   *a = (Expr *) expr1;
-		Expr	   *b = (Expr *) expr2;
-
-		if (a->opType != b->opType)
-			return false;
-		if (!phony_equal(a->oper, b->oper, levelsup))
-			return false;
-		if (!phony_equal((Node *) a->args, (Node *) b->args, levelsup))
-			return false;
-		return true;
-	}
-	if (IsA(expr1, Func))
-	{
-		Func	   *a = (Func *) expr1;
-		Func	   *b = (Func *) expr2;
-
-		if (a->funcid != b->funcid)
-			return false;
-		if (a->functype != b->functype)
-			return false;
-		return true;
-	}
-	if (IsA(expr1, List))
-	{
-		List	   *la = (List *) expr1;
-		List	   *lb = (List *) expr2;
-		List	   *l;
-
-		if (length(la) != length(lb))
-			return false;
-		foreach(l, la)
-		{
-			if (!phony_equal(lfirst(l), lfirst(lb), levelsup))
-				return false;
-			lb = lnext(lb);
-		}
-		return true;
-	}
-	/* If we get here, there was something weird in a JOIN's colvars list */
-	elog(ERROR, "phony_equal: unexpected node type %d", nodeTag(expr1));
-	return false;
-}
-
 /* ----------
  * get_rule_expr			- Parse back an expression
  * ----------
@@ -1695,7 +1553,7 @@ get_rule_expr(Node *node, deparse_context *context)
 				char	   *attname;
 
 				get_names_for_var(var, context, &refname, &attname);
-				if (context->varprefix || attname == NULL)
+				if (refname && (context->varprefix || attname == NULL))
 				{
 					if (strcmp(refname, "*NEW*") == 0)
 						appendStringInfo(buf, "new");
@@ -1767,6 +1625,10 @@ get_rule_expr(Node *node, deparse_context *context)
 						appendStringInfoChar(buf, ')');
 						break;
 
+					case FUNC_EXPR:
+						get_func_expr((Expr *) node, context);
+						break;
+
 					case OR_EXPR:
 						appendStringInfoChar(buf, '(');
 						get_rule_expr((Node *) lfirst(args), context);
@@ -1795,8 +1657,13 @@ get_rule_expr(Node *node, deparse_context *context)
 						appendStringInfoChar(buf, ')');
 						break;
 
-					case FUNC_EXPR:
-						get_func_expr((Expr *) node, context);
+					case SUBPLAN_EXPR:
+						/*
+						 * We cannot see an already-planned subplan in rule
+						 * deparsing, only while EXPLAINing a query plan.
+						 * For now, just punt.
+						 */
+						appendStringInfo(buf, "(subplan)");
 						break;
 
 					default:
@@ -1927,19 +1794,6 @@ get_rule_expr(Node *node, deparse_context *context)
 			{
 				CaseExpr   *caseexpr = (CaseExpr *) node;
 				List	   *temp;
-				char	   *refname;
-				char	   *attname;
-
-				/* Hack for providing aliases for FULL JOIN outputs */
-				if (get_alias_for_case(caseexpr, context,
-									   &refname, &attname))
-				{
-					if (context->varprefix)
-						appendStringInfo(buf, "%s.",
-										 quote_identifier(refname));
-					appendStringInfo(buf, "%s", quote_identifier(attname));
-					break;
-				}
 
 				appendStringInfo(buf, "CASE");
 				foreach(temp, caseexpr->args)
@@ -2015,6 +1869,28 @@ get_rule_expr(Node *node, deparse_context *context)
 			get_sublink_expr(node, context);
 			break;
 
+		case T_Param:
+			{
+				Param	   *param = (Param *) node;
+
+				switch (param->paramkind)
+				{
+					case PARAM_NAMED:
+					case PARAM_NEW:
+					case PARAM_OLD:
+						appendStringInfo(buf, "$%s", param->paramname);
+						break;
+					case PARAM_NUM:
+					case PARAM_EXEC:
+						appendStringInfo(buf, "$%d", param->paramid);
+						break;
+					default:
+						appendStringInfo(buf, "(param)");
+						break;
+				}
+			}
+			break;
+
 		default:
 			printf("\n%s\n", nodeToString(node));
 			elog(ERROR, "get_ruledef of %s: unknown node type %d in get_rule_expr()",
@@ -2442,16 +2318,6 @@ static void
 get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
-	deparse_namespace *dpns;
-	List	   *sv_namespace;
-
-	/*
-	 * FROM-clause items have limited visibility of query's namespace.
-	 * Save and restore the outer namespace setting while we munge it.
-	 */
-	dpns = (deparse_namespace *) lfirst(context->namespaces);
-	sv_namespace = dpns->namespace;
-	dpns->namespace = NIL;
 
 	if (IsA(jtnode, RangeTblRef))
 	{
@@ -2544,7 +2410,6 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			}
 			else if (j->quals)
 			{
-				dpns->namespace = makeList2(j->larg, j->rarg);
 				appendStringInfo(buf, " ON (");
 				get_rule_expr(j->quals, context);
 				appendStringInfoChar(buf, ')');
@@ -2575,8 +2440,6 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 	else
 		elog(ERROR, "get_from_clause_item: unexpected node type %d",
 			 nodeTag(jtnode));
-
-	dpns->namespace = sv_namespace;
 }
 
 /* ----------
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index f579d0267c3bef555a69f367c60e1620cce39cb4..882a29246fe45b207a3276b0d0f72c4b0cf7a828 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.105 2002/03/01 22:45:16 petere Exp $
+ * $Id: catversion.h,v 1.106 2002/03/12 00:51:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200203011
+#define CATALOG_VERSION_NO	200203111
 
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 996c2b9c738942fd57cc3495fc62fe64f4747147..9ae9feb739dfbdff1b78dbf21e3cf6b95900dfa6 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.159 2002/03/08 04:37:18 tgl Exp $
+ * $Id: parsenodes.h,v 1.160 2002/03/12 00:52:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -430,9 +430,12 @@ typedef struct TargetEntry
  * RangeTblEntry -
  *	  A range table is a List of RangeTblEntry nodes.
  *
- *	  Currently we use the same node type for both plain relation references
- *	  and sub-selects in the FROM clause.  It might be cleaner to abstract
- *	  the common fields into a "superclass" nodetype.
+ *	  A range table entry may represent a plain relation, a sub-select in
+ *	  FROM, or the result of a JOIN clause.  (Only explicit JOIN syntax
+ *	  produces an RTE, not the implicit join resulting from multiple FROM
+ *	  items.  This is because we only need the RTE to deal with SQL features
+ *	  like outer joins and join-output-column aliasing.)  Other special
+ *	  RTE types also exist, as indicated by RTEKind.
  *
  *	  alias is an Attr node representing the AS alias-clause attached to the
  *	  FROM expression, or NULL if no clause.
@@ -445,7 +448,7 @@ typedef struct TargetEntry
  *
  *	  inh is TRUE for relation references that should be expanded to include
  *	  inheritance children, if the rel has any.  This *must* be FALSE for
- *	  subquery RTEs.
+ *	  RTEs other than RTE_RELATION entries.
  *
  *	  inFromCl marks those range variables that are listed in the FROM clause.
  *	  In SQL, the query can only refer to range variables listed in the
@@ -465,12 +468,28 @@ typedef struct TargetEntry
  *	  (This allows rules to act as setuid gateways.)
  *--------------------
  */
+typedef enum RTEKind
+{
+	RTE_RELATION,				/* ordinary relation reference */
+	RTE_SUBQUERY,				/* subquery in FROM */
+	RTE_JOIN,					/* join */
+	RTE_SPECIAL					/* special rule relation (NEW or OLD) */
+} RTEKind;
+
 typedef struct RangeTblEntry
 {
 	NodeTag		type;
 
+	RTEKind		rtekind;		/* see above */
+
 	/*
-	 * Fields valid for a plain relation RTE (else NULL/zero):
+	 * XXX the fields applicable to only some rte kinds should be merged
+	 * into a union.  I didn't do this yet because the diffs would impact
+	 * a lot of code that is being actively worked on.  FIXME later.
+	 */
+
+	/*
+	 * Fields valid for a plain relation or inh_relation RTE (else NULL/zero):
 	 */
 	char	   *relname;		/* real name of the relation */
 	Oid			relid;			/* OID of the relation */
@@ -480,6 +499,21 @@ typedef struct RangeTblEntry
 	 */
 	Query	   *subquery;		/* the sub-query */
 
+	/*
+	 * Fields valid for a join RTE (else NULL):
+	 *
+	 * 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.
+	 */
+	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 */
+
 	/*
 	 * Fields valid in all RTEs:
 	 */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 111f87c24d360ee25c6e9ec04fda9d81fa62a9cf..66db9cf36404ca4a1f4585c3d1ea3c734e6f493d 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.53 2001/11/05 17:46:34 momjian Exp $
+ * $Id: plannodes.h,v 1.54 2002/03/12 00:52:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -254,6 +254,7 @@ 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;
@@ -262,6 +263,8 @@ 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
@@ -269,6 +272,7 @@ 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/nodes/primnodes.h b/src/include/nodes/primnodes.h
index c2aabaffe6aa2648605c0cc685a6c46c5193712d..43e4d5a41e20673cc5ee9c4a7705ab060708abf6 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: primnodes.h,v 1.57 2001/11/05 17:46:34 momjian Exp $
+ * $Id: primnodes.h,v 1.58 2002/03/12 00:52:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -513,10 +513,9 @@ typedef struct RangeTblRef
  * alias has a critical impact on semantics, because a join with an alias
  * restricts visibility of the tables/columns inside it.
  *
- * During parse analysis, colnames is filled with a list of String nodes
- * giving the column names (real or alias) of the output of the join,
- * and colvars is filled with a list of expressions that can be copied to
- * reference the output columns.
+ * During parse analysis, an RTE is created for the Join, and its index
+ * is filled into rtindex.  This RTE is present mainly so that Vars can
+ * be created that refer to the outputs of the join.
  *----------
  */
 typedef struct JoinExpr
@@ -529,9 +528,7 @@ typedef struct JoinExpr
 	List	   *using;			/* USING clause, if any (list of String) */
 	Node	   *quals;			/* qualifiers on join, if any */
 	struct Attr *alias;			/* user-written alias clause, if any */
-	List	   *colnames;		/* output column names (list of String) */
-	List	   *colvars;		/* output column nodes (list of
-								 * expressions) */
+	int			rtindex;		/* RT index assigned for join */
 } JoinExpr;
 
 /*----------
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index e14f1ea4e83d672278b59b05f9634bc07e0d3f45..d26d60c71be1b20b491ba443708474e070c6d6d5 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/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: relation.h,v 1.62 2002/03/01 06:01:20 tgl Exp $
+ * $Id: relation.h,v 1.63 2002/03/12 00:52:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,18 +39,39 @@ typedef enum CostSelector
  * RelOptInfo
  *		Per-relation information for planning/optimization
  *
- *		For planning purposes, a "base rel" is either a plain relation (a
- *		table) or the output of a sub-SELECT that appears in the range table.
- *		In either case it is uniquely identified by an RT index.  A "joinrel"
- *		is the joining of two or more base rels.  A joinrel is identified by
- *		the set of RT indexes for its component baserels.
- *
- *		Note that there is only one joinrel for any given set of component
- *		baserels, no matter what order we assemble them in; so an unordered
- *		set is the right datatype to identify it with.
- *
- *		Parts of this data structure are specific to various scan and join
- *		mechanisms.  It didn't seem worth creating new node types for them.
+ * For planning purposes, a "base rel" is either a plain relation (a table)
+ * or the output of a sub-SELECT that appears in the range table.
+ * In either case it is uniquely identified by an RT index.  A "joinrel"
+ * is the joining of two or more base rels.  A joinrel is identified by
+ * the set of RT indexes for its component baserels.  We create RelOptInfo
+ * nodes for each baserel and joinrel, and store them in the Query's
+ * base_rel_list and join_rel_list respectively.
+ *
+ * Note that there is only one joinrel for any given set of component
+ * baserels, no matter what order we assemble them in; so an unordered
+ * set is the right datatype to identify it with.
+ *
+ * We also have "other rels", which are like base rels in that they refer to
+ * single RT indexes; but they are not part of the join tree, and are stored
+ * in other_rel_list not base_rel_list.  An otherrel is created for each
+ * join RTE as an aid in processing Vars that refer to the join's outputs,
+ * but it serves no other purpose in planning.  It is important not to
+ * confuse this otherrel with the joinrel that represents the matching set
+ * of base relations.
+ *
+ * A second category of otherrels are those made for child relations of an
+ * inheritance scan (SELECT FROM foo*).  The parent table's RTE and
+ * corresponding baserel represent the whole result of the inheritance scan.
+ * The planner creates separate RTEs and associated RelOptInfos for each child
+ * table (including the parent table, in its capacity as a member of the
+ * inheritance set).  These RelOptInfos are physically identical to baserels,
+ * but are otherrels because they are not in the main join tree.  These added
+ * RTEs and otherrels are used to plan the scans of the individual tables in
+ * the inheritance set; then the parent baserel is given an Append plan
+ * comprising the best plans for the individual child tables.
+ *
+ * Parts of this data structure are specific to various scan and join
+ * mechanisms.  It didn't seem worth creating new node types for them.
  *
  *		relids - List of base-relation identifiers; it is a base relation
  *				if there is just one, a join relation if more than one
@@ -69,7 +90,7 @@ typedef enum CostSelector
  *		pruneable - flag to let the planner know whether it can prune the
  *					pathlist of this RelOptInfo or not.
  *
- *	 * If the relation is a base relation it will have these fields set:
+ * If the relation is a base relation it will have these fields set:
  *
  *		issubquery - true if baserel is a subquery RTE rather than a table
  *		indexlist - list of IndexOptInfo nodes for relation's indexes
@@ -82,25 +103,30 @@ typedef enum CostSelector
  *		upon creation of the RelOptInfo object; they are filled in when
  *		set_base_rel_pathlist processes the object.
  *
- *		Note: if a base relation is the root of an inheritance tree
- *		(SELECT FROM foo*) it is still considered a base rel.  We will
- *		generate a list of candidate Paths for accessing that table itself,
- *		and also generate baserel RelOptInfo nodes for each child table,
- *		with their own candidate Path lists.  Then, an AppendPath is built
- *		from the cheapest Path for each of these tables, and set to be the
- *		only available Path for the inheritance baserel.
+ *		For otherrels that are inheritance children, these fields are filled
+ *		in just as for a baserel.  In otherrels for join RTEs, these fields
+ *		are empty --- the only useful field of a join otherrel is its
+ *		outerjoinset.
+ *
+ * If the relation is a join relation it will have these fields set:
  *
- *	 * The presence of the remaining fields depends on the restrictions
- *		and joins that the relation participates in:
+ *		joinrti - RT index of corresponding JOIN RTE, if any; 0 if none
+ *		joinrteids - List of RT indexes of JOIN RTEs included in this join
+ *					 (including joinrti)
+ *
+ * The presence of the remaining fields depends on the restrictions
+ * and joins that the relation participates in:
  *
  *		baserestrictinfo - List of RestrictInfo nodes, containing info about
  *					each qualification clause in which this relation
  *					participates (only used for base rels)
  *		baserestrictcost - Estimated cost of evaluating the baserestrictinfo
  *					clauses at a single tuple (only used for base rels)
- *		outerjoinset - If the rel appears within the nullable side of an outer
- *					join, the list of all relids participating in the highest
- *					such outer join; else NIL (only used for base rels)
+ *		outerjoinset - For a base rel: if the rel appears within the nullable
+ *					side of an outer join, the list of all relids
+ *					participating in the highest such outer join; else NIL.
+ *					For a join otherrel: the list of all baserel relids
+ *					syntactically within the join.  Otherwise, unused.
  *		joininfo  - List of JoinInfo nodes, containing info about each join
  *					clause in which this relation participates
  *		innerjoin - List of Path nodes that represent indices that may be used
@@ -128,11 +154,20 @@ typedef enum CostSelector
  * until after the outer join is performed.
  *----------
  */
+typedef enum RelOptKind
+{
+	RELOPT_BASEREL,
+	RELOPT_JOINREL,
+	RELOPT_OTHER_JOIN_REL,
+	RELOPT_OTHER_CHILD_REL
+} RelOptKind;
 
 typedef struct RelOptInfo
 {
 	NodeTag		type;
 
+	RelOptKind	reloptkind;
+
 	/* all relations included in this RelOptInfo */
 	Relids		relids;			/* integer list of base relids (RT
 								 * indexes) */
@@ -155,6 +190,10 @@ typedef struct RelOptInfo
 	double		tuples;
 	struct Plan *subplan;
 
+	/* information about a join rel (not set for base rels!) */
+	Index		joinrti;
+	List	   *joinrteids;
+
 	/* used by various scans and joins: */
 	List	   *baserestrictinfo;		/* RestrictInfo structures (if
 										 * base rel) */
@@ -228,6 +267,16 @@ typedef struct IndexOptInfo
 	bool		unique;			/* if a unique index */
 } IndexOptInfo;
 
+
+/*
+ * A Var is considered to belong to a relation if it's either from one
+ * of the actual base rels making up the relation, or it's a join alias
+ * var that is included in the relation.
+ */
+#define VARISRELMEMBER(varno,rel) (intMember((varno), (rel)->relids) || \
+								   intMember((varno), (rel)->joinrteids))
+
+
 /*
  * PathKeys
  *
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 9be3cfde0102e8e176a398f4bdaa995b125315be..d9419df47d3dedabc4b3d456e1e0199b9b4ab599 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.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: pathnode.h,v 1.41 2001/11/05 17:46:34 momjian Exp $
+ * $Id: pathnode.h,v 1.42 2002/03/12 00:52:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -67,9 +67,11 @@ extern HashPath *create_hashjoin_path(Query *root,
 /*
  * prototypes for relnode.c
  */
-extern RelOptInfo *build_base_rel(Query *root, int relid);
+extern void build_base_rel(Query *root, int relid);
 extern RelOptInfo *build_other_rel(Query *root, int relid);
 extern RelOptInfo *find_base_rel(Query *root, int relid);
+extern RelOptInfo *find_other_rel(Query *root, int relid);
+extern RelOptInfo *find_other_rel_for_join(Query *root, List *relids);
 extern RelOptInfo *build_join_rel(Query *root,
 			   RelOptInfo *outer_rel,
 			   RelOptInfo *inner_rel,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 37209dfd5c37a2a2857fda28d6101b2855f0be65..919f3d23de4b10c7b7101edeb6fcda52ade8881a 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.54 2001/11/05 17:46:34 momjian Exp $
+ * $Id: planmain.h,v 1.55 2002/03/12 00:52:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -47,18 +47,19 @@ extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
 /*
  * prototypes for plan/initsplan.c
  */
+extern List *add_base_rels_to_query(Query *root, Node *jtnode);
 extern void build_base_rel_tlists(Query *root, List *tlist);
 extern Relids distribute_quals_to_rels(Query *root, Node *jtnode);
-extern List *add_missing_rels_to_query(Query *root, Node *jtnode);
 extern void process_implied_equality(Query *root, Node *item1, Node *item2,
 						 Oid sortop1, Oid sortop2);
 
 /*
  * prototypes for plan/setrefs.c
  */
-extern void set_plan_references(Plan *plan);
-extern List *join_references(List *clauses, List *outer_tlist,
-				List *inner_tlist, Index acceptable_rel);
+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);
 extern void fix_opids(Node *node);
 
 /*
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index c88da074a5826a33e5bff9351caf768a3028a7df..1153604e48a1ed930b24f699058871be347f12c7 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -7,14 +7,15 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: var.h,v 1.17 2001/11/05 17:46:34 momjian Exp $
+ * $Id: var.h,v 1.18 2002/03/12 00:52:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef VAR_H
 #define VAR_H
 
-#include "nodes/primnodes.h"
+#include "nodes/parsenodes.h"
+
 
 extern List *pull_varnos(Node *node);
 extern bool contain_var_reference(Node *node, int varno, int varattno,
@@ -22,5 +23,8 @@ 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);
 
 #endif   /* VAR_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 3bc575f6acecf73a7ac7ec6a08bb8cf1b7bdc2b7..1b579850f4eeac7ce4014c06a5fded3ddc4cc35a 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.28 2001/11/05 17:46:35 momjian Exp $
+ * $Id: parse_relation.h,v 1.29 2002/03/12 00:52:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,7 +16,7 @@
 
 #include "parser/parse_node.h"
 
-extern Node *refnameRangeOrJoinEntry(ParseState *pstate,
+extern RangeTblEntry *refnameRangeTblEntry(ParseState *pstate,
 						char *refname,
 						int *sublevels_up);
 extern void checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
@@ -36,14 +36,21 @@ extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
 							  Query *subquery,
 							  Attr *alias,
 							  bool inFromCl);
+extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
+						  List *colnames,
+						  JoinType jointype,
+						  List *coltypes,
+						  List *coltypmods,
+						  List *leftcols,
+						  List *rightcols,
+						  Attr *alias,
+						  bool inFromCl);
 extern void addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
 			  bool addToJoinList, bool addToNameSpace);
 extern RangeTblEntry *addImplicitRTE(ParseState *pstate, char *relname);
 extern void expandRTE(ParseState *pstate, RangeTblEntry *rte,
 		  List **colnames, List **colvars);
 extern List *expandRelAttrs(ParseState *pstate, RangeTblEntry *rte);
-extern List *expandJoinAttrs(ParseState *pstate, JoinExpr *join,
-				int sublevels_up);
 extern int	attnameAttNum(Relation rd, char *a);
 extern Name attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h
index 2c19c9cc45bb0fcce628d907e8e6a20f549c9110..f0dc6627f43a0b229f6426109f085de4c545dc1b 100644
--- a/src/include/parser/parsetree.h
+++ b/src/include/parser/parsetree.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsetree.h,v 1.16 2001/11/05 17:46:35 momjian Exp $
+ * $Id: parsetree.h,v 1.17 2002/03/12 00:52:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -41,7 +41,7 @@
  *
  *		Given the range index of a relation, return the corresponding
  *		relation OID.  Note that InvalidOid will be returned if the
- *		RTE is for a sub-select rather than a relation.
+ *		RTE is for a non-relation-type RTE.
  */
 #define getrelid(rangeindex,rangetable) \
 	(rt_fetch(rangeindex, rangetable)->relid)
@@ -52,4 +52,11 @@
  */
 extern char *get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum);
 
+/*
+ * Given an RTE and an attribute number, return the appropriate
+ * type and typemod info for that attribute of that RTE.
+ */
+extern void get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
+								   Oid *vartype, int32 *vartypmod);
+
 #endif   /* PARSETREE_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index e3bc6217841ee07075966bd37434f5509c025e7a..4c2dec5d106f0f24fd1cb8340402752ab2d9cdd8 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.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: builtins.h,v 1.171 2001/11/05 17:46:36 momjian Exp $
+ * $Id: builtins.h,v 1.172 2002/03/12 00:52:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -15,7 +15,7 @@
 #define BUILTINS_H
 
 #include "fmgr.h"
-#include "nodes/primnodes.h"
+#include "nodes/parsenodes.h"
 #include "storage/itemptr.h"	/* for setLastTid() */
 
 /*
@@ -343,6 +343,10 @@ extern Datum pg_get_expr(PG_FUNCTION_ARGS);
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix);
 extern List *deparse_context_for(char *relname, Oid relid);
+extern List *deparse_context_for_plan(int outer_varno, Node *outercontext,
+									  int inner_varno, Node *innercontext);
+extern Node *deparse_context_for_subplan(const char *name, List *tlist,
+										 List *rtable);
 
 /* tid.c */
 extern void setLastTid(const ItemPointer tid);
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 6774390046e30b394914fdbeaad0bbd4d0ba9804..cfe7ded08d9022b893d2f69bda91fb679648bcbe 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1846,8 +1846,33 @@ SELECT '' AS "xxx", *
 SELECT '' AS "xxx", *
   FROM J1_TBL UNION JOIN J2_TBL;
 ERROR:  UNION JOIN is not implemented yet
+--
+-- Multiway full join
+--
+CREATE TABLE t1 (name TEXT, n INTEGER);
+CREATE TABLE t2 (name TEXT, n INTEGER);
+CREATE TABLE t3 (name TEXT, n INTEGER);
+INSERT INTO t1 VALUES ( 'aa', 11 );
+INSERT INTO t2 VALUES ( 'aa', 12 );
+INSERT INTO t2 VALUES ( 'bb', 22 );
+INSERT INTO t2 VALUES ( 'dd', 42 );
+INSERT INTO t3 VALUES ( 'aa', 13 );
+INSERT INTO t3 VALUES ( 'bb', 23 );
+INSERT INTO t3 VALUES ( 'cc', 33 );
+SELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name);
+ name | n  | n  | n  
+------+----+----+----
+ aa   | 11 | 12 | 13
+ bb   |    | 22 | 23
+ cc   |    |    | 33
+ dd   |    | 42 |   
+(4 rows)
+
 --
 -- Clean up
 --
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
 DROP TABLE J1_TBL;
 DROP TABLE J2_TBL;
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index ae63a61c01fbb8741ea131e6bd49c18c754cdc97..91e64adfc9437d11fb1af311d29033ea22979695 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -198,10 +198,31 @@ SELECT '' AS "xxx", *
 SELECT '' AS "xxx", *
   FROM J1_TBL UNION JOIN J2_TBL;
 
+--
+-- Multiway full join
+--
+
+CREATE TABLE t1 (name TEXT, n INTEGER);
+CREATE TABLE t2 (name TEXT, n INTEGER);
+CREATE TABLE t3 (name TEXT, n INTEGER);
+
+INSERT INTO t1 VALUES ( 'aa', 11 );
+INSERT INTO t2 VALUES ( 'aa', 12 );
+INSERT INTO t2 VALUES ( 'bb', 22 );
+INSERT INTO t2 VALUES ( 'dd', 42 );
+INSERT INTO t3 VALUES ( 'aa', 13 );
+INSERT INTO t3 VALUES ( 'bb', 23 );
+INSERT INTO t3 VALUES ( 'cc', 33 );
+
+SELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name);
+
 --
 -- Clean up
 --
 
+DROP TABLE t1;
+DROP TABLE t2;
+DROP TABLE t3;
+
 DROP TABLE J1_TBL;
 DROP TABLE J2_TBL;
-