From ed5003c58401e5727fcdd970505972394c95febb Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 12 Sep 2000 21:07:18 +0000
Subject: [PATCH] First cut at full support for OUTER JOINs.  There are still a
 few loose ends to clean up (see my message of same date to pghackers), but
 mostly it works.  INITDB REQUIRED!

---
 src/backend/catalog/heap.c                    |   18 +-
 src/backend/commands/command.c                |  610 +++++-----
 src/backend/commands/creatinh.c               |   14 +-
 src/backend/commands/explain.c                |   14 +-
 src/backend/commands/view.c                   |   43 +-
 src/backend/executor/execMain.c               |   27 +-
 src/backend/executor/execTuples.c             |   73 +-
 src/backend/executor/execUtils.c              |   46 +-
 src/backend/executor/nodeHashjoin.c           |  144 ++-
 src/backend/executor/nodeMergejoin.c          | 1010 +++++++++++------
 src/backend/executor/nodeNestloop.c           |  194 ++--
 src/backend/nodes/copyfuncs.c                 |  114 +-
 src/backend/nodes/equalfuncs.c                |  123 +-
 src/backend/nodes/list.c                      |   21 +-
 src/backend/nodes/outfuncs.c                  |  163 ++-
 src/backend/nodes/print.c                     |   11 +-
 src/backend/nodes/readfuncs.c                 |  148 ++-
 src/backend/optimizer/README                  |   28 +-
 src/backend/optimizer/geqo/geqo_eval.c        |   26 +-
 src/backend/optimizer/path/allpaths.c         |  101 +-
 src/backend/optimizer/path/indxpath.c         |   19 +-
 src/backend/optimizer/path/joinpath.c         |  235 +++-
 src/backend/optimizer/path/joinrels.c         |  324 ++++--
 src/backend/optimizer/path/orindxpath.c       |    3 +-
 src/backend/optimizer/path/pathkeys.c         |   41 +-
 src/backend/optimizer/plan/createplan.c       |  279 +++--
 src/backend/optimizer/plan/initsplan.c        |  317 +++++-
 src/backend/optimizer/plan/planmain.c         |   51 +-
 src/backend/optimizer/plan/planner.c          |   60 +-
 src/backend/optimizer/plan/setrefs.c          |   17 +-
 src/backend/optimizer/plan/subselect.c        |   12 +-
 src/backend/optimizer/prep/prepkeyset.c       |    1 +
 src/backend/optimizer/prep/prepunion.c        |   46 +-
 src/backend/optimizer/util/clauses.c          |  143 ++-
 src/backend/optimizer/util/pathnode.c         |   16 +-
 src/backend/optimizer/util/relnode.c          |    7 +-
 src/backend/optimizer/util/restrictinfo.c     |   28 +-
 src/backend/optimizer/util/var.c              |   81 +-
 src/backend/parser/Makefile                   |    4 +-
 src/backend/parser/analyze.c                  |  242 ++--
 src/backend/parser/gram.y                     |  383 +++----
 src/backend/parser/parse_agg.c                |    7 +-
 src/backend/parser/parse_clause.c             |  993 ++++++++--------
 src/backend/parser/parse_expr.c               |   61 +-
 src/backend/parser/parse_func.c               |  117 +-
 src/backend/parser/parse_node.c               |   59 +-
 src/backend/parser/parse_relation.c           |  637 ++++++++---
 src/backend/parser/parse_target.c             |  142 ++-
 src/backend/parser/parser.c                   |   66 +-
 src/backend/parser/scan.l                     |   14 +-
 src/backend/rewrite/locks.c                   |   76 +-
 src/backend/rewrite/rewriteHandler.c          |  432 +++----
 src/backend/rewrite/rewriteManip.c            |  353 ++++--
 src/backend/utils/adt/ruleutils.c             |  409 ++++---
 src/include/catalog/catversion.h              |    4 +-
 src/include/executor/execdebug.h              |   26 +-
 src/include/executor/execdefs.h               |   14 +-
 src/include/executor/executor.h               |    6 +-
 src/include/nodes/execnodes.h                 |   29 +-
 src/include/nodes/nodes.h                     |   48 +-
 src/include/nodes/parsenodes.h                |   72 +-
 src/include/nodes/pg_list.h                   |    3 +-
 src/include/nodes/plannodes.h                 |   24 +-
 src/include/nodes/primnodes.h                 |   87 +-
 src/include/nodes/relation.h                  |   48 +-
 src/include/optimizer/clauses.h               |    8 +-
 src/include/optimizer/pathnode.h              |   37 +-
 src/include/optimizer/paths.h                 |   28 +-
 src/include/optimizer/planmain.h              |   11 +-
 src/include/optimizer/restrictinfo.h          |    4 +-
 src/include/parser/gramparse.h                |   17 +-
 src/include/parser/parse_clause.h             |    5 +-
 src/include/parser/parse_func.h               |   10 +-
 src/include/parser/parse_node.h               |   24 +-
 src/include/parser/parse_relation.h           |   40 +-
 src/include/parser/parsetree.h                |   31 +-
 src/include/rewrite/rewriteHandler.h          |    9 +-
 src/include/rewrite/rewriteManip.h            |    8 +-
 src/test/regress/expected/case.out            |   60 +-
 .../expected/geometry-cygwin-precision.out    |   90 +-
 .../regress/expected/geometry-i86-gnulibc.out |   90 +-
 .../expected/geometry-positive-zeros-bsd.out  |   90 +-
 .../expected/geometry-positive-zeros.out      |   90 +-
 .../expected/geometry-powerpc-aix4.out        |   90 +-
 .../geometry-powerpc-linux-gnulibc1.out       |   90 +-
 .../expected/geometry-solaris-precision.out   |   90 +-
 src/test/regress/expected/geometry.out        |   90 +-
 src/test/regress/expected/join.out            |  306 +++--
 src/test/regress/expected/point.out           |   42 +-
 src/test/regress/expected/rules.out           |   43 +-
 src/test/regress/expected/select_implicit.out |    6 +-
 src/test/regress/sql/join.sql                 |   22 +-
 src/test/regress/sql/rules.sql                |   33 +
 93 files changed, 6276 insertions(+), 4152 deletions(-)

diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 68bb8276981..44728bf9c9a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.143 2000/09/12 04:49:06 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.144 2000/09/12 21:06:46 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -1538,11 +1538,9 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin,
 	 */
 	rte = makeNode(RangeTblEntry);
 	rte->relname = RelationGetRelationName(rel);
-#ifndef DISABLE_EREF
-	rte->ref = makeNode(Attr);
-	rte->ref->relname = RelationGetRelationName(rel);
-#endif
 	rte->relid = RelationGetRelid(rel);
+	rte->eref = makeNode(Attr);
+	rte->eref->relname = RelationGetRelationName(rel);
 	rte->inh = false;
 	rte->inFromCl = true;
 	rte->skipAcl = false;
@@ -1623,11 +1621,9 @@ StoreRelCheck(Relation rel, char *ccname, char *ccbin)
 	 */
 	rte = makeNode(RangeTblEntry);
 	rte->relname = RelationGetRelationName(rel);
-#ifndef DISABLE_EREF
-	rte->ref = makeNode(Attr);
-	rte->ref->relname = RelationGetRelationName(rel);
-#endif
 	rte->relid = RelationGetRelid(rel);
+	rte->eref = makeNode(Attr);
+	rte->eref->relname = RelationGetRelationName(rel);
 	rte->inh = false;
 	rte->inFromCl = true;
 	rte->skipAcl = false;
@@ -1723,6 +1719,7 @@ AddRelationRawConstraints(Relation rel,
 	int			numoldchecks;
 	ConstrCheck *oldchecks;
 	ParseState *pstate;
+	RangeTblEntry *rte;
 	int			numchecks;
 	List	   *listptr;
 	Relation	relrel;
@@ -1752,7 +1749,8 @@ AddRelationRawConstraints(Relation rel,
 	 */
 	pstate = make_parsestate(NULL);
 	makeRangeTable(pstate, NULL);
-	addRangeTableEntry(pstate, relname, makeAttr(relname, NULL), false, true, true);
+	rte = addRangeTableEntry(pstate, relname, NULL, false, true);
+	addRTEtoJoinTree(pstate, rte);
 
 	/*
 	 * Process column default expressions.
diff --git a/src/backend/commands/command.c b/src/backend/commands/command.c
index 9535e197417..841806810e4 100644
--- a/src/backend/commands/command.c
+++ b/src/backend/commands/command.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.102 2000/09/12 05:09:43 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.103 2000/09/12 21:06:47 tgl Exp $
  *
  * NOTES
  *	  The PerformAddAttribute() code, like most of the relation
@@ -61,8 +61,6 @@ static bool is_viewr(char *relname);
 static bool is_view(Relation rel);
 
 
-
-
 /* --------------------------------
  *		PortalCleanup
  * --------------------------------
@@ -536,7 +534,6 @@ AlterTableAlterColumn(const char *relationName,
 	rel = heap_openr(relationName, AccessExclusiveLock);
 	if ( rel->rd_rel->relkind == RELKIND_VIEW )
 		elog(ERROR, "ALTER TABLE: %s is a view", relationName);
-
 	myrelid = RelationGetRelid(rel);
 	heap_close(rel, NoLock);
 
@@ -782,7 +779,7 @@ systable_getnext(void *scan)
  *	find a specified attribute in a node entry
  */
 static bool
-find_attribute_walker(Node *node, int attnum)
+find_attribute_walker(Node *node, int *attnump)
 {
 	if (node == NULL)
 		return false;
@@ -791,16 +788,17 @@ find_attribute_walker(Node *node, int attnum)
 		Var		   *var = (Var *) node;
 
 		if (var->varlevelsup == 0 && var->varno == 1 &&
-			var->varattno == attnum)
+			var->varattno == *attnump)
 			return true;
 	}
-	return expression_tree_walker(node, find_attribute_walker, (void *) attnum);
+	return expression_tree_walker(node, find_attribute_walker,
+								  (void *) attnump);
 }
 
 static bool
 find_attribute_in_node(Node *node, int attnum)
 {
-	return expression_tree_walker(node, find_attribute_walker, (void *) attnum);
+	return find_attribute_walker(node, &attnum);
 }
 
 /*
@@ -1096,7 +1094,6 @@ void
 AlterTableAddConstraint(char *relationName,
 						bool inh, Node *newConstraint)
 {
-
 	if (newConstraint == NULL)
 		elog(ERROR, "ALTER TABLE / ADD CONSTRAINT passed invalid constraint.");
 
@@ -1108,328 +1105,330 @@ AlterTableAddConstraint(char *relationName,
 	/* check to see if the table to be constrained is a view. */
 	if (is_viewr(relationName))
 		 elog(ERROR, "ALTER TABLE: Cannot add constraints to views.");
-		
+
 	switch (nodeTag(newConstraint))
 	{
 		case T_Constraint:
+		{
+			Constraint *constr = (Constraint *) newConstraint;
+
+			switch (constr->contype)
 			{
-				Constraint *constr=(Constraint *)newConstraint;
-				switch (constr->contype) {
-					case CONSTR_CHECK:
+				case CONSTR_CHECK:
+				{
+					ParseState *pstate;
+					bool successful = TRUE;
+					HeapScanDesc scan;
+					ExprContext *econtext;
+					TupleTableSlot *slot = makeNode(TupleTableSlot);
+					HeapTuple tuple;
+					RangeTblEntry *rte;
+					List       *rtlist;
+					List       *qual;
+					List       *constlist;
+					Relation	rel;
+					Node *expr;
+					char *name;
+
+					if (constr->name)
+						name=constr->name;
+					else
+						name="<unnamed>";
+
+					constlist=lcons(constr, NIL);
+
+					rel = heap_openr(relationName, AccessExclusiveLock);
+
+					/* make sure it is not a view */
+					if (rel->rd_rel->relkind == RELKIND_VIEW)
+						elog(ERROR, "ALTER TABLE: cannot add constraint to a view");
+
+					/*
+					 * Scan all of the rows, looking for a false match
+					 */
+					scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+					AssertState(scan != NULL);
+
+					/* 
+					 * We need to make a parse state and range table to allow
+					 * us to transformExpr and fix_opids to get a version of
+					 * the expression we can pass to ExecQual
+					 */
+					pstate = make_parsestate(NULL);
+					makeRangeTable(pstate, NULL);
+					rte = addRangeTableEntry(pstate, relationName, NULL,
+											 false, true);
+					addRTEtoJoinTree(pstate, rte);
+
+					/* Convert the A_EXPR in raw_expr into an EXPR */
+					expr = transformExpr(pstate, constr->raw_expr, EXPR_COLUMN_FIRST);
+
+					/*
+					 * Make sure it yields a boolean result.
+					 */
+					if (exprType(expr) != BOOLOID)
+						elog(ERROR, "CHECK '%s' does not yield boolean result",
+							 name);
+
+					/*
+					 * Make sure no outside relations are referred to.
+					 */
+					if (length(pstate->p_rtable) != 1)
+						elog(ERROR, "Only relation '%s' can be referenced in CHECK",
+							 relationName);
+
+					/*
+					 * Might as well try to reduce any constant expressions.
+					 */
+					expr = eval_const_expressions(expr);
+
+					/* And fix the opids */
+					fix_opids(expr);
+
+					qual = lcons(expr, NIL);
+
+					rte = makeNode(RangeTblEntry);
+					rte->relname = relationName;
+					rte->relid = RelationGetRelid(rel);
+					rte->eref = makeNode(Attr);
+					rte->eref->relname = relationName;
+					rtlist = lcons(rte, NIL);
+
+					/* 
+					 * Scan through the rows now, making the necessary things
+					 * for ExecQual, and then call it to evaluate the
+					 * expression.
+					 */
+					while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
 					{
-						ParseState *pstate;
-						bool successful=TRUE;
-						HeapScanDesc scan;
-					        ExprContext *econtext;
-					        TupleTableSlot *slot = makeNode(TupleTableSlot);
-						HeapTuple tuple;
-					        RangeTblEntry *rte = makeNode(RangeTblEntry);
-					        List       *rtlist;
-					        List       *qual;
-						List       *constlist;
-						Relation	rel;
-						Node *expr;
-						char *name;
-						if (constr->name)
-							name=constr->name;
-						else
-							name="<unnamed>";
-
-						rel = heap_openr(relationName, AccessExclusiveLock);
-
-						/* make sure it is not a view */
-						if (rel->rd_rel->relkind == RELKIND_VIEW)
-						   elog(ERROR, "ALTER TABLE: cannot add constraint to a view");
-
-						/*
-						 * Scan all of the rows, looking for a false match
-						 */
-						scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
-						AssertState(scan != NULL);
-
-						/* 
-						 *We need to make a parse state and range table to allow us
-						 * to transformExpr and fix_opids to get a version of the
-					 	 * expression we can pass to ExecQual
-						 */
-						pstate = make_parsestate(NULL);
-					        makeRangeTable(pstate, NULL);
-					        addRangeTableEntry(pstate, relationName, 
-							makeAttr(relationName, NULL), false, true,true);
-						constlist=lcons(constr, NIL);
-
-						/* Convert the A_EXPR in raw_expr into an EXPR */
-				                expr = transformExpr(pstate, constr->raw_expr, EXPR_COLUMN_FIRST);
-
-				                /*
-				                 * Make sure it yields a boolean result.
-				                 */
-				                if (exprType(expr) != BOOLOID)
-				                        elog(ERROR, "CHECK '%s' does not yield boolean result",
-                                			 name);
-
-				                /*
-				                 * Make sure no outside relations are referred to.
-				                 */
-				                if (length(pstate->p_rtable) != 1)
-                				        elog(ERROR, "Only relation '%s' can be referenced in CHECK",
-		                        	         relationName);
-
-        				        /*
-				                 * Might as well try to reduce any constant expressions.
-				                 */
-				                expr = eval_const_expressions(expr);
-
-						/* And fix the opids */
-						fix_opids(expr);
-
-						qual = lcons(expr, NIL);
-       						rte->relname = relationName;
-					        rte->ref = makeNode(Attr);
-					        rte->ref->relname = rte->relname;
-					        rte->relid = RelationGetRelid(rel);
-					        rtlist = lcons(rte, NIL);
-
-						/* 
-						 * Scan through the rows now, making the necessary things for
-						 * ExecQual, and then call it to evaluate the expression.
-						 */
-						while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+						slot->val = tuple;
+						slot->ttc_shouldFree = false;
+						slot->ttc_descIsNew = true;
+						slot->ttc_tupleDescriptor = rel->rd_att;
+						slot->ttc_buffer = InvalidBuffer;
+						slot->ttc_whichplan = -1;
+
+						econtext = MakeExprContext(slot, CurrentMemoryContext);
+						econtext->ecxt_range_table = rtlist; /* range table */
+						if (!ExecQual(qual, econtext, true))
 						{
-						        slot->val = tuple;
-						        slot->ttc_shouldFree = false;
-						        slot->ttc_descIsNew = true;
-						        slot->ttc_tupleDescriptor = rel->rd_att;
-						        slot->ttc_buffer = InvalidBuffer;
-						        slot->ttc_whichplan = -1;
-
-							econtext = MakeExprContext(slot, CurrentMemoryContext);
-						        econtext->ecxt_range_table = rtlist;            /* range table */
-						        if (!ExecQual(qual, econtext, true)) {
-								successful=false;
-								break;
-						        }
-							FreeExprContext(econtext);
+							successful=false;
+							break;
 						}
+						FreeExprContext(econtext);
+					}
 
-					        pfree(slot);
-					        pfree(rtlist);
-					        pfree(rte);
+					pfree(slot);
+					pfree(rtlist);
+					pfree(rte);
 
-						heap_endscan(scan);
-						heap_close(rel, NoLock);		
+					heap_endscan(scan);
+					heap_close(rel, NoLock);		
 
-						if (!successful) 
-						{
-							elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name);
-						}
-						/* 
-						 * Call AddRelationRawConstraints to do the real adding -- It duplicates some
-						 * of the above, but does not check the validity of the constraint against
-						 * tuples already in the table.
-						 */
-						AddRelationRawConstraints(rel, NIL, constlist);
-					        pfree(constlist);
-
-						break;
+					if (!successful) 
+					{
+						elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", name);
 					}
-					default:
-						elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
+					/* 
+					 * Call AddRelationRawConstraints to do the real adding --
+					 * It duplicates some of the above, but does not check the
+					 * validity of the constraint against tuples already in
+					 * the table.
+					 */
+					AddRelationRawConstraints(rel, NIL, constlist);
+					pfree(constlist);
+
+					break;
 				}
+				default:
+					elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
 			}
 			break;
+		}
 		case T_FkConstraint:
-			{
-				FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
-				Relation	rel, pkrel;
-				HeapScanDesc scan;
-				HeapTuple	tuple;
-				Trigger		trig;
-				List	   *list;
-				int			count;
-			        List       *indexoidlist,
-		                           *indexoidscan;
-				Form_pg_index indexStruct = NULL;
-				Form_pg_attribute *rel_attrs = NULL;
-        			int                     i;
-			        int found=0;
-
-				if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL &&
-				    get_temp_rel_by_username(relationName)==NULL) {
-					elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
-				}
+		{
+			FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
+			Relation	rel, pkrel;
+			HeapScanDesc scan;
+			HeapTuple	tuple;
+			Trigger		trig;
+			List	   *list;
+			int			count;
+			List       *indexoidlist,
+				*indexoidscan;
+			Form_pg_index indexStruct = NULL;
+			Form_pg_attribute *rel_attrs = NULL;
+			int                     i;
+			int found=0;
+
+			if (get_temp_rel_by_username(fkconstraint->pktable_name)!=NULL &&
+				get_temp_rel_by_username(relationName)==NULL) {
+				elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
+			}
+
+			/*
+			 * Grab an exclusive lock on the pk table, so that someone
+			 * doesn't delete rows out from under us.
+			 */
+
+			pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock);
+			if (pkrel->rd_rel->relkind != RELKIND_RELATION)
+				elog(ERROR, "referenced table \"%s\" not a relation", 
+					 fkconstraint->pktable_name);
+
+			/*
+			 * Grab an exclusive lock on the fk table, and then scan
+			 * through each tuple, calling the RI_FKey_Match_Ins
+			 * (insert trigger) as if that tuple had just been
+			 * inserted.  If any of those fail, it should elog(ERROR)
+			 * and that's that.
+			 */
+			rel = heap_openr(relationName, AccessExclusiveLock);
+			if (rel->rd_rel->relkind != RELKIND_RELATION)
+				elog(ERROR, "referencing table \"%s\" not a relation",
+					 relationName);
+
+			/* First we check for limited correctness of the constraint */
+
+			rel_attrs = pkrel->rd_att->attrs;
+			indexoidlist = RelationGetIndexList(pkrel);
 
-				/*
-				 * Grab an exclusive lock on the pk table, so that someone
-				 * doesn't delete rows out from under us.
-				 */
-
-				pkrel = heap_openr(fkconstraint->pktable_name, AccessExclusiveLock);
-				if (pkrel == NULL)
-						elog(ERROR, "referenced table \"%s\" not found",
-							 fkconstraint->pktable_name);
-
-				if (pkrel->rd_rel->relkind != RELKIND_RELATION)
-					elog(ERROR, "referenced table \"%s\" not a relation", 
-		                         fkconstraint->pktable_name);
-				
-
-				/*
-				 * Grab an exclusive lock on the fk table, and then scan
-				 * through each tuple, calling the RI_FKey_Match_Ins
-				 * (insert trigger) as if that tuple had just been
-				 * inserted.  If any of those fail, it should elog(ERROR)
-				 * and that's that.
-				 */
-				rel = heap_openr(relationName, AccessExclusiveLock);
-				if (rel == NULL)
-					elog(ERROR, "table \"%s\" not found",
-						relationName);
-
-				if (rel->rd_rel->relkind != RELKIND_RELATION)
-					elog(ERROR, "referencing table \"%s\" not a relation", relationName);
-
-				/* First we check for limited correctness of the constraint */
-
-			        rel_attrs = pkrel->rd_att->attrs;
-			        indexoidlist = RelationGetIndexList(pkrel);
-
-			        foreach(indexoidscan, indexoidlist)
-			        {
-			                Oid             indexoid = lfirsti(indexoidscan);
-			                HeapTuple       indexTuple;
-			                List *attrl;
-			                indexTuple = SearchSysCacheTuple(INDEXRELID,
-                                                                                 ObjectIdGetDatum(indexoid),
-                                                                                 0, 0, 0);
-			                if (!HeapTupleIsValid(indexTuple))
-                        			elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found",
-			                                 indexoid);
-			                indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
-
-			                if (indexStruct->indisunique) {
-                        			/* go through the fkconstraint->pk_attrs list */
-			                        foreach(attrl, fkconstraint->pk_attrs) {
-                        			        Ident *attr=lfirst(attrl);
-			                                found=0;
-                        			        for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++)
-			                                {
-                        			                int pkattno = indexStruct->indkey[i];
+			foreach(indexoidscan, indexoidlist)
+				{
+					Oid             indexoid = lfirsti(indexoidscan);
+					HeapTuple       indexTuple;
+					List *attrl;
+					indexTuple = SearchSysCacheTuple(INDEXRELID,
+													 ObjectIdGetDatum(indexoid),
+													 0, 0, 0);
+					if (!HeapTupleIsValid(indexTuple))
+						elog(ERROR, "transformFkeyGetPrimaryKey: index %u not found",
+							 indexoid);
+					indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
+
+					if (indexStruct->indisunique) {
+						/* go through the fkconstraint->pk_attrs list */
+						foreach(attrl, fkconstraint->pk_attrs) {
+							Ident *attr=lfirst(attrl);
+							found=0;
+							for (i = 0; i < INDEX_MAX_KEYS && indexStruct->indkey[i] != 0; i++)
+							{
+								int pkattno = indexStruct->indkey[i];
 								if (pkattno>0) {
 									char *name = NameStr(rel_attrs[pkattno-1]->attname);
-        	                			                if (strcmp(name, attr->name)==0) {
-                	                                			found=1;
-				                                                break;
-                        				                }
+									if (strcmp(name, attr->name)==0) {
+										found=1;
+										break;
+									}
 								}
-			                                }
-                        			        if (!found)
-			                                        break;
-                        			}
-			                }
-			                if (found)
-                        			break;          
-			                indexStruct = NULL;
-			        }
-			        if (!found)
-			                elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found",
-                        			 fkconstraint->pktable_name);
-
-			        freeList(indexoidlist);
-				heap_close(pkrel, NoLock);
-
-			        rel_attrs = rel->rd_att->attrs;
-				if (fkconstraint->fk_attrs!=NIL) {
-	                                int found=0;
-                	                List *fkattrs;
-                        	        Ident *fkattr;
-	                                foreach(fkattrs, fkconstraint->fk_attrs) {
-						int count=0;
-        	                                found=0;
-                	                        fkattr=lfirst(fkattrs);
-						for (; count < rel->rd_att->natts; count++) {
-							char *name = NameStr(rel->rd_att->attrs[count]->attname);
-							if (strcmp(name, fkattr->name)==0) {
-                                                	        found=1;
-                                                        	break;
-	                                                }
-        	                                }
-                	                        if (!found)
-                        	                        break;
-                                	}
-	                                if (!found)
-        	                                elog(ERROR, "columns referenced in foreign key constraint not found.");
-  	        	        }
-
-				trig.tgoid = 0;
-				if (fkconstraint->constr_name)
-					trig.tgname = fkconstraint->constr_name;
-				else
-					trig.tgname = "<unknown>";
-				trig.tgfoid = 0;
-				trig.tgtype = 0;
-				trig.tgenabled = TRUE;
-				trig.tgisconstraint = TRUE;
-				trig.tginitdeferred = FALSE;
-				trig.tgdeferrable = FALSE;
-
-				trig.tgargs = (char **) palloc(
-					 sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
-									   + length(fkconstraint->pk_attrs)));
-
-				if (fkconstraint->constr_name)
-					trig.tgargs[0] = fkconstraint->constr_name;
-				else
-					trig.tgargs[0] = "<unknown>";
-				trig.tgargs[1] = (char *) relationName;
-				trig.tgargs[2] = fkconstraint->pktable_name;
-				trig.tgargs[3] = fkconstraint->match_type;
-				count = 4;
-				foreach(list, fkconstraint->fk_attrs)
+							}
+							if (!found)
+								break;
+						}
+					}
+					if (found)
+						break;          
+					indexStruct = NULL;
+				}
+			if (!found)
+				elog(ERROR, "UNIQUE constraint matching given keys for referenced table \"%s\" not found",
+					 fkconstraint->pktable_name);
+
+			freeList(indexoidlist);
+			heap_close(pkrel, NoLock);
+
+			rel_attrs = rel->rd_att->attrs;
+			if (fkconstraint->fk_attrs!=NIL) {
+				int found=0;
+				List *fkattrs;
+				Ident *fkattr;
+				foreach(fkattrs, fkconstraint->fk_attrs) {
+					int count=0;
+					found=0;
+					fkattr=lfirst(fkattrs);
+					for (; count < rel->rd_att->natts; count++) {
+						char *name = NameStr(rel->rd_att->attrs[count]->attname);
+						if (strcmp(name, fkattr->name)==0) {
+							found=1;
+							break;
+						}
+					}
+					if (!found)
+						break;
+				}
+				if (!found)
+					elog(ERROR, "columns referenced in foreign key constraint not found.");
+			}
+
+			trig.tgoid = 0;
+			if (fkconstraint->constr_name)
+				trig.tgname = fkconstraint->constr_name;
+			else
+				trig.tgname = "<unknown>";
+			trig.tgfoid = 0;
+			trig.tgtype = 0;
+			trig.tgenabled = TRUE;
+			trig.tgisconstraint = TRUE;
+			trig.tginitdeferred = FALSE;
+			trig.tgdeferrable = FALSE;
+
+			trig.tgargs = (char **) palloc(
+				sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
+								  + length(fkconstraint->pk_attrs)));
+
+			if (fkconstraint->constr_name)
+				trig.tgargs[0] = fkconstraint->constr_name;
+			else
+				trig.tgargs[0] = "<unknown>";
+			trig.tgargs[1] = (char *) relationName;
+			trig.tgargs[2] = fkconstraint->pktable_name;
+			trig.tgargs[3] = fkconstraint->match_type;
+			count = 4;
+			foreach(list, fkconstraint->fk_attrs)
 				{
 					Ident	   *fk_at = lfirst(list);
 
 					trig.tgargs[count++] = fk_at->name;
 				}
-				foreach(list, fkconstraint->pk_attrs)
+			foreach(list, fkconstraint->pk_attrs)
 				{
 					Ident	   *pk_at = lfirst(list);
 
 					trig.tgargs[count++] = pk_at->name;
 				}
-				trig.tgnargs = count;
+			trig.tgnargs = count;
 
-				scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
-				AssertState(scan != NULL);
+			scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+			AssertState(scan != NULL);
 
-				while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
-				{
-					/* Make a call to the check function */
-					/* No parameters are passed, but we do set a context */
-					FunctionCallInfoData	fcinfo;
-					TriggerData				trigdata;
-
-					MemSet(&fcinfo, 0, sizeof(fcinfo));
-					/* We assume RI_FKey_check_ins won't look at flinfo... */
+			while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+			{
+				/* Make a call to the check function */
+				/* No parameters are passed, but we do set a context */
+				FunctionCallInfoData	fcinfo;
+				TriggerData				trigdata;
 
-					trigdata.type = T_TriggerData;
-					trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
-					trigdata.tg_relation = rel;
-					trigdata.tg_trigtuple = tuple;
-					trigdata.tg_newtuple = NULL;
-					trigdata.tg_trigger = &trig;
+				MemSet(&fcinfo, 0, sizeof(fcinfo));
+				/* We assume RI_FKey_check_ins won't look at flinfo... */
 
-					fcinfo.context = (Node *) &trigdata;
+				trigdata.type = T_TriggerData;
+				trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
+				trigdata.tg_relation = rel;
+				trigdata.tg_trigtuple = tuple;
+				trigdata.tg_newtuple = NULL;
+				trigdata.tg_trigger = &trig;
 
-					RI_FKey_check_ins(&fcinfo);
-				}
-				heap_endscan(scan);
-				heap_close(rel, NoLock);		/* close rel but keep
-												 * lock! */
+				fcinfo.context = (Node *) &trigdata;
 
-				pfree(trig.tgargs);
+				RI_FKey_check_ins(&fcinfo);
 			}
+			heap_endscan(scan);
+			heap_close(rel, NoLock);		/* close rel but keep
+											 * lock! */
+
+			pfree(trig.tgargs);
 			break;
+		}
 		default:
 			elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed");
 	}
@@ -1449,7 +1448,6 @@ AlterTableDropConstraint(const char *relationName,
 }
 
 
-
 /*
  * ALTER TABLE OWNER
  */
@@ -1464,14 +1462,14 @@ AlterTableOwner(const char *relationName, const char *newOwnerName)
 	/*
 	 * first check that we are a superuser
 	 */
-	if (! superuser() )
+	if (! superuser())
 		elog(ERROR, "ALTER TABLE: permission denied");
 
 	/*
 	 * look up the new owner in pg_shadow and get the sysid
 	 */
 	tuple = SearchSysCacheTuple(SHADOWNAME, PointerGetDatum(newOwnerName),
-							   0, 0, 0);
+								0, 0, 0);
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "ALTER TABLE: user \"%s\" not found", newOwnerName);
 
@@ -1510,10 +1508,9 @@ AlterTableOwner(const char *relationName, const char *newOwnerName)
 	 */
 	heap_freetuple(tuple);
 	heap_close(class_rel, RowExclusiveLock);
-
-	return;
 }
 
+
 /*
  * ALTER TABLE CREATE TOAST TABLE
  */
@@ -1579,6 +1576,7 @@ AlterTableCreateToastTable(const char *relationName, bool silent)
 	 * allow to create TOAST tables for views. But why not - someone
 	 * can insert into a view, so it shouldn't be impossible to hide
 	 * huge data there :-)
+	 *
 	 * Not any more.
 	 */
 	if (((Form_pg_class) GETSTRUCT(reltup))->relkind != RELKIND_RELATION)
@@ -1799,8 +1797,7 @@ LockTableCommand(LockStmt *lockstmt)
 }
 
 
-static
-bool
+static bool
 is_viewr(char *name)
 {
 	Relation rel = heap_openr(name, NoLock);
@@ -1812,18 +1809,15 @@ is_viewr(char *name)
 	return retval;
 }
 
-static
-bool
-is_view (Relation rel)
+static bool
+is_view(Relation rel)
 {
 	Relation	RewriteRelation;
 	HeapScanDesc scanDesc;
 	ScanKeyData scanKeyData;
 	HeapTuple	tuple;
 	Form_pg_rewrite data;
-
-
-	bool retval = 0;
+	bool retval = false;
 
 	/*
 	 * Open the pg_rewrite relation.
@@ -1849,7 +1843,7 @@ is_view (Relation rel)
 			data = (Form_pg_rewrite) GETSTRUCT(tuple);
 			if (data->ev_type == '1')
 			{
-				retval = 1;
+				retval = true;
 				break;
 			}
 		}
diff --git a/src/backend/commands/creatinh.c b/src/backend/commands/creatinh.c
index e39c24f8dfa..b6485850eb3 100644
--- a/src/backend/commands/creatinh.c
+++ b/src/backend/commands/creatinh.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/creatinh.c,v 1.63 2000/08/04 06:12:11 inoue Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/creatinh.c,v 1.64 2000/09/12 21:06:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -259,7 +259,6 @@ change_varattnos_walker(Node *node, const AttrNumber *newattno)
 	{
 		Var	*var = (Var *) node;
 
-		Assert(newattno != NULL);
 		if (var->varlevelsup == 0 && var->varno == 1)
 		{
 			/*
@@ -270,18 +269,19 @@ change_varattnos_walker(Node *node, const AttrNumber *newattno)
 			 */
 			Assert(newattno[var->varattno - 1] > 0);
 			var->varattno = newattno[var->varattno - 1];
-			return true;
 		}
-		else
-			return false;
+		return false;
 	}
-	return expression_tree_walker(node, change_varattnos_walker, (void *)newattno);
+	return expression_tree_walker(node, change_varattnos_walker,
+								  (void *) newattno);
 }
+
 static bool
 change_varattnos_of_a_node(Node *node, const AttrNumber *newattno)
 {
-	return expression_tree_walker(node, change_varattnos_walker, (void *)newattno);
+	return change_varattnos_walker(node, newattno);
 }
+
 /*
  * MergeAttributes
  *		Returns new schema given initial schema and supers.
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 25915fe42bd..2b3d8b85726 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.57 2000/06/18 22:43:58 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.58 2000/09/12 21:06:47 tgl Exp $
  *
  */
 
@@ -229,21 +229,21 @@ explain_outNode(StringInfo str, Plan *plan, int indent, ExplainState *es)
 
 				appendStringInfo(str, " on %s",
 								 stringStringInfo(rte->relname));
-				if (rte->ref != NULL)
+				if (rte->alias != NULL)
 				{
-					if ((strcmp(rte->ref->relname, rte->relname) != 0)
-						|| (length(rte->ref->attrs) > 0))
+					if ((strcmp(rte->alias->relname, rte->relname) != 0)
+						|| (length(rte->alias->attrs) > 0))
 					{
 						appendStringInfo(str, " %s",
-									stringStringInfo(rte->ref->relname));
+									stringStringInfo(rte->alias->relname));
 
-						if (length(rte->ref->attrs) > 0)
+						if (length(rte->alias->attrs) > 0)
 						{
 							List	   *c;
 							int			firstEntry = true;
 
 							appendStringInfo(str, " (");
-							foreach(c, rte->ref->attrs)
+							foreach(c, rte->alias->attrs)
 							{
 								if (!firstEntry)
 								{
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index af10805b71f..d1d63000999 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Id: view.c,v 1.47 2000/09/12 04:49:07 momjian Exp $
+ *	$Id: view.c,v 1.48 2000/09/12 21:06:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -116,12 +116,14 @@ char *
 MakeRetrieveViewRuleName(char *viewName)
 {
 	char	   *buf;
+#ifdef MULTIBYTE
+	int			len;
+#endif
 
 	buf = palloc(strlen(viewName) + 5);
 	snprintf(buf, strlen(viewName) + 5, "_RET%s", viewName);
 
 #ifdef MULTIBYTE
-	int len;
 	len = pg_mbcliplen(buf,strlen(buf),NAMEDATALEN-1);
 	buf[len] = '\0';
 #else
@@ -203,6 +205,10 @@ DefineViewRules(char *viewName, Query *viewParse)
  * Of course we must also increase the 'varnos' of all the Var nodes
  * by 2...
  *
+ * These extra RT entries are not actually used in the query, obviously.
+ * We add them so that views look the same as ON SELECT rules ---
+ * the rule rewriter assumes that ALL rules have OLD and NEW RTEs.
+ *
  * NOTE: these are destructive changes. It would be difficult to
  * make a complete copy of the parse tree and make the changes
  * in the copy.
@@ -211,43 +217,32 @@ DefineViewRules(char *viewName, Query *viewParse)
 static void
 UpdateRangeTableOfViewParse(char *viewName, Query *viewParse)
 {
-	List	   *old_rt;
 	List	   *new_rt;
 	RangeTblEntry *rt_entry1,
 			   *rt_entry2;
 
-	/*
-	 * first offset all var nodes by 2
-	 */
-	OffsetVarNodes((Node *) viewParse->targetList, 2, 0);
-	OffsetVarNodes(viewParse->qual, 2, 0);
-
-	OffsetVarNodes(viewParse->havingQual, 2, 0);
-
-
-	/*
-	 * find the old range table...
-	 */
-	old_rt = viewParse->rtable;
-
 	/*
 	 * create the 2 new range table entries and form the new range
 	 * table... OLD first, then NEW....
 	 */
-	rt_entry1 = addRangeTableEntry(NULL, (char *) viewName,
+	rt_entry1 = addRangeTableEntry(NULL, viewName,
 								   makeAttr("*OLD*", NULL),
-								   FALSE, FALSE, FALSE);
-	rt_entry2 = addRangeTableEntry(NULL, (char *) viewName,
+								   false, false);
+	rt_entry2 = addRangeTableEntry(NULL, viewName,
 								   makeAttr("*NEW*", NULL),
-								   FALSE, FALSE, FALSE);
-	new_rt = lcons(rt_entry2, old_rt);
-	new_rt = lcons(rt_entry1, new_rt);
+								   false, false);
+	new_rt = lcons(rt_entry1, lcons(rt_entry2, viewParse->rtable));
 
 	/*
 	 * Now the tricky part.... Update the range table in place... Be
 	 * careful here, or hell breaks loooooooooooooOOOOOOOOOOOOOOOOOOSE!
 	 */
 	viewParse->rtable = new_rt;
+
+	/*
+	 * now offset all var nodes by 2, and jointree RT indexes too.
+	 */
+	OffsetVarNodes((Node *) viewParse, 2, 0);
 }
 
 /*-------------------------------------------------------------------
@@ -270,7 +265,7 @@ DefineView(char *viewName, Query *viewParse)
 	viewTlist = viewParse->targetList;
 
 	/*
-	 * Create the "view" relation NOTE: if it already exists, the xaxt
+	 * Create the "view" relation NOTE: if it already exists, the xact
 	 * will be aborted.
 	 */
 	DefineVirtualRelation(viewName, viewTlist);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d25530b44fb..d46e0d30f55 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -27,7 +27,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.126 2000/09/12 04:49:08 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.127 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -399,16 +399,17 @@ ExecCheckQueryPerms(CmdType operation, Query *parseTree, Plan *plan)
 	 * If we have a result relation, determine whether the result rel is
 	 * scanned or merely written.  If scanned, we will insist on read
 	 * permission as well as modify permission.
+	 *
+	 * Note: it might look faster to apply rangeTableEntry_used(), but
+	 * that's not correct since it will trigger on jointree references
+	 * to the RTE.  We only want to know about actual Var nodes.
 	 */
 	if (resultRelation > 0)
 	{
-		List	   *qvars = pull_varnos(parseTree->qual);
-		List	   *tvars = pull_varnos((Node *) parseTree->targetList);
+		List	   *qvars = pull_varnos((Node *) parseTree);
 
-		resultIsScanned = (intMember(resultRelation, qvars) ||
-						   intMember(resultRelation, tvars));
+		resultIsScanned = intMember(resultRelation, qvars);
 		freeList(qvars);
-		freeList(tvars);
 	}
 
 	/*
@@ -571,8 +572,8 @@ ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation,
 				  bool isResultRelation, bool resultIsScanned)
 {
 	char	   *relName;
+	Oid			userid;
 	int32		aclcheck_result;
-	Oid		userid;
 
 	if (rte->skipAcl)
 	{
@@ -703,13 +704,11 @@ InitPlan(CmdType operation, Query *parseTree, Plan *plan, EState *estate)
 		 */
 		RelationInfo *resultRelationInfo;
 		Index		resultRelationIndex;
-		RangeTblEntry *rtentry;
 		Oid			resultRelationOid;
 		Relation	resultRelationDesc;
 
 		resultRelationIndex = resultRelation;
-		rtentry = rt_fetch(resultRelationIndex, rangeTable);
-		resultRelationOid = rtentry->relid;
+		resultRelationOid = getrelid(resultRelationIndex, rangeTable);
 		resultRelationDesc = heap_open(resultRelationOid, RowExclusiveLock);
 
 		if (resultRelationDesc->rd_rel->relkind == RELKIND_SEQUENCE)
@@ -770,7 +769,7 @@ InitPlan(CmdType operation, Query *parseTree, Plan *plan, EState *estate)
 
 			if (!(rm->info & ROW_MARK_FOR_UPDATE))
 				continue;
-			relid = rt_fetch(rm->rti, rangeTable)->relid;
+			relid = getrelid(rm->rti, rangeTable);
 			relation = heap_open(relid, RowShareLock);
 			erm = (execRowMark *) palloc(sizeof(execRowMark));
 			erm->relation = relation;
@@ -1623,10 +1622,10 @@ ExecRelCheck(Relation rel, TupleTableSlot *slot, EState *estate)
 		rte = makeNode(RangeTblEntry);
 
 		rte->relname = RelationGetRelationName(rel);
-		rte->ref = makeNode(Attr);
-		rte->ref->relname = rte->relname;
 		rte->relid = RelationGetRelid(rel);
-		/* inh, inFromCl, inJoinSet, skipAcl won't be used, leave them zero */
+		rte->eref = makeNode(Attr);
+		rte->eref->relname = rte->relname;
+		/* inh, inFromCl, skipAcl won't be used, leave them zero */
 
 		/* Set up single-entry range table */
 		econtext->ecxt_range_table = lcons(rte, NIL);
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 37b092fc20f..05474bc64bc 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.38 2000/07/12 02:37:02 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.39 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,11 +46,10 @@
  *								  type of tuple in a slot
  *
  *	 CONVENIENCE INITIALIZATION ROUTINES
- *		ExecInitResultTupleSlot    \	convience routines to initialize
+ *		ExecInitResultTupleSlot	   \	convenience routines to initialize
  *		ExecInitScanTupleSlot		\	the various tuple slots for nodes
- *		ExecInitMarkedTupleSlot		/  which store copies of tuples.
- *		ExecInitOuterTupleSlot	   /
- *		ExecInitHashTupleSlot	  /
+ *		ExecInitExtraTupleSlot		/	which store copies of tuples.
+ *		ExecInitNullTupleSlot	   /
  *
  *	 old routines:
  *		ExecGetTupType			- get type of tuple returned by this node
@@ -560,10 +559,11 @@ ExecSlotDescriptorIsNew(TupleTableSlot *slot)	/* slot to inspect */
  * ----------------------------------------------------------------
  */
 /* --------------------------------
- *		ExecInit{Result,Scan,Raw,Marked,Outer,Hash}TupleSlot
+ *		ExecInit{Result,Scan,Extra}TupleSlot
  *
- *		These are convenience routines to initialize the specfied slot
- *		in nodes inheriting the appropriate state.
+ *		These are convenience routines to initialize the specified slot
+ *		in nodes inheriting the appropriate state.  ExecInitExtraTupleSlot
+ *		is used for initializing special-purpose slots.
  * --------------------------------
  */
 #define INIT_SLOT_DEFS \
@@ -583,7 +583,7 @@ ExecInitResultTupleSlot(EState *estate, CommonState *commonstate)
 {
 	INIT_SLOT_DEFS;
 	INIT_SLOT_ALLOC;
-	commonstate->cs_ResultTupleSlot = (TupleTableSlot *) slot;
+	commonstate->cs_ResultTupleSlot = slot;
 }
 
 /* ----------------
@@ -595,50 +595,51 @@ ExecInitScanTupleSlot(EState *estate, CommonScanState *commonscanstate)
 {
 	INIT_SLOT_DEFS;
 	INIT_SLOT_ALLOC;
-	commonscanstate->css_ScanTupleSlot = (TupleTableSlot *) slot;
+	commonscanstate->css_ScanTupleSlot = slot;
 }
 
-#ifdef NOT_USED
 /* ----------------
- *		ExecInitMarkedTupleSlot
+ *		ExecInitExtraTupleSlot
  * ----------------
  */
-void
-ExecInitMarkedTupleSlot(EState *estate, MergeJoinState *mergestate)
+TupleTableSlot *
+ExecInitExtraTupleSlot(EState *estate)
 {
 	INIT_SLOT_DEFS;
 	INIT_SLOT_ALLOC;
-	mergestate->mj_MarkedTupleSlot = (TupleTableSlot *) slot;
+	return slot;
 }
 
-#endif
-
 /* ----------------
- *		ExecInitOuterTupleSlot
+ *		ExecInitNullTupleSlot
+ *
+ * Build a slot containing an all-nulls tuple of the given type.
+ * This is used as a substitute for an input tuple when performing an
+ * outer join.
  * ----------------
  */
-void
-ExecInitOuterTupleSlot(EState *estate, HashJoinState *hashstate)
+TupleTableSlot *
+ExecInitNullTupleSlot(EState *estate, TupleDesc tupType)
 {
-	INIT_SLOT_DEFS;
-	INIT_SLOT_ALLOC;
-	hashstate->hj_OuterTupleSlot = slot;
-}
+	TupleTableSlot*   slot = ExecInitExtraTupleSlot(estate);
+	/*
+	 * Since heap_getattr() will treat attributes beyond a tuple's t_natts
+	 * as being NULL, we can make an all-nulls tuple just by making it be of
+	 * zero length.  However, the slot descriptor must match the real tupType.
+	 */
+	HeapTuple	nullTuple;
+	Datum		values[1];
+	char		nulls[1];
+	static struct tupleDesc NullTupleDesc;		/* we assume this inits to
+												 * zeroes */
 
-/* ----------------
- *		ExecInitHashTupleSlot
- * ----------------
- */
-#ifdef NOT_USED
-void
-ExecInitHashTupleSlot(EState *estate, HashJoinState *hashstate)
-{
-	INIT_SLOT_DEFS;
-	INIT_SLOT_ALLOC;
-	hashstate->hj_HashTupleSlot = slot;
+	ExecSetSlotDescriptor(slot, tupType);
+
+	nullTuple = heap_formtuple(&NullTupleDesc, values, nulls);
+
+	return ExecStoreTuple(nullTuple, slot, InvalidBuffer, true);
 }
 
-#endif
 
 static TupleTableSlot *
 NodeGetResultTupleSlot(Plan *node)
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 63c1e9e157f..39ae7dff10a 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.65 2000/08/22 04:06:19 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execUtils.c,v 1.66 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -275,53 +275,17 @@ void
 ExecAssignResultTypeFromTL(Plan *node, CommonState *commonstate)
 {
 	List	   *targetList;
-	int			i;
+	TupleDesc	tupDesc;
 	int			len;
-	List	   *tl;
-	TargetEntry *tle;
-	List	   *fjtl;
-	TupleDesc	origTupDesc;
 
 	targetList = node->targetlist;
-	origTupDesc = ExecTypeFromTL(targetList);
+	tupDesc = ExecTypeFromTL(targetList);
 	len = ExecTargetListLength(targetList);
 
-	fjtl = NIL;
-	tl = targetList;
-	i = 0;
-	while (tl != NIL || fjtl != NIL)
-	{
-		if (fjtl != NIL)
-		{
-			tle = lfirst(fjtl);
-			fjtl = lnext(fjtl);
-		}
-		else
-		{
-			tle = lfirst(tl);
-			tl = lnext(tl);
-		}
-#ifdef SETS_FIXED
-		if (!tl_is_resdom(tle))
-		{
-			Fjoin	   *fj = (Fjoin *) lfirst(tle);
-
-			/* it is a FJoin */
-			fjtl = lnext(tle);
-			tle = fj->fj_innerNode;
-		}
-#endif
-		i++;
-	}
-
 	if (len > 0)
-	{
-		ExecAssignResultType(commonstate,
-							 origTupDesc);
-	}
+		ExecAssignResultType(commonstate, tupDesc);
 	else
-		ExecAssignResultType(commonstate,
-							 (TupleDesc) NULL);
+		ExecAssignResultType(commonstate, (TupleDesc) NULL);
 }
 
 /* ----------------
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 4b3b4a82505..d0eef4380b7 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.33 2000/08/24 03:29:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeHashjoin.c,v 1.34 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -50,7 +50,8 @@ ExecHashJoin(HashJoin *node)
 	Hash	   *hashNode;
 	List	   *hjclauses;
 	Expr	   *clause;
-	List	   *qual;
+	List	   *joinqual;
+	List	   *otherqual;
 	ScanDirection dir;
 	TupleTableSlot *inntuple;
 	Node	   *outerVar;
@@ -70,11 +71,12 @@ ExecHashJoin(HashJoin *node)
 	hjstate = node->hashjoinstate;
 	hjclauses = node->hashclauses;
 	clause = lfirst(hjclauses);
-	estate = node->join.state;
-	qual = node->join.qual;
+	estate = node->join.plan.state;
+	joinqual = node->join.joinqual;
+	otherqual = node->join.plan.qual;
 	hashNode = (Hash *) innerPlan(node);
 	outerNode = outerPlan(node);
-	hashPhaseDone = node->hashdone;
+	hashPhaseDone = hjstate->hj_hashdone;
 	dir = estate->es_direction;
 
 	/* -----------------
@@ -132,7 +134,7 @@ ExecHashJoin(HashJoin *node)
 			hashNode->hashstate->hashtable = hashtable;
 			innerTupleSlot = ExecProcNode((Plan *) hashNode, (Plan *) node);
 		}
-		node->hashdone = true;
+		hjstate->hj_hashdone = true;
 		/* ----------------
 		 * Open temp files for outer batches, if needed.
 		 * Note that file buffers are palloc'd in regular executor context.
@@ -153,11 +155,10 @@ ExecHashJoin(HashJoin *node)
 
 	for (;;)
 	{
-
 		/*
-		 * if the current outer tuple is nil, get a new one
+		 * If we don't have an outer tuple, get the next one
 		 */
-		if (TupIsNull(outerTupleSlot))
+		if (hjstate->hj_NeedNewOuter)
 		{
 			outerTupleSlot = ExecHashJoinOuterGetTuple(outerNode,
 													   (Plan *) node,
@@ -173,11 +174,15 @@ ExecHashJoin(HashJoin *node)
 				return NULL;
 			}
 
+			hjstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
+			econtext->ecxt_outertuple = outerTupleSlot;
+			hjstate->hj_NeedNewOuter = false;
+			hjstate->hj_MatchedOuter = false;
+
 			/*
 			 * now we have an outer tuple, find the corresponding bucket
 			 * for this tuple from the hash table
 			 */
-			econtext->ecxt_outertuple = outerTupleSlot;
 			hjstate->hj_CurBucketNo = ExecHashGetBucket(hashtable, econtext,
 														outerVar);
 			hjstate->hj_CurTuple = NULL;
@@ -205,7 +210,7 @@ ExecHashJoin(HashJoin *node)
 					hashtable->outerBatchSize[batchno]++;
 					ExecHashJoinSaveTuple(outerTupleSlot->val,
 									 hashtable->outerBatchFile[batchno]);
-					ExecClearTuple(outerTupleSlot);
+					hjstate->hj_NeedNewOuter = true;
 					continue;	/* loop around for a new outer tuple */
 				}
 			}
@@ -223,7 +228,7 @@ ExecHashJoin(HashJoin *node)
 				break;			/* out of matches */
 
 			/*
-			 * we've got a match, but still need to test qpqual
+			 * we've got a match, but still need to test non-hashed quals
 			 */
 			inntuple = ExecStoreTuple(curtuple,
 									  hjstate->hj_HashTupleSlot,
@@ -231,35 +236,77 @@ ExecHashJoin(HashJoin *node)
 									  false);	/* don't pfree this tuple */
 			econtext->ecxt_innertuple = inntuple;
 
-			/* reset temp memory each time to avoid leaks from qpqual */
+			/* reset temp memory each time to avoid leaks from qual expr */
 			ResetExprContext(econtext);
 
 			/* ----------------
 			 * if we pass the qual, then save state for next call and
 			 * have ExecProject form the projection, store it
 			 * in the tuple table, and return the slot.
+			 *
+			 * Only the joinquals determine MatchedOuter status,
+			 * but all quals must pass to actually return the tuple.
 			 * ----------------
 			 */
-			if (ExecQual(qual, econtext, false))
+			if (ExecQual(joinqual, econtext, false))
 			{
-				TupleTableSlot *result;
+				hjstate->hj_MatchedOuter = true;
 
-				hjstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
-				result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
-				if (isDone != ExprEndResult)
+				if (otherqual == NIL || ExecQual(otherqual, econtext, false))
 				{
-					hjstate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
-					return result;
+					TupleTableSlot *result;
+
+					result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
+
+					if (isDone != ExprEndResult)
+					{
+						hjstate->jstate.cs_TupFromTlist =
+							(isDone == ExprMultipleResult);
+						return result;
+					}
 				}
 			}
 		}
 
 		/* ----------------
 		 *	 Now the current outer tuple has run out of matches,
-		 *	 so we free it and loop around to get a new outer tuple.
+		 *	 so check whether to emit a dummy outer-join tuple.
+		 *	 If not, loop around to get a new outer tuple.
 		 * ----------------
 		 */
-		ExecClearTuple(outerTupleSlot);
+		hjstate->hj_NeedNewOuter = true;
+
+		if (! hjstate->hj_MatchedOuter &&
+			node->join.jointype == JOIN_LEFT)
+		{
+			/*
+			 * We are doing an outer join and there were no join matches
+			 * for this outer tuple.  Generate a fake join tuple with
+			 * nulls for the inner tuple, and return it if it passes
+			 * the non-join quals.
+			 */
+			econtext->ecxt_innertuple = hjstate->hj_NullInnerTupleSlot;
+
+			if (ExecQual(otherqual, econtext, false))
+			{
+				/* ----------------
+				 *	qualification was satisfied so we project and
+				 *	return the slot containing the result tuple
+				 *	using ExecProject().
+				 * ----------------
+				 */
+				TupleTableSlot *result;
+
+				result = ExecProject(hjstate->jstate.cs_ProjInfo, &isDone);
+
+				if (isDone != ExprEndResult)
+				{
+					hjstate->jstate.cs_TupFromTlist =
+						(isDone == ExprMultipleResult);
+					return result;
+				}
+			}
+		}
 	}
 }
 
@@ -280,14 +327,13 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
 	 *	assign the node's execution state
 	 * ----------------
 	 */
-	node->join.state = estate;
+	node->join.plan.state = estate;
 
 	/* ----------------
 	 * create state structure
 	 * ----------------
 	 */
 	hjstate = makeNode(HashJoinState);
-
 	node->hashjoinstate = hjstate;
 
 	/* ----------------
@@ -298,14 +344,6 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
 	 */
 	ExecAssignExprContext(estate, &hjstate->jstate);
 
-#define HASHJOIN_NSLOTS 2
-	/* ----------------
-	 *	tuple table initialization
-	 * ----------------
-	 */
-	ExecInitResultTupleSlot(estate, &hjstate->jstate);
-	ExecInitOuterTupleSlot(estate, hjstate);
-
 	/* ----------------
 	 * initializes child nodes
 	 * ----------------
@@ -316,6 +354,28 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
 	ExecInitNode(outerNode, estate, (Plan *) node);
 	ExecInitNode((Plan *) hashNode, estate, (Plan *) node);
 
+#define HASHJOIN_NSLOTS 3
+	/* ----------------
+	 *	tuple table initialization
+	 * ----------------
+	 */
+	ExecInitResultTupleSlot(estate, &hjstate->jstate);
+	hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate);
+
+	switch (node->join.jointype)
+	{
+		case JOIN_INNER:
+			break;
+		case JOIN_LEFT:
+			hjstate->hj_NullInnerTupleSlot =
+				ExecInitNullTupleSlot(estate,
+									  ExecGetTupType((Plan *) hashNode));
+			break;
+		default:
+			elog(ERROR, "ExecInitHashJoin: unsupported join type %d",
+				 (int) node->join.jointype);
+	}
+
 	/* ----------------
 	 *	now for some voodoo.  our temporary tuple slot
 	 *	is actually the result tuple slot of the Hash node
@@ -331,11 +391,6 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
 
 		hjstate->hj_HashTupleSlot = slot;
 	}
-	hjstate->hj_OuterTupleSlot->ttc_tupleDescriptor = ExecGetTupType(outerNode);
-
-/*
-	hjstate->hj_OuterTupleSlot->ttc_execTupDescriptor = ExecGetExecTupDesc(outerNode);
-*/
 
 	/* ----------------
 	 *	initialize tuple type and projection info
@@ -344,20 +399,25 @@ ExecInitHashJoin(HashJoin *node, EState *estate, Plan *parent)
 	ExecAssignResultTypeFromTL((Plan *) node, &hjstate->jstate);
 	ExecAssignProjectionInfo((Plan *) node, &hjstate->jstate);
 
+	ExecSetSlotDescriptor(hjstate->hj_OuterTupleSlot,
+						  ExecGetTupType(outerNode));
+
 	/* ----------------
 	 *	initialize hash-specific info
 	 * ----------------
 	 */
 
-	node->hashdone = false;
+	hjstate->hj_hashdone = false;
 
 	hjstate->hj_HashTable = (HashJoinTable) NULL;
 	hjstate->hj_CurBucketNo = 0;
 	hjstate->hj_CurTuple = (HashJoinTuple) NULL;
 	hjstate->hj_InnerHashKey = (Node *) NULL;
 
-	hjstate->jstate.cs_OuterTupleSlot = (TupleTableSlot *) NULL;
+	hjstate->jstate.cs_OuterTupleSlot = NULL;
 	hjstate->jstate.cs_TupFromTlist = false;
+	hjstate->hj_NeedNewOuter = true;
+	hjstate->hj_MatchedOuter = false;
 
 	return TRUE;
 }
@@ -646,10 +706,10 @@ ExecReScanHashJoin(HashJoin *node, ExprContext *exprCtxt, Plan *parent)
 {
 	HashJoinState *hjstate = node->hashjoinstate;
 
-	if (!node->hashdone)
+	if (!hjstate->hj_hashdone)
 		return;
 
-	node->hashdone = false;
+	hjstate->hj_hashdone = false;
 
 	/*
 	 * Unfortunately, currently we have to destroy hashtable in all
@@ -667,6 +727,8 @@ ExecReScanHashJoin(HashJoin *node, ExprContext *exprCtxt, Plan *parent)
 
 	hjstate->jstate.cs_OuterTupleSlot = (TupleTableSlot *) NULL;
 	hjstate->jstate.cs_TupFromTlist = false;
+	hjstate->hj_NeedNewOuter = true;
+	hjstate->hj_MatchedOuter = false;
 
 	/*
 	 * if chgParam of subnodes is not null then plans will be re-scanned
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 5a2f45028a0..9d4ca0a8d54 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.37 2000/08/24 03:29:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.38 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,11 +16,10 @@
  * INTERFACE ROUTINES
  *		ExecMergeJoin			mergejoin outer and inner relations.
  *		ExecInitMergeJoin		creates and initializes run time states
- *		ExecEndMergeJoin		cleand up the node.
+ *		ExecEndMergeJoin		cleans up the node.
  *
  * NOTES
  *		Essential operation of the merge join algorithm is as follows:
- *		(** indicates the tuples satisfy the merge clause).
  *
  *		Join {												   -
  *			get initial outer and inner tuples				INITIALIZE
@@ -42,19 +41,19 @@
  *			}												   -
  *		}													   -
  *
- *		Skip Outer {										SKIPOUTER
+ *		Skip Outer {										SKIPOUTER_BEGIN
  *			if (inner == outer) Join Tuples					JOINTUPLES
- *			while (outer < inner)							SKIPOUTER
- *				advance outer								SKIPOUTER
- *			if (outer > inner)								SKIPOUTER
+ *			while (outer < inner)							SKIPOUTER_TEST
+ *				advance outer								SKIPOUTER_ADVANCE
+ *			if (outer > inner)								SKIPOUTER_TEST
  *				Skip Inner									SKIPINNER
  *		}													   -
  *
- *		Skip Inner {										SKIPINNER
+ *		Skip Inner {										SKIPINNER_BEGIN
  *			if (inner == outer) Join Tuples					JOINTUPLES
- *			while (outer > inner)							SKIPINNER
- *				advance inner								SKIPINNER
- *			if (outer < inner)								SKIPINNER
+ *			while (outer > inner)							SKIPINNER_TEST
+ *				advance inner								SKIPINNER_ADVANCE
+ *			if (outer < inner)								SKIPINNER_TEST
  *				Skip Outer									SKIPOUTER
  *		}													   -
  *
@@ -68,6 +67,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/printtup.h"
 #include "catalog/pg_operator.h"
 #include "executor/execdebug.h"
 #include "executor/execdefs.h"
@@ -273,52 +273,39 @@ MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext)
  * ----------------------------------------------------------------
  */
 #ifdef EXEC_MERGEJOINDEBUG
-void
-			ExecMergeTupleDumpInner(ExprContext *econtext);
 
-void
-ExecMergeTupleDumpInner(ExprContext *econtext)
+static void
+ExecMergeTupleDumpOuter(MergeJoinState *mergestate)
 {
-	TupleTableSlot *innerSlot;
+	TupleTableSlot *outerSlot = mergestate->mj_OuterTupleSlot;
 
-	printf("==== inner tuple ====\n");
-	innerSlot = econtext->ecxt_innertuple;
-	if (TupIsNull(innerSlot))
+	printf("==== outer tuple ====\n");
+	if (TupIsNull(outerSlot))
 		printf("(nil)\n");
 	else
-		MJ_debugtup(innerSlot->val,
-					innerSlot->ttc_tupleDescriptor);
+		MJ_debugtup(outerSlot->val,
+					outerSlot->ttc_tupleDescriptor);
 }
 
-void
-			ExecMergeTupleDumpOuter(ExprContext *econtext);
-
-void
-ExecMergeTupleDumpOuter(ExprContext *econtext)
+static void
+ExecMergeTupleDumpInner(MergeJoinState *mergestate)
 {
-	TupleTableSlot *outerSlot;
+	TupleTableSlot *innerSlot = mergestate->mj_InnerTupleSlot;
 
-	printf("==== outer tuple ====\n");
-	outerSlot = econtext->ecxt_outertuple;
-	if (TupIsNull(outerSlot))
+	printf("==== inner tuple ====\n");
+	if (TupIsNull(innerSlot))
 		printf("(nil)\n");
 	else
-		MJ_debugtup(outerSlot->val,
-					outerSlot->ttc_tupleDescriptor);
+		MJ_debugtup(innerSlot->val,
+					innerSlot->ttc_tupleDescriptor);
 }
 
-void ExecMergeTupleDumpMarked(ExprContext *econtext,
-						 MergeJoinState *mergestate);
-
-void
-ExecMergeTupleDumpMarked(ExprContext *econtext,
-						 MergeJoinState *mergestate)
+static void
+ExecMergeTupleDumpMarked(MergeJoinState *mergestate)
 {
-	TupleTableSlot *markedSlot;
+	TupleTableSlot *markedSlot = mergestate->mj_MarkedTupleSlot;
 
 	printf("==== marked tuple ====\n");
-	markedSlot = mergestate->mj_MarkedTupleSlot;
-
 	if (TupIsNull(markedSlot))
 		printf("(nil)\n");
 	else
@@ -326,17 +313,14 @@ ExecMergeTupleDumpMarked(ExprContext *econtext,
 					markedSlot->ttc_tupleDescriptor);
 }
 
-void
-			ExecMergeTupleDump(ExprContext *econtext, MergeJoinState *mergestate);
-
-void
-ExecMergeTupleDump(ExprContext *econtext, MergeJoinState *mergestate)
+static void
+ExecMergeTupleDump(MergeJoinState *mergestate)
 {
 	printf("******** ExecMergeTupleDump ********\n");
 
-	ExecMergeTupleDumpInner(econtext);
-	ExecMergeTupleDumpOuter(econtext);
-	ExecMergeTupleDumpMarked(econtext, mergestate);
+	ExecMergeTupleDumpOuter(mergestate);
+	ExecMergeTupleDumpInner(mergestate);
+	ExecMergeTupleDumpMarked(mergestate);
 
 	printf("******** \n");
 }
@@ -404,7 +388,8 @@ ExecMergeJoin(MergeJoin *node)
 	List	   *innerSkipQual;
 	List	   *outerSkipQual;
 	List	   *mergeclauses;
-	List	   *qual;
+	List	   *joinqual;
+	List	   *otherqual;
 	bool		qualResult;
 	bool		compareResult;
 	Plan	   *innerPlan;
@@ -412,27 +397,48 @@ ExecMergeJoin(MergeJoin *node)
 	Plan	   *outerPlan;
 	TupleTableSlot *outerTupleSlot;
 	ExprContext *econtext;
-#ifdef ENABLE_OUTER_JOINS
-	/*
-	 * These should be set from the expression context! - thomas
-	 * 1999-02-20
-	 */
-	static bool isLeftJoin = true;
-	static bool isRightJoin = false;
-#endif
+	bool		doFillOuter;
+	bool		doFillInner;
 
 	/* ----------------
 	 *	get information from node
 	 * ----------------
 	 */
 	mergestate = node->mergestate;
-	estate = node->join.state;
+	estate = node->join.plan.state;
 	direction = estate->es_direction;
 	innerPlan = innerPlan((Plan *) node);
 	outerPlan = outerPlan((Plan *) node);
 	econtext = mergestate->jstate.cs_ExprContext;
 	mergeclauses = node->mergeclauses;
-	qual = node->join.qual;
+	joinqual = node->join.joinqual;
+	otherqual = node->join.plan.qual;
+
+	switch (node->join.jointype)
+	{
+		case JOIN_INNER:
+			doFillOuter = false;
+			doFillInner = false;
+			break;
+		case JOIN_LEFT:
+			doFillOuter = true;
+			doFillInner = false;
+			break;
+		case JOIN_FULL:
+			doFillOuter = true;
+			doFillInner = true;
+			break;
+		case JOIN_RIGHT:
+			doFillOuter = false;
+			doFillInner = true;
+			break;
+		default:
+			elog(ERROR, "ExecMergeJoin: unsupported join type %d",
+				 (int) node->join.jointype);
+			doFillOuter = false; /* keep compiler quiet */
+			doFillInner = false;
+			break;
+	}
 
 	if (ScanDirectionIsForward(direction))
 	{
@@ -483,7 +489,7 @@ ExecMergeJoin(MergeJoin *node)
 		 *		  improved readability.
 		 * ----------------
 		 */
-		MJ_dump(econtext, mergestate);
+		MJ_dump(mergestate);
 
 		switch (mergestate->mj_JoinState)
 		{
@@ -491,46 +497,60 @@ ExecMergeJoin(MergeJoin *node)
 				/*
 				 * EXEC_MJ_INITIALIZE means that this is the first time
 				 * ExecMergeJoin() has been called and so we have to
-				 * initialize the inner, outer and marked tuples as well
-				 * as various stuff in the expression context.
+				 * fetch the first tuple for both outer and inner subplans.
+				 * If we fail to get a tuple here, then that subplan is
+				 * empty, and we either end the join or go to one of the
+				 * fill-remaining-tuples states.
 				 */
 			case EXEC_MJ_INITIALIZE:
 				MJ_printf("ExecMergeJoin: EXEC_MJ_INITIALIZE\n");
 
-				/*
-				 * Note: at this point, if either of our inner or outer
-				 * tuples are nil, then the join ends immediately because
-				 * we know one of the subplans is empty.
-				 */
-				innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
-				if (TupIsNull(innerTupleSlot))
+				outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+				mergestate->mj_OuterTupleSlot = outerTupleSlot;
+				if (TupIsNull(outerTupleSlot))
 				{
-					MJ_printf("ExecMergeJoin: **** inner tuple is nil ****\n");
+					MJ_printf("ExecMergeJoin: outer subplan is empty\n");
+					if (doFillInner)
+					{
+						/*
+						 * Need to emit right-join tuples for remaining
+						 * inner tuples.  We set MatchedInner = true to
+						 * force the ENDOUTER state to advance inner.
+						 */
+						mergestate->mj_JoinState = EXEC_MJ_ENDOUTER;
+						mergestate->mj_MatchedInner = true;
+						break;
+					}
+					/* Otherwise we're done. */
 					return NULL;
 				}
 
-				outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
-				if (TupIsNull(outerTupleSlot))
+				innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+				mergestate->mj_InnerTupleSlot = innerTupleSlot;
+				if (TupIsNull(innerTupleSlot))
 				{
-					MJ_printf("ExecMergeJoin: **** outer tuple is nil ****\n");
+					MJ_printf("ExecMergeJoin: inner subplan is empty\n");
+					if (doFillOuter)
+					{
+						/*
+						 * Need to emit left-join tuples for remaining
+						 * outer tuples.  We set MatchedOuter = true to
+						 * force the ENDINNER state to advance outer.
+						 */
+						mergestate->mj_JoinState = EXEC_MJ_ENDINNER;
+						mergestate->mj_MatchedOuter = true;
+						break;
+					}
+					/* Otherwise we're done. */
 					return NULL;
 				}
 
 				/* ----------------
-				 *	 store the inner and outer tuple in the merge state
+				 *	OK, we have the initial tuples.  Begin by skipping
+				 *	unmatched inner tuples.
 				 * ----------------
 				 */
-				econtext->ecxt_innertuple = innerTupleSlot;
-				econtext->ecxt_outertuple = outerTupleSlot;
-
-				mergestate->mj_MarkedTupleSlot->ttc_tupleDescriptor =
-					innerTupleSlot->ttc_tupleDescriptor;
-
-				/* ----------------
-				 *	initialize merge join state to skip inner tuples.
-				 * ----------------
-				 */
-				mergestate->mj_JoinState = EXEC_MJ_SKIPINNER;
+				mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_BEGIN;
 				break;
 
 				/*
@@ -541,9 +561,10 @@ ExecMergeJoin(MergeJoin *node)
 				 */
 			case EXEC_MJ_JOINMARK:
 				MJ_printf("ExecMergeJoin: EXEC_MJ_JOINMARK\n");
+
 				ExecMarkPos(innerPlan);
 
-				MarkInnerTuple(econtext->ecxt_innertuple, mergestate);
+				MarkInnerTuple(mergestate->mj_InnerTupleSlot, mergestate);
 
 				mergestate->mj_JoinState = EXEC_MJ_JOINTEST;
 				break;
@@ -562,7 +583,12 @@ ExecMergeJoin(MergeJoin *node)
 
 				ResetExprContext(econtext);
 
-				qualResult = ExecQual((List *) mergeclauses, econtext, false);
+				outerTupleSlot = mergestate->mj_OuterTupleSlot;
+				econtext->ecxt_outertuple = outerTupleSlot;
+				innerTupleSlot = mergestate->mj_InnerTupleSlot;
+				econtext->ecxt_innertuple = innerTupleSlot;
+
+				qualResult = ExecQual(mergeclauses, econtext, false);
 				MJ_DEBUG_QUAL(mergeclauses, qualResult);
 
 				if (qualResult)
@@ -578,38 +604,57 @@ ExecMergeJoin(MergeJoin *node)
 				 */
 			case EXEC_MJ_JOINTUPLES:
 				MJ_printf("ExecMergeJoin: EXEC_MJ_JOINTUPLES\n");
+
 				mergestate->mj_JoinState = EXEC_MJ_NEXTINNER;
 
 				/*
-				 * Check the qpqual to see if we actually want to return
-				 * this join tuple.  If not, can proceed with merge.
+				 * Check the extra qual conditions to see if we actually
+				 * want to return this join tuple.  If not, can proceed with
+				 * merge.  We must distinguish the additional joinquals
+				 * (which must pass to consider the tuples "matched" for
+				 * outer-join logic) from the otherquals (which must pass
+				 * before we actually return the tuple).
 				 *
-				 * (We don't bother with a ResetExprContext here, on the
+				 * We don't bother with a ResetExprContext here, on the
 				 * assumption that we just did one before checking the merge
-				 * qual.  One per tuple should be sufficient.)
+				 * qual.  One per tuple should be sufficient.  Also, the
+				 * econtext's tuple pointers were set up before checking
+				 * the merge qual, so we needn't do it again.
 				 */
-				qualResult = ExecQual((List *) qual, econtext, false);
-				MJ_DEBUG_QUAL(qual, qualResult);
+				qualResult = (joinqual == NIL ||
+							  ExecQual(joinqual, econtext, false));
+				MJ_DEBUG_QUAL(joinqual, qualResult);
 
 				if (qualResult)
 				{
-					/* ----------------
-					 *	qualification succeeded.  now form the desired
-					 *	projection tuple and return the slot containing it.
-					 * ----------------
-					 */
-					TupleTableSlot *result;
-					ExprDoneCond isDone;
+					mergestate->mj_MatchedOuter = true;
+					mergestate->mj_MatchedInner = true;
 
-					MJ_printf("ExecMergeJoin: **** returning tuple ****\n");
+					qualResult = (otherqual == NIL ||
+								  ExecQual(otherqual, econtext, false));
+					MJ_DEBUG_QUAL(otherqual, qualResult);
 
-					result = ExecProject(mergestate->jstate.cs_ProjInfo,
-										 &isDone);
-
-					if (isDone != ExprEndResult)
+					if (qualResult)
 					{
-						mergestate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
-						return result;
+						/* ----------------
+						 *	qualification succeeded.  now form the desired
+						 *	projection tuple and return the slot containing it.
+						 * ----------------
+						 */
+						TupleTableSlot *result;
+						ExprDoneCond isDone;
+
+						MJ_printf("ExecMergeJoin: returning tuple\n");
+
+						result = ExecProject(mergestate->jstate.cs_ProjInfo,
+											 &isDone);
+
+						if (isDone != ExprEndResult)
+						{
+							mergestate->jstate.cs_TupFromTlist =
+								(isDone == ExprMultipleResult);
+							return result;
+						}
 					}
 				}
 				break;
@@ -618,17 +663,60 @@ ExecMergeJoin(MergeJoin *node)
 				 * EXEC_MJ_NEXTINNER means advance the inner scan to the
 				 * next tuple. If the tuple is not nil, we then proceed to
 				 * test it against the join qualification.
+				 *
+				 * Before advancing, we check to see if we must emit an
+				 * outer-join fill tuple for this inner tuple.
 				 */
 			case EXEC_MJ_NEXTINNER:
 				MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTINNER\n");
 
+				if (doFillInner && !mergestate->mj_MatchedInner)
+				{
+					/*
+					 * Generate a fake join tuple with nulls for the outer
+					 * tuple, and return it if it passes the non-join quals.
+					 */
+					mergestate->mj_MatchedInner = true;	/* do it only once */
+
+					ResetExprContext(econtext);
+
+					outerTupleSlot = mergestate->mj_NullOuterTupleSlot;
+					econtext->ecxt_outertuple = outerTupleSlot;
+					innerTupleSlot = mergestate->mj_InnerTupleSlot;
+					econtext->ecxt_innertuple = innerTupleSlot;
+
+					if (ExecQual(otherqual, econtext, false))
+					{
+						/* ----------------
+						 *	qualification succeeded.  now form the desired
+						 *	projection tuple and return the slot containing it.
+						 * ----------------
+						 */
+						TupleTableSlot *result;
+						ExprDoneCond isDone;
+
+						MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+						result = ExecProject(mergestate->jstate.cs_ProjInfo,
+											 &isDone);
+
+						if (isDone != ExprEndResult)
+						{
+							mergestate->jstate.cs_TupFromTlist =
+								(isDone == ExprMultipleResult);
+							return result;
+						}
+					}
+				}
+
 				/* ----------------
 				 *	now we get the next inner tuple, if any
 				 * ----------------
 				 */
 				innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+				mergestate->mj_InnerTupleSlot = innerTupleSlot;
 				MJ_DEBUG_PROC_NODE(innerTupleSlot);
-				econtext->ecxt_innertuple = innerTupleSlot;
+				mergestate->mj_MatchedInner = false;
 
 				if (TupIsNull(innerTupleSlot))
 					mergestate->mj_JoinState = EXEC_MJ_NEXTOUTER;
@@ -650,23 +738,81 @@ ExecMergeJoin(MergeJoin *node)
 				 * so get a new outer tuple and then
 				 * proceed to test it against the marked tuple
 				 * (EXEC_MJ_TESTOUTER)
+				 *
+				 * Before advancing, we check to see if we must emit an
+				 * outer-join fill tuple for this outer tuple.
 				 *------------------------------------------------
 				 */
 			case EXEC_MJ_NEXTOUTER:
 				MJ_printf("ExecMergeJoin: EXEC_MJ_NEXTOUTER\n");
 
+				if (doFillOuter && !mergestate->mj_MatchedOuter)
+				{
+					/*
+					 * Generate a fake join tuple with nulls for the inner
+					 * tuple, and return it if it passes the non-join quals.
+					 */
+					mergestate->mj_MatchedOuter = true;	/* do it only once */
+
+					ResetExprContext(econtext);
+
+					outerTupleSlot = mergestate->mj_OuterTupleSlot;
+					econtext->ecxt_outertuple = outerTupleSlot;
+					innerTupleSlot = mergestate->mj_NullInnerTupleSlot;
+					econtext->ecxt_innertuple = innerTupleSlot;
+
+					if (ExecQual(otherqual, econtext, false))
+					{
+						/* ----------------
+						 *	qualification succeeded.  now form the desired
+						 *	projection tuple and return the slot containing it.
+						 * ----------------
+						 */
+						TupleTableSlot *result;
+						ExprDoneCond isDone;
+
+						MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+						result = ExecProject(mergestate->jstate.cs_ProjInfo,
+											 &isDone);
+
+						if (isDone != ExprEndResult)
+						{
+							mergestate->jstate.cs_TupFromTlist =
+								(isDone == ExprMultipleResult);
+							return result;
+						}
+					}
+				}
+
+				/* ----------------
+				 *	now we get the next outer tuple, if any
+				 * ----------------
+				 */
 				outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+				mergestate->mj_OuterTupleSlot = outerTupleSlot;
 				MJ_DEBUG_PROC_NODE(outerTupleSlot);
-				econtext->ecxt_outertuple = outerTupleSlot;
+				mergestate->mj_MatchedOuter = false;
 
 				/* ----------------
-				 *	if the outer tuple is null then we know
-				 *	we are done with the join
+				 *	if the outer tuple is null then we are done with the
+				 *	join, unless we have inner tuples we need to null-fill.
 				 * ----------------
 				 */
 				if (TupIsNull(outerTupleSlot))
 				{
-					MJ_printf("ExecMergeJoin: **** outer tuple is nil ****\n");
+					MJ_printf("ExecMergeJoin: end of outer subplan\n");
+					innerTupleSlot = mergestate->mj_InnerTupleSlot;
+					if (doFillInner && !TupIsNull(innerTupleSlot))
+					{
+						/*
+						 * Need to emit right-join tuples for remaining
+						 * inner tuples.
+						 */
+						mergestate->mj_JoinState = EXEC_MJ_ENDOUTER;
+						break;
+					}
+					/* Otherwise we're done. */
 					return NULL;
 				}
 
@@ -712,39 +858,45 @@ ExecMergeJoin(MergeJoin *node)
 
 				/* ----------------
 				 *	here we compare the outer tuple with the marked inner tuple
-				 *	by using the marked tuple in place of the inner tuple.
 				 * ----------------
 				 */
-				innerTupleSlot = econtext->ecxt_innertuple;
-				econtext->ecxt_innertuple = mergestate->mj_MarkedTupleSlot;
-
 				ResetExprContext(econtext);
 
-				qualResult = ExecQual((List *) mergeclauses, econtext, false);
+				outerTupleSlot = mergestate->mj_OuterTupleSlot;
+				econtext->ecxt_outertuple = outerTupleSlot;
+				innerTupleSlot = mergestate->mj_MarkedTupleSlot;
+				econtext->ecxt_innertuple = innerTupleSlot;
+
+				qualResult = ExecQual(mergeclauses, econtext, false);
 				MJ_DEBUG_QUAL(mergeclauses, qualResult);
 
 				if (qualResult)
 				{
 
 					/*
-					 * the merge clause matched so now we juggle the slots
-					 * back the way they were and proceed to JOINTEST.
+					 * the merge clause matched so now we restore the inner
+					 * scan position to the first mark, and loop back to
+					 * JOINTEST.  Actually, since we know the mergeclause
+					 * matches, we can skip JOINTEST and go straight to
+					 * JOINTUPLES.
 					 *
-					 * I can't understand why we have to go to JOINTEST and
-					 * compare outer tuple with the same inner one again
-					 * -> go to JOINTUPLES...	 - vadim 02/27/98
+					 * NOTE: we do not need to worry about the MatchedInner
+					 * state for the rescanned inner tuples.  We know all
+					 * of them will match this new outer tuple and therefore
+					 * won't be emitted as fill tuples.  This works *only*
+					 * because we require the extra joinquals to be nil when
+					 * doing a right or full join --- otherwise some of the
+					 * rescanned tuples might fail the extra joinquals.
 					 */
-
 					ExecRestrPos(innerPlan);
 					mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES;
 				}
 				else
 				{
-					econtext->ecxt_innertuple = innerTupleSlot;
 					/* ----------------
 					 *	if the inner tuple was nil and the new outer
 					 *	tuple didn't match the marked outer tuple then
-					 *	we may have the case:
+					 *	we have the case:
 					 *
 					 *			 outer inner
 					 *			   4	 4	- marked tuple
@@ -753,31 +905,33 @@ ExecMergeJoin(MergeJoin *node)
 					 *			   7
 					 *
 					 *	which means that all subsequent outer tuples will be
-					 *	larger than our inner tuples.
+					 *	larger than our marked inner tuples.  So we're done.
 					 * ----------------
 					 */
+					innerTupleSlot = mergestate->mj_InnerTupleSlot;
 					if (TupIsNull(innerTupleSlot))
 					{
-#ifdef ENABLE_OUTER_JOINS
-						if (isLeftJoin)
+						if (doFillOuter)
 						{
-							/* continue on to null fill outer tuples */
-							mergestate->mj_JoinState = EXEC_MJ_FILLOUTER;
+							/*
+							 * Need to emit left-join tuples for remaining
+							 * outer tuples.
+							 */
+							mergestate->mj_JoinState = EXEC_MJ_ENDINNER;
 							break;
 						}
-#endif
-						MJ_printf("ExecMergeJoin: **** weird case 1 ****\n");
+						/* Otherwise we're done. */
 						return NULL;
 					}
 
 					/* continue on to skip outer tuples */
-					mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER;
+					mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_BEGIN;
 				}
 				break;
 
 				/*----------------------------------------------------------
 				 * EXEC_MJ_SKIPOUTER means skip over tuples in the outer plan
-				 * until we find an outer tuple > current inner tuple.
+				 * until we find an outer tuple >= current inner tuple.
 				 *
 				 * For example:
 				 *
@@ -790,10 +944,14 @@ ExecMergeJoin(MergeJoin *node)
 				 *
 				 * we have to advance the outer scan
 				 * until we find the outer 8.
+				 *
+				 * To avoid redundant tests, we divide this into three
+				 * sub-states: BEGIN, TEST, ADVANCE.
 				 *----------------------------------------------------------
 				 */
-			case EXEC_MJ_SKIPOUTER:
-				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER\n");
+			case EXEC_MJ_SKIPOUTER_BEGIN:
+				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_BEGIN\n");
+
 				/* ----------------
 				 *	before we advance, make sure the current tuples
 				 *	do not satisfy the mergeclauses.  If they do, then
@@ -802,23 +960,39 @@ ExecMergeJoin(MergeJoin *node)
 				 */
 				ResetExprContext(econtext);
 
-				qualResult = ExecQual((List *) mergeclauses, econtext, false);
+				outerTupleSlot = mergestate->mj_OuterTupleSlot;
+				econtext->ecxt_outertuple = outerTupleSlot;
+				innerTupleSlot = mergestate->mj_InnerTupleSlot;
+				econtext->ecxt_innertuple = innerTupleSlot;
+
+				qualResult = ExecQual(mergeclauses, econtext, false);
 				MJ_DEBUG_QUAL(mergeclauses, qualResult);
 
 				if (qualResult)
 				{
 					ExecMarkPos(innerPlan);
 
-					MarkInnerTuple(econtext->ecxt_innertuple, mergestate);
+					MarkInnerTuple(innerTupleSlot, mergestate);
 
 					mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES;
 					break;
 				}
 
+				mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_TEST;
+				break;
+
+			case EXEC_MJ_SKIPOUTER_TEST:
+				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_TEST\n");
+
 				/* ----------------
 				 *	ok, now test the skip qualification
 				 * ----------------
 				 */
+				outerTupleSlot = mergestate->mj_OuterTupleSlot;
+				econtext->ecxt_outertuple = outerTupleSlot;
+				innerTupleSlot = mergestate->mj_InnerTupleSlot;
+				econtext->ecxt_innertuple = innerTupleSlot;
+
 				compareResult = MergeCompare(mergeclauses,
 											 outerSkipQual,
 											 econtext);
@@ -827,42 +1001,12 @@ ExecMergeJoin(MergeJoin *node)
 
 				/* ----------------
 				 *	compareResult is true as long as we should
-				 *	continue skipping tuples.
+				 *	continue skipping outer tuples.
 				 * ----------------
 				 */
 				if (compareResult)
 				{
-#ifdef ENABLE_OUTER_JOINS
-					/* ----------------
-					 *	if this is a left or full outer join, then fill
-					 * ----------------
-					 */
-					if (isLeftJoin)
-					{
-						mergestate->mj_JoinState = EXEC_MJ_FILLOUTER;
-						break;
-					}
-#endif
-
-					outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
-					MJ_DEBUG_PROC_NODE(outerTupleSlot);
-					econtext->ecxt_outertuple = outerTupleSlot;
-
-					/* ----------------
-					 *	if the outer tuple is null then we know
-					 *	we are done with the join
-					 * ----------------
-					 */
-					if (TupIsNull(outerTupleSlot))
-					{
-						MJ_printf("ExecMergeJoin: **** outerTuple is nil ****\n");
-						return NULL;
-					}
-					/* ----------------
-					 *	otherwise test the new tuple against the skip qual.
-					 *	(we remain in the EXEC_MJ_SKIPOUTER state)
-					 * ----------------
-					 */
+					mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_ADVANCE;
 					break;
 				}
 
@@ -880,14 +1024,99 @@ ExecMergeJoin(MergeJoin *node)
 				MJ_DEBUG_MERGE_COMPARE(innerSkipQual, compareResult);
 
 				if (compareResult)
-					mergestate->mj_JoinState = EXEC_MJ_SKIPINNER;
+					mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_BEGIN;
 				else
 					mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
 				break;
 
+				/*------------------------------------------------
+				 * Before advancing, we check to see if we must emit an
+				 * outer-join fill tuple for this outer tuple.
+				 *------------------------------------------------
+				 */
+			case EXEC_MJ_SKIPOUTER_ADVANCE:
+				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPOUTER_ADVANCE\n");
+
+				if (doFillOuter && !mergestate->mj_MatchedOuter)
+				{
+					/*
+					 * Generate a fake join tuple with nulls for the inner
+					 * tuple, and return it if it passes the non-join quals.
+					 */
+					mergestate->mj_MatchedOuter = true;	/* do it only once */
+
+					ResetExprContext(econtext);
+
+					outerTupleSlot = mergestate->mj_OuterTupleSlot;
+					econtext->ecxt_outertuple = outerTupleSlot;
+					innerTupleSlot = mergestate->mj_NullInnerTupleSlot;
+					econtext->ecxt_innertuple = innerTupleSlot;
+
+					if (ExecQual(otherqual, econtext, false))
+					{
+						/* ----------------
+						 *	qualification succeeded.  now form the desired
+						 *	projection tuple and return the slot containing it.
+						 * ----------------
+						 */
+						TupleTableSlot *result;
+						ExprDoneCond isDone;
+
+						MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+						result = ExecProject(mergestate->jstate.cs_ProjInfo,
+											 &isDone);
+
+						if (isDone != ExprEndResult)
+						{
+							mergestate->jstate.cs_TupFromTlist =
+								(isDone == ExprMultipleResult);
+							return result;
+						}
+					}
+				}
+
+				/* ----------------
+				 *	now we get the next outer tuple, if any
+				 * ----------------
+				 */
+				outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+				mergestate->mj_OuterTupleSlot = outerTupleSlot;
+				MJ_DEBUG_PROC_NODE(outerTupleSlot);
+				mergestate->mj_MatchedOuter = false;
+
+				/* ----------------
+				 *	if the outer tuple is null then we are done with the
+				 *	join, unless we have inner tuples we need to null-fill.
+				 * ----------------
+				 */
+				if (TupIsNull(outerTupleSlot))
+				{
+					MJ_printf("ExecMergeJoin: end of outer subplan\n");
+					innerTupleSlot = mergestate->mj_InnerTupleSlot;
+					if (doFillInner && !TupIsNull(innerTupleSlot))
+					{
+						/*
+						 * Need to emit right-join tuples for remaining
+						 * inner tuples.
+						 */
+						mergestate->mj_JoinState = EXEC_MJ_ENDOUTER;
+						break;
+					}
+					/* Otherwise we're done. */
+					return NULL;
+				}
+
+				/* ----------------
+				 *	otherwise test the new tuple against the skip qual.
+				 * ----------------
+				 */
+				mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_TEST;
+				break;
+
 				/*-----------------------------------------------------------
 				 * EXEC_MJ_SKIPINNER means skip over tuples in the inner plan
-				 * until we find an inner tuple > current outer tuple.
+				 * until we find an inner tuple >= current outer tuple.
 				 *
 				 * For example:
 				 *
@@ -901,10 +1130,13 @@ ExecMergeJoin(MergeJoin *node)
 				 * we have to advance the inner scan
 				 * until we find the inner 12.
 				 *
+				 * To avoid redundant tests, we divide this into three
+				 * sub-states: BEGIN, TEST, ADVANCE.
 				 *-------------------------------------------------------
 				 */
-			case EXEC_MJ_SKIPINNER:
-				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER\n");
+			case EXEC_MJ_SKIPINNER_BEGIN:
+				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_BEGIN\n");
+
 				/* ----------------
 				 *	before we advance, make sure the current tuples
 				 *	do not satisfy the mergeclauses.  If they do, then
@@ -913,23 +1145,39 @@ ExecMergeJoin(MergeJoin *node)
 				 */
 				ResetExprContext(econtext);
 
-				qualResult = ExecQual((List *) mergeclauses, econtext, false);
+				outerTupleSlot = mergestate->mj_OuterTupleSlot;
+				econtext->ecxt_outertuple = outerTupleSlot;
+				innerTupleSlot = mergestate->mj_InnerTupleSlot;
+				econtext->ecxt_innertuple = innerTupleSlot;
+
+				qualResult = ExecQual(mergeclauses, econtext, false);
 				MJ_DEBUG_QUAL(mergeclauses, qualResult);
 
 				if (qualResult)
 				{
 					ExecMarkPos(innerPlan);
 
-					MarkInnerTuple(econtext->ecxt_innertuple, mergestate);
+					MarkInnerTuple(innerTupleSlot, mergestate);
 
 					mergestate->mj_JoinState = EXEC_MJ_JOINTUPLES;
 					break;
 				}
 
+				mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_TEST;
+				break;
+
+			case EXEC_MJ_SKIPINNER_TEST:
+				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_TEST\n");
+
 				/* ----------------
 				 *	ok, now test the skip qualification
 				 * ----------------
 				 */
+				outerTupleSlot = mergestate->mj_OuterTupleSlot;
+				econtext->ecxt_outertuple = outerTupleSlot;
+				innerTupleSlot = mergestate->mj_InnerTupleSlot;
+				econtext->ecxt_innertuple = innerTupleSlot;
+
 				compareResult = MergeCompare(mergeclauses,
 											 innerSkipQual,
 											 econtext);
@@ -938,70 +1186,20 @@ ExecMergeJoin(MergeJoin *node)
 
 				/* ----------------
 				 *	compareResult is true as long as we should
-				 *	continue skipping tuples.
+				 *	continue skipping inner tuples.
 				 * ----------------
 				 */
 				if (compareResult)
 				{
-#ifdef ENABLE_OUTER_JOINS
-					/* ----------------
-					 *	if this is a right or full outer join, then fill
-					 * ----------------
-					 */
-					if (isRightJoin)
-					{
-						mergestate->mj_JoinState = EXEC_MJ_FILLINNER;
-						break;
-					}
-#endif
-
-					/* ----------------
-					 *	now try and get a new inner tuple
-					 * ----------------
-					 */
-					innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
-					MJ_DEBUG_PROC_NODE(innerTupleSlot);
-					econtext->ecxt_innertuple = innerTupleSlot;
-
-					/* ----------------
-					 *	if the inner tuple is null then we know
-					 *	we have to restore the inner scan
-					 *	and advance to the next outer tuple
-					 * ----------------
-					 */
-					if (TupIsNull(innerTupleSlot))
-					{
-						/* ----------------
-						 *	this is an interesting case.. all our
-						 *	inner tuples are smaller then our outer
-						 *	tuples so we never found an inner tuple
-						 *	to mark.
-						 *
-						 *			  outer inner
-						 *	 outer tuple -	5	  4
-						 *					5	  4
-						 *					6	 nil  - inner tuple
-						 *					7
-						 *
-						 *	This means the join should end.
-						 * ----------------
-						 */
-						MJ_printf("ExecMergeJoin: **** weird case 2 ****\n");
-						return NULL;
-					}
-
-					/* ----------------
-					 *	otherwise test the new tuple against the skip qual.
-					 *	(we remain in the EXEC_MJ_SKIPINNER state)
-					 * ----------------
-					 */
+					mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_ADVANCE;
 					break;
 				}
 
 				/* ----------------
-				 *	compare finally failed and we have stopped skipping
-				 *	inner tuples so now check the outer skip qual
-				 *	to see if we should now skip outer tuples...
+				 *	now check the outer skip qual to see if we
+				 *	should now skip outer tuples... if we fail the
+				 *	outer skip qual, then we know we have a new pair
+				 *	of matching tuples.
 				 * ----------------
 				 */
 				compareResult = MergeCompare(mergeclauses,
@@ -1011,120 +1209,237 @@ ExecMergeJoin(MergeJoin *node)
 				MJ_DEBUG_MERGE_COMPARE(outerSkipQual, compareResult);
 
 				if (compareResult)
-					mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER;
+					mergestate->mj_JoinState = EXEC_MJ_SKIPOUTER_BEGIN;
 				else
 					mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
-
 				break;
 
-#ifdef ENABLE_OUTER_JOINS
-
-				/*
-				 * EXEC_MJ_FILLINNER means we have an unmatched inner
-				 * tuple which must be null-expanded into the projection
-				 * tuple. get the next inner tuple and reset markers
-				 * (EXEC_MJ_JOINMARK).
+				/*------------------------------------------------
+				 * Before advancing, we check to see if we must emit an
+				 * outer-join fill tuple for this inner tuple.
+				 *------------------------------------------------
 				 */
-			case EXEC_MJ_FILLINNER:
-				MJ_printf("ExecMergeJoin: EXEC_MJ_FILLINNER\n");
-				mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
+			case EXEC_MJ_SKIPINNER_ADVANCE:
+				MJ_printf("ExecMergeJoin: EXEC_MJ_SKIPINNER_ADVANCE\n");
 
-				/* ----------------
-				 *	project the inner tuple into the result
-				 * ----------------
-				 */
-				MJ_printf("ExecMergeJoin: project inner tuple into the result (not yet implemented)\n");
+				if (doFillInner && !mergestate->mj_MatchedInner)
+				{
+					/*
+					 * Generate a fake join tuple with nulls for the outer
+					 * tuple, and return it if it passes the non-join quals.
+					 */
+					mergestate->mj_MatchedInner = true;	/* do it only once */
+
+					ResetExprContext(econtext);
+
+					outerTupleSlot = mergestate->mj_NullOuterTupleSlot;
+					econtext->ecxt_outertuple = outerTupleSlot;
+					innerTupleSlot = mergestate->mj_InnerTupleSlot;
+					econtext->ecxt_innertuple = innerTupleSlot;
+
+					if (ExecQual(otherqual, econtext, false))
+					{
+						/* ----------------
+						 *	qualification succeeded.  now form the desired
+						 *	projection tuple and return the slot containing it.
+						 * ----------------
+						 */
+						TupleTableSlot *result;
+						ExprDoneCond isDone;
+
+						MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+						result = ExecProject(mergestate->jstate.cs_ProjInfo,
+											 &isDone);
+
+						if (isDone != ExprEndResult)
+						{
+							mergestate->jstate.cs_TupFromTlist =
+								(isDone == ExprMultipleResult);
+							return result;
+						}
+					}
+				}
 
 				/* ----------------
-				 *	now skip this inner tuple
+				 *	now we get the next inner tuple, if any
 				 * ----------------
 				 */
 				innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+				mergestate->mj_InnerTupleSlot = innerTupleSlot;
 				MJ_DEBUG_PROC_NODE(innerTupleSlot);
-				econtext->ecxt_innertuple = innerTupleSlot;
+				mergestate->mj_MatchedInner = false;
 
 				/* ----------------
-				 *	if the inner tuple is null then we know
-				 *	we have to restore the inner scan
-				 *	and advance to the next outer tuple
+				 *	if the inner tuple is null then we are done with the
+				 *	join, unless we have outer tuples we need to null-fill.
 				 * ----------------
 				 */
 				if (TupIsNull(innerTupleSlot))
 				{
-					if (isLeftJoin && !TupIsNull(outerTupleSlot))
+					MJ_printf("ExecMergeJoin: end of inner subplan\n");
+					outerTupleSlot = mergestate->mj_OuterTupleSlot;
+					if (doFillOuter && !TupIsNull(outerTupleSlot))
 					{
-						mergestate->mj_JoinState = EXEC_MJ_FILLOUTER;
-						MJ_printf("ExecMergeJoin: try to complete outer fill\n");
+						/*
+						 * Need to emit left-join tuples for remaining
+						 * outer tuples.
+						 */
+						mergestate->mj_JoinState = EXEC_MJ_ENDINNER;
 						break;
 					}
-
-					MJ_printf("ExecMergeJoin: **** weird case 2 ****\n");
+					/* Otherwise we're done. */
 					return NULL;
 				}
 
 				/* ----------------
 				 *	otherwise test the new tuple against the skip qual.
-				 *	(we move to the EXEC_MJ_JOINMARK state)
 				 * ----------------
 				 */
+				mergestate->mj_JoinState = EXEC_MJ_SKIPINNER_TEST;
 				break;
 
 				/*
-				 * EXEC_MJ_FILLOUTER means we have an unmatched outer
-				 * tuple which must be null-expanded into the projection
-				 * tuple. get the next outer tuple and reset markers
-				 * (EXEC_MJ_JOINMARK).
+				 * EXEC_MJ_ENDOUTER means we have run out of outer tuples,
+				 * but are doing a right/full join and therefore must null-
+				 * fill any remaing unmatched inner tuples.
 				 */
-			case EXEC_MJ_FILLOUTER:
-				MJ_printf("ExecMergeJoin: EXEC_MJ_FILLOUTER\n");
-				mergestate->mj_JoinState = EXEC_MJ_JOINMARK;
+			case EXEC_MJ_ENDOUTER:
+				MJ_printf("ExecMergeJoin: EXEC_MJ_ENDOUTER\n");
+
+				Assert(doFillInner);
+
+				if (!mergestate->mj_MatchedInner)
+				{
+					/*
+					 * Generate a fake join tuple with nulls for the outer
+					 * tuple, and return it if it passes the non-join quals.
+					 */
+					mergestate->mj_MatchedInner = true;	/* do it only once */
+
+					ResetExprContext(econtext);
+
+					outerTupleSlot = mergestate->mj_NullOuterTupleSlot;
+					econtext->ecxt_outertuple = outerTupleSlot;
+					innerTupleSlot = mergestate->mj_InnerTupleSlot;
+					econtext->ecxt_innertuple = innerTupleSlot;
+
+					if (ExecQual(otherqual, econtext, false))
+					{
+						/* ----------------
+						 *	qualification succeeded.  now form the desired
+						 *	projection tuple and return the slot containing it.
+						 * ----------------
+						 */
+						TupleTableSlot *result;
+						ExprDoneCond isDone;
+
+						MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+						result = ExecProject(mergestate->jstate.cs_ProjInfo,
+											 &isDone);
+
+						if (isDone != ExprEndResult)
+						{
+							mergestate->jstate.cs_TupFromTlist =
+								(isDone == ExprMultipleResult);
+							return result;
+						}
+					}
+				}
 
 				/* ----------------
-				 *	project the outer tuple into the result
+				 *	now we get the next inner tuple, if any
 				 * ----------------
 				 */
-				MJ_printf("ExecMergeJoin: project outer tuple into the result (not yet implemented)\n");
+				innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+				mergestate->mj_InnerTupleSlot = innerTupleSlot;
+				MJ_DEBUG_PROC_NODE(innerTupleSlot);
+				mergestate->mj_MatchedInner = false;
+
+				if (TupIsNull(innerTupleSlot))
+				{
+					MJ_printf("ExecMergeJoin: end of inner subplan\n");
+					return NULL;
+				}
+
+				/* Else remain in ENDOUTER state and process next tuple. */
+				break;
+
+				/*
+				 * EXEC_MJ_ENDINNER means we have run out of inner tuples,
+				 * but are doing a left/full join and therefore must null-
+				 * fill any remaing unmatched outer tuples.
+				 */
+			case EXEC_MJ_ENDINNER:
+				MJ_printf("ExecMergeJoin: EXEC_MJ_ENDINNER\n");
+
+				Assert(doFillOuter);
+
+				if (!mergestate->mj_MatchedOuter)
+				{
+					/*
+					 * Generate a fake join tuple with nulls for the inner
+					 * tuple, and return it if it passes the non-join quals.
+					 */
+					mergestate->mj_MatchedOuter = true;	/* do it only once */
+
+					ResetExprContext(econtext);
+
+					outerTupleSlot = mergestate->mj_OuterTupleSlot;
+					econtext->ecxt_outertuple = outerTupleSlot;
+					innerTupleSlot = mergestate->mj_NullInnerTupleSlot;
+					econtext->ecxt_innertuple = innerTupleSlot;
+
+					if (ExecQual(otherqual, econtext, false))
+					{
+						/* ----------------
+						 *	qualification succeeded.  now form the desired
+						 *	projection tuple and return the slot containing it.
+						 * ----------------
+						 */
+						TupleTableSlot *result;
+						ExprDoneCond isDone;
+
+						MJ_printf("ExecMergeJoin: returning fill tuple\n");
+
+						result = ExecProject(mergestate->jstate.cs_ProjInfo,
+											 &isDone);
+
+						if (isDone != ExprEndResult)
+						{
+							mergestate->jstate.cs_TupFromTlist =
+								(isDone == ExprMultipleResult);
+							return result;
+						}
+					}
+				}
 
 				/* ----------------
-				 *	now skip this outer tuple
+				 *	now we get the next outer tuple, if any
 				 * ----------------
 				 */
 				outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
+				mergestate->mj_OuterTupleSlot = outerTupleSlot;
 				MJ_DEBUG_PROC_NODE(outerTupleSlot);
-				econtext->ecxt_outertuple = outerTupleSlot;
+				mergestate->mj_MatchedOuter = false;
 
-				/* ----------------
-				 *	if the outer tuple is null then we know
-				 *	we are done with the left half of the join
-				 * ----------------
-				 */
 				if (TupIsNull(outerTupleSlot))
 				{
-					if (isRightJoin && !TupIsNull(innerTupleSlot))
-					{
-						mergestate->mj_JoinState = EXEC_MJ_FILLINNER;
-						MJ_printf("ExecMergeJoin: try to complete inner fill\n");
-						break;
-					}
-
-					MJ_printf("ExecMergeJoin: **** outerTuple is nil ****\n");
+					MJ_printf("ExecMergeJoin: end of outer subplan\n");
 					return NULL;
 				}
 
-				/* ----------------
-				 *	otherwise test the new tuple against the skip qual.
-				 *	(we move to the EXEC_MJ_JOINMARK state)
-				 * ----------------
-				 */
+				/* Else remain in ENDINNER state and process next tuple. */
 				break;
-#endif
 
 				/*
 				 * if we get here it means our code is fouled up and so we
 				 * just end the join prematurely.
 				 */
 			default:
-				elog(NOTICE, "ExecMergeJoin: invalid join state. aborting");
+				elog(NOTICE, "ExecMergeJoin: invalid join state %d, aborting",
+					 mergestate->mj_JoinState);
 				return NULL;
 		}
 	}
@@ -1143,7 +1458,6 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
 {
 	MergeJoinState *mergestate;
 	List	   *joinclauses;
-	TupleTableSlot *mjSlot;
 
 	MJ1_printf("ExecInitMergeJoin: %s\n",
 			   "initializing node");
@@ -1153,17 +1467,13 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
 	 *	get the range table and direction from it
 	 * ----------------
 	 */
-	node->join.state = estate;
+	node->join.plan.state = estate;
 
 	/* ----------------
 	 *	create new merge state for node
 	 * ----------------
 	 */
 	mergestate = makeNode(MergeJoinState);
-	mergestate->mj_OuterSkipQual = NIL;
-	mergestate->mj_InnerSkipQual = NIL;
-	mergestate->mj_JoinState = 0;
-	mergestate->mj_MarkedTupleSlot = NULL;
 	node->mergestate = mergestate;
 
 	/* ----------------
@@ -1174,22 +1484,67 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
 	 */
 	ExecAssignExprContext(estate, &mergestate->jstate);
 
-#define MERGEJOIN_NSLOTS 2
+	/* ----------------
+	 *	initialize subplans
+	 * ----------------
+	 */
+	ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
+	ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
+
+#define MERGEJOIN_NSLOTS 4
 	/* ----------------
 	 *	tuple table initialization
-	 *
-	 *	XXX why aren't we getting a tuple table slot in the normal way?
 	 * ----------------
 	 */
 	ExecInitResultTupleSlot(estate, &mergestate->jstate);
-	mjSlot = makeNode(TupleTableSlot);
-	mjSlot->val = NULL;
-	mjSlot->ttc_shouldFree = true;
-	mjSlot->ttc_descIsNew = true;
-	mjSlot->ttc_tupleDescriptor = NULL;
-	mjSlot->ttc_buffer = InvalidBuffer;
-	mjSlot->ttc_whichplan = -1;
-	mergestate->mj_MarkedTupleSlot = mjSlot;
+
+	mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(estate);
+	ExecSetSlotDescriptor(mergestate->mj_MarkedTupleSlot,
+						  ExecGetTupType(innerPlan((Plan *) node)));
+
+	switch (node->join.jointype)
+	{
+		case JOIN_INNER:
+			break;
+		case JOIN_LEFT:
+			mergestate->mj_NullInnerTupleSlot =
+				ExecInitNullTupleSlot(estate,
+									  ExecGetTupType(innerPlan((Plan*) node)));
+			break;
+		case JOIN_RIGHT:
+			mergestate->mj_NullOuterTupleSlot =
+				ExecInitNullTupleSlot(estate,
+									  ExecGetTupType(outerPlan((Plan*) node)));
+			/*
+			 * Can't handle right or full join with non-nil extra joinclauses.
+			 */
+			if (node->join.joinqual != NIL)
+				elog(ERROR, "RIGHT JOIN is only supported with mergejoinable join conditions");
+			break;
+		case JOIN_FULL:
+			mergestate->mj_NullOuterTupleSlot =
+				ExecInitNullTupleSlot(estate,
+									  ExecGetTupType(outerPlan((Plan*) node)));
+			mergestate->mj_NullInnerTupleSlot =
+				ExecInitNullTupleSlot(estate,
+									  ExecGetTupType(innerPlan((Plan*) node)));
+			/*
+			 * Can't handle right or full join with non-nil extra joinclauses.
+			 */
+			if (node->join.joinqual != NIL)
+				elog(ERROR, "FULL JOIN is only supported with mergejoinable join conditions");
+			break;
+		default:
+			elog(ERROR, "ExecInitMergeJoin: unsupported join type %d",
+				 (int) node->join.jointype);
+	}
+
+	/* ----------------
+	 *	initialize tuple type and projection info
+	 * ----------------
+	 */
+	ExecAssignResultTypeFromTL((Plan *) node, &mergestate->jstate);
+	ExecAssignProjectionInfo((Plan *) node, &mergestate->jstate);
 
 	/* ----------------
 	 *	form merge skip qualifications
@@ -1210,22 +1565,12 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
 	 * ----------------
 	 */
 	mergestate->mj_JoinState = EXEC_MJ_INITIALIZE;
-
-	/* ----------------
-	 *	initialize subplans
-	 * ----------------
-	 */
-	ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
-	ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
-
-	/* ----------------
-	 *	initialize tuple type and projection info
-	 * ----------------
-	 */
-	ExecAssignResultTypeFromTL((Plan *) node, &mergestate->jstate);
-	ExecAssignProjectionInfo((Plan *) node, &mergestate->jstate);
-
 	mergestate->jstate.cs_TupFromTlist = false;
+	mergestate->mj_MatchedOuter = false;
+	mergestate->mj_MatchedInner = false;
+	mergestate->mj_OuterTupleSlot = NULL;
+	mergestate->mj_InnerTupleSlot = NULL;
+
 	/* ----------------
 	 *	initialization successful
 	 * ----------------
@@ -1285,15 +1630,11 @@ ExecEndMergeJoin(MergeJoin *node)
 	ExecEndNode((Plan *) outerPlan((Plan *) node), (Plan *) node);
 
 	/* ----------------
-	 *	clean out the tuple table so that we don't try and
-	 *	pfree the marked tuples..  see HACK ALERT at the top of
-	 *	this file.
+	 *	clean out the tuple table
 	 * ----------------
 	 */
 	ExecClearTuple(mergestate->jstate.cs_ResultTupleSlot);
 	ExecClearTuple(mergestate->mj_MarkedTupleSlot);
-	pfree(mergestate->mj_MarkedTupleSlot);
-	mergestate->mj_MarkedTupleSlot = NULL;
 
 	MJ1_printf("ExecEndMergeJoin: %s\n",
 			   "node processing ended");
@@ -1303,14 +1644,15 @@ void
 ExecReScanMergeJoin(MergeJoin *node, ExprContext *exprCtxt, Plan *parent)
 {
 	MergeJoinState *mergestate = node->mergestate;
-	TupleTableSlot *mjSlot = mergestate->mj_MarkedTupleSlot;
 
-	ExecClearTuple(mjSlot);
-	mjSlot->ttc_tupleDescriptor = NULL;
-	mjSlot->ttc_descIsNew = true;
-	mjSlot->ttc_whichplan = -1;
+	ExecClearTuple(mergestate->mj_MarkedTupleSlot);
 
 	mergestate->mj_JoinState = EXEC_MJ_INITIALIZE;
+	mergestate->jstate.cs_TupFromTlist = false;
+	mergestate->mj_MatchedOuter = false;
+	mergestate->mj_MatchedInner = false;
+	mergestate->mj_OuterTupleSlot = NULL;
+	mergestate->mj_InnerTupleSlot = NULL;
 
 	/*
 	 * if chgParam of subnodes is not null then plans will be re-scanned
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 3685232c7e4..5abd4ffc3a1 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.20 2000/08/24 03:29:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.21 2000/09/12 21:06:48 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -62,10 +62,10 @@ ExecNestLoop(NestLoop *node)
 	NestLoopState *nlstate;
 	Plan	   *innerPlan;
 	Plan	   *outerPlan;
-	bool		needNewOuterTuple;
 	TupleTableSlot *outerTupleSlot;
 	TupleTableSlot *innerTupleSlot;
-	List	   *qual;
+	List	   *joinqual;
+	List	   *otherqual;
 	ExprContext *econtext;
 
 	/* ----------------
@@ -75,9 +75,10 @@ ExecNestLoop(NestLoop *node)
 	ENL1_printf("getting info from node");
 
 	nlstate = node->nlstate;
-	qual = node->join.qual;
-	outerPlan = outerPlan(&node->join);
-	innerPlan = innerPlan(&node->join);
+	joinqual = node->join.joinqual;
+	otherqual = node->join.plan.qual;
+	outerPlan = outerPlan((Plan *) node);
+	innerPlan = innerPlan((Plan *) node);
 	econtext = nlstate->jstate.cs_ExprContext;
 
 	/* ----------------
@@ -115,7 +116,7 @@ ExecNestLoop(NestLoop *node)
 
 	/* ----------------
 	 *	Ok, everything is setup for the join so now loop until
-	 *	we return a qualifying join tuple..
+	 *	we return a qualifying join tuple.
 	 * ----------------
 	 */
 	ENL1_printf("entering main loop");
@@ -123,44 +124,14 @@ ExecNestLoop(NestLoop *node)
 	for (;;)
 	{
 		/* ----------------
-		 *	The essential idea now is to get the next inner tuple
-		 *	and join it with the current outer tuple.
+		 *	If we don't have an outer tuple, get the next one and
+		 *	reset the inner scan.
 		 * ----------------
 		 */
-		needNewOuterTuple = TupIsNull(outerTupleSlot);
-
-		/* ----------------
-		 *	if we have an outerTuple, try to get the next inner tuple.
-		 * ----------------
-		 */
-		if (!needNewOuterTuple)
-		{
-			ENL1_printf("getting new inner tuple");
-
-			innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
-			econtext->ecxt_innertuple = innerTupleSlot;
-
-			if (TupIsNull(innerTupleSlot))
-			{
-				ENL1_printf("no inner tuple, need new outer tuple");
-				needNewOuterTuple = true;
-			}
-		}
-
-		/* ----------------
-		 *	loop until we have a new outer tuple and a new
-		 *	inner tuple.
-		 * ----------------
-		 */
-		while (needNewOuterTuple)
+		if (nlstate->nl_NeedNewOuter)
 		{
-			/* ----------------
-			 *	now try to get the next outer tuple
-			 * ----------------
-			 */
 			ENL1_printf("getting new outer tuple");
 			outerTupleSlot = ExecProcNode(outerPlan, (Plan *) node);
-			econtext->ecxt_outertuple = outerTupleSlot;
 
 			/* ----------------
 			 *	if there are no more outer tuples, then the join
@@ -175,12 +146,14 @@ ExecNestLoop(NestLoop *node)
 
 			ENL1_printf("saving new outer tuple information");
 			nlstate->jstate.cs_OuterTupleSlot = outerTupleSlot;
+			econtext->ecxt_outertuple = outerTupleSlot;
+			nlstate->nl_NeedNewOuter = false;
+			nlstate->nl_MatchedOuter = false;
 
 			/* ----------------
-			 *	now rescan the inner plan and get a new inner tuple
+			 *	now rescan the inner plan
 			 * ----------------
 			 */
-
 			ENL1_printf("rescanning inner plan");
 
 			/*
@@ -189,48 +162,101 @@ ExecNestLoop(NestLoop *node)
 			 * expr context.
 			 */
 			ExecReScan(innerPlan, econtext, (Plan *) node);
+		}
+
+		/* ----------------
+		 *	we have an outerTuple, try to get the next inner tuple.
+		 * ----------------
+		 */
+		ENL1_printf("getting new inner tuple");
+
+		innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
+		econtext->ecxt_innertuple = innerTupleSlot;
 
-			ENL1_printf("getting new inner tuple");
+		if (TupIsNull(innerTupleSlot))
+		{
+			ENL1_printf("no inner tuple, need new outer tuple");
 
-			innerTupleSlot = ExecProcNode(innerPlan, (Plan *) node);
-			econtext->ecxt_innertuple = innerTupleSlot;
+			nlstate->nl_NeedNewOuter = true;
 
-			if (TupIsNull(innerTupleSlot))
-				ENL1_printf("couldn't get inner tuple - need new outer tuple");
-			else
+			if (! nlstate->nl_MatchedOuter &&
+				node->join.jointype == JOIN_LEFT)
 			{
-				ENL1_printf("got inner and outer tuples");
-				needNewOuterTuple = false;
+				/*
+				 * We are doing an outer join and there were no join matches
+				 * for this outer tuple.  Generate a fake join tuple with
+				 * nulls for the inner tuple, and return it if it passes
+				 * the non-join quals.
+				 */
+				econtext->ecxt_innertuple = nlstate->nl_NullInnerTupleSlot;
+
+				ENL1_printf("testing qualification for outer-join tuple");
+
+				if (ExecQual(otherqual, econtext, false))
+				{
+					/* ----------------
+					 *	qualification was satisfied so we project and
+					 *	return the slot containing the result tuple
+					 *	using ExecProject().
+					 * ----------------
+					 */
+					TupleTableSlot *result;
+					ExprDoneCond isDone;
+
+					ENL1_printf("qualification succeeded, projecting tuple");
+
+					result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
+
+					if (isDone != ExprEndResult)
+					{
+						nlstate->jstate.cs_TupFromTlist =
+							(isDone == ExprMultipleResult);
+						return result;
+					}
+				}
 			}
-		}						/* while (needNewOuterTuple) */
+			/*
+			 * Otherwise just return to top of loop for a new outer tuple.
+			 */
+			continue;
+		}
 
 		/* ----------------
 		 *	 at this point we have a new pair of inner and outer
 		 *	 tuples so we test the inner and outer tuples to see
-		 *	 if they satisify the node's qualification.
+		 *	 if they satisfy the node's qualification.
+		 *
+		 *	 Only the joinquals determine MatchedOuter status,
+		 *	 but all quals must pass to actually return the tuple.
 		 * ----------------
 		 */
 		ENL1_printf("testing qualification");
 
-		if (ExecQual((List *) qual, econtext, false))
+		if (ExecQual(joinqual, econtext, false))
 		{
-			/* ----------------
-			 *	qualification was satisified so we project and
-			 *	return the slot containing the result tuple
-			 *	using ExecProject().
-			 * ----------------
-			 */
-			TupleTableSlot *result;
-			ExprDoneCond isDone;
+			nlstate->nl_MatchedOuter = true;
 
-			ENL1_printf("qualification succeeded, projecting tuple");
-
-			result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
-
-			if (isDone != ExprEndResult)
+			if (otherqual == NIL || ExecQual(otherqual, econtext, false))
 			{
-				nlstate->jstate.cs_TupFromTlist = (isDone == ExprMultipleResult);
-				return result;
+				/* ----------------
+				 *	qualification was satisfied so we project and
+				 *	return the slot containing the result tuple
+				 *	using ExecProject().
+				 * ----------------
+				 */
+				TupleTableSlot *result;
+				ExprDoneCond isDone;
+
+				ENL1_printf("qualification succeeded, projecting tuple");
+
+				result = ExecProject(nlstate->jstate.cs_ProjInfo, &isDone);
+
+				if (isDone != ExprEndResult)
+				{
+					nlstate->jstate.cs_TupFromTlist =
+						(isDone == ExprMultipleResult);
+					return result;
+				}
 			}
 		}
 
@@ -264,7 +290,7 @@ ExecInitNestLoop(NestLoop *node, EState *estate, Plan *parent)
 	 *	assign execution state to node
 	 * ----------------
 	 */
-	node->join.state = estate;
+	node->join.plan.state = estate;
 
 	/* ----------------
 	 *	  create new nest loop state
@@ -281,19 +307,33 @@ ExecInitNestLoop(NestLoop *node, EState *estate, Plan *parent)
 	 */
 	ExecAssignExprContext(estate, &nlstate->jstate);
 
-#define NESTLOOP_NSLOTS 1
 	/* ----------------
-	 *	tuple table initialization
+	 *	  now initialize children
 	 * ----------------
 	 */
-	ExecInitResultTupleSlot(estate, &nlstate->jstate);
+	ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
+	ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
 
+#define NESTLOOP_NSLOTS 2
 	/* ----------------
-	 *	  now initialize children
+	 *	tuple table initialization
 	 * ----------------
 	 */
-	ExecInitNode(outerPlan((Plan *) node), estate, (Plan *) node);
-	ExecInitNode(innerPlan((Plan *) node), estate, (Plan *) node);
+	ExecInitResultTupleSlot(estate, &nlstate->jstate);
+
+	switch (node->join.jointype)
+	{
+		case JOIN_INNER:
+			break;
+		case JOIN_LEFT:
+			nlstate->nl_NullInnerTupleSlot =
+				ExecInitNullTupleSlot(estate,
+									  ExecGetTupType(innerPlan((Plan*) node)));
+			break;
+		default:
+			elog(ERROR, "ExecInitNestLoop: unsupported join type %d",
+				 (int) node->join.jointype);
+	}
 
 	/* ----------------
 	 *	initialize tuple type and projection info
@@ -308,6 +348,8 @@ ExecInitNestLoop(NestLoop *node, EState *estate, Plan *parent)
 	 */
 	nlstate->jstate.cs_OuterTupleSlot = NULL;
 	nlstate->jstate.cs_TupFromTlist = false;
+	nlstate->nl_NeedNewOuter = true;
+	nlstate->nl_MatchedOuter = false;
 
 	NL1_printf("ExecInitNestLoop: %s\n",
 			   "node initialized");
@@ -394,4 +436,6 @@ ExecReScanNestLoop(NestLoop *node, ExprContext *exprCtxt, Plan *parent)
 	/* let outerPlan to free its result tuple ... */
 	nlstate->jstate.cs_OuterTupleSlot = NULL;
 	nlstate->jstate.cs_TupFromTlist = false;
+	nlstate->nl_NeedNewOuter = true;
+	nlstate->nl_MatchedOuter = false;
 }
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7270d3116d8..77f17ee0a60 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.120 2000/08/11 23:45:31 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.121 2000/09/12 21:06:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -311,8 +311,12 @@ _copyTidScan(TidScan *from)
 static void
 CopyJoinFields(Join *from, Join *newnode)
 {
-	/* nothing extra */
-	return;
+	newnode->jointype = from->jointype;
+	Node_Copy(from, newnode, joinqual);
+	/* 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,
+									  pull_subplans((Node *) newnode->joinqual));
 }
 
 
@@ -381,8 +385,8 @@ _copyMergeJoin(MergeJoin *from)
 	/*
 	 * We must add subplans in mergeclauses to the new plan's subPlan list
 	 */
-	if (from->join.subPlan != NIL)
-		newnode->join.subPlan = nconc(newnode->join.subPlan,
+	if (from->join.plan.subPlan != NIL)
+		newnode->join.plan.subPlan = nconc(newnode->join.plan.subPlan,
 						  pull_subplans((Node *) newnode->mergeclauses));
 
 	return newnode;
@@ -414,8 +418,8 @@ _copyHashJoin(HashJoin *from)
 	/*
 	 * We must add subplans in hashclauses to the new plan's subPlan list
 	 */
-	if (from->join.subPlan != NIL)
-		newnode->join.subPlan = nconc(newnode->join.subPlan,
+	if (from->join.plan.subPlan != NIL)
+		newnode->join.plan.subPlan = nconc(newnode->join.plan.subPlan,
 						   pull_subplans((Node *) newnode->hashclauses));
 
 	return newnode;
@@ -510,21 +514,6 @@ _copyGroupClause(GroupClause *from)
 	return newnode;
 }
 
-static JoinExpr *
-_copyJoinExpr(JoinExpr *from)
-{
-	JoinExpr *newnode = makeNode(JoinExpr);
-
-	newnode->jointype = from->jointype;
-	newnode->isNatural = from->isNatural;
-	Node_Copy(from, newnode, larg);
-	Node_Copy(from, newnode, rarg);
-	Node_Copy(from, newnode, alias);
-	Node_Copy(from, newnode, quals);
-
-	return newnode;
-}
-
 /* ----------------
  *		_copyUnique
  * ----------------
@@ -914,6 +903,34 @@ _copyRelabelType(RelabelType *from)
 	return newnode;
 }
 
+static RangeTblRef *
+_copyRangeTblRef(RangeTblRef *from)
+{
+	RangeTblRef *newnode = makeNode(RangeTblRef);
+
+	newnode->rtindex = from->rtindex;
+
+	return newnode;
+}
+
+static JoinExpr *
+_copyJoinExpr(JoinExpr *from)
+{
+	JoinExpr *newnode = makeNode(JoinExpr);
+
+	newnode->jointype = from->jointype;
+	newnode->isNatural = from->isNatural;
+	Node_Copy(from, newnode, larg);
+	Node_Copy(from, newnode, rarg);
+	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);
+
+	return newnode;
+}
+
 /* ----------------
  *		_copyCaseExpr
  * ----------------
@@ -1014,6 +1031,7 @@ _copyRelOptInfo(RelOptInfo *from)
 
 	Node_Copy(from, newnode, baserestrictinfo);
 	newnode->baserestrictcost = from->baserestrictcost;
+	newnode->outerjoinset = listCopy(from->outerjoinset);
 	Node_Copy(from, newnode, joininfo);
 	Node_Copy(from, newnode, innerjoin);
 
@@ -1137,6 +1155,7 @@ _copyIndexPath(IndexPath *from)
 	Node_Copy(from, newnode, indexqual);
 	newnode->indexscandir = from->indexscandir;
 	newnode->joinrelids = listCopy(from->joinrelids);
+	newnode->alljoinquals = from->alljoinquals;
 	newnode->rows = from->rows;
 
 	return newnode;
@@ -1177,6 +1196,7 @@ _copyTidPath(TidPath *from)
 static void
 CopyJoinPathFields(JoinPath *from, JoinPath *newnode)
 {
+	newnode->jointype = from->jointype;
 	Node_Copy(from, newnode, outerjoinpath);
 	Node_Copy(from, newnode, innerjoinpath);
 	Node_Copy(from, newnode, joinrestrictinfo);
@@ -1286,6 +1306,7 @@ _copyRestrictInfo(RestrictInfo *from)
 	 * ----------------
 	 */
 	Node_Copy(from, newnode, clause);
+	newnode->isjoinqual = from->isjoinqual;
 	Node_Copy(from, newnode, subclauseindices);
 	newnode->mergejoinoperator = from->mergejoinoperator;
 	newnode->left_sortop = from->left_sortop;
@@ -1370,12 +1391,11 @@ _copyRangeTblEntry(RangeTblEntry *from)
 
 	if (from->relname)
 		newnode->relname = pstrdup(from->relname);
-	Node_Copy(from, newnode, ref);
-	Node_Copy(from, newnode, eref);
 	newnode->relid = from->relid;
+	Node_Copy(from, newnode, alias);
+	Node_Copy(from, newnode, eref);
 	newnode->inh = from->inh;
 	newnode->inFromCl = from->inFromCl;
-	newnode->inJoinSet = from->inJoinSet;
 	newnode->skipAcl = from->skipAcl;
 
 	return newnode;
@@ -1526,18 +1546,6 @@ _copyTypeName(TypeName *from)
 	return newnode;
 }
 
-static RelExpr *
-_copyRelExpr(RelExpr *from)
-{
-	RelExpr   *newnode = makeNode(RelExpr);
-
-	if (from->relname)
-		newnode->relname = pstrdup(from->relname);
-	newnode->inh = from->inh;
-
-	return newnode;
-}
-
 static SortGroupBy *
 _copySortGroupBy(SortGroupBy *from)
 {
@@ -1555,7 +1563,20 @@ _copyRangeVar(RangeVar *from)
 {
 	RangeVar   *newnode = makeNode(RangeVar);
 
-	Node_Copy(from, newnode, relExpr);
+	if (from->relname)
+		newnode->relname = pstrdup(from->relname);
+	newnode->inh = from->inh;
+	Node_Copy(from, newnode, name);
+
+	return newnode;
+}
+
+static RangeSubselect *
+_copyRangeSubselect(RangeSubselect *from)
+{
+	RangeSubselect   *newnode = makeNode(RangeSubselect);
+
+	Node_Copy(from, newnode, subquery);
 	Node_Copy(from, newnode, name);
 
 	return newnode;
@@ -1650,6 +1671,8 @@ _copyQuery(Query *from)
 	newnode->hasSubLinks = from->hasSubLinks;
 
 	Node_Copy(from, newnode, rtable);
+	Node_Copy(from, newnode, jointree);
+
 	Node_Copy(from, newnode, targetList);
 	Node_Copy(from, newnode, qual);
 	Node_Copy(from, newnode, rowMark);
@@ -2548,6 +2571,12 @@ copyObject(void *from)
 		case T_RelabelType:
 			retval = _copyRelabelType(from);
 			break;
+		case T_RangeTblRef:
+			retval = _copyRangeTblRef(from);
+			break;
+		case T_JoinExpr:
+			retval = _copyJoinExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -2809,15 +2838,15 @@ copyObject(void *from)
 		case T_TypeCast:
 			retval = _copyTypeCast(from);
 			break;
-		case T_RelExpr:
-			retval = _copyRelExpr(from);
-			break;
 		case T_SortGroupBy:
 			retval = _copySortGroupBy(from);
 			break;
 		case T_RangeVar:
 			retval = _copyRangeVar(from);
 			break;
+		case T_RangeSubselect:
+			retval = _copyRangeSubselect(from);
+			break;
 		case T_TypeName:
 			retval = _copyTypeName(from);
 			break;
@@ -2845,9 +2874,6 @@ copyObject(void *from)
 		case T_GroupClause:
 			retval = _copyGroupClause(from);
 			break;
-		case T_JoinExpr:
-			retval = _copyJoinExpr(from);
-			break;
 		case T_CaseExpr:
 			retval = _copyCaseExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b059e5cd5f0..51a7a03fc1b 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.72 2000/08/11 23:45:31 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.73 2000/09/12 21:06:49 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -256,6 +256,26 @@ _equalSubLink(SubLink *a, SubLink *b)
 	return true;
 }
 
+static bool
+_equalArrayRef(ArrayRef *a, ArrayRef *b)
+{
+	if (a->refelemtype != b->refelemtype)
+		return false;
+	if (a->refattrlength != b->refattrlength)
+		return false;
+	if (a->refelemlength != b->refelemlength)
+		return false;
+	if (a->refelembyval != b->refelembyval)
+		return false;
+	if (!equal(a->refupperindexpr, b->refupperindexpr))
+		return false;
+	if (!equal(a->reflowerindexpr, b->reflowerindexpr))
+		return false;
+	if (!equal(a->refexpr, b->refexpr))
+		return false;
+	return equal(a->refassgnexpr, b->refassgnexpr);
+}
+
 static bool
 _equalFieldSelect(FieldSelect *a, FieldSelect *b)
 {
@@ -283,23 +303,37 @@ _equalRelabelType(RelabelType *a, RelabelType *b)
 }
 
 static bool
-_equalArrayRef(ArrayRef *a, ArrayRef *b)
+_equalRangeTblRef(RangeTblRef *a, RangeTblRef *b)
 {
-	if (a->refelemtype != b->refelemtype)
+	if (a->rtindex != b->rtindex)
 		return false;
-	if (a->refattrlength != b->refattrlength)
+
+	return true;
+}
+
+static bool
+_equalJoinExpr(JoinExpr *a, JoinExpr *b)
+{
+	if (a->jointype != b->jointype)
 		return false;
-	if (a->refelemlength != b->refelemlength)
+	if (a->isNatural != b->isNatural)
 		return false;
-	if (a->refelembyval != b->refelembyval)
+	if (!equal(a->larg, b->larg))
 		return false;
-	if (!equal(a->refupperindexpr, b->refupperindexpr))
+	if (!equal(a->rarg, b->rarg))
 		return false;
-	if (!equal(a->reflowerindexpr, b->reflowerindexpr))
+	if (!equal(a->using, b->using))
 		return false;
-	if (!equal(a->refexpr, b->refexpr))
+	if (!equal(a->quals, b->quals))
 		return false;
-	return equal(a->refassgnexpr, b->refassgnexpr);
+	if (!equal(a->alias, b->alias))
+		return false;
+	if (!equal(a->colnames, b->colnames))
+		return false;
+	if (!equal(a->colvars, b->colvars))
+		return false;
+
+	return true;
 }
 
 /*
@@ -370,6 +404,8 @@ _equalIndexPath(IndexPath *a, IndexPath *b)
 		return false;
 	if (!equali(a->joinrelids, b->joinrelids))
 		return false;
+	if (a->alljoinquals != b->alljoinquals)
+		return false;
 
 	/*
 	 * Skip 'rows' because of possibility of floating-point roundoff
@@ -395,6 +431,8 @@ _equalJoinPath(JoinPath *a, JoinPath *b)
 {
 	if (!_equalPath((Path *) a, (Path *) b))
 		return false;
+	if (a->jointype != b->jointype)
+		return false;
 	if (!equal(a->outerjoinpath, b->outerjoinpath))
 		return false;
 	if (!equal(a->innerjoinpath, b->innerjoinpath))
@@ -457,6 +495,8 @@ _equalRestrictInfo(RestrictInfo *a, RestrictInfo *b)
 {
 	if (!equal(a->clause, b->clause))
 		return false;
+	if (a->isjoinqual != b->isjoinqual)
+		return false;
 	if (!equal(a->subclauseindices, b->subclauseindices))
 		return false;
 	if (a->mergejoinoperator != b->mergejoinoperator)
@@ -557,6 +597,8 @@ _equalQuery(Query *a, Query *b)
 		return false;
 	if (!equal(a->rtable, b->rtable))
 		return false;
+	if (!equal(a->jointree, b->jointree))
+		return false;
 	if (!equal(a->targetList, b->targetList))
 		return false;
 	if (!equal(a->qual, b->qual))
@@ -1476,31 +1518,33 @@ _equalTypeCast(TypeCast *a, TypeCast *b)
 }
 
 static bool
-_equalRelExpr(RelExpr *a, RelExpr *b)
+_equalSortGroupBy(SortGroupBy *a, SortGroupBy *b)
 {
-	if (!equalstr(a->relname, b->relname))
+	if (!equalstr(a->useOp, b->useOp))
 		return false;
-	if (a->inh != b->inh)
+	if (!equal(a->node, b->node))
 		return false;
 
 	return true;
 }
 
 static bool
-_equalSortGroupBy(SortGroupBy *a, SortGroupBy *b)
+_equalRangeVar(RangeVar *a, RangeVar *b)
 {
-	if (!equalstr(a->useOp, b->useOp))
+	if (!equalstr(a->relname, b->relname))
 		return false;
-	if (!equal(a->node, b->node))
+	if (a->inh != b->inh)
+		return false;
+	if (!equal(a->name, b->name))
 		return false;
 
 	return true;
 }
 
 static bool
-_equalRangeVar(RangeVar *a, RangeVar *b)
+_equalRangeSubselect(RangeSubselect *a, RangeSubselect *b)
 {
-	if (!equal(a->relExpr, b->relExpr))
+	if (!equal(a->subquery, b->subquery))
 		return false;
 	if (!equal(a->name, b->name))
 		return false;
@@ -1605,17 +1649,16 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
 {
 	if (!equalstr(a->relname, b->relname))
 		return false;
-	if (!equal(a->ref, b->ref))
-		return false;
-	/* XXX what about eref? */
 	if (a->relid != b->relid)
 		return false;
+	if (!equal(a->alias, b->alias))
+		return false;
+	if (!equal(a->eref, b->eref))
+		return false;
 	if (a->inh != b->inh)
 		return false;
 	if (a->inFromCl != b->inFromCl)
 		return false;
-	if (a->inJoinSet != b->inJoinSet)
-		return false;
 	if (a->skipAcl != b->skipAcl)
 		return false;
 
@@ -1644,25 +1687,6 @@ _equalRowMark(RowMark *a, RowMark *b)
 	return true;
 }
 
-static bool
-_equalJoinExpr(JoinExpr *a, JoinExpr *b)
-{
-	if (a->jointype != b->jointype)
-		return false;
-	if (a->isNatural != b->isNatural)
-		return false;
-	if (!equal(a->larg, b->larg))
-		return false;
-	if (!equal(a->rarg, b->rarg))
-		return false;
-	if (!equal(a->alias, b->alias))
-		return false;
-	if (!equal(a->quals, b->quals))
-		return false;
-
-	return true;
-}
-
 static bool
 _equalFkConstraint(FkConstraint *a, FkConstraint *b)
 {
@@ -1808,6 +1832,12 @@ equal(void *a, void *b)
 		case T_RelabelType:
 			retval = _equalRelabelType(a, b);
 			break;
+		case T_RangeTblRef:
+			retval = _equalRangeTblRef(a, b);
+			break;
+		case T_JoinExpr:
+			retval = _equalJoinExpr(a, b);
+			break;
 
 		case T_RelOptInfo:
 			retval = _equalRelOptInfo(a, b);
@@ -2067,15 +2097,15 @@ equal(void *a, void *b)
 		case T_TypeCast:
 			retval = _equalTypeCast(a, b);
 			break;
-		case T_RelExpr:
-			retval = _equalRelExpr(a, b);
-			break;
 		case T_SortGroupBy:
 			retval = _equalSortGroupBy(a, b);
 			break;
 		case T_RangeVar:
 			retval = _equalRangeVar(a, b);
 			break;
+		case T_RangeSubselect:
+			retval = _equalRangeSubselect(a, b);
+			break;
 		case T_TypeName:
 			retval = _equalTypeName(a, b);
 			break;
@@ -2104,9 +2134,6 @@ equal(void *a, void *b)
 			/* GroupClause is equivalent to SortClause */
 			retval = _equalSortClause(a, b);
 			break;
-		case T_JoinExpr:
-			retval = _equalJoinExpr(a, b);
-			break;
 		case T_CaseExpr:
 			retval = _equalCaseExpr(a, b);
 			break;
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 45f42dc5024..e94b357d24b 100644
--- a/src/backend/nodes/list.c
+++ b/src/backend/nodes/list.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.32 2000/06/09 01:44:12 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.33 2000/09/12 21:06:49 tgl Exp $
  *
  * NOTES
  *	  XXX a few of the following functions are duplicated to handle
@@ -351,6 +351,25 @@ member(void *l1, List *l2)
 	return false;
 }
 
+/*
+ * like member(), but use when pointer-equality comparison is sufficient
+ */
+bool
+ptrMember(void *l1, List *l2)
+{
+	List	   *i;
+
+	foreach(i, l2)
+	{
+		if (l1 == ((void *) lfirst(i)))
+			return true;
+	}
+	return false;
+}
+
+/*
+ * membership test for integer lists
+ */
 bool
 intMember(int l1, List *l2)
 {
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 14f2ab106c7..8b24b82122f 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.125 2000/08/08 15:41:26 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.126 2000/09/12 21:06:49 tgl Exp $
  *
  * NOTES
  *	  Every (plan) node in POSTGRES has an associated "out" routine which
@@ -268,6 +268,9 @@ _outQuery(StringInfo str, Query *node)
 	appendStringInfo(str, " :rtable ");
 	_outNode(str, node->rtable);
 
+	appendStringInfo(str, " :jointree ");
+	_outNode(str, node->jointree);
+
 	appendStringInfo(str, " :targetlist ");
 	_outNode(str, node->targetList);
 
@@ -389,7 +392,6 @@ _outAppend(StringInfo str, Append *node)
 					 " :inheritrelid %u :inheritrtable ",
 					 node->inheritrelid);
 	_outNode(str, node->inheritrtable);
-
 }
 
 /*
@@ -400,7 +402,9 @@ _outJoin(StringInfo str, Join *node)
 {
 	appendStringInfo(str, " JOIN ");
 	_outPlanInfo(str, (Plan *) node);
-
+	appendStringInfo(str, " :jointype %d :joinqual ",
+					 (int) node->jointype);
+	_outNode(str, node->joinqual);
 }
 
 /*
@@ -411,6 +415,9 @@ _outNestLoop(StringInfo str, NestLoop *node)
 {
 	appendStringInfo(str, " NESTLOOP ");
 	_outPlanInfo(str, (Plan *) node);
+	appendStringInfo(str, " :jointype %d :joinqual ",
+					 (int) node->join.jointype);
+	_outNode(str, node->join.joinqual);
 }
 
 /*
@@ -421,6 +428,9 @@ _outMergeJoin(StringInfo str, MergeJoin *node)
 {
 	appendStringInfo(str, " MERGEJOIN ");
 	_outPlanInfo(str, (Plan *) node);
+	appendStringInfo(str, " :jointype %d :joinqual ",
+					 (int) node->join.jointype);
+	_outNode(str, node->join.joinqual);
 
 	appendStringInfo(str, " :mergeclauses ");
 	_outNode(str, node->mergeclauses);
@@ -434,17 +444,14 @@ _outHashJoin(StringInfo str, HashJoin *node)
 {
 	appendStringInfo(str, " HASHJOIN ");
 	_outPlanInfo(str, (Plan *) node);
+	appendStringInfo(str, " :jointype %d :joinqual ",
+					 (int) node->join.jointype);
+	_outNode(str, node->join.joinqual);
 
 	appendStringInfo(str, " :hashclauses ");
 	_outNode(str, node->hashclauses);
-
-	appendStringInfo(str,
-					 " :hashjoinop %u ",
+	appendStringInfo(str, " :hashjoinop %u ",
 					 node->hashjoinop);
-
-	appendStringInfo(str,
-					 " :hashdone %d ",
-					 node->hashdone);
 }
 
 static void
@@ -757,32 +764,6 @@ _outSubLink(StringInfo str, SubLink *node)
 	_outNode(str, node->subselect);
 }
 
-/*
- *	FieldSelect
- */
-static void
-_outFieldSelect(StringInfo str, FieldSelect *node)
-{
-	appendStringInfo(str, " FIELDSELECT :arg ");
-	_outNode(str, node->arg);
-
-	appendStringInfo(str, " :fieldnum %d :resulttype %u :resulttypmod %d ",
-					 node->fieldnum, node->resulttype, node->resulttypmod);
-}
-
-/*
- *	RelabelType
- */
-static void
-_outRelabelType(StringInfo str, RelabelType *node)
-{
-	appendStringInfo(str, " RELABELTYPE :arg ");
-	_outNode(str, node->arg);
-
-	appendStringInfo(str, " :resulttype %u :resulttypmod %d ",
-					 node->resulttype, node->resulttypmod);
-}
-
 /*
  *	ArrayRef is a subclass of Expr
  */
@@ -846,6 +827,66 @@ _outParam(StringInfo str, Param *node)
 	appendStringInfo(str, " :paramtype %u ", node->paramtype);
 }
 
+/*
+ *	FieldSelect
+ */
+static void
+_outFieldSelect(StringInfo str, FieldSelect *node)
+{
+	appendStringInfo(str, " FIELDSELECT :arg ");
+	_outNode(str, node->arg);
+
+	appendStringInfo(str, " :fieldnum %d :resulttype %u :resulttypmod %d ",
+					 node->fieldnum, node->resulttype, node->resulttypmod);
+}
+
+/*
+ *	RelabelType
+ */
+static void
+_outRelabelType(StringInfo str, RelabelType *node)
+{
+	appendStringInfo(str, " RELABELTYPE :arg ");
+	_outNode(str, node->arg);
+
+	appendStringInfo(str, " :resulttype %u :resulttypmod %d ",
+					 node->resulttype, node->resulttypmod);
+}
+
+/*
+ *	RangeTblRef
+ */
+static void
+_outRangeTblRef(StringInfo str, RangeTblRef *node)
+{
+	appendStringInfo(str, " RANGETBLREF %d ",
+					 node->rtindex);
+}
+
+/*
+ *	JoinExpr
+ */
+static void
+_outJoinExpr(StringInfo str, JoinExpr *node)
+{
+	appendStringInfo(str, " JOINEXPR :jointype %d :isNatural %s :larg ",
+					 (int) node->jointype,
+					 node->isNatural ? "true" : "false");
+	_outNode(str, node->larg);
+	appendStringInfo(str, " :rarg ");
+	_outNode(str, node->rarg);
+	appendStringInfo(str, " :using ");
+	_outNode(str, node->using);
+	appendStringInfo(str, " :quals ");
+	_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);
+}
+
 /*
  *	Stuff from execnodes.h
  */
@@ -897,6 +938,11 @@ _outRelOptInfo(StringInfo str, RelOptInfo *node)
 					 node->pruneable ? "true" : "false");
 	_outNode(str, node->baserestrictinfo);
 
+	appendStringInfo(str,
+					 " :baserestrictcost %.2f :outerjoinset ",
+					 node->baserestrictcost);
+	_outIntList(str, node->outerjoinset);
+
 	appendStringInfo(str, " :joininfo ");
 	_outNode(str, node->joininfo);
 
@@ -931,14 +977,14 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
 {
 	appendStringInfo(str, " RTE :relname ");
 	_outToken(str, node->relname);
-	appendStringInfo(str, " :ref ");
-	_outNode(str, node->ref);
-	appendStringInfo(str,
-			 " :relid %u :inh %s :inFromCl %s :inJoinSet %s :skipAcl %s",
-					 node->relid,
+	appendStringInfo(str, " :relid %u :alias ",
+					 node->relid);
+	_outNode(str, node->alias);
+	appendStringInfo(str, " :eref ");
+	_outNode(str, node->eref);
+	appendStringInfo(str, " :inh %s :inFromCl %s :skipAcl %s",
 					 node->inh ? "true" : "false",
 					 node->inFromCl ? "true" : "false",
-					 node->inJoinSet ? "true" : "false",
 					 node->skipAcl ? "true" : "false");
 }
 
@@ -985,7 +1031,8 @@ _outIndexPath(StringInfo str, IndexPath *node)
 					 (int) node->indexscandir);
 	_outIntList(str, node->joinrelids);
 
-	appendStringInfo(str, " :rows %.2f ",
+	appendStringInfo(str, " :alljoinquals %s :rows %.2f ",
+					 node->alljoinquals ? "true" : "false",
 					 node->rows);
 }
 
@@ -1021,7 +1068,8 @@ _outNestPath(StringInfo str, NestPath *node)
 					 node->path.startup_cost,
 					 node->path.total_cost);
 	_outNode(str, node->path.pathkeys);
-	appendStringInfo(str, " :outerjoinpath ");
+	appendStringInfo(str, " :jointype %d :outerjoinpath ",
+					 (int) node->jointype);
 	_outNode(str, node->outerjoinpath);
 	appendStringInfo(str, " :innerjoinpath ");
 	_outNode(str, node->innerjoinpath);
@@ -1041,7 +1089,8 @@ _outMergePath(StringInfo str, MergePath *node)
 					 node->jpath.path.startup_cost,
 					 node->jpath.path.total_cost);
 	_outNode(str, node->jpath.path.pathkeys);
-	appendStringInfo(str, " :outerjoinpath ");
+	appendStringInfo(str, " :jointype %d :outerjoinpath ",
+					 (int) node->jpath.jointype);
 	_outNode(str, node->jpath.outerjoinpath);
 	appendStringInfo(str, " :innerjoinpath ");
 	_outNode(str, node->jpath.innerjoinpath);
@@ -1070,7 +1119,8 @@ _outHashPath(StringInfo str, HashPath *node)
 					 node->jpath.path.startup_cost,
 					 node->jpath.path.total_cost);
 	_outNode(str, node->jpath.path.pathkeys);
-	appendStringInfo(str, " :outerjoinpath ");
+	appendStringInfo(str, " :jointype %d :outerjoinpath ",
+					 (int) node->jpath.jointype);
 	_outNode(str, node->jpath.outerjoinpath);
 	appendStringInfo(str, " :innerjoinpath ");
 	_outNode(str, node->jpath.innerjoinpath);
@@ -1101,7 +1151,8 @@ _outRestrictInfo(StringInfo str, RestrictInfo *node)
 	appendStringInfo(str, " RESTRICTINFO :clause ");
 	_outNode(str, node->clause);
 
-	appendStringInfo(str, " :subclauseindices ");
+	appendStringInfo(str, " :isjoinqual %s :subclauseindices ",
+					 node->isjoinqual ? "true" : "false");
 	_outNode(str, node->subclauseindices);
 
 	appendStringInfo(str, " :mergejoinoperator %u ", node->mergejoinoperator);
@@ -1483,12 +1534,6 @@ _outNode(StringInfo str, void *obj)
 			case T_SubLink:
 				_outSubLink(str, obj);
 				break;
-			case T_FieldSelect:
-				_outFieldSelect(str, obj);
-				break;
-			case T_RelabelType:
-				_outRelabelType(str, obj);
-				break;
 			case T_ArrayRef:
 				_outArrayRef(str, obj);
 				break;
@@ -1501,6 +1546,18 @@ _outNode(StringInfo str, void *obj)
 			case T_Param:
 				_outParam(str, obj);
 				break;
+			case T_FieldSelect:
+				_outFieldSelect(str, obj);
+				break;
+			case T_RelabelType:
+				_outRelabelType(str, obj);
+				break;
+			case T_RangeTblRef:
+				_outRangeTblRef(str, obj);
+				break;
+			case T_JoinExpr:
+				_outJoinExpr(str, obj);
+				break;
 			case T_EState:
 				_outEState(str, obj);
 				break;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index 104735cf6f6..7bf78e134bc 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.39 2000/06/18 22:44:05 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.40 2000/09/12 21:06:49 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -133,7 +133,7 @@ print_rt(List *rtable)
 		RangeTblEntry *rte = lfirst(l);
 
 		printf("%d\t%s(%s)\t%u\t%d\t%s\n",
-			   i, rte->relname, rte->ref->relname, rte->relid,
+			   i, rte->relname, rte->eref->relname, rte->relid,
 			   rte->inFromCl,
 			   (rte->inh ? "inh" : ""));
 		i++;
@@ -157,7 +157,6 @@ print_expr(Node *expr, List *rtable)
 	if (IsA(expr, Var))
 	{
 		Var		   *var = (Var *) expr;
-		RangeTblEntry *rt;
 		char	   *relname,
 				   *attname;
 
@@ -173,10 +172,10 @@ print_expr(Node *expr, List *rtable)
 				break;
 			default:
 				{
+					RangeTblEntry *rt;
+
 					rt = rt_fetch(var->varno, rtable);
-					relname = rt->relname;
-					if (rt->ref && rt->ref->relname)
-						relname = rt->ref->relname;		/* table renamed */
+					relname = rt->eref->relname;
 					attname = get_attname(rt->relid, var->varattno);
 				}
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 17e0396e5fe..00a6407db8b 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.95 2000/08/08 15:41:27 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.96 2000/09/12 21:06:49 tgl Exp $
  *
  * NOTES
  *	  Most of the read functions for plan nodes are tested. (In fact, they
@@ -119,6 +119,9 @@ _readQuery()
 	token = lsptok(NULL, &length);		/* skip :rtable */
 	local_node->rtable = nodeRead(true);
 
+	token = lsptok(NULL, &length);		/* skip :jointree */
+	local_node->jointree = nodeRead(true);
+
 	token = lsptok(NULL, &length);		/* skip :targetlist */
 	local_node->targetList = nodeRead(true);
 
@@ -335,14 +338,22 @@ _readAppend()
 
 /* ----------------
  *		_getJoin
- *
- * In case Join is not the same structure as Plan someday.
  * ----------------
  */
 static void
 _getJoin(Join *node)
 {
+	char	   *token;
+	int			length;
+
 	_getPlan((Plan *) node);
+
+	token = lsptok(NULL, &length);		/* skip the :jointype */
+	token = lsptok(NULL, &length);		/* get the jointype */
+	node->jointype = (JoinType) atoi(token);
+
+	token = lsptok(NULL, &length);		/* skip the :joinqual */
+	node->joinqual = nodeRead(true);	/* get the joinqual */
 }
 
 
@@ -399,6 +410,7 @@ _readMergeJoin()
 	local_node = makeNode(MergeJoin);
 
 	_getJoin((Join *) local_node);
+
 	token = lsptok(NULL, &length);		/* eat :mergeclauses */
 	local_node->mergeclauses = nodeRead(true);	/* now read it */
 
@@ -429,19 +441,13 @@ _readHashJoin()
 	token = lsptok(NULL, &length);		/* get hashjoinop */
 	local_node->hashjoinop = strtoul(token, NULL, 10);
 
-	token = lsptok(NULL, &length);		/* eat :hashdone */
-	token = lsptok(NULL, &length);		/* eat hashdone */
-	local_node->hashdone = false;
-
 	return local_node;
 }
 
 /* ----------------
  *		_getScan
  *
- *	Scan is a subclass of Node
- *	(Actually, according to the plannodes.h include file, it is a
- *	subclass of Plan.  This is why _getPlan is used here.)
+ *	Scan is a subclass of Plan.
  *
  *	Scan gets its own get function since stuff inherits it.
  * ----------------
@@ -462,7 +468,7 @@ _getScan(Scan *node)
 /* ----------------
  *		_readScan
  *
- * Scan is a subclass of Plan (Not Node, see above).
+ * Scan is a subclass of Plan.
  * ----------------
  */
 static Scan *
@@ -1154,6 +1160,74 @@ _readRelabelType()
 	return local_node;
 }
 
+/* ----------------
+ *		_readRangeTblRef
+ *
+ *	RangeTblRef is a subclass of Node
+ * ----------------
+ */
+static RangeTblRef *
+_readRangeTblRef()
+{
+	RangeTblRef *local_node;
+	char	   *token;
+	int			length;
+
+	local_node = makeNode(RangeTblRef);
+
+	token = lsptok(NULL, &length);		/* get rtindex */
+	local_node->rtindex = atoi(token);
+
+	return local_node;
+}
+
+/* ----------------
+ *		_readJoinExpr
+ *
+ *	JoinExpr is a subclass of Node
+ * ----------------
+ */
+static JoinExpr *
+_readJoinExpr()
+{
+	JoinExpr   *local_node;
+	char	   *token;
+	int			length;
+
+	local_node = makeNode(JoinExpr);
+
+	token = lsptok(NULL, &length);		/* eat :jointype */
+	token = lsptok(NULL, &length);		/* get jointype */
+	local_node->jointype = (JoinType) atoi(token);
+
+	token = lsptok(NULL, &length);		/* eat :isNatural */
+	token = lsptok(NULL, &length);		/* get :isNatural */
+	local_node->isNatural = (token[0] == 't') ? true : false;
+
+	token = lsptok(NULL, &length);		/* eat :larg */
+	local_node->larg = nodeRead(true);	/* now read it */
+
+	token = lsptok(NULL, &length);		/* eat :rarg */
+	local_node->rarg = nodeRead(true);	/* now read it */
+
+	token = lsptok(NULL, &length);		/* eat :using */
+	local_node->using = nodeRead(true);	/* now read it */
+
+	token = lsptok(NULL, &length);		/* eat :quals */
+	local_node->quals = nodeRead(true);	/* now read it */
+
+	token = lsptok(NULL, &length);		/* eat :alias */
+	local_node->alias = nodeRead(true);	/* now read it */
+
+	token = lsptok(NULL, &length);		/* eat :colnames */
+	local_node->colnames = nodeRead(true); /* now read it */
+
+	token = lsptok(NULL, &length);		/* eat :colvars */
+	local_node->colvars = nodeRead(true); /* now read it */
+
+	return local_node;
+}
+
 /*
  *	Stuff from execnodes.h
  */
@@ -1252,7 +1326,14 @@ _readRelOptInfo()
 	local_node->pruneable = (token[0] == 't') ? true : false;
 
 	token = lsptok(NULL, &length);		/* get :baserestrictinfo */
-	local_node->baserestrictinfo = nodeRead(true);		/* now read it */
+	local_node->baserestrictinfo = nodeRead(true); /* now read it */
+
+	token = lsptok(NULL, &length);		/* get :baserestrictcost */
+	token = lsptok(NULL, &length);		/* now read it */
+	local_node->baserestrictcost = (Cost) atof(token);
+
+	token = lsptok(NULL, &length);		/* get :outerjoinset */
+	local_node->outerjoinset = toIntList(nodeRead(true)); /* now read it */
 
 	token = lsptok(NULL, &length);		/* get :joininfo */
 	local_node->joininfo = nodeRead(true);		/* now read it */
@@ -1324,13 +1405,16 @@ _readRangeTblEntry()
 	else
 		local_node->relname = debackslash(token, length);
 
-	token = lsptok(NULL, &length);		/* eat :ref */
-	local_node->ref = nodeRead(true);	/* now read it */
-
 	token = lsptok(NULL, &length);		/* eat :relid */
 	token = lsptok(NULL, &length);		/* get :relid */
 	local_node->relid = strtoul(token, NULL, 10);
 
+	token = lsptok(NULL, &length);		/* eat :alias */
+	local_node->alias = nodeRead(true);	/* now read it */
+
+	token = lsptok(NULL, &length);		/* eat :eref */
+	local_node->eref = nodeRead(true);	/* now read it */
+
 	token = lsptok(NULL, &length);		/* eat :inh */
 	token = lsptok(NULL, &length);		/* get :inh */
 	local_node->inh = (token[0] == 't') ? true : false;
@@ -1339,10 +1423,6 @@ _readRangeTblEntry()
 	token = lsptok(NULL, &length);		/* get :inFromCl */
 	local_node->inFromCl = (token[0] == 't') ? true : false;
 
-	token = lsptok(NULL, &length);		/* eat :inJoinSet */
-	token = lsptok(NULL, &length);		/* get :inJoinSet */
-	local_node->inJoinSet = (token[0] == 't') ? true : false;
-
 	token = lsptok(NULL, &length);		/* eat :skipAcl */
 	token = lsptok(NULL, &length);		/* get :skipAcl */
 	local_node->skipAcl = (token[0] == 't') ? true : false;
@@ -1444,6 +1524,10 @@ _readIndexPath()
 	token = lsptok(NULL, &length);		/* get :joinrelids */
 	local_node->joinrelids = toIntList(nodeRead(true));
 
+	token = lsptok(NULL, &length);		/* get :alljoinquals */
+	token = lsptok(NULL, &length);		/* now read it */
+	local_node->alljoinquals = (token[0] == 't') ? true : false;
+
 	token = lsptok(NULL, &length);		/* get :rows */
 	token = lsptok(NULL, &length);		/* now read it */
 	local_node->rows = atof(token);
@@ -1520,6 +1604,10 @@ _readNestPath()
 	token = lsptok(NULL, &length);		/* get :pathkeys */
 	local_node->path.pathkeys = nodeRead(true); /* now read it */
 
+	token = lsptok(NULL, &length);		/* get :jointype */
+	token = lsptok(NULL, &length);		/* now read it */
+	local_node->jointype = (JoinType) atoi(token);
+
 	token = lsptok(NULL, &length);		/* get :outerjoinpath */
 	local_node->outerjoinpath = nodeRead(true); /* now read it */
 
@@ -1527,7 +1615,7 @@ _readNestPath()
 	local_node->innerjoinpath = nodeRead(true); /* now read it */
 
 	token = lsptok(NULL, &length);		/* get :joinrestrictinfo */
-	local_node->joinrestrictinfo = nodeRead(true);		/* now read it */
+	local_node->joinrestrictinfo = nodeRead(true); /* now read it */
 
 	return local_node;
 }
@@ -1562,6 +1650,10 @@ _readMergePath()
 	token = lsptok(NULL, &length);		/* get :pathkeys */
 	local_node->jpath.path.pathkeys = nodeRead(true);	/* now read it */
 
+	token = lsptok(NULL, &length);		/* get :jointype */
+	token = lsptok(NULL, &length);		/* now read it */
+	local_node->jpath.jointype = (JoinType) atoi(token);
+
 	token = lsptok(NULL, &length);		/* get :outerjoinpath */
 	local_node->jpath.outerjoinpath = nodeRead(true);	/* now read it */
 
@@ -1569,7 +1661,7 @@ _readMergePath()
 	local_node->jpath.innerjoinpath = nodeRead(true);	/* now read it */
 
 	token = lsptok(NULL, &length);		/* get :joinrestrictinfo */
-	local_node->jpath.joinrestrictinfo = nodeRead(true);		/* now read it */
+	local_node->jpath.joinrestrictinfo = nodeRead(true); /* now read it */
 
 	token = lsptok(NULL, &length);		/* get :path_mergeclauses */
 	local_node->path_mergeclauses = nodeRead(true);		/* now read it */
@@ -1613,6 +1705,10 @@ _readHashPath()
 	token = lsptok(NULL, &length);		/* get :pathkeys */
 	local_node->jpath.path.pathkeys = nodeRead(true);	/* now read it */
 
+	token = lsptok(NULL, &length);		/* get :jointype */
+	token = lsptok(NULL, &length);		/* now read it */
+	local_node->jpath.jointype = (JoinType) atoi(token);
+
 	token = lsptok(NULL, &length);		/* get :outerjoinpath */
 	local_node->jpath.outerjoinpath = nodeRead(true);	/* now read it */
 
@@ -1620,7 +1716,7 @@ _readHashPath()
 	local_node->jpath.innerjoinpath = nodeRead(true);	/* now read it */
 
 	token = lsptok(NULL, &length);		/* get :joinrestrictinfo */
-	local_node->jpath.joinrestrictinfo = nodeRead(true);		/* now read it */
+	local_node->jpath.joinrestrictinfo = nodeRead(true); /* now read it */
 
 	token = lsptok(NULL, &length);		/* get :path_hashclauses */
 	local_node->path_hashclauses = nodeRead(true);		/* now read it */
@@ -1672,6 +1768,10 @@ _readRestrictInfo()
 	token = lsptok(NULL, &length);		/* get :clause */
 	local_node->clause = nodeRead(true);		/* now read it */
 
+	token = lsptok(NULL, &length);		/* get :isjoinqual */
+	token = lsptok(NULL, &length);		/* now read it */
+	local_node->isjoinqual = (token[0] == 't') ? true : false;
+
 	token = lsptok(NULL, &length);		/* get :subclauseindices */
 	local_node->subclauseindices = nodeRead(true);		/* now read it */
 
@@ -1789,6 +1889,10 @@ parsePlanString(void)
 		return_value = _readFieldSelect();
 	else if (length == 11 && strncmp(token, "RELABELTYPE", length) == 0)
 		return_value = _readRelabelType();
+	else if (length == 11 && strncmp(token, "RANGETBLREF", length) == 0)
+		return_value = _readRangeTblRef();
+	else if (length == 8 && strncmp(token, "JOINEXPR", length) == 0)
+		return_value = _readJoinExpr();
 	else if (length == 3 && strncmp(token, "AGG", length) == 0)
 		return_value = _readAgg();
 	else if (length == 4 && strncmp(token, "HASH", length) == 0)
diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README
index a867cd885ed..38901ede1fd 100644
--- a/src/backend/optimizer/README
+++ b/src/backend/optimizer/README
@@ -35,10 +35,10 @@ RelOptInfo.pathlist.  (Actually, we discard Paths that are obviously
 inferior alternatives before they ever get into the pathlist --- what
 ends up in the pathlist is the cheapest way of generating each potentially
 useful sort ordering of the relation.)  Also create RelOptInfo.joininfo
-nodes that list all the joins that involve this relation.  For example,
-the WHERE clause "tab1.col1 = tab2.col1" generates a JoinInfo for tab1
-listing tab2 as an unjoined relation, and also one for tab2 showing tab1
-as an unjoined relation.
+nodes that list all the join clauses that involve this relation.  For
+example, the WHERE clause "tab1.col1 = tab2.col1" generates a JoinInfo
+for tab1 listing tab2 as an unjoined relation, and also one for tab2
+showing tab1 as an unjoined relation.
 
 If we have only a single base relation in the query, we are done now.
 Otherwise we have to figure out how to join the base relations into a
@@ -128,6 +128,19 @@ Once we have built the final join rel, we use either the cheapest path
 for it or the cheapest path with the desired ordering (if that's cheaper
 than applying a sort to the cheapest other path).
 
+The above dynamic-programming search is only conducted for simple cross
+joins (ie, SELECT FROM tab1, tab2, ...).  When the FROM clause contains
+explicit JOIN clauses, we join rels in exactly the order implied by the
+join tree.  Searching for the best possible join order is done only at
+the top implicit-cross-join level.  For example, in
+	SELECT FROM tab1, tab2, (tab3 NATURAL JOIN tab4)
+we will always join tab3 to tab4 and then consider all ways to join that
+result to tab1 and tab2.  Note that the JOIN syntax only constrains the
+order of joining --- we will still consider all available Paths and
+join methods for each JOIN operator.  We also consider both sides of
+the JOIN operator as inner or outer (so that we can transform RIGHT JOIN
+into LEFT JOIN).
+
 
 Optimizer Functions
 -------------------
@@ -158,13 +171,12 @@ planner()
    get a target list that only contains column names, no expressions
    if none, then return
 ---subplanner()
-    make list of relations in target
-    make list of relations in where clause
+    make list of base relations used in query
     split up the qual into restrictions (a=1) and joins (b=c)
-    find relation clauses can do merge sort and hash joins
+    find relation clauses that can do merge sort and hash joins
 ----make_one_rel()
      set_base_rel_pathlist()
-      find scan and all index paths for each relation
+      find scan and all index paths for each base relation
       find selectivity of columns used in joins
 -----make_one_rel_by_joins()
       jump to geqo if needed
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
index 7b9542cb1b2..f32b0d64eeb 100644
--- a/src/backend/optimizer/geqo/geqo_eval.c
+++ b/src/backend/optimizer/geqo/geqo_eval.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: geqo_eval.c,v 1.53 2000/07/28 02:13:16 tgl Exp $
+ * $Id: geqo_eval.c,v 1.54 2000/09/12 21:06:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -93,11 +93,11 @@ geqo_eval(Query *root, Gene *tour, int num_gene)
  * Returns a new join relation incorporating all joins in a left-sided tree.
  */
 RelOptInfo *
-gimme_tree(Query *root, Gene *tour, int rel_count, int num_gene, RelOptInfo *old_rel)
+gimme_tree(Query *root, Gene *tour, int rel_count, int num_gene,
+		   RelOptInfo *old_rel)
 {
 	RelOptInfo *inner_rel;		/* current relation */
 	int			base_rel_index;
-	RelOptInfo *new_rel;
 
 	if (rel_count < num_gene)
 	{							/* tree not yet finished */
@@ -116,16 +116,22 @@ gimme_tree(Query *root, Gene *tour, int rel_count, int num_gene, RelOptInfo *old
 		else
 		{						/* tree main part */
 			List	   *acceptable_rels = lcons(inner_rel, NIL);
-
-			new_rel = make_rels_by_clause_joins(root, old_rel,
-												acceptable_rels);
-			if (!new_rel)
+			List	   *new_rels;
+			RelOptInfo *new_rel;
+
+			new_rels = make_rels_by_clause_joins(root, old_rel,
+												 acceptable_rels);
+			/* Shouldn't get more than one result */
+			Assert(length(new_rels) <= 1);
+			if (new_rels == NIL)
 			{
-				new_rel = make_rels_by_clauseless_joins(root, old_rel,
-														acceptable_rels);
-				if (!new_rel)
+				new_rels = make_rels_by_clauseless_joins(root, old_rel,
+														 acceptable_rels);
+				Assert(length(new_rels) <= 1);
+				if (new_rels == NIL)
 					elog(ERROR, "gimme_tree: failed to construct join rel");
 			}
+			new_rel = (RelOptInfo *) lfirst(new_rels);
 
 			rel_count++;
 			Assert(length(new_rel->relids) == rel_count);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 999364d5637..605b60b5845 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.62 2000/05/31 00:28:22 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.63 2000/09/12 21:06:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -26,7 +26,9 @@ int			geqo_rels = DEFAULT_GEQO_RELS;
 
 
 static void set_base_rel_pathlist(Query *root);
-static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed);
+static List *build_jointree_rels(Query *root);
+static RelOptInfo *make_one_rel_by_joins(Query *root, int levels_needed,
+										 List *initial_rels);
 
 #ifdef OPTIMIZER_DEBUG
 static void debug_print_rel(Query *root, RelOptInfo *rel);
@@ -43,27 +45,38 @@ RelOptInfo *
 make_one_rel(Query *root)
 {
 	int			levels_needed;
+	List	   *initial_rels;
 
 	/*
-	 * Set the number of join (not nesting) levels yet to be processed.
+	 * Count the number of top-level jointree nodes.  This is the depth
+	 * of the dynamic-programming algorithm we must employ to consider
+	 * all ways of joining the top-level nodes.  Currently, we build
+	 * JoinExpr joins in exactly the order implied by the join expression,
+	 * so no dynamic-programming search is needed within a JoinExpr.
 	 */
-	levels_needed = length(root->base_rel_list);
+	levels_needed = length(root->jointree);
 
 	if (levels_needed <= 0)
-		return NULL;
+		return NULL;			/* nothing to do? */
 
 	/*
 	 * Generate access paths for the base rels.
 	 */
 	set_base_rel_pathlist(root);
 
+	/*
+	 * Construct a list of rels corresponding to the toplevel jointree nodes.
+	 * This may contain both base rels and rels constructed according to
+	 * explicit JOIN directives.
+	 */
+	initial_rels = build_jointree_rels(root);
+
 	if (levels_needed == 1)
 	{
-
 		/*
-		 * Single relation, no more processing is required.
+		 * Single jointree node, so we're done.
 		 */
-		return (RelOptInfo *) lfirst(root->base_rel_list);
+		return (RelOptInfo *) lfirst(initial_rels);
 	}
 	else
 	{
@@ -71,7 +84,7 @@ make_one_rel(Query *root)
 		/*
 		 * Generate join tree.
 		 */
-		return make_one_rel_by_joins(root, levels_needed);
+		return make_one_rel_by_joins(root, levels_needed, initial_rels);
 	}
 }
 
@@ -125,20 +138,47 @@ set_base_rel_pathlist(Query *root)
 	}
 }
 
+/*
+ * build_jointree_rels
+ *	  Construct a RelOptInfo for each item in the query's jointree.
+ *
+ * At present, we handle explicit joins in the FROM clause exactly as
+ * specified, with no search for other join orders.  Only the cross-product
+ * joins at the top level are involved in the dynamic-programming search.
+ */
+static List *
+build_jointree_rels(Query *root)
+{
+	List	   *rels = NIL;
+	List	   *jt;
+
+	foreach(jt, root->jointree)
+	{
+		Node	   *jtnode = (Node *) lfirst(jt);
+
+		rels = lappend(rels, make_rel_from_jointree(root, jtnode));
+	}
+	return rels;
+}
+
 /*
  * make_one_rel_by_joins
  *	  Find all possible joinpaths for a query by successively finding ways
  *	  to join component relations into join relations.
  *
  * 'levels_needed' is the number of iterations needed, ie, the number of
- *		base relations present in the query
+ *		independent jointree items in the query.  This is > 1.
+ *
+ * 'initial_rels' is a list of RelOptInfo nodes for each independent
+ *		jointree item.  These are the components to be joined together.
  *
  * Returns the final level of join relations, i.e., the relation that is
  * the result of joining all the original relations together.
  */
 static RelOptInfo *
-make_one_rel_by_joins(Query *root, int levels_needed)
+make_one_rel_by_joins(Query *root, int levels_needed, List *initial_rels)
 {
+	List	  **joinitems;
 	int			lev;
 	RelOptInfo *rel;
 
@@ -152,34 +192,35 @@ make_one_rel_by_joins(Query *root, int levels_needed)
 
 	/*
 	 * We employ a simple "dynamic programming" algorithm: we first find
-	 * all ways to build joins of two base relations, then all ways to
-	 * build joins of three base relations (from two-base-rel joins and
-	 * other base rels), then four-base-rel joins, and so on until we have
-	 * considered all ways to join all N relations into one rel.
+	 * all ways to build joins of two jointree items, then all ways to
+	 * build joins of three items (from two-item joins and single items),
+	 * then four-item joins, and so on until we have considered all ways
+	 * to join all the items into one rel.
+	 *
+	 * joinitems[j] is a list of all the j-item rels.  Initially we set
+	 * joinitems[1] to represent all the single-jointree-item relations.
 	 */
+	joinitems = (List **) palloc((levels_needed+1) * sizeof(List *));
+	MemSet(joinitems, 0, (levels_needed+1) * sizeof(List *));
+
+	joinitems[1] = initial_rels;
 
 	for (lev = 2; lev <= levels_needed; lev++)
 	{
-		List	   *first_old_rel = root->join_rel_list;
 		List	   *x;
 
 		/*
 		 * Determine all possible pairs of relations to be joined at this
 		 * level, and build paths for making each one from every available
-		 * pair of lower-level relations.  Results are prepended to
-		 * root->join_rel_list.
+		 * pair of lower-level relations.
 		 */
-		make_rels_by_joins(root, lev);
+		joinitems[lev] = make_rels_by_joins(root, lev, joinitems);
 
 		/*
-		 * The relations created at the current level will appear at the
-		 * front of root->join_rel_list.
+		 * Do cleanup work on each just-processed rel.
 		 */
-		foreach(x, root->join_rel_list)
+		foreach(x, joinitems[lev])
 		{
-			if (x == first_old_rel)
-				break;			/* no more rels added at this level */
-
 			rel = (RelOptInfo *) lfirst(x);
 
 #ifdef NOT_USED
@@ -202,14 +243,12 @@ make_one_rel_by_joins(Query *root, int levels_needed)
 	}
 
 	/*
-	 * Now, the front of the join_rel_list should be the single rel
+	 * We should have a single rel at the final level,
 	 * representing the join of all the base rels.
 	 */
-	Assert(length(root->join_rel_list) > 0);
-	rel = (RelOptInfo *) lfirst(root->join_rel_list);
-	Assert(length(rel->relids) == levels_needed);
-	Assert(length(root->join_rel_list) == 1 ||
-		   length(((RelOptInfo *) lsecond(root->join_rel_list))->relids) < levels_needed);
+	Assert(length(joinitems[levels_needed]) == 1);
+	rel = (RelOptInfo *) lfirst(joinitems[levels_needed]);
+	Assert(length(rel->relids) == length(root->base_rel_list));
 
 	return rel;
 }
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 05f32d25972..3156a951314 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.94 2000/08/24 03:29:04 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/indxpath.c,v 1.95 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1482,7 +1482,9 @@ index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index,
 	{
 		List	   *clausegroup = lfirst(i);
 		IndexPath  *pathnode = makeNode(IndexPath);
-		List	   *indexquals;
+		List	   *indexquals = NIL;
+		bool		alljoinquals = true;
+		List	   *temp;
 
 		/* XXX this code ought to be merged with create_index_path? */
 
@@ -1496,7 +1498,16 @@ index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index,
 		 */
 		pathnode->path.pathkeys = NIL;
 
-		indexquals = get_actual_clauses(clausegroup);
+		/* extract bare indexqual clauses, check whether all from JOIN/ON */
+		foreach(temp, clausegroup)
+		{
+			RestrictInfo *clause = (RestrictInfo *) lfirst(temp);
+
+			indexquals = lappend(indexquals, clause->clause);
+			if (! clause->isjoinqual)
+				alljoinquals = false;
+		}
+
 		/* expand special operators to indexquals the executor can handle */
 		indexquals = expand_indexqual_conditions(indexquals);
 
@@ -1514,6 +1525,8 @@ index_innerjoin(Query *root, RelOptInfo *rel, IndexOptInfo *index,
 		/* joinrelids saves the rels needed on the outer side of the join */
 		pathnode->joinrelids = lfirst(outerrelids_list);
 
+		pathnode->alljoinquals = alljoinquals;
+
 		/*
 		 * We must compute the estimated number of output rows for the
 		 * indexscan.  This is less than rel->rows because of the
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index c2ca38490e3..367e1ac9767 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.55 2000/05/30 00:49:47 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinpath.c,v 1.56 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,27 +25,32 @@
 #include "utils/lsyscache.h"
 
 static void sort_inner_and_outer(Query *root, RelOptInfo *joinrel,
-					 RelOptInfo *outerrel, RelOptInfo *innerrel,
-					 List *restrictlist, List *mergeclause_list);
+								 RelOptInfo *outerrel, RelOptInfo *innerrel,
+								 List *restrictlist, List *mergeclause_list,
+								 JoinType jointype);
 static void match_unsorted_outer(Query *root, RelOptInfo *joinrel,
-					 RelOptInfo *outerrel, RelOptInfo *innerrel,
-					 List *restrictlist, List *mergeclause_list);
+								 RelOptInfo *outerrel, RelOptInfo *innerrel,
+								 List *restrictlist, List *mergeclause_list,
+								 JoinType jointype);
 
 #ifdef NOT_USED
 static void match_unsorted_inner(Query *root, RelOptInfo *joinrel,
-					 RelOptInfo *outerrel, RelOptInfo *innerrel,
-					 List *restrictlist, List *mergeclause_list);
+								 RelOptInfo *outerrel, RelOptInfo *innerrel,
+								 List *restrictlist, List *mergeclause_list,
+								 JoinType jointype);
 
 #endif
 static void hash_inner_and_outer(Query *root, RelOptInfo *joinrel,
-					 RelOptInfo *outerrel, RelOptInfo *innerrel,
-					 List *restrictlist);
-static Path *best_innerjoin(List *join_paths, List *outer_relid);
+								 RelOptInfo *outerrel, RelOptInfo *innerrel,
+								 List *restrictlist, JoinType jointype);
+static Path *best_innerjoin(List *join_paths, List *outer_relid,
+							JoinType jointype);
 static Selectivity estimate_disbursion(Query *root, Var *var);
 static List *select_mergejoin_clauses(RelOptInfo *joinrel,
-						 RelOptInfo *outerrel,
-						 RelOptInfo *innerrel,
-						 List *restrictlist);
+									  RelOptInfo *outerrel,
+									  RelOptInfo *innerrel,
+									  List *restrictlist,
+									  JoinType jointype);
 
 
 /*
@@ -64,6 +69,7 @@ add_paths_to_joinrel(Query *root,
 					 RelOptInfo *joinrel,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
+					 JoinType jointype,
 					 List *restrictlist)
 {
 	List	   *mergeclause_list = NIL;
@@ -75,14 +81,15 @@ add_paths_to_joinrel(Query *root,
 		mergeclause_list = select_mergejoin_clauses(joinrel,
 													outerrel,
 													innerrel,
-													restrictlist);
+													restrictlist,
+													jointype);
 
 	/*
 	 * 1. Consider mergejoin paths where both relations must be explicitly
 	 * sorted.
 	 */
 	sort_inner_and_outer(root, joinrel, outerrel, innerrel,
-						 restrictlist, mergeclause_list);
+						 restrictlist, mergeclause_list, jointype);
 
 	/*
 	 * 2. Consider paths where the outer relation need not be explicitly
@@ -90,7 +97,7 @@ add_paths_to_joinrel(Query *root,
 	 * path is already ordered.
 	 */
 	match_unsorted_outer(root, joinrel, outerrel, innerrel,
-						 restrictlist, mergeclause_list);
+						 restrictlist, mergeclause_list, jointype);
 
 #ifdef NOT_USED
 
@@ -107,7 +114,7 @@ add_paths_to_joinrel(Query *root,
 	 * other order.
 	 */
 	match_unsorted_inner(root, joinrel, outerrel, innerrel,
-						 restrictlist, mergeclause_list);
+						 restrictlist, mergeclause_list, jointype);
 #endif
 
 	/*
@@ -116,7 +123,7 @@ add_paths_to_joinrel(Query *root,
 	 */
 	if (enable_hashjoin)
 		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
-							 restrictlist);
+							 restrictlist, jointype);
 }
 
 /*
@@ -131,6 +138,7 @@ add_paths_to_joinrel(Query *root,
  *		clauses that apply to this join
  * 'mergeclause_list' is a list of RestrictInfo nodes for available
  *		mergejoin clauses in this join
+ * 'jointype' is the type of join to do
  */
 static void
 sort_inner_and_outer(Query *root,
@@ -138,7 +146,8 @@ sort_inner_and_outer(Query *root,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
 					 List *restrictlist,
-					 List *mergeclause_list)
+					 List *mergeclause_list,
+					 JoinType jointype)
 {
 	List	   *i;
 
@@ -187,10 +196,10 @@ sort_inner_and_outer(Query *root,
 		 */
 		outerkeys = make_pathkeys_for_mergeclauses(root,
 												   curclause_list,
-												   outerrel->targetlist);
+												   outerrel);
 		innerkeys = make_pathkeys_for_mergeclauses(root,
 												   curclause_list,
-												   innerrel->targetlist);
+												   innerrel);
 		/* Build pathkeys representing output sort order. */
 		merge_pathkeys = build_join_pathkeys(outerkeys,
 											 joinrel->targetlist,
@@ -204,6 +213,7 @@ sort_inner_and_outer(Query *root,
 		 */
 		add_path(joinrel, (Path *)
 				 create_mergejoin_path(joinrel,
+									   jointype,
 									   outerrel->cheapest_total_path,
 									   innerrel->cheapest_total_path,
 									   restrictlist,
@@ -243,6 +253,7 @@ sort_inner_and_outer(Query *root,
  *		clauses that apply to this join
  * 'mergeclause_list' is a list of RestrictInfo nodes for available
  *		mergejoin clauses in this join
+ * 'jointype' is the type of join to do
  */
 static void
 match_unsorted_outer(Query *root,
@@ -250,16 +261,33 @@ match_unsorted_outer(Query *root,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
 					 List *restrictlist,
-					 List *mergeclause_list)
+					 List *mergeclause_list,
+					 JoinType jointype)
 {
+	bool		nestjoinOK;
 	Path	   *bestinnerjoin;
 	List	   *i;
 
+	/*
+	 * Nestloop only supports inner and left joins.
+	 */
+	switch (jointype)
+	{
+		case JOIN_INNER:
+		case JOIN_LEFT:
+			nestjoinOK = true;
+			break;
+		default:
+			nestjoinOK = false;
+			break;
+	}
+
 	/*
 	 * Get the best innerjoin indexpath (if any) for this outer rel. It's
 	 * the same for all outer paths.
 	 */
-	bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids);
+	bestinnerjoin = best_innerjoin(innerrel->innerjoin, outerrel->relids,
+								   jointype);
 
 	foreach(i, outerrel->pathlist)
 	{
@@ -282,31 +310,38 @@ match_unsorted_outer(Query *root,
 											 joinrel->targetlist,
 											 root->equi_key_list);
 
-		/*
-		 * Always consider a nestloop join with this outer and cheapest-
-		 * total-cost inner.  Consider nestloops using the cheapest-
-		 * startup-cost inner as well, and the best innerjoin indexpath.
-		 */
-		add_path(joinrel, (Path *)
-				 create_nestloop_path(joinrel,
-									  outerpath,
-									  innerrel->cheapest_total_path,
-									  restrictlist,
-									  merge_pathkeys));
-		if (innerrel->cheapest_startup_path != innerrel->cheapest_total_path)
-			add_path(joinrel, (Path *)
-					 create_nestloop_path(joinrel,
-										  outerpath,
-										  innerrel->cheapest_startup_path,
-										  restrictlist,
-										  merge_pathkeys));
-		if (bestinnerjoin != NULL)
+		if (nestjoinOK)
+		{
+			/*
+			 * Always consider a nestloop join with this outer and cheapest-
+			 * total-cost inner.  Consider nestloops using the cheapest-
+			 * startup-cost inner as well, and the best innerjoin indexpath.
+			 */
 			add_path(joinrel, (Path *)
 					 create_nestloop_path(joinrel,
+										  jointype,
 										  outerpath,
-										  bestinnerjoin,
+										  innerrel->cheapest_total_path,
 										  restrictlist,
 										  merge_pathkeys));
+			if (innerrel->cheapest_startup_path !=
+				innerrel->cheapest_total_path)
+				add_path(joinrel, (Path *)
+						 create_nestloop_path(joinrel,
+											  jointype,
+											  outerpath,
+											  innerrel->cheapest_startup_path,
+											  restrictlist,
+											  merge_pathkeys));
+			if (bestinnerjoin != NULL)
+				add_path(joinrel, (Path *)
+						 create_nestloop_path(joinrel,
+											  jointype,
+											  outerpath,
+											  bestinnerjoin,
+											  restrictlist,
+											  merge_pathkeys));
+		}
 
 		/* Look for useful mergeclauses (if any) */
 		mergeclauses = find_mergeclauses_for_pathkeys(outerpath->pathkeys,
@@ -319,7 +354,7 @@ match_unsorted_outer(Query *root,
 		/* Compute the required ordering of the inner path */
 		innersortkeys = make_pathkeys_for_mergeclauses(root,
 													   mergeclauses,
-												   innerrel->targetlist);
+													   innerrel);
 
 		/*
 		 * Generate a mergejoin on the basis of sorting the cheapest
@@ -328,6 +363,7 @@ match_unsorted_outer(Query *root,
 		 */
 		add_path(joinrel, (Path *)
 				 create_mergejoin_path(joinrel,
+									   jointype,
 									   outerpath,
 									   innerrel->cheapest_total_path,
 									   restrictlist,
@@ -373,6 +409,7 @@ match_unsorted_outer(Query *root,
 					newclauses = mergeclauses;
 				add_path(joinrel, (Path *)
 						 create_mergejoin_path(joinrel,
+											   jointype,
 											   outerpath,
 											   innerpath,
 											   restrictlist,
@@ -409,6 +446,7 @@ match_unsorted_outer(Query *root,
 					}
 					add_path(joinrel, (Path *)
 							 create_mergejoin_path(joinrel,
+												   jointype,
 												   outerpath,
 												   innerpath,
 												   restrictlist,
@@ -437,6 +475,7 @@ match_unsorted_outer(Query *root,
  *		clauses that apply to this join
  * 'mergeclause_list' is a list of RestrictInfo nodes for available
  *		mergejoin clauses in this join
+ * 'jointype' is the type of join to do
  */
 static void
 match_unsorted_inner(Query *root,
@@ -444,7 +483,8 @@ match_unsorted_inner(Query *root,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
 					 List *restrictlist,
-					 List *mergeclause_list)
+					 List *mergeclause_list,
+					 JoinType jointype)
 {
 	List	   *i;
 
@@ -466,7 +506,7 @@ match_unsorted_inner(Query *root,
 		/* Compute the required ordering of the outer path */
 		outersortkeys = make_pathkeys_for_mergeclauses(root,
 													   mergeclauses,
-												   outerrel->targetlist);
+													   outerrel);
 
 		/*
 		 * Generate a mergejoin on the basis of sorting the cheapest
@@ -478,6 +518,7 @@ match_unsorted_inner(Query *root,
 											 root->equi_key_list);
 		add_path(joinrel, (Path *)
 				 create_mergejoin_path(joinrel,
+									   jointype,
 									   outerrel->cheapest_total_path,
 									   innerpath,
 									   restrictlist,
@@ -506,6 +547,7 @@ match_unsorted_inner(Query *root,
 											 root->equi_key_list);
 		add_path(joinrel, (Path *)
 				 create_mergejoin_path(joinrel,
+									   jointype,
 									   totalouterpath,
 									   innerpath,
 									   restrictlist,
@@ -524,6 +566,7 @@ match_unsorted_inner(Query *root,
 												 root->equi_key_list);
 			add_path(joinrel, (Path *)
 					 create_mergejoin_path(joinrel,
+										   jointype,
 										   startupouterpath,
 										   innerpath,
 										   restrictlist,
@@ -547,18 +590,36 @@ match_unsorted_inner(Query *root,
  * 'innerrel' is the inner join relation
  * 'restrictlist' contains all of the RestrictInfo nodes for restriction
  *		clauses that apply to this join
+ * 'jointype' is the type of join to do
  */
 static void
 hash_inner_and_outer(Query *root,
 					 RelOptInfo *joinrel,
 					 RelOptInfo *outerrel,
 					 RelOptInfo *innerrel,
-					 List *restrictlist)
+					 List *restrictlist,
+					 JoinType jointype)
 {
 	Relids		outerrelids = outerrel->relids;
 	Relids		innerrelids = innerrel->relids;
+	bool		isouterjoin;
 	List	   *i;
 
+	/*
+	 * Hashjoin only supports inner and left joins.
+	 */
+	switch (jointype)
+	{
+		case JOIN_INNER:
+			isouterjoin = false;
+			break;
+		case JOIN_LEFT:
+			isouterjoin = true;
+			break;
+		default:
+			return;
+	}
+
 	/*
 	 * Scan the join's restrictinfo list to find hashjoinable clauses that
 	 * are usable with this pair of sub-relations.	Since we currently
@@ -581,6 +642,13 @@ hash_inner_and_outer(Query *root,
 		if (restrictinfo->hashjoinoperator == InvalidOid)
 			continue;			/* not hashjoinable */
 
+		/*
+		 * If processing an outer join, only use explicit join clauses for
+		 * hashing.  For inner joins we need not be so picky.
+		 */
+		if (isouterjoin && !restrictinfo->isjoinqual)
+			continue;
+
 		clause = restrictinfo->clause;
 		/* these must be OK, since check_hashjoinable accepted the clause */
 		left = get_leftop(clause);
@@ -609,6 +677,7 @@ hash_inner_and_outer(Query *root,
 		 */
 		add_path(joinrel, (Path *)
 				 create_hashjoin_path(joinrel,
+									  jointype,
 									  outerrel->cheapest_total_path,
 									  innerrel->cheapest_total_path,
 									  restrictlist,
@@ -617,6 +686,7 @@ hash_inner_and_outer(Query *root,
 		if (outerrel->cheapest_startup_path != outerrel->cheapest_total_path)
 			add_path(joinrel, (Path *)
 					 create_hashjoin_path(joinrel,
+										  jointype,
 										  outerrel->cheapest_startup_path,
 										  innerrel->cheapest_total_path,
 										  restrictlist,
@@ -641,26 +711,49 @@ hash_inner_and_outer(Query *root,
  * usable path.
  */
 static Path *
-best_innerjoin(List *join_paths, Relids outer_relids)
+best_innerjoin(List *join_paths, Relids outer_relids, JoinType jointype)
 {
 	Path	   *cheapest = (Path *) NULL;
+	bool		isouterjoin;
 	List	   *join_path;
 
+	/*
+	 * Nestloop only supports inner and left joins.
+	 */
+	switch (jointype)
+	{
+		case JOIN_INNER:
+			isouterjoin = false;
+			break;
+		case JOIN_LEFT:
+			isouterjoin = true;
+			break;
+		default:
+			return NULL;
+	}
+
 	foreach(join_path, join_paths)
 	{
-		Path	   *path = (Path *) lfirst(join_path);
+		IndexPath   *path = (IndexPath *) lfirst(join_path);
 
 		Assert(IsA(path, IndexPath));
 
+		/*
+		 * If processing an outer join, only use explicit join clauses in the
+		 * inner indexscan.  For inner joins we need not be so picky.
+		 */
+		if (isouterjoin && !path->alljoinquals)
+			continue;
+
 		/*
 		 * path->joinrelids is the set of base rels that must be part of
 		 * outer_relids in order to use this inner path, because those
 		 * rels are used in the index join quals of this inner path.
 		 */
-		if (is_subseti(((IndexPath *) path)->joinrelids, outer_relids) &&
+		if (is_subseti(path->joinrelids, outer_relids) &&
 			(cheapest == NULL ||
-			 compare_path_costs(path, cheapest, TOTAL_COST) < 0))
-			cheapest = path;
+			 compare_path_costs((Path *) path, cheapest, TOTAL_COST) < 0))
+			cheapest = (Path *) path;
 	}
 	return cheapest;
 }
@@ -684,6 +777,9 @@ estimate_disbursion(Query *root, Var *var)
 
 	relid = getrelid(var->varno, root->rtable);
 
+	if (relid == InvalidOid)
+		return 0.1;
+
 	return (Selectivity) get_attdisbursion(relid, var->varattno, 0.1);
 }
 
@@ -707,11 +803,13 @@ static List *
 select_mergejoin_clauses(RelOptInfo *joinrel,
 						 RelOptInfo *outerrel,
 						 RelOptInfo *innerrel,
-						 List *restrictlist)
+						 List *restrictlist,
+						 JoinType jointype)
 {
 	List	   *result_list = NIL;
 	Relids		outerrelids = outerrel->relids;
 	Relids		innerrelids = innerrel->relids;
+	bool		isouterjoin = IS_OUTER_JOIN(jointype);
 	List	   *i;
 
 	foreach(i, restrictlist)
@@ -721,6 +819,37 @@ select_mergejoin_clauses(RelOptInfo *joinrel,
 		Var		   *left,
 				   *right;
 
+		/*
+		 * If processing an outer join, only use explicit join clauses in the
+		 * merge.  For inner joins we need not be so picky.
+		 *
+		 * Furthermore, if it is a right/full join then *all* the explicit
+		 * join clauses must be mergejoinable, else the executor will fail.
+		 * If we are asked for a right join then just return NIL to indicate
+		 * no mergejoin is possible (we can handle it as a left join instead).
+		 * If we are asked for a full join then emit an error, because there
+		 * is no fallback.
+		 */
+		if (isouterjoin)
+		{
+			if (!restrictinfo->isjoinqual)
+				continue;
+			switch (jointype)
+			{
+				case JOIN_RIGHT:
+					if (restrictinfo->mergejoinoperator == InvalidOid)
+						return NIL;	/* not mergejoinable */
+					break;
+				case JOIN_FULL:
+					if (restrictinfo->mergejoinoperator == InvalidOid)
+						elog(ERROR, "FULL JOIN is only supported with mergejoinable join conditions");
+					break;
+				default:
+					/* otherwise, it's OK to have nonmergeable join quals */
+					break;
+			}
+		}
+
 		if (restrictinfo->mergejoinoperator == InvalidOid)
 			continue;			/* not mergejoinable */
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 741efe928c2..3cab2daba5c 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.46 2000/05/30 00:49:47 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/joinrels.c,v 1.47 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,62 +19,52 @@
 
 
 static RelOptInfo *make_join_rel(Query *root, RelOptInfo *rel1,
-			  RelOptInfo *rel2);
+								 RelOptInfo *rel2, JoinType jointype);
 
 
 /*
  * make_rels_by_joins
  *	  Consider ways to produce join relations containing exactly 'level'
- *	  base relations.  (This is one step of the dynamic-programming method
+ *	  jointree items.  (This is one step of the dynamic-programming method
  *	  embodied in make_one_rel_by_joins.)  Join rel nodes for each feasible
- *	  combination of base rels are created and added to the front of the
- *	  query's join_rel_list.  Implementation paths are created for each
- *	  such joinrel, too.
+ *	  combination of lower-level rels are created and returned in a list.
+ *	  Implementation paths are created for each such joinrel, too.
  *
- * Returns nothing, but adds entries to root->join_rel_list.
+ * level: level of rels we want to make this time.
+ * joinrels[j], 1 <= j < level, is a list of rels containing j items.
  */
-void
-make_rels_by_joins(Query *root, int level)
+List *
+make_rels_by_joins(Query *root, int level, List **joinrels)
 {
-	List	   *first_old_rel = root->join_rel_list;
+	List	   *result_rels = NIL;
+	List	   *new_rels;
+	List	   *nr;
 	List	   *r;
+	int			k;
 
 	/*
 	 * First, consider left-sided and right-sided plans, in which rels of
-	 * exactly level-1 member relations are joined against base relations.
-	 * We prefer to join using join clauses, but if we find a rel of
-	 * level-1 members that has no join clauses, we will generate
-	 * Cartesian-product joins against all base rels not already contained
-	 * in it.
+	 * exactly level-1 member relations are joined against initial relations.
+	 * We prefer to join using join clauses, but if we find a rel of level-1
+	 * members that has no join clauses, we will generate Cartesian-product
+	 * joins against all initial rels not already contained in it.
 	 *
-	 * In the first pass (level == 2), we try to join each base rel to each
-	 * base rel that appears later in base_rel_list.  (The mirror-image
+	 * In the first pass (level == 2), we try to join each initial rel to each
+	 * initial rel that appears later in joinrels[1].  (The mirror-image
 	 * joins are handled automatically by make_join_rel.)  In later
-	 * passes, we try to join rels of size level-1 from join_rel_list to
-	 * each base rel in base_rel_list.
-	 *
-	 * We assume that the rels already present in join_rel_list appear in
-	 * decreasing order of level (number of members).  This should be true
-	 * since we always add new higher-level rels to the front of the list.
+	 * passes, we try to join rels of size level-1 from joinrels[level-1]
+	 * to each initial rel in joinrels[1].
 	 */
-	if (level == 2)
-		r = root->base_rel_list;/* level-1 is base rels */
-	else
-		r = root->join_rel_list;
-	for (; r != NIL; r = lnext(r))
+	foreach(r, joinrels[level-1])
 	{
 		RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
-		int			old_level = length(old_rel->relids);
 		List	   *other_rels;
 
-		if (old_level != level - 1)
-			break;
-
 		if (level == 2)
-			other_rels = lnext(r);		/* only consider remaining base
+			other_rels = lnext(r);		/* only consider remaining initial
 										 * rels */
 		else
-			other_rels = root->base_rel_list;	/* consider all base rels */
+			other_rels = joinrels[1];	/* consider all initial rels */
 
 		if (old_rel->joininfo != NIL)
 		{
@@ -87,9 +77,9 @@ make_rels_by_joins(Query *root, int level)
 			 * have those other rels collected into a join rel.  See also
 			 * the last-ditch case below.
 			 */
-			make_rels_by_clause_joins(root,
-									  old_rel,
-									  other_rels);
+			new_rels = make_rels_by_clause_joins(root,
+												 old_rel,
+												 other_rels);
 		}
 		else
 		{
@@ -98,64 +88,90 @@ make_rels_by_joins(Query *root, int level)
 			 * Oops, we have a relation that is not joined to any other
 			 * relation.  Cartesian product time.
 			 */
-			make_rels_by_clauseless_joins(root,
-										  old_rel,
-										  other_rels);
+			new_rels = make_rels_by_clauseless_joins(root,
+													 old_rel,
+													 other_rels);
+		}
+
+		/*
+		 * At levels above 2 we will generate the same joined relation
+		 * in multiple ways --- for example (a join b) join c is the same
+		 * RelOptInfo as (b join c) join a, though the second case will
+		 * add a different set of Paths to it.  To avoid making extra work
+		 * for subsequent passes, do not enter the same RelOptInfo into our
+		 * output list multiple times.
+		 */
+		foreach(nr, new_rels)
+		{
+			RelOptInfo	   *jrel = (RelOptInfo *) lfirst(nr);
+
+			if (!ptrMember(jrel, result_rels))
+				result_rels = lcons(jrel, result_rels);
 		}
 	}
 
 	/*
-	 * Now, consider "bushy plans" in which relations of k base rels are
-	 * joined to relations of level-k base rels, for 2 <= k <= level-2.
-	 * The previous loop left r pointing to the first rel of level
-	 * level-2.
+	 * Now, consider "bushy plans" in which relations of k initial rels are
+	 * joined to relations of level-k initial rels, for 2 <= k <= level-2.
 	 *
 	 * We only consider bushy-plan joins for pairs of rels where there is a
 	 * suitable join clause, in order to avoid unreasonable growth of
 	 * planning time.
 	 */
-	for (; r != NIL; r = lnext(r))
+	for (k = 2; ; k++)
 	{
-		RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
-		int			old_level = length(old_rel->relids);
-		List	   *r2;
+		int			other_level = level - k;
 
 		/*
-		 * We can quit once past the halfway point (make_join_rel took
-		 * care of making the opposite-direction joins)
+		 * Since make_join_rel(x, y) handles both x,y and y,x cases,
+		 * we only need to go as far as the halfway point.
 		 */
-		if (old_level * 2 < level)
+		if (k > other_level)
 			break;
 
-		if (old_rel->joininfo == NIL)
-			continue;			/* we ignore clauseless joins here */
-
-		foreach(r2, lnext(r))
+		foreach(r, joinrels[k])
 		{
-			RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
-			int			new_level = length(new_rel->relids);
-
-			if (old_level + new_level > level)
-				continue;		/* scan down to new_rels of right size */
-			if (old_level + new_level < level)
-				break;			/* no more new_rels of right size */
-			if (nonoverlap_setsi(old_rel->relids, new_rel->relids))
+			RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
+			List	   *other_rels;
+			List	   *r2;
+
+			if (old_rel->joininfo == NIL)
+				continue;		/* we ignore clauseless joins here */
+
+			if (k == other_level)
+				other_rels = lnext(r); /* only consider remaining rels */
+			else
+				other_rels = joinrels[other_level];
+
+			foreach(r2, other_rels)
 			{
-				List	   *i;
-
-				/*
-				 * OK, we can build a rel of the right level from this
-				 * pair of rels.  Do so if there is at least one usable
-				 * join clause.
-				 */
-				foreach(i, old_rel->joininfo)
-				{
-					JoinInfo   *joininfo = (JoinInfo *) lfirst(i);
+				RelOptInfo *new_rel = (RelOptInfo *) lfirst(r2);
 
-					if (is_subseti(joininfo->unjoined_relids, new_rel->relids))
+				if (nonoverlap_setsi(old_rel->relids, new_rel->relids))
+				{
+					List	   *i;
+
+					/*
+					 * OK, we can build a rel of the right level from this
+					 * pair of rels.  Do so if there is at least one usable
+					 * join clause.
+					 */
+					foreach(i, old_rel->joininfo)
 					{
-						make_join_rel(root, old_rel, new_rel);
-						break;
+						JoinInfo   *joininfo = (JoinInfo *) lfirst(i);
+
+						if (is_subseti(joininfo->unjoined_relids,
+									   new_rel->relids))
+						{
+							RelOptInfo *jrel;
+
+							jrel = make_join_rel(root, old_rel, new_rel,
+												 JOIN_INNER);
+							/* Avoid making duplicate entries ... */
+							if (!ptrMember(jrel, result_rels))
+								result_rels = lcons(jrel, result_rels);
+							break; /* need not consider more joininfos */
+						}
 					}
 				}
 			}
@@ -174,39 +190,41 @@ make_rels_by_joins(Query *root, int level)
 	 * no choice but to make cartesian joins.  We consider only left-sided
 	 * and right-sided cartesian joins in this case (no bushy).
 	 */
-	if (root->join_rel_list == first_old_rel)
+	if (result_rels == NIL)
 	{
 		/* This loop is just like the first one, except we always call
 		 * make_rels_by_clauseless_joins().
 		 */
-		if (level == 2)
-			r = root->base_rel_list; /* level-1 is base rels */
-		else
-			r = root->join_rel_list;
-		for (; r != NIL; r = lnext(r))
+		foreach(r, joinrels[level-1])
 		{
 			RelOptInfo *old_rel = (RelOptInfo *) lfirst(r);
-			int			old_level = length(old_rel->relids);
 			List	   *other_rels;
 
-			if (old_level != level - 1)
-				break;
-
 			if (level == 2)
-				other_rels = lnext(r); /* only consider remaining base
+				other_rels = lnext(r); /* only consider remaining initial
 										* rels */
 			else
-				other_rels = root->base_rel_list; /* consider all base rels */
+				other_rels = joinrels[1]; /* consider all initial rels */
+
+			new_rels = make_rels_by_clauseless_joins(root,
+													 old_rel,
+													 other_rels);
+
+			foreach(nr, new_rels)
+			{
+				RelOptInfo	   *jrel = (RelOptInfo *) lfirst(nr);
 
-			make_rels_by_clauseless_joins(root,
-										  old_rel,
-										  other_rels);
+				if (!ptrMember(jrel, result_rels))
+					result_rels = lcons(jrel, result_rels);
+			}
 		}
 
-		if (root->join_rel_list == first_old_rel)
+		if (result_rels == NIL)
 			elog(ERROR, "make_rels_by_joins: failed to build any %d-way joins",
 				 level);
 	}
+
+	return result_rels;
 }
 
 /*
@@ -214,28 +232,23 @@ make_rels_by_joins(Query *root, int level)
  *	  Build joins between the given relation 'old_rel' and other relations
  *	  that are mentioned within old_rel's joininfo nodes (i.e., relations
  *	  that participate in join clauses that 'old_rel' also participates in).
- *	  The join rel nodes are added to root->join_rel_list.
+ *	  The join rel nodes are returned in a list.
  *
  * 'old_rel' is the relation entry for the relation to be joined
  * 'other_rels': other rels to be considered for joining
  *
- * Currently, this is only used with base rels in other_rels, but it would
- * work for joining to joinrels too, if the caller ensures there is no
+ * Currently, this is only used with initial rels in other_rels, but it
+ * will work for joining to joinrels too, if the caller ensures there is no
  * membership overlap between old_rel and the rels in other_rels.  (We need
- * no extra test for overlap for base rels, since the is_subset test can
+ * no extra test for overlap for initial rels, since the is_subset test can
  * only succeed when other_rel is not already part of old_rel.)
- *
- * Returns NULL if no suitable joins were found, else the last suitable
- * joinrel processed.  (The only caller who checks the return value is
- * geqo_eval.c, and it sets things up so there can be no more than one
- * "suitable" joinrel; so we don't bother with returning a list.)
  */
-RelOptInfo *
+List *
 make_rels_by_clause_joins(Query *root,
 						  RelOptInfo *old_rel,
 						  List *other_rels)
 {
-	RelOptInfo *result = NULL;
+	List	   *result = NIL;
 	List	   *i,
 			   *j;
 
@@ -249,7 +262,9 @@ make_rels_by_clause_joins(Query *root,
 			RelOptInfo *other_rel = (RelOptInfo *) lfirst(j);
 
 			if (is_subseti(unjoined_relids, other_rel->relids))
-				result = make_join_rel(root, old_rel, other_rel);
+				result = lcons(make_join_rel(root, old_rel, other_rel,
+											 JOIN_INNER),
+							   result);
 		}
 	}
 
@@ -261,24 +276,20 @@ make_rels_by_clause_joins(Query *root,
  *	  Given a relation 'old_rel' and a list of other relations
  *	  'other_rels', create a join relation between 'old_rel' and each
  *	  member of 'other_rels' that isn't already included in 'old_rel'.
+ *	  The join rel nodes are returned in a list.
  *
  * 'old_rel' is the relation entry for the relation to be joined
  * 'other_rels': other rels to be considered for joining
  *
- * Currently, this is only used with base rels in other_rels, but it would
+ * Currently, this is only used with initial rels in other_rels, but it would
  * work for joining to joinrels too.
- *
- * Returns NULL if no suitable joins were found, else the last suitable
- * joinrel processed.  (The only caller who checks the return value is
- * geqo_eval.c, and it sets things up so there can be no more than one
- * "suitable" joinrel; so we don't bother with returning a list.)
  */
-RelOptInfo *
+List *
 make_rels_by_clauseless_joins(Query *root,
 							  RelOptInfo *old_rel,
 							  List *other_rels)
 {
-	RelOptInfo *result = NULL;
+	List	   *result = NIL;
 	List	   *i;
 
 	foreach(i, other_rels)
@@ -286,13 +297,61 @@ make_rels_by_clauseless_joins(Query *root,
 		RelOptInfo *other_rel = (RelOptInfo *) lfirst(i);
 
 		if (nonoverlap_setsi(other_rel->relids, old_rel->relids))
-			result = make_join_rel(root, old_rel, other_rel);
+			result = lcons(make_join_rel(root, old_rel, other_rel,
+										 JOIN_INNER),
+						   result);
 	}
 
 	return result;
 }
 
 
+/*
+ * make_rel_from_jointree
+ *		Find or build a RelOptInfojoin rel representing a specific
+ *		jointree item.  For JoinExprs, we only consider the construction
+ *		path that corresponds exactly to what the user wrote.
+ */
+RelOptInfo *
+make_rel_from_jointree(Query *root, Node *jtnode)
+{
+	if (IsA(jtnode, RangeTblRef))
+	{
+		int			varno = ((RangeTblRef *) jtnode)->rtindex;
+
+		return get_base_rel(root, varno);
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+		RelOptInfo *rel,
+				   *lrel,
+				   *rrel;
+
+		/* Recurse */
+		lrel = make_rel_from_jointree(root, j->larg);
+		rrel = make_rel_from_jointree(root, j->rarg);
+
+		/* Make this join rel */
+		rel = make_join_rel(root, lrel, rrel, j->jointype);
+
+		/*
+		 * Since we are only going to consider this one way to do it,
+		 * we're done generating Paths for this joinrel and can now select
+		 * the cheapest.  In fact we *must* do so now, since next level up
+		 * will need it!
+		 */
+		set_cheapest(rel);
+
+		return rel;
+	}
+	else
+		elog(ERROR, "make_rel_from_jointree: unexpected node type %d",
+			 nodeTag(jtnode));
+	return NULL;				/* keep compiler quiet */
+}
+
+
 /*
  * make_join_rel
  *	   Find or create a join RelOptInfo that represents the join of
@@ -300,10 +359,10 @@ make_rels_by_clauseless_joins(Query *root,
  *	   created with the two rels as outer and inner rel.
  *	   (The join rel may already contain paths generated from other
  *	   pairs of rels that add up to the same set of base rels.)
- *	   The join rel is stored in the query's join_rel_list.
  */
 static RelOptInfo *
-make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2)
+make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2,
+			  JoinType jointype)
 {
 	RelOptInfo *joinrel;
 	List	   *restrictlist;
@@ -315,10 +374,39 @@ make_join_rel(Query *root, RelOptInfo *rel1, RelOptInfo *rel2)
 	joinrel = get_join_rel(root, rel1, rel2, &restrictlist);
 
 	/*
-	 * We consider paths using each rel as both outer and inner.
+	 * Consider paths using each rel as both outer and inner.
 	 */
-	add_paths_to_joinrel(root, joinrel, rel1, rel2, restrictlist);
-	add_paths_to_joinrel(root, joinrel, rel2, rel1, restrictlist);
+	switch (jointype)
+	{
+		case JOIN_INNER:
+			add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_INNER,
+								 restrictlist);
+			add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_INNER,
+								 restrictlist);
+			break;
+		case JOIN_LEFT:
+			add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_LEFT,
+								 restrictlist);
+			add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_RIGHT,
+								 restrictlist);
+			break;
+		case JOIN_FULL:
+			add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_FULL,
+								 restrictlist);
+			add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_FULL,
+								 restrictlist);
+			break;
+		case JOIN_RIGHT:
+			add_paths_to_joinrel(root, joinrel, rel1, rel2, JOIN_RIGHT,
+								 restrictlist);
+			add_paths_to_joinrel(root, joinrel, rel2, rel1, JOIN_LEFT,
+								 restrictlist);
+			break;
+		default:
+			elog(ERROR, "make_join_rel: unsupported join type %d",
+				 (int) jointype);
+			break;
+	}
 
 	return joinrel;
 }
diff --git a/src/backend/optimizer/path/orindxpath.c b/src/backend/optimizer/path/orindxpath.c
index 62a02836fec..2a76f63eb7c 100644
--- a/src/backend/optimizer/path/orindxpath.c
+++ b/src/backend/optimizer/path/orindxpath.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.40 2000/05/30 00:49:47 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/orindxpath.c,v 1.41 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,6 +101,7 @@ create_or_index_paths(Query *root,
 
 				/* This isn't a nestloop innerjoin, so: */
 				pathnode->joinrelids = NIL;		/* no join clauses here */
+				pathnode->alljoinquals = false;
 				pathnode->rows = rel->rows;
 
 				best_or_subclause_indices(root,
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 6d7b67bee3d..c6eccddab19 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.24 2000/08/08 15:41:31 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/pathkeys.c,v 1.25 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -694,8 +694,8 @@ find_mergeclauses_for_pathkeys(List *pathkeys, List *restrictinfos)
  *
  * 'mergeclauses' is a list of RestrictInfos for mergejoin clauses
  *			that will be used in a merge join.
- * 'tlist' is a relation target list for either the inner or outer
- *			side of the proposed join rel.	(Not actually needed anymore)
+ * 'rel' is the relation the pathkeys will apply to (ie, either the inner
+ *			or outer side of the proposed join rel).
  *
  * Returns a pathkeys list that can be applied to the indicated relation.
  *
@@ -706,7 +706,7 @@ find_mergeclauses_for_pathkeys(List *pathkeys, List *restrictinfos)
 List *
 make_pathkeys_for_mergeclauses(Query *root,
 							   List *mergeclauses,
-							   List *tlist)
+							   RelOptInfo *rel)
 {
 	List	   *pathkeys = NIL;
 	List	   *i;
@@ -722,30 +722,37 @@ make_pathkeys_for_mergeclauses(Query *root,
 		Assert(restrictinfo->mergejoinoperator != InvalidOid);
 
 		/*
-		 * Find the key and sortop needed for this mergeclause.
-		 *
-		 * Both sides of the mergeclause should appear in one of the query's
-		 * pathkey equivalence classes, so it doesn't matter which one we
-		 * use here.
+		 * Which key and sortop is needed for this relation?
 		 */
 		key = (Node *) get_leftop(restrictinfo->clause);
 		sortop = restrictinfo->left_sortop;
+		if (!IsA(key, Var) ||
+			!intMember(((Var *) key)->varno, rel->relids))
+		{
+			key = (Node *) get_rightop(restrictinfo->clause);
+			sortop = restrictinfo->right_sortop;
+			if (!IsA(key, Var) ||
+				!intMember(((Var *) key)->varno, rel->relids))
+				elog(ERROR, "make_pathkeys_for_mergeclauses: can't identify which side of mergeclause to use");
+		}
 
 		/*
-		 * Find pathkey sublist for this sort item.  We expect to find the
-		 * canonical set including the mergeclause's left and right sides;
-		 * if we get back just the one item, something is rotten.
+		 * Find or create canonical pathkey sublist for this sort item.
 		 */
 		item = makePathKeyItem(key, sortop);
 		pathkey = make_canonical_pathkey(root, item);
-		Assert(length(pathkey) > 1);
 
 		/*
-		 * Since the item we just made is not in the returned canonical
-		 * set, we can free it --- this saves a useful amount of storage
-		 * in a big join tree.
+		 * Most of the time we will get back a canonical pathkey set
+		 * including both the mergeclause's left and right sides (the only
+		 * case where we don't is if the mergeclause appeared in an OUTER
+		 * JOIN, which causes us not to generate an equijoin set from it).
+		 * Therefore, most of the time the item we just made is not part
+		 * of the returned structure, and we can free it.  This check
+		 * saves a useful amount of storage in a big join tree.
 		 */
-		pfree(item);
+		if (item != (PathKeyItem *) lfirst(pathkey))
+			pfree(item);
 
 		pathkeys = lappend(pathkeys, pathkey);
 	}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index c049f5d86b6..96dc3327b7f 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.95 2000/08/13 02:50:06 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.96 2000/09/12 21:06:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,36 +42,47 @@ static IndexScan *create_indexscan_node(Query *root, IndexPath *best_path,
 static TidScan *create_tidscan_node(TidPath *best_path, List *tlist,
 					List *scan_clauses);
 static NestLoop *create_nestloop_node(NestPath *best_path, List *tlist,
-					 List *clauses, Plan *outer_node, List *outer_tlist,
-					 Plan *inner_node, List *inner_tlist);
+									  List *joinclauses, List *otherclauses,
+									  Plan *outer_node, List *outer_tlist,
+									  Plan *inner_node, List *inner_tlist);
 static MergeJoin *create_mergejoin_node(MergePath *best_path, List *tlist,
-					  List *clauses, Plan *outer_node, List *outer_tlist,
-					  Plan *inner_node, List *inner_tlist);
+										List *joinclauses, List *otherclauses,
+										Plan *outer_node, List *outer_tlist,
+										Plan *inner_node, List *inner_tlist);
 static HashJoin *create_hashjoin_node(HashPath *best_path, List *tlist,
-					 List *clauses, Plan *outer_node, List *outer_tlist,
-					 Plan *inner_node, List *inner_tlist);
+									  List *joinclauses, List *otherclauses,
+									  Plan *outer_node, List *outer_tlist,
+									  Plan *inner_node, List *inner_tlist);
 static List *fix_indxqual_references(List *indexquals, IndexPath *index_path);
 static List *fix_indxqual_sublist(List *indexqual, int baserelid, Oid relam,
 					 Form_pg_index index);
 static Node *fix_indxqual_operand(Node *node, int baserelid,
 					 Form_pg_index index,
 					 Oid *opclass);
+static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
 static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
 			   List *indxid, List *indxqual,
 			   List *indxqualorig,
 			   ScanDirection indexscandir);
 static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
 			 List *tideval);
-static NestLoop *make_nestloop(List *qptlist, List *qpqual, Plan *lefttree,
-			  Plan *righttree);
-static HashJoin *make_hashjoin(List *tlist, List *qpqual,
-			  List *hashclauses, Plan *lefttree, Plan *righttree);
+static NestLoop *make_nestloop(List *tlist,
+							   List *joinclauses, List *otherclauses,
+							   Plan *lefttree, Plan *righttree,
+							   JoinType jointype);
+static HashJoin *make_hashjoin(List *tlist,
+							   List *joinclauses, List *otherclauses,
+							   List *hashclauses,
+							   Plan *lefttree, Plan *righttree,
+							   JoinType jointype);
 static Hash *make_hash(List *tlist, Node *hashkey, Plan *lefttree);
-static MergeJoin *make_mergejoin(List *tlist, List *qpqual,
-			   List *mergeclauses, Plan *righttree, Plan *lefttree);
+static MergeJoin *make_mergejoin(List *tlist,
+								 List *joinclauses, List *otherclauses,
+								 List *mergeclauses,
+								 Plan *lefttree, Plan *righttree,
+								 JoinType jointype);
 static void copy_path_costsize(Plan *dest, Path *src);
 static void copy_plan_costsize(Plan *dest, Plan *src);
-static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
 
 /*
  * create_plan
@@ -195,7 +206,8 @@ create_join_node(Query *root, JoinPath *best_path, List *tlist)
 	List	   *outer_tlist;
 	Plan	   *inner_node;
 	List	   *inner_tlist;
-	List	   *clauses;
+	List	   *joinclauses;
+	List	   *otherclauses;
 	Join	   *retval = NULL;
 
 	outer_node = create_plan(root, best_path->outerjoinpath);
@@ -204,14 +216,25 @@ create_join_node(Query *root, JoinPath *best_path, List *tlist)
 	inner_node = create_plan(root, best_path->innerjoinpath);
 	inner_tlist = inner_node->targetlist;
 
-	clauses = get_actual_clauses(best_path->joinrestrictinfo);
+	if (IS_OUTER_JOIN(best_path->jointype))
+	{
+		get_actual_join_clauses(best_path->joinrestrictinfo,
+								&joinclauses, &otherclauses);
+	}
+	else
+	{
+		/* We can treat all clauses alike for an inner join */
+		joinclauses = get_actual_clauses(best_path->joinrestrictinfo);
+		otherclauses = NIL;
+	}
 
 	switch (best_path->path.pathtype)
 	{
 		case T_MergeJoin:
 			retval = (Join *) create_mergejoin_node((MergePath *) best_path,
 													tlist,
-													clauses,
+													joinclauses,
+													otherclauses,
 													outer_node,
 													outer_tlist,
 													inner_node,
@@ -220,7 +243,8 @@ create_join_node(Query *root, JoinPath *best_path, List *tlist)
 		case T_HashJoin:
 			retval = (Join *) create_hashjoin_node((HashPath *) best_path,
 												   tlist,
-												   clauses,
+												   joinclauses,
+												   otherclauses,
 												   outer_node,
 												   outer_tlist,
 												   inner_node,
@@ -229,7 +253,8 @@ create_join_node(Query *root, JoinPath *best_path, List *tlist)
 		case T_NestLoop:
 			retval = (Join *) create_nestloop_node((NestPath *) best_path,
 												   tlist,
-												   clauses,
+												   joinclauses,
+												   otherclauses,
 												   outer_node,
 												   outer_tlist,
 												   inner_node,
@@ -411,30 +436,6 @@ create_indexscan_node(Query *root,
 	return scan_node;
 }
 
-static TidScan *
-make_tidscan(List *qptlist,
-			 List *qpqual,
-			 Index scanrelid,
-			 List *tideval)
-{
-	TidScan    *node = makeNode(TidScan);
-	Plan	   *plan = &node->scan.plan;
-
-	/* cost should be inserted by caller */
-	plan->state = (EState *) NULL;
-	plan->targetlist = qptlist;
-	plan->qual = qpqual;
-	plan->lefttree = NULL;
-	plan->righttree = NULL;
-	node->scan.scanrelid = scanrelid;
-	node->tideval = copyObject(tideval);		/* XXX do we really need a
-												 * copy? */
-	node->needRescan = false;
-	node->scan.scanstate = (CommonScanState *) NULL;
-
-	return node;
-}
-
 /*
  * create_tidscan_node
  *	 Returns a tidscan node for the base relation scanned by 'best_path'
@@ -488,7 +489,8 @@ create_tidscan_node(TidPath *best_path, List *tlist, List *scan_clauses)
 static NestLoop *
 create_nestloop_node(NestPath *best_path,
 					 List *tlist,
-					 List *clauses,
+					 List *joinclauses,
+					 List *otherclauses,
 					 Plan *outer_node,
 					 List *outer_tlist,
 					 Plan *inner_node,
@@ -535,7 +537,8 @@ create_nestloop_node(NestPath *best_path,
 			 * attnos, and may have been commuted as well).
 			 */
 			if (length(indxqualorig) == 1)		/* single indexscan? */
-				clauses = set_difference(clauses, lfirst(indxqualorig));
+				joinclauses = set_difference(joinclauses,
+											 lfirst(indxqualorig));
 
 			/* only refs to outer vars get changed in the inner indexqual */
 			innerscan->indxqualorig = join_references(indxqualorig,
@@ -577,15 +580,26 @@ create_nestloop_node(NestPath *best_path,
 											inner_node);
 	}
 
+	/*
+	 * Set quals to contain INNER/OUTER var references.
+	 */
+	joinclauses = join_references(joinclauses,
+								  outer_tlist,
+								  inner_tlist,
+								  (Index) 0);
+	otherclauses = join_references(otherclauses,
+								   outer_tlist,
+								   inner_tlist,
+								   (Index) 0);
+
 	join_node = make_nestloop(tlist,
-							  join_references(clauses,
-											  outer_tlist,
-											  inner_tlist,
-											  (Index) 0),
+							  joinclauses,
+							  otherclauses,
 							  outer_node,
-							  inner_node);
+							  inner_node,
+							  best_path->jointype);
 
-	copy_path_costsize(&join_node->join, &best_path->path);
+	copy_path_costsize(&join_node->join.plan, &best_path->path);
 
 	return join_node;
 }
@@ -593,14 +607,14 @@ create_nestloop_node(NestPath *best_path,
 static MergeJoin *
 create_mergejoin_node(MergePath *best_path,
 					  List *tlist,
-					  List *clauses,
+					  List *joinclauses,
+					  List *otherclauses,
 					  Plan *outer_node,
 					  List *outer_tlist,
 					  Plan *inner_node,
 					  List *inner_tlist)
 {
-	List	   *qpqual,
-			   *mergeclauses;
+	List	   *mergeclauses;
 	MergeJoin  *join_node;
 
 	mergeclauses = get_actual_clauses(best_path->path_mergeclauses);
@@ -610,10 +624,18 @@ create_mergejoin_node(MergePath *best_path,
 	 * the list of quals that must be checked as qpquals. Set those
 	 * clauses to contain INNER/OUTER var references.
 	 */
-	qpqual = join_references(set_difference(clauses, mergeclauses),
-							 outer_tlist,
-							 inner_tlist,
-							 (Index) 0);
+	joinclauses = join_references(set_difference(joinclauses, mergeclauses),
+								  outer_tlist,
+								  inner_tlist,
+								  (Index) 0);
+
+	/*
+	 * Fix the additional qpquals too.
+	 */
+	otherclauses = join_references(otherclauses,
+								   outer_tlist,
+								   inner_tlist,
+								   (Index) 0);
 
 	/*
 	 * Now set the references in the mergeclauses and rearrange them so
@@ -640,13 +662,54 @@ create_mergejoin_node(MergePath *best_path,
 									inner_node,
 									best_path->innersortkeys);
 
+	/*
+	 * The executor requires the inner side of a mergejoin to support "mark"
+	 * and "restore" operations.  Not all plan types do, so we must be careful
+	 * not to generate an invalid plan.  If necessary, an invalid inner plan
+	 * can be handled by inserting a Materialize node.
+	 *
+	 * Since the inner side must be ordered, and only Sorts and IndexScans can
+	 * create order to begin with, you might think there's no problem --- but
+	 * you'd be wrong.  Nestloop and merge joins can *preserve* the order of
+	 * their inputs, so they can be selected as the input of a mergejoin,
+	 * and that won't work in the present executor.
+	 *
+	 * Doing this here is a bit of a kluge since the cost of the Materialize
+	 * wasn't taken into account in our earlier decisions.  But Materialize
+	 * is hard to estimate a cost for, and the above consideration shows that
+	 * this is a rare case anyway, so this seems an acceptable way to proceed.
+	 *
+	 * This check must agree with ExecMarkPos/ExecRestrPos in
+	 * executor/execAmi.c!
+	 */
+	switch (nodeTag(inner_node))
+	{
+		case T_SeqScan:
+		case T_IndexScan:
+		case T_Material:
+		case T_Sort:
+			/* OK, these inner plans support mark/restore */
+			break;
+
+		default:
+			/* Ooops, need to materialize the inner plan */
+			inner_node = (Plan *) make_material(inner_tlist,
+												inner_node);
+			break;
+	}
+
+	/*
+	 * Now we can build the mergejoin node.
+	 */
 	join_node = make_mergejoin(tlist,
-							   qpqual,
+							   joinclauses,
+							   otherclauses,
 							   mergeclauses,
+							   outer_node,
 							   inner_node,
-							   outer_node);
+							   best_path->jpath.jointype);
 
-	copy_path_costsize(&join_node->join, &best_path->jpath.path);
+	copy_path_costsize(&join_node->join.plan, &best_path->jpath.path);
 
 	return join_node;
 }
@@ -654,13 +717,13 @@ create_mergejoin_node(MergePath *best_path,
 static HashJoin *
 create_hashjoin_node(HashPath *best_path,
 					 List *tlist,
-					 List *clauses,
+					 List *joinclauses,
+					 List *otherclauses,
 					 Plan *outer_node,
 					 List *outer_tlist,
 					 Plan *inner_node,
 					 List *inner_tlist)
 {
-	List	   *qpqual;
 	List	   *hashclauses;
 	HashJoin   *join_node;
 	Hash	   *hash_node;
@@ -679,10 +742,18 @@ create_hashjoin_node(HashPath *best_path,
 	 * the list of quals that must be checked as qpquals. Set those
 	 * clauses to contain INNER/OUTER var references.
 	 */
-	qpqual = join_references(set_difference(clauses, hashclauses),
-							 outer_tlist,
-							 inner_tlist,
-							 (Index) 0);
+	joinclauses = join_references(set_difference(joinclauses, hashclauses),
+								  outer_tlist,
+								  inner_tlist,
+								  (Index) 0);
+
+	/*
+	 * Fix the additional qpquals too.
+	 */
+	otherclauses = join_references(otherclauses,
+								   outer_tlist,
+								   inner_tlist,
+								   (Index) 0);
 
 	/*
 	 * Now set the references in the hashclauses and rearrange them so
@@ -701,12 +772,14 @@ create_hashjoin_node(HashPath *best_path,
 	 */
 	hash_node = make_hash(inner_tlist, innerhashkey, inner_node);
 	join_node = make_hashjoin(tlist,
-							  qpqual,
+							  joinclauses,
+							  otherclauses,
 							  hashclauses,
 							  outer_node,
-							  (Plan *) hash_node);
+							  (Plan *) hash_node,
+							  best_path->jpath.jointype);
 
-	copy_path_costsize(&join_node->join, &best_path->jpath.path);
+	copy_path_costsize(&join_node->join.plan, &best_path->jpath.path);
 
 	return join_node;
 }
@@ -1065,45 +1138,75 @@ make_indexscan(List *qptlist,
 	return node;
 }
 
+static TidScan *
+make_tidscan(List *qptlist,
+			 List *qpqual,
+			 Index scanrelid,
+			 List *tideval)
+{
+	TidScan    *node = makeNode(TidScan);
+	Plan	   *plan = &node->scan.plan;
+
+	/* cost should be inserted by caller */
+	plan->state = (EState *) NULL;
+	plan->targetlist = qptlist;
+	plan->qual = qpqual;
+	plan->lefttree = NULL;
+	plan->righttree = NULL;
+	node->scan.scanrelid = scanrelid;
+	node->tideval = copyObject(tideval);		/* XXX do we really need a
+												 * copy? */
+	node->needRescan = false;
+	node->scan.scanstate = (CommonScanState *) NULL;
+
+	return node;
+}
+
 
 static NestLoop *
-make_nestloop(List *qptlist,
-			  List *qpqual,
+make_nestloop(List *tlist,
+			  List *joinclauses,
+			  List *otherclauses,
 			  Plan *lefttree,
-			  Plan *righttree)
+			  Plan *righttree,
+			  JoinType jointype)
 {
 	NestLoop   *node = makeNode(NestLoop);
-	Plan	   *plan = &node->join;
+	Plan	   *plan = &node->join.plan;
 
 	/* cost should be inserted by caller */
 	plan->state = (EState *) NULL;
-	plan->targetlist = qptlist;
-	plan->qual = qpqual;
+	plan->targetlist = tlist;
+	plan->qual = otherclauses;
 	plan->lefttree = lefttree;
 	plan->righttree = righttree;
-	node->nlstate = (NestLoopState *) NULL;
+	node->join.jointype = jointype;
+	node->join.joinqual = joinclauses;
 
 	return node;
 }
 
 static HashJoin *
 make_hashjoin(List *tlist,
-			  List *qpqual,
+			  List *joinclauses,
+			  List *otherclauses,
 			  List *hashclauses,
 			  Plan *lefttree,
-			  Plan *righttree)
+			  Plan *righttree,
+			  JoinType jointype)
 {
 	HashJoin   *node = makeNode(HashJoin);
-	Plan	   *plan = &node->join;
+	Plan	   *plan = &node->join.plan;
 
 	/* cost should be inserted by caller */
 	plan->state = (EState *) NULL;
 	plan->targetlist = tlist;
-	plan->qual = qpqual;
+	plan->qual = otherclauses;
 	plan->lefttree = lefttree;
 	plan->righttree = righttree;
 	node->hashclauses = hashclauses;
-	node->hashdone = false;
+	node->join.jointype = jointype;
+	node->join.joinqual = joinclauses;
 
 	return node;
 }
@@ -1133,21 +1236,25 @@ make_hash(List *tlist, Node *hashkey, Plan *lefttree)
 
 static MergeJoin *
 make_mergejoin(List *tlist,
-			   List *qpqual,
+			   List *joinclauses,
+			   List *otherclauses,
 			   List *mergeclauses,
+			   Plan *lefttree,
 			   Plan *righttree,
-			   Plan *lefttree)
+			   JoinType jointype)
 {
 	MergeJoin  *node = makeNode(MergeJoin);
-	Plan	   *plan = &node->join;
+	Plan	   *plan = &node->join.plan;
 
 	/* cost should be inserted by caller */
 	plan->state = (EState *) NULL;
 	plan->targetlist = tlist;
-	plan->qual = qpqual;
+	plan->qual = otherclauses;
 	plan->lefttree = lefttree;
 	plan->righttree = righttree;
 	node->mergeclauses = mergeclauses;
+	node->join.jointype = jointype;
+	node->join.joinqual = joinclauses;
 
 	return node;
 }
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 8ffd35c9bb0..bf728ca1bdc 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.49 2000/08/13 02:50:07 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/initsplan.c,v 1.50 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -26,13 +26,18 @@
 #include "optimizer/planmain.h"
 #include "optimizer/tlist.h"
 #include "optimizer/var.h"
+#include "parser/parsetree.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_type.h"
 #include "utils/lsyscache.h"
 
 
-static void add_restrict_and_join_to_rel(Query *root, Node *clause);
+static void mark_baserels_for_outer_join(Query *root, Relids rels,
+										 Relids outerrels);
+static void add_restrict_and_join_to_rel(Query *root, Node *clause,
+										 bool isjoinqual,
+										 Relids outerjoinrelids);
 static void add_join_info_to_rels(Query *root, RestrictInfo *restrictinfo,
 					  Relids join_relids);
 static void add_vars_to_targetlist(Query *root, List *vars);
@@ -47,14 +52,14 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  *****************************************************************************/
 
 /*
- * make_var_only_tlist
+ * 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
- *	  *query_relation_list*.  Creates targetlist entries for each member of
- *	  'tlist' and adds them to the tlist field of the appropriate rel node.
+ *	  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
-make_var_only_tlist(Query *root, List *tlist)
+build_base_rel_tlists(Query *root, List *tlist)
 {
 	List	   *tlist_vars = pull_var_clause((Node *) tlist, false);
 
@@ -82,48 +87,75 @@ add_vars_to_targetlist(Query *root, List *vars)
 	}
 }
 
-/*
+/*----------
  * add_missing_rels_to_query
  *
- *	  If we have a range variable in the FROM clause that does not appear
+ *	  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 will be joined.	For instance, "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", it also gets turned into a join (between
- *	  foo as foo and foo as f).
+ *	  relation list so that it can be processed.  For instance,
+ *			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).
  *
  *	  To avoid putting useless entries into the per-relation targetlists,
  *	  this should only be called after all the variables in the targetlist
  *	  and quals have been processed by the routines above.
+ *
+ *	  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.
+ *----------
  */
-void
-add_missing_rels_to_query(Query *root)
+List *
+add_missing_rels_to_query(Query *root, Node *jtnode)
 {
-	int			varno = 1;
-	List	   *l;
+	List	   *result = NIL;
 
-	foreach(l, root->rtable)
+	if (jtnode == NULL)
+		return NIL;
+	if (IsA(jtnode, List))
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		List	   *l;
 
-		if (rte->inJoinSet)
+		foreach(l, (List *) jtnode)
 		{
-			RelOptInfo *rel = get_base_rel(root, varno);
+			result = nconc(result,
+						   add_missing_rels_to_query(root, lfirst(l)));
+		}
+	}
+	else if (IsA(jtnode, RangeTblRef))
+	{
+		int			varno = ((RangeTblRef *) jtnode)->rtindex;
+		RelOptInfo *rel = get_base_rel(root, varno);
 
-			/*
-			 * If the rel isn't otherwise referenced, give it a dummy
-			 * targetlist consisting of its own OID.
-			 */
-			if (rel->targetlist == NIL)
-			{
-				Var		   *var = makeVar(varno, ObjectIdAttributeNumber,
-										  OIDOID, -1, 0);
+		/*
+		 * If the rel isn't otherwise referenced, give it a dummy
+		 * targetlist consisting of its own OID.
+		 */
+		if (rel->targetlist == NIL)
+		{
+			Var		   *var = makeVar(varno, ObjectIdAttributeNumber,
+									  OIDOID, -1, 0);
 
-				add_var_to_tlist(rel, var);
-			}
+			add_var_to_tlist(rel, var);
 		}
-		varno++;
+
+		result = lcons(rel, NIL);
 	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		result = add_missing_rels_to_query(root, j->larg);
+		result = nconc(result,
+					   add_missing_rels_to_query(root, j->rarg));
+	}
+	else
+		elog(ERROR, "add_missing_rels_to_query: unexpected node type %d",
+			 nodeTag(jtnode));
+	return result;
 }
 
 
@@ -134,11 +166,145 @@ add_missing_rels_to_query(Query *root)
  *****************************************************************************/
 
 
+/*
+ * add_join_quals_to_rels
+ *	  Recursively scan the join tree for JOIN/ON (and JOIN/USING) qual
+ *	  clauses, and add these to the appropriate JoinInfo lists.  Also,
+ *	  mark base RelOptInfos with outerjoinset information, which will
+ *	  be needed for proper placement of WHERE clauses during
+ *	  add_restrict_and_join_to_rels().
+ *
+ * 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
+ * available.  However, we cannot do this within an outer join since the qual
+ * might eliminate matching rows and cause a NULL row to be added improperly.
+ * Therefore, rels appearing within (the nullable side of) an outer join
+ * are marked with outerjoinset = list of Relids used at the outer join node.
+ * This list will be added to the list of rels referenced by quals using
+ * such a rel, thereby forcing them up the join tree to the right level.
+ *
+ * To ease the calculation of these values, add_join_quals_to_rels() returns
+ * the list of Relids involved in its own level of join.  This is just an
+ * internal convenience; no outside callers pay attention to the result.
+ */
+Relids
+add_join_quals_to_rels(Query *root, Node *jtnode)
+{
+	Relids		result = NIL;
+
+	if (jtnode == NULL)
+		return result;
+	if (IsA(jtnode, List))
+	{
+		List	   *l;
+
+		/*
+		 * Note: we assume it's impossible to see same RT index from more
+		 * than one subtree, so nconc() is OK rather than LispUnioni().
+		 */
+		foreach(l, (List *) jtnode)
+			result = nconc(result,
+						   add_join_quals_to_rels(root, lfirst(l)));
+	}
+	else if (IsA(jtnode, RangeTblRef))
+	{
+		int			varno = ((RangeTblRef *) jtnode)->rtindex;
+
+		/* No quals to deal with, just return correct result */
+		result = lconsi(varno, NIL);
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+		Relids		leftids,
+					rightids,
+					outerjoinids;
+		List	   *qual;
+
+		/*
+		 * Order of operations here is subtle and critical.  First we recurse
+		 * to handle sub-JOINs.  Their join quals will be placed without
+		 * regard for whether this level is an outer join, which is correct.
+		 * Then, if we are an outer join, we mark baserels contained within
+		 * the nullable side(s) with our own rel list; this will restrict
+		 * placement of subsequent quals using those rels, including our own
+		 * quals, quals above us in the join tree, and WHERE quals.
+		 * Finally we place our own join quals.
+		 */
+		leftids = add_join_quals_to_rels(root, j->larg);
+		rightids = add_join_quals_to_rels(root, j->rarg);
+
+		result = nconc(listCopy(leftids), rightids);
+
+		outerjoinids = NIL;
+		switch (j->jointype)
+		{
+			case JOIN_INNER:
+				/* Inner join adds no restrictions for quals */
+				break;
+			case JOIN_LEFT:
+				mark_baserels_for_outer_join(root, rightids, result);
+				outerjoinids = result;
+				break;
+			case JOIN_FULL:
+				mark_baserels_for_outer_join(root, result, result);
+				outerjoinids = result;
+				break;
+			case JOIN_RIGHT:
+				mark_baserels_for_outer_join(root, leftids, result);
+				outerjoinids = result;
+				break;
+			case JOIN_UNION:
+				/*
+				 * This is where we fail if upper levels of planner haven't
+				 * rewritten UNION JOIN as an Append ...
+				 */
+				elog(ERROR, "UNION JOIN is not implemented yet");
+				break;
+			default:
+				elog(ERROR, "add_join_quals_to_rels: unsupported join type %d",
+					 (int) j->jointype);
+				break;
+		}
+
+		foreach(qual, (List *) j->quals)
+			add_restrict_and_join_to_rel(root, (Node *) lfirst(qual),
+										 true, outerjoinids);
+	}
+	else
+		elog(ERROR, "add_join_quals_to_rels: unexpected node type %d",
+			 nodeTag(jtnode));
+	return result;
+}
+
+/*
+ * mark_baserels_for_outer_join
+ *	  Mark all base rels listed in 'rels' as having the given outerjoinset.
+ */
+static void
+mark_baserels_for_outer_join(Query *root, Relids rels, Relids outerrels)
+{
+	List	   *relid;
+
+	foreach(relid, rels)
+	{
+		RelOptInfo *rel = get_base_rel(root, lfirsti(relid));
+
+		/*
+		 * Since we do this bottom-up, any outer-rels previously marked
+		 * should be within the new outer join set.
+		 */
+		Assert(is_subseti(rel->outerjoinset, outerrels));
+
+		rel->outerjoinset = outerrels;
+	}
+}
+
 /*
  * add_restrict_and_join_to_rels
  *	  Fill RestrictInfo and JoinInfo lists of relation entries for all
  *	  relations appearing within clauses.  Creates new relation entries if
- *	  necessary, adding them to *query_relation_list*.
+ *	  necessary, adding them to root->base_rel_list.
  *
  * 'clauses': the list of clauses in the cnfify'd query qualification.
  */
@@ -148,7 +314,8 @@ add_restrict_and_join_to_rels(Query *root, List *clauses)
 	List	   *clause;
 
 	foreach(clause, clauses)
-		add_restrict_and_join_to_rel(root, (Node *) lfirst(clause));
+		add_restrict_and_join_to_rel(root, (Node *) lfirst(clause),
+									 false, NIL);
 }
 
 /*
@@ -157,17 +324,31 @@ add_restrict_and_join_to_rels(Query *root, List *clauses)
  *	  (depending on whether the clause is a join) of each base relation
  *	  mentioned in the clause.	A RestrictInfo node is created and added to
  *	  the appropriate list for each rel.  Also, if the clause uses a
- *	  mergejoinable operator, enter the left- and right-side expressions
- *	  into the query's lists of equijoined vars.
+ *	  mergejoinable operator and is not an outer-join qual, enter the left-
+ *	  and right-side expressions into the query's lists of equijoined vars.
+ *
+ * isjoinqual is true if the clause came from JOIN/ON or JOIN/USING;
+ * we have to mark the created RestrictInfo accordingly.  If the JOIN
+ * is an OUTER join, the caller must set outerjoinrelids = all relids of join,
+ * which will override the joinrel identifiers extracted from the clause
+ * itself.  For inner join quals and WHERE clauses, set outerjoinrelids = NIL.
+ * (Passing the whole list, and not just an "isouterjoin" boolean, is simply
+ * a speed optimization: we could extract the same list from the base rels'
+ * outerjoinsets, but since add_join_quals_to_rels() already knows what we
+ * should use, might as well pass it in instead of recalculating it.)
  */
 static void
-add_restrict_and_join_to_rel(Query *root, Node *clause)
+add_restrict_and_join_to_rel(Query *root, Node *clause,
+							 bool isjoinqual,
+							 Relids outerjoinrelids)
 {
 	RestrictInfo *restrictinfo = makeNode(RestrictInfo);
 	Relids		relids;
 	List	   *vars;
+	bool		can_be_equijoin;
 
 	restrictinfo->clause = (Expr *) clause;
+	restrictinfo->isjoinqual = isjoinqual;
 	restrictinfo->subclauseindices = NIL;
 	restrictinfo->mergejoinoperator = InvalidOid;
 	restrictinfo->left_sortop = InvalidOid;
@@ -179,6 +360,44 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
 	 */
 	clause_get_relids_vars(clause, &relids, &vars);
 
+	/*
+	 * If caller has given us a join relid list, use it; otherwise, we must
+	 * scan the referenced base rels and add in any outer-join rel lists.
+	 * This prevents the clause from being applied at a lower level of joining
+	 * than any OUTER JOIN that should be evaluated before it.
+	 */
+	if (outerjoinrelids)
+	{
+		/* Safety check: parser should have enforced this to start with */
+		if (! is_subseti(relids, outerjoinrelids))
+			elog(ERROR, "JOIN qualification may not refer to other relations");
+		relids = outerjoinrelids;
+		can_be_equijoin = false;
+	}
+	else
+	{
+		Relids		newrelids = relids;
+		List	   *relid;
+
+		/* We rely on LispUnioni to be nondestructive of its input lists... */
+		can_be_equijoin = true;
+		foreach(relid, relids)
+		{
+			RelOptInfo *rel = get_base_rel(root, lfirsti(relid));
+
+			if (rel->outerjoinset)
+			{
+				newrelids = LispUnioni(newrelids, rel->outerjoinset);
+				/*
+				 * Because application of the qual will be delayed by outer
+				 * join, we mustn't assume its vars are equal everywhere.
+				 */
+				can_be_equijoin = false;
+			}
+		}
+		relids = newrelids;
+	}
+
 	if (length(relids) == 1)
 	{
 
@@ -199,7 +418,8 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
 		 * that "a.x = a.y AND a.x = b.z AND a.y = c.q" allows us to
 		 * consider z and q equal after their rels are joined.
 		 */
-		check_mergejoinable(restrictinfo);
+		if (can_be_equijoin)
+			check_mergejoinable(restrictinfo);
 	}
 	else if (relids != NIL)
 	{
@@ -209,11 +429,11 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
 		 * the relid list.	Set additional RestrictInfo fields for
 		 * joining.
 		 *
-		 * We need the merge info whether or not mergejoin is enabled (for
-		 * constructing equijoined-var lists), but we don't bother setting
-		 * hash info if hashjoin is disabled.
+		 * We don't bother setting the merge/hashjoin info if we're not
+		 * going to need it.
 		 */
-		check_mergejoinable(restrictinfo);
+		if (enable_mergejoin || can_be_equijoin)
+			check_mergejoinable(restrictinfo);
 		if (enable_hashjoin)
 			check_hashjoinable(restrictinfo);
 
@@ -223,7 +443,7 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
 		add_join_info_to_rels(root, restrictinfo, relids);
 
 		/*
-		 * Add vars used in the join clause to targetlists of member
+		 * Add vars used in the join clause to targetlists of their
 		 * relations, so that they will be emitted by the plan nodes that
 		 * scan those relations (else they won't be available at the join
 		 * node!).
@@ -241,12 +461,14 @@ add_restrict_and_join_to_rel(Query *root, Node *clause)
 	}
 
 	/*
-	 * If the clause has a mergejoinable operator, then the two sides
+	 * If the clause has a mergejoinable operator, and is not an outer-join
+	 * qualification nor bubbled up due to an outer join, then the two sides
 	 * represent equivalent PathKeyItems for path keys: any path that is
-	 * sorted by one side will also be sorted by the other (after joining,
-	 * that is).  Record the key equivalence for future use.
+	 * sorted by one side will also be sorted by the other (as soon as the
+	 * two rels are joined, that is).  Record the key equivalence for future
+	 * use.
 	 */
-	if (restrictinfo->mergejoinoperator != InvalidOid)
+	if (can_be_equijoin && restrictinfo->mergejoinoperator != InvalidOid)
 		add_equijoined_keys(root, restrictinfo);
 }
 
@@ -392,7 +614,8 @@ process_implied_equality(Query *root, Node *item1, Node *item2,
 									 BOOLOID); /* operator result type */
 	clause->args = lcons(item1, lcons(item2, NIL));
 
-	add_restrict_and_join_to_rel(root, (Node *) clause);
+	add_restrict_and_join_to_rel(root, (Node *) clause,
+								 false, NIL);
 }
 
 
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index abb468aa8d1..1fcbe64e888 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.58 2000/08/13 02:50:07 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.59 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -28,6 +28,7 @@
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
 #include "optimizer/tlist.h"
+#include "parser/parsetree.h"
 #include "utils/memutils.h"
 
 
@@ -41,11 +42,8 @@ static Plan *subplanner(Query *root, List *flat_tlist, List *qual,
  *	  not any fancier features.
  *
  * tlist is the target list the query should produce (NOT root->targetList!)
- * qual is the qualification of the query (likewise!)
  * tuple_fraction is the fraction of tuples we expect will be retrieved
  *
- * qual must already have been converted to implicit-AND form.
- *
  * Note: the Query node now also includes a query_pathkeys field, which
  * is both an input and an output of query_planner().  The input value
  * signals query_planner that the indicated sort order is wanted in the
@@ -75,9 +73,9 @@ static Plan *subplanner(Query *root, List *flat_tlist, List *qual,
 Plan *
 query_planner(Query *root,
 			  List *tlist,
-			  List *qual,
 			  double tuple_fraction)
 {
+	List	   *normal_qual;
 	List	   *noncachable_qual;
 	List	   *constant_qual;
 	List	   *var_only_tlist;
@@ -96,7 +94,7 @@ query_planner(Query *root,
 		root->query_pathkeys = NIL;		/* signal unordered result */
 
 		/* Make childless Result node to evaluate given tlist. */
-		return (Plan *) make_result(tlist, (Node *) qual, (Plan *) NULL);
+		return (Plan *) make_result(tlist, root->qual, (Plan *) NULL);
 	}
 
 	/*
@@ -111,10 +109,12 @@ query_planner(Query *root,
 	 * noncachable functions but no vars, such as "WHERE random() < 0.5".
 	 * These cannot be treated as normal restriction or join quals, but
 	 * they're not constants either.  Instead, attach them to the qpqual
-	 * of the top-level plan, so that they get evaluated once per potential
+	 * of the top plan, so that they get evaluated once per potential
 	 * output tuple.
 	 */
-	qual = pull_constant_clauses(qual, &noncachable_qual, &constant_qual);
+	normal_qual = pull_constant_clauses((List *) root->qual,
+										&noncachable_qual,
+										&constant_qual);
 
 	/*
 	 * Create a target list that consists solely of (resdom var) target
@@ -132,7 +132,7 @@ query_planner(Query *root,
 	/*
 	 * Choose the best access path and build a plan for it.
 	 */
-	subplan = subplanner(root, var_only_tlist, qual, tuple_fraction);
+	subplan = subplanner(root, var_only_tlist, normal_qual, tuple_fraction);
 
 	/*
 	 * Handle the noncachable quals.
@@ -188,6 +188,8 @@ subplanner(Query *root,
 		   List *qual,
 		   double tuple_fraction)
 {
+	List	   *joined_rels;
+	List	   *brel;
 	RelOptInfo *final_rel;
 	Plan	   *resultplan;
 	MemoryContext mycontext;
@@ -196,7 +198,7 @@ subplanner(Query *root,
 	Path	   *presortedpath;
 
 	/*
-	 * Initialize the targetlist and qualification, adding entries to
+	 * 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
@@ -207,13 +209,29 @@ subplanner(Query *root,
 	root->join_rel_list = NIL;
 	root->equi_key_list = NIL;
 
-	make_var_only_tlist(root, flat_tlist);
+	build_base_rel_tlists(root, flat_tlist);
+	(void) add_join_quals_to_rels(root, (Node *) root->jointree);
+	/* this must happen after add_join_quals_to_rels: */
 	add_restrict_and_join_to_rels(root, qual);
 
 	/*
-	 * Make sure we have RelOptInfo nodes for all relations used.
+	 * Make sure we have RelOptInfo nodes for all relations to be joined.
+	 */
+	joined_rels = add_missing_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.
 	 */
-	add_missing_rels_to_query(root);
+	foreach(brel, root->base_rel_list)
+	{
+		RelOptInfo *baserel = (RelOptInfo *) lfirst(brel);
+		int		relid = lfirsti(baserel->relids);
+
+		if (! ptrMember(baserel, joined_rels))
+			elog(ERROR, "Internal error: no jointree entry for rel %s (%d)",
+				 rt_fetch(relid, root->rtable)->eref->relname, relid);
+	}
 
 	/*
 	 * Use the completed lists of equijoined keys to deduce any implied
@@ -258,12 +276,11 @@ subplanner(Query *root,
 		 * We expect to end up here for a trivial INSERT ... VALUES query
 		 * (which will have a target relation, so it gets past
 		 * query_planner's check for empty range table; but the target rel
-		 * is unreferenced and not marked inJoinSet, so we find there is
-		 * nothing to join).
+		 * is not in the join tree, so we find there is nothing to join).
 		 *
 		 * It's also possible to get here if the query was rewritten by the
-		 * rule processor (creating rangetable entries not marked
-		 * inJoinSet) but the rules either did nothing or were simplified
+		 * rule processor (creating dummy rangetable entries that are not in
+		 * the join tree) but the rules either did nothing or were simplified
 		 * to nothing by constant-expression folding.  So, don't complain.
 		 */
 		root->query_pathkeys = NIL;		/* signal unordered result */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4be9b05bb90..7ffbb4666d9 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.88 2000/08/21 20:55:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.89 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,6 +29,7 @@
 #include "utils/lsyscache.h"
 
 
+static void preprocess_join_conditions(Query *parse, Node *jtnode);
 static List *make_subplanTargetList(Query *parse, List *tlist,
 					   AttrNumber **groupColIdx);
 static Plan *make_groupplan(List *group_tlist, bool tuplePerGroup,
@@ -163,6 +164,7 @@ subquery_planner(Query *parse, double tuple_fraction)
 	 * canonicalize_qual?
 	 */
 	parse->qual = (Node *) canonicalize_qual((Expr *) parse->qual, true);
+
 #ifdef OPTIMIZER_DEBUG
 	printf("After canonicalize_qual()\n");
 	pprint(parse->qual);
@@ -211,6 +213,9 @@ subquery_planner(Query *parse, double tuple_fraction)
 		parse->havingQual = SS_replace_correlation_vars(parse->havingQual);
 	}
 
+	/* Do all the above for each qual condition (ON clause) in the join tree */
+	preprocess_join_conditions(parse, (Node *) parse->jointree);
+
 	/* Do the main planning (potentially recursive) */
 
 	return union_planner(parse, tuple_fraction);
@@ -224,6 +229,58 @@ subquery_planner(Query *parse, double tuple_fraction)
 	 */
 }
 
+/*
+ * preprocess_join_conditions
+ *		Recursively scan the query's jointree and do subquery_planner's
+ *		qual preprocessing work on each ON condition found therein.
+ */
+static void
+preprocess_join_conditions(Query *parse, Node *jtnode)
+{
+	if (jtnode == NULL)
+		return;
+	if (IsA(jtnode, List))
+	{
+		List	   *l;
+
+		foreach(l, (List *) jtnode)
+			preprocess_join_conditions(parse, lfirst(l));
+	}
+	else if (IsA(jtnode, RangeTblRef))
+	{
+		/* nothing to do here */
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		preprocess_join_conditions(parse, j->larg);
+		preprocess_join_conditions(parse, j->rarg);
+
+		/* Simplify constant expressions */
+		j->quals = eval_const_expressions(j->quals);
+
+		/* Canonicalize the qual, and convert it to implicit-AND format */
+		j->quals = (Node *) canonicalize_qual((Expr *) j->quals, true);
+
+		/* Expand SubLinks to SubPlans */
+		if (parse->hasSubLinks)
+		{
+			j->quals = SS_process_sublinks(j->quals);
+			/*
+			 * ON conditions, like WHERE clauses, are evaluated pre-GROUP;
+			 * so we allow ungrouped vars in them.
+			 */
+		}
+
+		/* Replace uplevel vars with Param nodes */
+		if (PlannerQueryLevel > 1)
+			j->quals = SS_replace_correlation_vars(j->quals);
+	}
+	else
+		elog(ERROR, "preprocess_join_conditions: unexpected node type %d",
+			 nodeTag(jtnode));
+}
 
 /*--------------------
  * union_planner
@@ -542,7 +599,6 @@ union_planner(Query *parse,
 		/* Generate the (sub) plan */
 		result_plan = query_planner(parse,
 									sub_tlist,
-									(List *) parse->qual,
 									tuple_fraction);
 
 		/*
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index d8a09c017dd..d30636c185e 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.64 2000/06/04 20:50:50 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.65 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -106,11 +106,13 @@ set_plan_references(Plan *plan)
 			set_join_references((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);
 			fix_expr_references(plan, (Node *) plan->targetlist);
 			fix_expr_references(plan, (Node *) plan->qual);
+			fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
 			fix_expr_references(plan,
 								(Node *) ((MergeJoin *) plan)->mergeclauses);
 			break;
@@ -118,6 +120,7 @@ set_plan_references(Plan *plan)
 			set_join_references((Join *) plan);
 			fix_expr_references(plan, (Node *) plan->targetlist);
 			fix_expr_references(plan, (Node *) plan->qual);
+			fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual);
 			fix_expr_references(plan,
 								(Node *) ((HashJoin *) plan)->hashclauses);
 			break;
@@ -236,15 +239,15 @@ fix_expr_references(Plan *plan, Node *node)
 static void
 set_join_references(Join *join)
 {
-	Plan	   *outer = join->lefttree;
-	Plan	   *inner = join->righttree;
+	Plan	   *outer = join->plan.lefttree;
+	Plan	   *inner = join->plan.righttree;
 	List	   *outer_tlist = ((outer == NULL) ? NIL : outer->targetlist);
 	List	   *inner_tlist = ((inner == NULL) ? NIL : inner->targetlist);
 
-	join->targetlist = join_references(join->targetlist,
-									   outer_tlist,
-									   inner_tlist,
-									   (Index) 0);
+	join->plan.targetlist = join_references(join->plan.targetlist,
+											outer_tlist,
+											inner_tlist,
+											(Index) 0);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index b0772b83f1c..24a0aae55cd 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.40 2000/08/06 04:13:22 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.41 2000/09/12 21:06:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -649,12 +649,21 @@ SS_finalize_plan(Plan *plan)
 			 */
 			break;
 
+		case T_NestLoop:
+			finalize_primnode((Node *) ((Join *) plan)->joinqual,
+							  &results);
+			break;
+
 		case T_MergeJoin:
+			finalize_primnode((Node *) ((Join *) plan)->joinqual,
+							  &results);
 			finalize_primnode((Node *) ((MergeJoin *) plan)->mergeclauses,
 							  &results);
 			break;
 
 		case T_HashJoin:
+			finalize_primnode((Node *) ((Join *) plan)->joinqual,
+							  &results);
 			finalize_primnode((Node *) ((HashJoin *) plan)->hashclauses,
 							  &results);
 			break;
@@ -671,7 +680,6 @@ SS_finalize_plan(Plan *plan)
 
 		case T_Agg:
 		case T_SeqScan:
-		case T_NestLoop:
 		case T_Material:
 		case T_Sort:
 		case T_Unique:
diff --git a/src/backend/optimizer/prep/prepkeyset.c b/src/backend/optimizer/prep/prepkeyset.c
index fc192e6f28b..a28e329e537 100644
--- a/src/backend/optimizer/prep/prepkeyset.c
+++ b/src/backend/optimizer/prep/prepkeyset.c
@@ -107,6 +107,7 @@ transformKeySetQuery(Query *origNode)
 		Node_Copy(origNode, unionNode, distinctClause);
 		Node_Copy(origNode, unionNode, sortClause);
 		Node_Copy(origNode, unionNode, rtable);
+		Node_Copy(origNode, unionNode, jointree);
 		Node_Copy(origNode, unionNode, targetList);
 
 		origNode->unionClause = lappend(origNode->unionClause, unionNode);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index f069cafdf66..d284dd51e02 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.51 2000/06/20 04:22:16 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.52 2000/09/12 21:06:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -528,12 +528,9 @@ fix_parsetree_attnums(Index rt_index,
 	context.new_relid = new_relid;
 	context.sublevels_up = 0;
 
-	/*
-	 * We must scan both the targetlist and qual, but we know the
-	 * havingQual is empty, so we can ignore it.
-	 */
-	fix_parsetree_attnums_walker((Node *) parsetree->targetList, &context);
-	fix_parsetree_attnums_walker((Node *) parsetree->qual, &context);
+	query_tree_walker(parsetree,
+					  fix_parsetree_attnums_walker,
+					  (void *) &context);
 }
 
 /*
@@ -565,38 +562,17 @@ fix_parsetree_attnums_walker(Node *node,
 		}
 		return false;
 	}
-	if (IsA(node, SubLink))
+	if (IsA(node, Query))
 	{
+		/* Recurse into subselects */
+		bool		result;
 
-		/*
-		 * Standard expression_tree_walker will not recurse into
-		 * subselect, but here we must do so.
-		 */
-		SubLink    *sub = (SubLink *) node;
-
-		if (fix_parsetree_attnums_walker((Node *) (sub->lefthand), context))
-			return true;
 		context->sublevels_up++;
-		if (fix_parsetree_attnums_walker((Node *) (sub->subselect), context))
-		{
-			context->sublevels_up--;
-			return true;
-		}
+		result = query_tree_walker((Query *) node,
+								   fix_parsetree_attnums_walker,
+								   (void *) context);
 		context->sublevels_up--;
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Reach here after recursing down into subselect above... */
-		Query	   *qry = (Query *) node;
-
-		if (fix_parsetree_attnums_walker((Node *) (qry->targetList), context))
-			return true;
-		if (fix_parsetree_attnums_walker((Node *) (qry->qual), context))
-			return true;
-		if (fix_parsetree_attnums_walker((Node *) (qry->havingQual), context))
-			return true;
-		return false;
+		return result;
 	}
 	return expression_tree_walker(node, fix_parsetree_attnums_walker,
 								  (void *) context);
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index cf0b6dd703c..36c7abd85b5 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.73 2000/08/24 03:29:05 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.74 2000/09/12 21:06:58 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -591,7 +591,7 @@ check_subplans_for_ungrouped_vars_walker(Node *node,
 					elog(ERROR, "cache lookup of attribute %d in relation %u failed",
 						 var->varattno, rte->relid);
 				elog(ERROR, "Sub-SELECT uses un-GROUPed attribute %s.%s from outer query",
-					 rte->ref->relname, attname);
+					 rte->eref->relname, attname);
 			}
 		}
 	}
@@ -1639,25 +1639,44 @@ simplify_op_or_func(Expr *expr, List *args)
  * will have List structure at the top level, and it handles TargetEntry nodes
  * so that a scan of a target list can be handled without additional code.
  * (But only the "expr" part of a TargetEntry is examined, unless the walker
- * chooses to process TargetEntry nodes specially.)
+ * chooses to process TargetEntry nodes specially.)  Also, RangeTblRef and
+ * JoinExpr nodes are handled, so that qual expressions in a jointree can be
+ * processed without additional code.
+ *
+ * expression_tree_walker will handle SubLink and SubPlan nodes by recursing
+ * normally into the "lefthand" arguments (which belong to the outer plan).
+ * It will also call the walker on the sub-Query node; however, when
+ * expression_tree_walker itself is called on a Query node, it does nothing
+ * and returns "false".  The net effect is that unless the walker does
+ * something special at a Query node, sub-selects will not be visited
+ * during an expression tree walk.  This is exactly the behavior wanted
+ * in many cases --- and for those walkers that do want to recurse into
+ * sub-selects, special behavior is typically needed anyway at the entry
+ * to a sub-select (such as incrementing a depth counter).  A walker that
+ * wants to examine sub-selects should include code along the lines of:
+ *
+ *		if (IsA(node, Query))
+ *		{
+ *			adjust context for subquery;
+ *			result = query_tree_walker((Query *) node, my_walker, context);
+ *			restore context if needed;
+ *			return result;
+ *		}
  *
- * expression_tree_walker will handle a SUBPLAN_EXPR node by recursing into
- * the args and slink->oper lists (which belong to the outer plan), but it
- * will *not* visit the inner plan, since that's typically what expression
- * tree walkers want.  A walker that wants to visit the subplan can force
- * appropriate behavior by recognizing subplan expression nodes and doing
- * the right thing.
+ * query_tree_walker is a convenience routine (see below) that calls the
+ * walker on all the expression subtrees of the given Query node.
  *
- * Bare SubLink nodes (without a SUBPLAN_EXPR) are handled by recursing into
- * the "lefthand" argument list only.  (A bare SubLink should be seen only if
- * the tree has not yet been processed by subselect.c.)  Again, this can be
- * overridden by the walker, but it seems to be the most useful default
- * behavior.
+ * NOTE: currently, because make_subplan() clears the subselect link in
+ * a SubLink node, it is not actually possible to recurse into subselects
+ * of an already-planned expression tree.  This is OK for current uses,
+ * but ought to be cleaned up when we redesign querytree processing.
  *--------------------
  */
 
 bool
-			expression_tree_walker(Node *node, bool (*walker) (), void *context)
+expression_tree_walker(Node *node,
+					   bool (*walker) (),
+					   void *context)
 {
 	List	   *temp;
 
@@ -1677,6 +1696,7 @@ bool
 		case T_Const:
 		case T_Var:
 		case T_Param:
+		case T_RangeTblRef:
 			/* primitive node types with no subnodes */
 			break;
 		case T_Expr:
@@ -1750,17 +1770,31 @@ bool
 
 				/*
 				 * If the SubLink has already been processed by
-				 * subselect.c, it will have lefthand=NIL, and we only
-				 * need to look at the oper list.  Otherwise we only need
-				 * to look at lefthand (the Oper nodes in the oper list
-				 * are deemed uninteresting).
+				 * subselect.c, it will have lefthand=NIL, and we need to
+				 * scan the oper list.  Otherwise we only need to look at
+				 * the lefthand list (the incomplete Oper nodes in the oper
+				 * list are deemed uninteresting, perhaps even confusing).
 				 */
 				if (sublink->lefthand)
-					return walker((Node *) sublink->lefthand, context);
+				{
+					if (walker((Node *) sublink->lefthand, context))
+						return true;
+				}
 				else
-					return walker((Node *) sublink->oper, context);
+				{
+					if (walker((Node *) sublink->oper, context))
+						return true;
+				}
+				/*
+				 * Also invoke the walker on the sublink's Query node,
+				 * so it can recurse into the sub-query if it wants to.
+				 */
+				return walker(sublink->subselect, context);
 			}
 			break;
+		case T_Query:
+			/* Do nothing with a sub-Query, per discussion above */
+			break;
 		case T_List:
 			foreach(temp, (List *) node)
 			{
@@ -1770,6 +1804,23 @@ bool
 			break;
 		case T_TargetEntry:
 			return walker(((TargetEntry *) node)->expr, context);
+		case T_JoinExpr:
+			{
+				JoinExpr    *join = (JoinExpr *) node;
+
+				if (walker(join->larg, context))
+					return true;
+				if (walker(join->rarg, context))
+					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.
+				 */
+			}
+			break;
 		default:
 			elog(ERROR, "expression_tree_walker: Unexpected node type %d",
 				 nodeTag(node));
@@ -1778,6 +1829,37 @@ bool
 	return false;
 }
 
+/*
+ * query_tree_walker --- initiate a walk of a Query's expressions
+ *
+ * This routine exists just to reduce the number of places that need to know
+ * where all the expression subtrees of a Query are.  Note it can be used
+ * for starting a walk at top level of a Query regardless of whether the
+ * walker intends to descend into subqueries.  It is also useful for
+ * descending into subqueries within a walker.
+ */
+bool
+query_tree_walker(Query *query,
+				  bool (*walker) (),
+				  void *context)
+{
+	Assert(query != NULL && IsA(query, Query));
+
+	if (walker((Node *) query->targetList, context))
+		return true;
+	if (walker(query->qual, context))
+		return true;
+	if (walker(query->havingQual, context))
+		return true;
+	if (walker((Node *) query->jointree, context))
+		return true;
+	/*
+	 * XXX for subselect-in-FROM, may need to examine rtable as well
+	 */
+	return false;
+}
+
+
 /*--------------------
  * expression_tree_mutator() is designed to support routines that make a
  * modified copy of an expression tree, with some nodes being added,
@@ -1838,7 +1920,9 @@ bool
  */
 
 Node *
-			expression_tree_mutator(Node *node, Node *(*mutator) (), void *context)
+expression_tree_mutator(Node *node,
+						Node *(*mutator) (),
+						void *context)
 {
 
 	/*
@@ -1866,6 +1950,7 @@ Node *
 		case T_Const:
 		case T_Var:
 		case T_Param:
+		case T_RangeTblRef:
 			/* primitive node types with no subnodes */
 			return (Node *) copyObject(node);
 		case T_Expr:
@@ -2044,6 +2129,20 @@ Node *
 				return (Node *) newnode;
 			}
 			break;
+		case T_JoinExpr:
+			{
+				JoinExpr *join = (JoinExpr *) node;
+				JoinExpr *newnode;
+
+				FLATCOPY(newnode, join, JoinExpr);
+				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 */
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "expression_tree_mutator: Unexpected node type %d",
 				 nodeTag(node));
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 5588e91e5b7..fc73bb2b664 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.64 2000/05/30 00:49:49 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/pathnode.c,v 1.65 2000/09/12 21:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -119,7 +119,9 @@ set_cheapest(RelOptInfo *parent_rel)
 	Path	   *cheapest_total_path;
 
 	Assert(IsA(parent_rel, RelOptInfo));
-	Assert(pathlist != NIL);
+
+	if (pathlist == NIL)
+		elog(ERROR, "Unable to devise a query plan for the given query");
 
 	cheapest_startup_path = cheapest_total_path = (Path *) lfirst(pathlist);
 
@@ -352,6 +354,7 @@ create_index_path(Query *root,
 	 * number of rows is the same as the parent rel's estimate.
 	 */
 	pathnode->joinrelids = NIL; /* no join clauses here */
+	pathnode->alljoinquals = false;
 	pathnode->rows = rel->rows;
 
 	cost_index(&pathnode->path, root, rel, index, indexquals, false);
@@ -393,6 +396,7 @@ create_tidscan_path(RelOptInfo *rel, List *tideval)
  *	  relations.
  *
  * 'joinrel' is the join relation.
+ * 'jointype' is the type of join required
  * 'outer_path' is the outer path
  * 'inner_path' is the inner path
  * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
@@ -403,6 +407,7 @@ create_tidscan_path(RelOptInfo *rel, List *tideval)
  */
 NestPath   *
 create_nestloop_path(RelOptInfo *joinrel,
+					 JoinType jointype,
 					 Path *outer_path,
 					 Path *inner_path,
 					 List *restrict_clauses,
@@ -412,6 +417,7 @@ create_nestloop_path(RelOptInfo *joinrel,
 
 	pathnode->path.pathtype = T_NestLoop;
 	pathnode->path.parent = joinrel;
+	pathnode->jointype = jointype;
 	pathnode->outerjoinpath = outer_path;
 	pathnode->innerjoinpath = inner_path;
 	pathnode->joinrestrictinfo = restrict_clauses;
@@ -428,6 +434,7 @@ create_nestloop_path(RelOptInfo *joinrel,
  *	  two relations
  *
  * 'joinrel' is the join relation
+ * 'jointype' is the type of join required
  * 'outer_path' is the outer path
  * 'inner_path' is the inner path
  * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
@@ -440,6 +447,7 @@ create_nestloop_path(RelOptInfo *joinrel,
  */
 MergePath  *
 create_mergejoin_path(RelOptInfo *joinrel,
+					  JoinType jointype,
 					  Path *outer_path,
 					  Path *inner_path,
 					  List *restrict_clauses,
@@ -463,6 +471,7 @@ create_mergejoin_path(RelOptInfo *joinrel,
 
 	pathnode->jpath.path.pathtype = T_MergeJoin;
 	pathnode->jpath.path.parent = joinrel;
+	pathnode->jpath.jointype = jointype;
 	pathnode->jpath.outerjoinpath = outer_path;
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
@@ -486,6 +495,7 @@ create_mergejoin_path(RelOptInfo *joinrel,
  *	  Creates a pathnode corresponding to a hash join between two relations.
  *
  * 'joinrel' is the join relation
+ * 'jointype' is the type of join required
  * 'outer_path' is the cheapest outer path
  * 'inner_path' is the cheapest inner path
  * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
@@ -496,6 +506,7 @@ create_mergejoin_path(RelOptInfo *joinrel,
  */
 HashPath   *
 create_hashjoin_path(RelOptInfo *joinrel,
+					 JoinType jointype,
 					 Path *outer_path,
 					 Path *inner_path,
 					 List *restrict_clauses,
@@ -506,6 +517,7 @@ create_hashjoin_path(RelOptInfo *joinrel,
 
 	pathnode->jpath.path.pathtype = T_HashJoin;
 	pathnode->jpath.path.parent = joinrel;
+	pathnode->jpath.jointype = jointype;
 	pathnode->jpath.outerjoinpath = outer_path;
 	pathnode->jpath.innerjoinpath = inner_path;
 	pathnode->jpath.joinrestrictinfo = restrict_clauses;
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 070fabf7669..87e87597d11 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.27 2000/06/18 22:44:12 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/relnode.c,v 1.28 2000/09/12 21:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -72,6 +72,7 @@ get_base_rel(Query *root, int relid)
 	rel->tuples = 0;
 	rel->baserestrictinfo = NIL;
 	rel->baserestrictcost = 0;
+	rel->outerjoinset = NIL;
 	rel->joininfo = NIL;
 	rel->innerjoin = NIL;
 
@@ -178,6 +179,7 @@ get_join_rel(Query *root,
 	joinrel->tuples = 0;
 	joinrel->baserestrictinfo = NIL;
 	joinrel->baserestrictcost = 0;
+	joinrel->outerjoinset = NIL;
 	joinrel->joininfo = NIL;
 	joinrel->innerjoin = NIL;
 
@@ -216,8 +218,7 @@ get_join_rel(Query *root,
 							   restrictlist);
 
 	/*
-	 * Add the joinrel to the front of the query's joinrel list.
-	 * (allpaths.c depends on this ordering!)
+	 * Add the joinrel to the query's joinrel list.
 	 */
 	root->join_rel_list = lcons(joinrel, root->join_rel_list);
 
diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c
index 3ce924de1bb..adbfd884c36 100644
--- a/src/backend/optimizer/util/restrictinfo.c
+++ b/src/backend/optimizer/util/restrictinfo.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.10 2000/05/30 00:49:49 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/restrictinfo.c,v 1.11 2000/09/12 21:06:58 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -54,3 +54,29 @@ get_actual_clauses(List *restrictinfo_list)
 	}
 	return result;
 }
+
+/*
+ * get_actual_join_clauses
+ *
+ * Extract clauses from 'restrictinfo_list', separating those that
+ * came from JOIN/ON conditions from those that didn't.
+ */
+void
+get_actual_join_clauses(List *restrictinfo_list,
+						List **joinquals, List **otherquals)
+{
+	List	   *temp;
+
+	*joinquals = NIL;
+	*otherquals = NIL;
+
+	foreach(temp, restrictinfo_list)
+	{
+		RestrictInfo *clause = (RestrictInfo *) lfirst(temp);
+
+		if (clause->isjoinqual)
+			*joinquals = lappend(*joinquals, clause->clause);
+		else
+			*otherquals = lappend(*otherquals, clause->clause);
+	}
+}
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index bed7be7f08a..ec9f9dafd0b 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.26 2000/04/12 17:15:24 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.27 2000/09/12 21:06:59 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,17 +16,25 @@
 
 #include "postgres.h"
 
+#include "nodes/plannodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/var.h"
 
 
+typedef struct
+{
+	List	   *varlist;
+	int			sublevels_up;
+} pull_varnos_context;
+
 typedef struct
 {
 	List	   *varlist;
 	bool		includeUpperVars;
 } pull_var_clause_context;
 
-static bool pull_varnos_walker(Node *node, List **listptr);
+static bool pull_varnos_walker(Node *node,
+							   pull_varnos_context *context);
 static bool contain_var_clause_walker(Node *node, void *context);
 static bool pull_var_clause_walker(Node *node,
 					   pull_var_clause_context *context);
@@ -35,21 +43,39 @@ static bool pull_var_clause_walker(Node *node,
 /*
  *		pull_varnos
  *
- *		Create a list of all the distinct varnos present in a parsetree
- *		(tlist or qual).  Note that only varnos attached to level-zero
- *		Vars are considered --- upper Vars refer to some other rtable!
+ *		Create a list of all the distinct varnos present in a parsetree.
+ *		Only varnos that reference level-zero rtable entries are considered.
+ *
+ * NOTE: unlike other routines in this file, pull_varnos() is used on
+ * not-yet-planned expressions.  It may therefore find bare SubLinks,
+ * and if so it needs to recurse into them to look for uplevel references
+ * to the desired rtable level!  But when we find a completed SubPlan,
+ * we only need to look at the parameters passed to the subplan.
  */
 List *
 pull_varnos(Node *node)
 {
-	List	   *result = NIL;
+	pull_varnos_context context;
+
+	context.varlist = NIL;
+	context.sublevels_up = 0;
+
+	/*
+	 * Must be prepared to start with a Query or a bare expression tree;
+	 * if it's a Query, go straight to query_tree_walker to make sure that
+	 * sublevels_up doesn't get incremented prematurely.
+	 */
+	if (node && IsA(node, Query))
+		query_tree_walker((Query *) node, pull_varnos_walker,
+						  (void *) &context);
+	else
+		pull_varnos_walker(node, &context);
 
-	pull_varnos_walker(node, &result);
-	return result;
+	return context.varlist;
 }
 
 static bool
-pull_varnos_walker(Node *node, List **listptr)
+pull_varnos_walker(Node *node, pull_varnos_context *context)
 {
 	if (node == NULL)
 		return false;
@@ -57,11 +83,42 @@ pull_varnos_walker(Node *node, List **listptr)
 	{
 		Var		   *var = (Var *) node;
 
-		if (var->varlevelsup == 0 && !intMember(var->varno, *listptr))
-			*listptr = lconsi(var->varno, *listptr);
+		if (var->varlevelsup == context->sublevels_up &&
+			!intMember(var->varno, context->varlist))
+			context->varlist = lconsi(var->varno, context->varlist);
+		return false;
+	}
+	if (is_subplan(node))
+	{
+		/*
+		 * Already-planned subquery.  Examine the args list (parameters
+		 * to be passed to subquery), as well as the "oper" list which
+		 * is executed by the outer query.  But short-circuit recursion into
+		 * the subquery itself, which would be a waste of effort.
+		 */
+		Expr	   *expr = (Expr *) node;
+
+		if (pull_varnos_walker((Node*) ((SubPlan*) expr->oper)->sublink->oper,
+							   context))
+			return true;
+		if (pull_varnos_walker((Node *) expr->args,
+							   context))
+			return true;
 		return false;
 	}
-	return expression_tree_walker(node, pull_varnos_walker, (void *) listptr);
+	if (IsA(node, Query))
+	{
+		/* Recurse into not-yet-planned subquery */
+		bool		result;
+
+		context->sublevels_up++;
+		result = query_tree_walker((Query *) node, pull_varnos_walker,
+								   (void *) context);
+		context->sublevels_up--;
+		return result;
+	}
+	return expression_tree_walker(node, pull_varnos_walker,
+								  (void *) context);
 }
 
 /*
diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile
index 6ef084d04b6..68fed79d382 100644
--- a/src/backend/parser/Makefile
+++ b/src/backend/parser/Makefile
@@ -2,7 +2,7 @@
 #
 # Makefile for parser
 #
-# $Header: /cvsroot/pgsql/src/backend/parser/Makefile,v 1.29 2000/08/28 11:53:19 petere Exp $
+# $Header: /cvsroot/pgsql/src/backend/parser/Makefile,v 1.30 2000/09/12 21:07:00 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -31,7 +31,7 @@ $(srcdir)/gram.c $(srcdir)/parse.h: gram.y
 
 $(srcdir)/scan.c: scan.l
 ifdef FLEX
-	$(FLEX) $(FLEXFLAGS) -o'$@' $<
+	$(FLEX) $(FLEXFLAGS) -Pbase_yy -o'$@' $<
 else
 	@$(missing) flex $< $@
 endif
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index fe21804a2c4..0165ef15c21 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Id: analyze.c,v 1.156 2000/08/29 04:20:44 momjian Exp $
+ *	$Id: analyze.c,v 1.157 2000/09/12 21:07:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,6 +25,7 @@
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
 #include "parser/parse_type.h"
+#include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/relcache.h"
@@ -54,6 +55,8 @@ static void transformConstraintAttrs(List *constraintList);
 static void transformColumnType(ParseState *pstate, ColumnDef *column);
 static void transformFkeyCheckAttrs(FkConstraint *fkconstraint);
 
+static void release_pstate_resources(ParseState *pstate);
+
 /* kluge to return extra info from transformCreateStmt() */
 static List *extras_before;
 static List *extras_after;
@@ -71,28 +74,22 @@ List *
 parse_analyze(List *pl, ParseState *parentParseState)
 {
 	List	   *result = NIL;
-	ParseState *pstate;
-	Query	   *parsetree;
 
 	while (pl != NIL)
 	{
+		ParseState *pstate = make_parsestate(parentParseState);
+		Query	   *parsetree;
+
 		extras_before = extras_after = NIL;
-		pstate = make_parsestate(parentParseState);
 
 		parsetree = transformStmt(pstate, lfirst(pl));
-		if (pstate->p_target_relation != NULL)
-			heap_close(pstate->p_target_relation, AccessShareLock);
-		pstate->p_target_relation = NULL;
-		pstate->p_target_rangetblentry = NULL;
+		release_pstate_resources(pstate);
 
 		while (extras_before != NIL)
 		{
 			result = lappend(result,
-						   transformStmt(pstate, lfirst(extras_before)));
-			if (pstate->p_target_relation != NULL)
-				heap_close(pstate->p_target_relation, AccessShareLock);
-			pstate->p_target_relation = NULL;
-			pstate->p_target_rangetblentry = NULL;
+							 transformStmt(pstate, lfirst(extras_before)));
+			release_pstate_resources(pstate);
 			extras_before = lnext(extras_before);
 		}
 
@@ -102,10 +99,7 @@ parse_analyze(List *pl, ParseState *parentParseState)
 		{
 			result = lappend(result,
 							 transformStmt(pstate, lfirst(extras_after)));
-			if (pstate->p_target_relation != NULL)
-				heap_close(pstate->p_target_relation, AccessShareLock);
-			pstate->p_target_relation = NULL;
-			pstate->p_target_rangetblentry = NULL;
+			release_pstate_resources(pstate);
 			extras_after = lnext(extras_after);
 		}
 
@@ -116,6 +110,15 @@ parse_analyze(List *pl, ParseState *parentParseState)
 	return result;
 }
 
+static void
+release_pstate_resources(ParseState *pstate)
+{
+	if (pstate->p_target_relation != NULL)
+		heap_close(pstate->p_target_relation, AccessShareLock);
+	pstate->p_target_relation = NULL;
+	pstate->p_target_rangetblentry = NULL;
+}
+
 /*
  * transformStmt -
  *	  transform a Parse tree. If it is an optimizable statement, turn it
@@ -176,11 +179,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 						Resdom	   *rd;
 
 						id = nth(i, n->aliases);
-						Assert(nodeTag(id) == T_Ident);
+						Assert(IsA(id, Ident));
 						te = nth(i, targetList);
-						Assert(nodeTag(te) == T_TargetEntry);
+						Assert(IsA(te, TargetEntry));
 						rd = te->resdom;
-						Assert(nodeTag(rd) == T_Resdom);
+						Assert(IsA(rd, Resdom));
 						rd->resname = pstrdup(id->name);
 					}
 				}
@@ -290,15 +293,17 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->commandType = CMD_DELETE;
 
 	/* set up a range table */
-	makeRangeTable(pstate, NULL);
-	setTargetTable(pstate, stmt->relname, stmt->inh);
+	makeRangeTable(pstate, NIL);
+	setTargetTable(pstate, stmt->relname, stmt->inh, true);
 
 	qry->distinctClause = NIL;
 
 	/* fix where clause */
 	qry->qual = transformWhereClause(pstate, stmt->whereClause);
 
+	/* done building the rtable */
 	qry->rtable = pstate->p_rtable;
+	qry->jointree = pstate->p_jointree;
 	qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
@@ -387,12 +392,14 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 *
 	 * In particular, it's time to add the INSERT target to the rangetable.
 	 * (We didn't want it there until now since it shouldn't be visible in
-	 * the SELECT part.)
+	 * the SELECT part.)  Note that the INSERT target is NOT added to the
+	 * join tree, since we don't want to join over it.
 	 */
-	setTargetTable(pstate, stmt->relname, FALSE);
+	setTargetTable(pstate, stmt->relname, false, false);
 
 	/* now the range table will not change */
 	qry->rtable = pstate->p_rtable;
+	qry->jointree = pstate->p_jointree;
 	qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
 	/* Prepare to assign non-conflicting resnos to resjunk attributes */
@@ -908,7 +915,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
 	while (dlist != NIL)
 	{
 		constraint = lfirst(dlist);
-		Assert(nodeTag(constraint) == T_Constraint);
+		Assert(IsA(constraint, Constraint));
 		Assert((constraint->contype == CONSTR_PRIMARY)
 			   || (constraint->contype == CONSTR_UNIQUE));
 
@@ -1427,17 +1434,68 @@ static Query *
 transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
 {
 	Query	   *qry;
-	Query	   *action;
-	List	   *actions;
+	RangeTblEntry *oldrte;
+	RangeTblEntry *newrte;
 
 	qry = makeNode(Query);
 	qry->commandType = CMD_UTILITY;
+	qry->utilityStmt = (Node *) stmt;
+
+	/*
+	 * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW'
+	 * equal to 2.  Set up their RTEs in the main pstate for use
+	 * in parsing the rule qualification.
+	 */
+	Assert(pstate->p_rtable == NIL);
+	oldrte = addRangeTableEntry(pstate, stmt->object->relname,
+								makeAttr("*OLD*", NULL),
+								false, true);
+	newrte = addRangeTableEntry(pstate, stmt->object->relname,
+								makeAttr("*NEW*", NULL),
+								false, true);
+	/*
+	 * They must be in the jointree too for lookup purposes, but only add
+	 * the one(s) that are relevant for the current kind of rule.  In an
+	 * UPDATE rule, quals must refer to OLD.field or NEW.field to be
+	 * unambiguous, but there's no need to be so picky for INSERT & DELETE.
+	 * (Note we marked the RTEs "inFromCl = true" above to allow unqualified
+	 * references to their fields.)
+	 */
+	switch (stmt->event)
+	{
+		case CMD_SELECT:
+			addRTEtoJoinTree(pstate, oldrte);
+			break;
+		case CMD_UPDATE:
+			addRTEtoJoinTree(pstate, oldrte);
+			addRTEtoJoinTree(pstate, newrte);
+			break;
+		case CMD_INSERT:
+			addRTEtoJoinTree(pstate, newrte);
+			break;
+		case CMD_DELETE:
+			addRTEtoJoinTree(pstate, oldrte);
+			break;
+		default:
+			elog(ERROR, "transformRuleStmt: unexpected event type %d",
+				 (int) stmt->event);
+			break;
+	}
+
+	/* take care of the where clause */
+	stmt->whereClause = transformWhereClause(pstate, stmt->whereClause);
+
+	if (length(pstate->p_rtable) != 2) /* naughty, naughty... */
+		elog(ERROR, "Rule WHERE condition may not contain references to other relations");
+
+	/* save info about sublinks in where clause */
+	qry->hasSubLinks = pstate->p_hasSubLinks;
 
 	/*
-	 * 'instead nothing' rules with a qualification need a query a
+	 * 'instead nothing' rules with a qualification need a query
 	 * rangetable so the rewrite handler can add the negated rule
 	 * qualification to the original query. We create a query with the new
-	 * command type CMD_NOTHING here that is treated special by the
+	 * command type CMD_NOTHING here that is treated specially by the
 	 * rewrite system.
 	 */
 	if (stmt->actions == NIL)
@@ -1445,54 +1503,95 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
 		Query	   *nothing_qry = makeNode(Query);
 
 		nothing_qry->commandType = CMD_NOTHING;
-
-		addRangeTableEntry(pstate, stmt->object->relname,
-						   makeAttr("*OLD*", NULL),
-						   FALSE, FALSE, FALSE);
-		addRangeTableEntry(pstate, stmt->object->relname,
-						   makeAttr("*NEW*", NULL),
-						   FALSE, FALSE, FALSE);
-
 		nothing_qry->rtable = pstate->p_rtable;
+		nothing_qry->jointree = NIL; /* no join actually wanted */
 
 		stmt->actions = lappend(NIL, nothing_qry);
 	}
-
-	actions = stmt->actions;
-
-	/*
-	 * transform each statment, like parse_analyze()
-	 */
-	while (actions != NIL)
+	else
 	{
+		List	   *actions;
 
 		/*
-		 * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW'
-		 * equal to 2.
+		 * transform each statement, like parse_analyze()
 		 */
-		addRangeTableEntry(pstate, stmt->object->relname,
-						   makeAttr("*OLD*", NULL),
-						   FALSE, FALSE, FALSE);
-		addRangeTableEntry(pstate, stmt->object->relname,
-						   makeAttr("*NEW*", NULL),
-						   FALSE, FALSE, FALSE);
-
-		pstate->p_last_resno = 1;
-		pstate->p_is_rule = true;		/* for expand all */
-		pstate->p_hasAggs = false;
-
-		action = (Query *) lfirst(actions);
-		if (action->commandType != CMD_NOTHING)
-			lfirst(actions) = transformStmt(pstate, lfirst(actions));
-		actions = lnext(actions);
-	}
+		foreach(actions, stmt->actions)
+		{
+			ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
+			Query	   *sub_qry;
+			bool		has_old,
+						has_new;
 
-	/* take care of the where clause */
-	stmt->whereClause = transformWhereClause(pstate, stmt->whereClause);
+			/*
+			 * Set up OLD/NEW in the rtable for this statement.  The entries
+			 * are marked not inFromCl because we don't want them to be
+			 * referred to by unqualified field names nor "*" in the rule
+			 * actions.  We don't need to add them to the jointree for
+			 * qualified-name lookup, either (see qualifiedNameToVar()).
+			 */
+			oldrte = addRangeTableEntry(sub_pstate, stmt->object->relname,
+										makeAttr("*OLD*", NULL),
+										false, false);
+			newrte = addRangeTableEntry(sub_pstate, stmt->object->relname,
+										makeAttr("*NEW*", NULL),
+										false, false);
 
-	qry->hasSubLinks = pstate->p_hasSubLinks;
+			/* Transform the rule action statement */
+			sub_qry = transformStmt(sub_pstate, lfirst(actions));
+
+			/*
+			 * Validate action's use of OLD/NEW, qual too
+			 */
+			has_old =
+				rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) ||
+				rangeTableEntry_used(stmt->whereClause, PRS2_OLD_VARNO, 0);
+			has_new =
+				rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) ||
+				rangeTableEntry_used(stmt->whereClause, PRS2_NEW_VARNO, 0);
+
+			switch (stmt->event)
+			{
+				case CMD_SELECT:
+					if (has_old)
+						elog(ERROR, "ON SELECT rule may not use OLD");
+					if (has_new)
+						elog(ERROR, "ON SELECT rule may not use NEW");
+					break;
+				case CMD_UPDATE:
+					/* both are OK */
+					break;
+				case CMD_INSERT:
+					if (has_old)
+						elog(ERROR, "ON INSERT rule may not use OLD");
+					break;
+				case CMD_DELETE:
+					if (has_new)
+						elog(ERROR, "ON DELETE rule may not use NEW");
+					break;
+				default:
+					elog(ERROR, "transformRuleStmt: unexpected event type %d",
+						 (int) stmt->event);
+					break;
+			}
+
+			/*
+			 * For efficiency's sake, add OLD to the rule action's jointree
+			 * only if it was actually referenced in the statement or qual.
+			 * NEW is not really a relation and should never be added.
+			 */
+			if (has_old)
+			{
+				addRTEtoJoinTree(sub_pstate, oldrte);
+				sub_qry->jointree = sub_pstate->p_jointree;
+			}
+
+			lfirst(actions) = sub_qry;
+
+			release_pstate_resources(sub_pstate);
+			pfree(sub_pstate);
+		}
+	}
 
-	qry->utilityStmt = (Node *) stmt;
 	return qry;
 }
 
@@ -1558,6 +1657,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->intersectClause = stmt->intersectClause;
 
 	qry->rtable = pstate->p_rtable;
+	qry->jointree = pstate->p_jointree;
 
 	if (stmt->forUpdate != NULL)
 		transformForUpdate(qry, stmt->forUpdate);
@@ -1585,17 +1685,17 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	 * do this with REPLACE in POSTQUEL so we keep the feature.
 	 */
 	makeRangeTable(pstate, stmt->fromClause);
-	setTargetTable(pstate, stmt->relname, stmt->inh);
+	setTargetTable(pstate, stmt->relname, stmt->inh, true);
 
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
 	qry->qual = transformWhereClause(pstate, stmt->whereClause);
 
-	qry->hasSubLinks = pstate->p_hasSubLinks;
-
 	qry->rtable = pstate->p_rtable;
+	qry->jointree = pstate->p_jointree;
 	qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
+	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasAggs = pstate->p_hasAggs;
 	if (pstate->p_hasAggs)
 		parseCheckAggregates(pstate, qry);
@@ -1689,7 +1789,7 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt)
 			transformColumnType(pstate, (ColumnDef *) stmt->def);
 			break;
 		case 'C':
-			if (stmt->def && nodeTag(stmt->def) == T_FkConstraint)
+			if (stmt->def && IsA(stmt->def, FkConstraint))
 			{
 				CreateTrigStmt *fk_trigger;
 				List	   *fk_attr;
@@ -2085,7 +2185,7 @@ transformForUpdate(Query *qry, List *forUpdate)
 			i++;
 		}
 		if (l2 == NULL)
-			elog(ERROR, "FOR UPDATE: relation '%s' not found in FROM clause",
+			elog(ERROR, "FOR UPDATE: relation \"%s\" not found in FROM clause",
 				 relname);
 	}
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e970ab1871..301be9eb9b9 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.188 2000/09/12 05:09:44 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.189 2000/09/12 21:07:01 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -36,6 +36,7 @@
 #include <ctype.h>
 
 #include "postgres.h"
+
 #include "access/htup.h"
 #include "access/xact.h"
 #include "catalog/catname.h"
@@ -77,15 +78,10 @@ static Node *makeA_Expr(int oper, char *opname, Node *lexpr, Node *rexpr);
 static Node *makeTypeCast(Node *arg, TypeName *typename);
 static Node *makeRowExpr(char *opr, List *largs, List *rargs);
 static void mapTargetColumns(List *source, List *target);
-static void param_type_init(Oid *typev, int nargs);
 static bool exprIsNullConstant(Node *arg);
 static Node *doNegate(Node *n);
 static void doNegateFloat(Value *v);
 
-/* old versions of flex define this as a macro */
-#if defined(yywrap)
-#undef yywrap
-#endif /* yywrap */
 %}
 
 
@@ -95,6 +91,7 @@ static void doNegateFloat(Value *v);
 	char				chr;
 	char				*str;
 	bool				boolean;
+	JoinType			jtype;
 	List				*list;
 	Node				*node;
 	Value				*value;
@@ -108,7 +105,6 @@ static void doNegateFloat(Value *v);
 	JoinExpr			*jexpr;
 	IndexElem			*ielem;
 	RangeVar			*range;
-	RelExpr				*relexp;
 	A_Indices			*aind;
 	ResTarget			*target;
 	ParamNo				*paramno;
@@ -194,19 +190,8 @@ static void doNegateFloat(Value *v);
 %type <boolean>	opt_table
 %type <boolean>	opt_chain, opt_trans
 
-%type <jexpr>	from_expr, join_clause, join_expr
-%type <jexpr>	join_clause_with_union, join_expr_with_union
 %type <node>	join_outer, join_qual
-%type <ival>	join_type
-%type <list>	using_list
-%type <ident>	using_expr
-/***
-#ifdef ENABLE_ORACLE_JOIN_SYNTAX
-%type <list>	oracle_list
-%type <jexpr>	oracle_expr
-%type <boolean>	oracle_outer
-#endif
-***/
+%type <jtype>	join_type
 
 %type <list>	extract_list, position_list
 %type <list>	substr_list, substr_from, substr_for, trim_list
@@ -246,8 +231,9 @@ static void doNegateFloat(Value *v);
 %type <attr>	event_object, attr, alias_clause
 %type <sortgroupby>		sortby
 %type <ielem>	index_elem, func_index
-%type <range>	table_expr
-%type <relexp>	relation_expr
+%type <node>	table_ref
+%type <jexpr>	joined_table
+%type <range>	relation_expr
 %type <target>	target_el, update_target_el
 %type <paramno> ParamNo
 
@@ -356,6 +342,12 @@ static void doNegateFloat(Value *v);
 		TEMP, TOAST, TRUNCATE, TRUSTED, 
 		UNLISTEN, UNTIL, VACUUM, VALID, VERBOSE, VERSION
 
+/* The grammar thinks these are keywords, but they are not in the keywords.c
+ * list and so can never be entered directly.  The filter in parser.c
+ * creates these tokens when required.
+ */
+%token			UNIONJOIN
+
 /* Special keywords, not in the query language - see the "lex" file */
 %token <str>	IDENT, FCONST, SCONST, Op
 %token <ival>	ICONST, PARAM
@@ -364,7 +356,9 @@ static void doNegateFloat(Value *v);
 %token			OP
 
 /* precedence: lowest to highest */
-%left		UNION INTERSECT EXCEPT
+%left		UNION EXCEPT
+%left		INTERSECT
+%left		JOIN UNIONJOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
 %left		OR
 %left		AND
 %right		NOT
@@ -800,7 +794,7 @@ VariableSetStmt:  SET ColId TO var_value
 					n->value = $3;
 					$$ = (Node *) n;
 #else
-					elog(ERROR, "SET NAMES is not supported.");
+					elog(ERROR, "SET NAMES is not supported");
 #endif
 				}
 		;
@@ -1031,7 +1025,6 @@ AlterTableStmt:
 					n->relname = $3;
 					$$ = (Node *)n;
 				}
-
 /* ALTER TABLE <name> OWNER TO UserId */
 		| ALTER TABLE relation_name OWNER TO UserId
 				{
@@ -2956,7 +2949,7 @@ CreatedbStmt:  CREATE DATABASE database_name WITH createdb_opt_location createdb
 					CreatedbStmt *n;
 
 					if ($5 == NULL && $6 == -1)
-						elog(ERROR, "CREATE DATABASE WITH requires at least one option.");
+						elog(ERROR, "CREATE DATABASE WITH requires at least one option");
 
                     n = makeNode(CreatedbStmt);
 					n->dbname = $3;
@@ -3465,7 +3458,7 @@ SelectStmt:	  select_clause sort_clause for_update_clause opt_select_limit
 /* This rule parses Select statements that can appear within set operations,
  * including UNION, INTERSECT and EXCEPT.  '(' and ')' can be used to specify
  * the ordering of the set operations.  Without '(' and ')' we want the
- * operations to be left associative.
+ * operations to be ordered per the precedence specs at the head of this file.
  *
  * Note that sort clauses cannot be included at this level --- a sort clause
  * can only appear at the end of the complete Select, and it will be handled
@@ -3486,10 +3479,12 @@ select_clause: '(' select_clause ')'
 			{
 				$$ = $1; 
 			}
-		| select_clause EXCEPT select_clause
+		| select_clause EXCEPT opt_all select_clause
 			{
 				$$ = (Node *)makeA_Expr(AND,NULL,$1,
-										makeA_Expr(NOT,NULL,NULL,$3));
+										makeA_Expr(NOT,NULL,NULL,$4));
+				if ($3)
+					elog(ERROR, "EXCEPT ALL is not implemented yet");
 			}
 		| select_clause UNION opt_all select_clause
 			{	
@@ -3506,9 +3501,11 @@ select_clause: '(' select_clause ')'
 				}
 				$$ = (Node *)makeA_Expr(OR,NULL,$1,$4);
 			}
-		| select_clause INTERSECT select_clause
+		| select_clause INTERSECT opt_all select_clause
 			{
-				$$ = (Node *)makeA_Expr(AND,NULL,$1,$3);
+				$$ = (Node *)makeA_Expr(AND,NULL,$1,$4);
+				if ($3)
+					elog(ERROR, "INTERSECT ALL is not implemented yet");
 			}
 		; 
 
@@ -3741,113 +3738,63 @@ update_list:  OF va_list						{ $$ = $2; }
  *****************************************************************************/
 
 from_clause:  FROM from_list					{ $$ = $2; }
-/***
-#ifdef ENABLE_ORACLE_JOIN_SYNTAX
-		| FROM oracle_list						{ $$ = $2; }
-#endif
-***/
-		| FROM from_expr						{ $$ = lcons($2, NIL); }
 		| /*EMPTY*/								{ $$ = NIL; }
 		;
 
-from_list:  from_list ',' table_expr			{ $$ = lappend($1, $3); }
-		| table_expr							{ $$ = lcons($1, NIL); }
-		;
-
-/***********
- * This results in one shift/reduce conflict, presumably due to the trailing "(+)"
- * - Thomas 1999-09-20
- *
-#ifdef ENABLE_ORACLE_JOIN_SYNTAX
-oracle_list:  oracle_expr						{ $$ = lcons($1, NIL); }
-		;
-
-oracle_expr:  ColId ',' ColId oracle_outer
-				{
-					elog(ERROR,"Oracle OUTER JOIN not yet supported");
-					$$ = NULL;
-				}
-		| oracle_outer ColId ',' ColId
-				{
-					elog(ERROR,"Oracle OUTER JOIN not yet supported");
-					$$ = NULL;
-				}
-		;
-
-oracle_outer:  '(' '+' ')'						{ $$ = TRUE; }
-		;
-#endif
-***********/
-
-from_expr:  '(' join_clause_with_union ')' alias_clause
-				{
-					JoinExpr *j = $2;
-					j->alias = $4;
-					$$ = j;
-				}
-		| join_clause
-				{	$$ = $1; }
-		;
-
-table_expr:  relation_expr alias_clause
-				{
-					$$ = makeNode(RangeVar);
-					$$->relExpr = $1;
-					$$->name = $2;
-
-#ifdef DISABLE_JOIN_SYNTAX
-					if (($2 != NULL) && ($2->attrs != NULL))
-						elog(ERROR, "Column aliases in table expressions not yet supported");
-#endif
-				}
+from_list:  from_list ',' table_ref				{ $$ = lappend($1, $3); }
+		| table_ref								{ $$ = lcons($1, NIL); }
 		;
 
-alias_clause:  AS ColId '(' name_list ')'
-				{
-					$$ = makeNode(Attr);
-					$$->relname = $2;
-					$$->attrs = $4;
-				}
-		| AS ColId
+/*
+ * table_ref is where an alias clause can be attached.  Note we cannot make
+ * alias_clause have an empty production because that causes parse conflicts
+ * between table_ref := '(' joined_table ')' alias_clause
+ * and joined_table := '(' joined_table ')'.  So, we must have the
+ * redundant-looking productions here instead.
+ */
+table_ref:  relation_expr
 				{
-					$$ = makeNode(Attr);
-					$$->relname = $2;
+					$$ = (Node *) $1;
 				}
-		| ColId '(' name_list ')'
+		| relation_expr alias_clause
 				{
-					$$ = makeNode(Attr);
-					$$->relname = $1;
-					$$->attrs = $3;
+					$1->name = $2;
+					$$ = (Node *) $1;
 				}
-		| ColId
+		| '(' select_clause ')'
 				{
-					$$ = makeNode(Attr);
-					$$->relname = $1;
+					RangeSubselect *n = makeNode(RangeSubselect);
+					n->subquery = $2;
+					n->name = NULL;
+					$$ = (Node *) n;
 				}
-		| /*EMPTY*/
+		| '(' select_clause ')' alias_clause
 				{
-					$$ = NULL;  /* no qualifiers */
+					RangeSubselect *n = makeNode(RangeSubselect);
+					n->subquery = $2;
+					n->name = $4;
+					$$ = (Node *) n;
 				}
-		;
-
-/* A UNION JOIN is the same as a FULL OUTER JOIN which *omits*
- * all result rows which would have matched on an INNER JOIN.
- * Syntactically, must enclose the UNION JOIN in parens to avoid
- * conflicts with SELECT/UNION.
- */
-join_clause:  join_clause join_expr
+		| joined_table
 				{
-					$2->larg = (Node *)$1;
-					$$ = $2;
+					$$ = (Node *) $1;
 				}
-		| table_expr join_expr
+		| '(' joined_table ')' alias_clause
 				{
-					$2->larg = (Node *)$1;
-					$$ = $2;
+					$2->alias = $4;
+					$$ = (Node *) $2;
 				}
 		;
 
-/* This is everything but the left side of a join.
+/*
+ * It may seem silly to separate joined_table from table_ref, but there is
+ * method in SQL92's madness: if you don't do it this way you get reduce-
+ * reduce conflicts, because it's not clear to the parser generator whether
+ * to expect alias_clause after ')' or not.  For the same reason we must
+ * treat 'JOIN' and 'join_type JOIN' separately, rather than allowing
+ * join_type to expand to empty; if we try it, the parser generator can't
+ * figure out when to reduce an empty join_type right after table_ref.
+ *
  * Note that a CROSS JOIN is the same as an unqualified
  * INNER JOIN, and an INNER JOIN/ON has the same shape
  * but a qualification expression to limit membership.
@@ -3855,71 +3802,122 @@ join_clause:  join_clause join_expr
  * tables and the shape is determined by which columns are
  * in common. We'll collect columns during the later transformations.
  */
-join_expr:  join_type JOIN table_expr join_qual
+
+joined_table:  '(' joined_table ')'
+				{
+					$$ = $2;
+				}
+		| table_ref CROSS JOIN table_ref
 				{
+					/* CROSS JOIN is same as unqualified inner join */
 					JoinExpr *n = makeNode(JoinExpr);
-					n->jointype = $1;
-					n->rarg = (Node *)$3;
-					n->quals = (List *)$4;
+					n->jointype = JOIN_INNER;
+					n->isNatural = FALSE;
+					n->larg = $1;
+					n->rarg = $4;
+					n->using = NIL;
+					n->quals = NULL;
 					$$ = n;
 				}
-		| NATURAL join_type JOIN table_expr
+		| table_ref UNIONJOIN table_ref
 				{
+					/* UNION JOIN is made into 1 token to avoid shift/reduce
+					 * conflict against regular UNION keyword.
+					 */
 					JoinExpr *n = makeNode(JoinExpr);
-					n->jointype = $2;
-					n->isNatural = TRUE;
-					n->rarg = (Node *)$4;
-					n->quals = NULL; /* figure out which columns later... */
+					n->jointype = JOIN_UNION;
+					n->isNatural = FALSE;
+					n->larg = $1;
+					n->rarg = $3;
+					n->using = NIL;
+					n->quals = NULL;
 					$$ = n;
 				}
-		| CROSS JOIN table_expr
+		| table_ref join_type JOIN table_ref join_qual
 				{
 					JoinExpr *n = makeNode(JoinExpr);
-					n->jointype = INNER_P;
+					n->jointype = $2;
 					n->isNatural = FALSE;
-					n->rarg = (Node *)$3;
-					n->quals = NULL;
+					n->larg = $1;
+					n->rarg = $4;
+					if ($5 != NULL && IsA($5, List))
+						n->using = (List *) $5;	/* USING clause */
+					else
+						n->quals = $5; /* ON clause */
 					$$ = n;
 				}
-		;
-
-join_clause_with_union:  join_clause_with_union join_expr_with_union
+		| table_ref JOIN table_ref join_qual
 				{
-					$2->larg = (Node *)$1;
-					$$ = $2;
+					/* letting join_type reduce to empty doesn't work */
+					JoinExpr *n = makeNode(JoinExpr);
+					n->jointype = JOIN_INNER;
+					n->isNatural = FALSE;
+					n->larg = $1;
+					n->rarg = $3;
+					if ($4 != NULL && IsA($4, List))
+						n->using = (List *) $4;	/* USING clause */
+					else
+						n->quals = $4; /* ON clause */
+					$$ = n;
 				}
-		| table_expr join_expr_with_union
+		| table_ref NATURAL join_type JOIN table_ref
 				{
-					$2->larg = (Node *)$1;
-					$$ = $2;
+					JoinExpr *n = makeNode(JoinExpr);
+					n->jointype = $3;
+					n->isNatural = TRUE;
+					n->larg = $1;
+					n->rarg = $5;
+					n->using = NIL; /* figure out which columns later... */
+					n->quals = NULL; /* fill later */
+					$$ = n;
 				}
-		;
-
-join_expr_with_union:  join_expr
-				{	$$ = $1; }
-		| UNION JOIN table_expr
+		| table_ref NATURAL JOIN table_ref
 				{
+					/* letting join_type reduce to empty doesn't work */
 					JoinExpr *n = makeNode(JoinExpr);
-					n->jointype = UNION;
-					n->rarg = (Node *)$3;
-					n->quals = NULL;
+					n->jointype = JOIN_INNER;
+					n->isNatural = TRUE;
+					n->larg = $1;
+					n->rarg = $4;
+					n->using = NIL; /* figure out which columns later... */
+					n->quals = NULL; /* fill later */
 					$$ = n;
+				}
+		;
 
-					elog(ERROR,"UNION JOIN not yet implemented");
+alias_clause:  AS ColId '(' name_list ')'
+				{
+					$$ = makeNode(Attr);
+					$$->relname = $2;
+					$$->attrs = $4;
+				}
+		| AS ColId
+				{
+					$$ = makeNode(Attr);
+					$$->relname = $2;
+				}
+		| ColId '(' name_list ')'
+				{
+					$$ = makeNode(Attr);
+					$$->relname = $1;
+					$$->attrs = $3;
+				}
+		| ColId
+				{
+					$$ = makeNode(Attr);
+					$$->relname = $1;
 				}
 		;
 
-/* OUTER is just noise... */
-join_type:  FULL join_outer						{ $$ = FULL; }
-		| LEFT join_outer						{ $$ = LEFT; }
-		| RIGHT join_outer						{ $$ = RIGHT; }
-		| OUTER_P								{ $$ = LEFT; }
-		| INNER_P								{ $$ = INNER_P; }
-		| /*EMPTY*/								{ $$ = INNER_P; }
+join_type:  FULL join_outer						{ $$ = JOIN_FULL; }
+		| LEFT join_outer						{ $$ = JOIN_LEFT; }
+		| RIGHT join_outer						{ $$ = JOIN_RIGHT; }
+		| INNER_P								{ $$ = JOIN_INNER; }
 		;
 
+/* OUTER is just noise... */
 join_outer:  OUTER_P							{ $$ = NULL; }
-		| /*EMPTY*/								{ $$ = NULL;  /* no qualifiers */ }
+		| /*EMPTY*/								{ $$ = NULL; }
 		;
 
 /* JOIN qualification clauses
@@ -3927,60 +3925,43 @@ join_outer:  OUTER_P							{ $$ = NULL; }
  *  USING ( column list ) allows only unqualified column names,
  *                        which must match between tables.
  *  ON expr allows more general qualifications.
- * - thomas 1999-01-07
+ *
+ * We return USING as a List node, while an ON-expr will not be a List.
  */
 
-join_qual:  USING '(' using_list ')'			{ $$ = (Node *)$3; }
-		| ON a_expr								{ $$ = (Node *)$2; }
+join_qual:  USING '(' name_list ')'				{ $$ = (Node *) $3; }
+		| ON a_expr								{ $$ = $2; }
 		;
 
-using_list:  using_list ',' using_expr			{ $$ = lappend($1, $3); }
-		| using_expr							{ $$ = lcons($1, NIL); }
-		;
-
-using_expr:  ColId
-				{
-					/* could be a column name or a relation_name */
-					Ident *n = makeNode(Ident);
-					n->name = $1;
-					n->indirection = NULL;
-					$$ = n;
-				}
-		;
-
-where_clause:  WHERE a_expr						{ $$ = $2; }
-		| /*EMPTY*/								{ $$ = NULL;  /* no qualifiers */ }
-		;
 
 relation_expr:	relation_name
 				{
     				/* default inheritance */
-					$$ = makeNode(RelExpr);
+					$$ = makeNode(RangeVar);
 					$$->relname = $1;
 					$$->inh = SQL_inheritance;
+					$$->name = NULL;
 				}
 		| relation_name '*'				%prec '='
 				{
 					/* inheritance query */
-					$$ = makeNode(RelExpr);
+					$$ = makeNode(RangeVar);
 					$$->relname = $1;
 					$$->inh = TRUE;
+					$$->name = NULL;
 				}
 		| ONLY relation_name			%prec '='
 				{
 					/* no inheritance */
-					$$ = makeNode(RelExpr);
+					$$ = makeNode(RangeVar);
 					$$->relname = $2;
 					$$->inh = FALSE;
+					$$->name = NULL;
                 }
 		;
 
-opt_array_bounds:	'[' ']' opt_array_bounds
-				{  $$ = lcons(makeInteger(-1), $3); }
-		| '[' Iconst ']' opt_array_bounds
-				{  $$ = lcons(makeInteger($2), $4); }
-		| /*EMPTY*/
-				{  $$ = NIL; }
+where_clause:  WHERE a_expr						{ $$ = $2; }
+		| /*EMPTY*/								{ $$ = NULL;  /* no qualifiers */ }
 		;
 
 
@@ -4023,6 +4004,14 @@ Typename:  SimpleTypename opt_array_bounds
 				}
 		;
 
+opt_array_bounds:	'[' ']' opt_array_bounds
+				{  $$ = lcons(makeInteger(-1), $3); }
+		| '[' Iconst ']' opt_array_bounds
+				{  $$ = lcons(makeInteger($2), $4); }
+		| /*EMPTY*/
+				{  $$ = NIL; }
+		;
+
 SimpleTypename:  ConstTypename
 		| ConstInterval
 		;
@@ -6024,29 +6013,19 @@ xlateSqlType(char *name)
 
 void parser_init(Oid *typev, int nargs)
 {
+	saved_relname[0] = '\0';
 	QueryIsRule = FALSE;
-	saved_relname[0]= '\0';
-
-	param_type_init(typev, nargs);
-}
-
-
-/*
- * param_type_init()
- *
- * Keep enough information around to fill out the type of param nodes
- * used in postquel functions
- */
-static void
-param_type_init(Oid *typev, int nargs)
-{
-	pfunc_num_args = nargs;
+	/*
+	 * Keep enough information around to fill out the type of param nodes
+	 * used in postquel functions
+	 */
 	param_type_info = typev;
+	pfunc_num_args = nargs;
 }
 
 Oid param_type(int t)
 {
-	if ((t > pfunc_num_args) || (t == 0))
+	if ((t > pfunc_num_args) || (t <= 0))
 		return InvalidOid;
 	return param_type_info[t - 1];
 }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index bbc8f5c7076..955be022e4e 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.39 2000/07/17 03:05:02 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_agg.c,v 1.40 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -152,6 +152,11 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
 	 */
 	if (contain_agg_clause(qry->qual))
 		elog(ERROR, "Aggregates not allowed in WHERE clause");
+	/*
+	 * ON-conditions in JOIN expressions are like WHERE clauses.
+	 */
+	if (contain_agg_clause((Node *) qry->jointree))
+		elog(ERROR, "Aggregates not allowed in JOIN conditions");
 
 	/*
 	 * No aggregates allowed in GROUP BY clauses, either.
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 3f874cc9643..c35b41b911b 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.65 2000/06/15 03:32:19 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.66 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,7 +18,9 @@
 #include "access/heapam.h"
 #include "optimizer/tlist.h"
 #include "nodes/makefuncs.h"
+#include "parser/analyze.h"
 #include "parser/parse.h"
+#include "parser/parsetree.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
@@ -33,57 +35,81 @@
 
 static char *clauseText[] = {"ORDER BY", "GROUP BY", "DISTINCT ON"};
 
+static void extractUniqueColumns(List *common_colnames,
+								 List *src_colnames, List *src_colvars,
+								 List **res_colnames, List **res_colvars);
+static Node *transformUsingClause(ParseState *pstate,
+								  List *leftVars, List *rightVars);
+static RangeTblRef *transformTableEntry(ParseState *pstate, RangeVar *r);
+static RangeTblRef *transformRangeSubselect(ParseState *pstate,
+											RangeSubselect *r);
+static Node *transformFromClauseItem(ParseState *pstate, Node *n);
 static TargetEntry *findTargetlistEntry(ParseState *pstate, Node *node,
 					List *tlist, int clause);
-static void parseFromClause(ParseState *pstate, List *frmList);
-static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
 static List *addTargetToSortList(TargetEntry *tle, List *sortlist,
 					List *targetlist, char *opname);
 static bool exprIsInSortList(Node *expr, List *sortList, List *targetList);
 
-#ifndef DISABLE_OUTER_JOINS
-static List *transformUsingClause(ParseState *pstate, List *using,
-								  List *left, List *right);
-#endif
-
 
 /*
  * makeRangeTable -
  *	  Build the initial range table from the FROM clause.
+ *
+ * The range table constructed here may grow as we transform the expressions
+ * in the query's quals and target list. (Note that this happens because in
+ * POSTQUEL, we allow references to relations not specified in the
+ * from-clause.  PostgreSQL keeps this extension to standard SQL.)
+ *
+ * Note: we assume that pstate's p_rtable and p_jointree lists were
+ * initialized to NIL when the pstate was created.  We will add onto
+ * any entries already present --- this is needed for rule processing!
  */
 void
 makeRangeTable(ParseState *pstate, List *frmList)
 {
-	/* Currently, nothing to do except this: */
-	parseFromClause(pstate, frmList);
+	List	   *fl;
+
+	/*
+	 * The grammar will have produced a list of RangeVars, RangeSubselects,
+	 * and/or JoinExprs. Transform each one, and then add it to the join tree.
+	 */
+	foreach(fl, frmList)
+	{
+		Node	   *n = lfirst(fl);
+
+		n = transformFromClauseItem(pstate, n);
+		pstate->p_jointree = lappend(pstate->p_jointree, n);
+	}
 }
 
 /*
  * setTargetTable
- *	  Add the target relation of INSERT or UPDATE to the range table,
+ *	  Add the target relation of INSERT/UPDATE/DELETE to the range table,
  *	  and make the special links to it in the ParseState.
  *
- *	  Note that the target is not marked as either inFromCl or inJoinSet.
+ *	  inJoinSet says whether to add the target to the join tree.
  *	  For INSERT, we don't want the target to be joined to; it's a
  *	  destination of tuples, not a source.	For UPDATE/DELETE, we do
- *	  need to scan or join the target.	This will happen without the
- *	  inJoinSet flag because the planner's preprocess_targetlist()
- *	  adds the destination's CTID attribute to the targetlist, and
- *	  therefore the destination will be a referenced table even if
- *	  there is no other use of any of its attributes.  Tricky, eh?
+ *	  need to scan or join the target.
  */
 void
-setTargetTable(ParseState *pstate, char *relname, bool inh)
+setTargetTable(ParseState *pstate, char *relname, bool inh, bool inJoinSet)
 {
 	RangeTblEntry *rte;
 
 	/* look for relname only at current nesting level... */
 	if (refnameRangeTablePosn(pstate, relname, NULL) == 0)
-		rte = addRangeTableEntry(pstate, relname,
-								 makeAttr(relname, NULL),
-								 inh, FALSE, FALSE);
+	{
+		rte = addRangeTableEntry(pstate, relname, NULL, inh, false);
+	}
 	else
+	{
 		rte = refnameRangeTableEntry(pstate, relname);
+		/* XXX what if pre-existing entry has wrong inh setting? */
+	}
+
+	if (inJoinSet)
+		addRTEtoJoinTree(pstate, rte);
 
 	/* This could only happen for multi-action rules */
 	if (pstate->p_target_relation != NULL)
@@ -95,625 +121,500 @@ setTargetTable(ParseState *pstate, char *relname, bool inh)
 }
 
 
-static Node *
-mergeInnerJoinQuals(ParseState *pstate, Node *clause)
-{
-	List	   *jquals;
-
-	foreach(jquals, pstate->p_join_quals)
-	{
-		Node	   *jqual = (Node *) lfirst(jquals);
-
-		if (clause == NULL)
-			clause = jqual;
-		else
-		{
-			A_Expr	   *a = makeNode(A_Expr);
-
-			a->oper = AND;
-			a->opname = NULL;
-			a->lexpr = clause;
-			a->rexpr = jqual;
-			clause = (Node *) a;
-		}
-	}
-
-	/* Make sure that we don't add same quals twice... */
-	pstate->p_join_quals = NIL;
-
-	return clause;
-}	/* mergeInnerJoinQuals() */
-
 /*
- * transformWhereClause -
- *	  transforms the qualification and make sure it is of type Boolean
+ * Extract all not-in-common columns from column lists of a source table
  */
-Node *
-transformWhereClause(ParseState *pstate, Node *clause)
-{
-	Node	   *qual;
-
-	if (pstate->p_join_quals != NIL)
-		clause = mergeInnerJoinQuals(pstate, clause);
-
-	if (clause == NULL)
-		return NULL;
-
-	pstate->p_in_where_clause = true;
-	qual = transformExpr(pstate, clause, EXPR_COLUMN_FIRST);
-	pstate->p_in_where_clause = false;
-
-	if (exprType(qual) != BOOLOID)
-	{
-		elog(ERROR, "WHERE clause must return type bool, not type %s",
-			 typeidTypeName(exprType(qual)));
-	}
-	return qual;
-}
-
-#ifndef DISABLE_JOIN_SYNTAX
-char *
-			AttrString(Attr *attr);
-
-char *
-AttrString(Attr *attr)
-{
-	Value	   *val;
-
-	Assert(length(attr->attrs) == 1);
-
-	val = lfirst(attr->attrs);
-
-	Assert(IsA(val, String));
-
-	return strVal(val);
-}
-
-List *
-			ListTableAsAttrs(ParseState *pstate, char *table);
-List *
-ListTableAsAttrs(ParseState *pstate, char *table)
-{
-	Attr	   *attr = expandTable(pstate, table, TRUE);
-	List	   *rlist = NIL;
-	List	   *col;
-
-	foreach(col, attr->attrs)
-	{
-		Attr	   *a = makeAttr(table, strVal((Value *) lfirst(col)));
-
-		rlist = lappend(rlist, a);
-	}
-
-	return rlist;
-}
-
-List *
-			makeUniqueAttrList(List *candidates, List *idents);
-List *
-makeUniqueAttrList(List *attrs, List *filter)
+static void
+extractUniqueColumns(List *common_colnames,
+					 List *src_colnames, List *src_colvars,
+					 List **res_colnames, List **res_colvars)
 {
-	List	   *result = NULL;
-	List	   *candidate;
+	List	   *new_colnames = NIL;
+	List	   *new_colvars = NIL;
+	List	   *lnames,
+			   *lvars = src_colvars;
 
-	foreach(candidate, attrs)
+	foreach(lnames, src_colnames)
 	{
-		List	   *fmember;
-		bool		match = FALSE;
-		Attr	   *cattr = lfirst(candidate);
+		char	   *colname = strVal(lfirst(lnames));
+		bool		match = false;
+		List	   *cnames;
 
-		Assert(IsA(cattr, Attr));
-		Assert(length(cattr->attrs) == 1);
-
-		foreach(fmember, filter)
+		foreach(cnames, common_colnames)
 		{
-			Attr	   *fattr = lfirst(fmember);
-
-			Assert(IsA(fattr, Attr));
-			Assert(length(fattr->attrs) == 1);
+			char	   *ccolname = strVal(lfirst(cnames));
 
-			if (strcmp(strVal(lfirst(cattr->attrs)), strVal(lfirst(fattr->attrs))) == 0)
+			if (strcmp(colname, ccolname) == 0)
 			{
-				match = TRUE;
+				match = true;
 				break;
 			}
 		}
 
 		if (!match)
-			result = lappend(result, cattr);
-	}
-
-	return result;
-}
-
-List *
-			makeAttrList(Attr *attr);
-
-List *
-makeAttrList(Attr *attr)
-{
-	List	   *result = NULL;
-
-	char	   *name = attr->relname;
-	List	   *col;
-
-	foreach(col, attr->attrs)
-	{
-		Attr	   *newattr = makeAttr(name, strVal((Value *) lfirst(col)));
-
-		result = lappend(result, newattr);
-	}
-
-	return result;
-}
-#ifdef NOT_USED
-/* ExpandAttrs()
- * Take an existing attribute node and return a list of attribute nodes
- * with one attribute name per node.
- */
-List *
-ExpandAttrs(Attr *attr)
-{
-	List	   *col;
-	char	   *relname = attr->relname;
-	List	   *rlist = NULL;
-
-	Assert(attr != NULL);
-
-	if ((attr->attrs == NULL) || (length(attr->attrs) <= 1))
-		return lcons(attr, NIL);
-
-	foreach(col, attr->attrs)
-	{
-		Attr	   *attr = lfirst(col);
+		{
+			new_colnames = lappend(new_colnames, lfirst(lnames));
+			new_colvars = lappend(new_colvars, lfirst(lvars));
+		}
 
-		rlist = lappend(rlist, makeAttr(relname, AttrString(attr)));
+		lvars = lnext(lvars);
 	}
 
-	return rlist;
+	*res_colnames = new_colnames;
+	*res_colvars = new_colvars;
 }
-#endif
 
 /* transformUsingClause()
- * Take an ON or USING clause from a join expression and expand if necessary.
- * Result is an implicitly-ANDed list of untransformed qualification clauses.
+ * Build a complete ON clause from a partially-transformed USING list.
+ * We are given lists of Var nodes representing left and right match columns.
+ * Result is a transformed qualification expression.
  */
-static List *
-transformUsingClause(ParseState *pstate, List *usingList,
-					 List *leftList, List *rightList)
+static Node *
+transformUsingClause(ParseState *pstate, List *leftVars, List *rightVars)
 {
-	List	   *result = NIL;
-	List	   *using;
+	Node	   *result = NULL;
+	List	   *lvars,
+			   *rvars = rightVars;
 
-	foreach(using, usingList)
+	/*
+	 * We cheat a little bit here by building an untransformed operator
+	 * tree whose leaves are the already-transformed Vars.  This is OK
+	 * because transformExpr() won't complain about already-transformed
+	 * subnodes.
+	 */
+	foreach(lvars, leftVars)
 	{
-		Attr	   *uattr = lfirst(using);
-		Attr	   *lattr = NULL,
-				   *rattr = NULL;
-		List	   *col;
+		Node	   *lvar = (Node *) lfirst(lvars);
+		Node	   *rvar = (Node *) lfirst(rvars);
 		A_Expr	   *e;
 
-		/*
-		 * find the first instances of this column in the shape list and
-		 * the last table in the shape list...
-		 */
-		foreach(col, leftList)
-		{
-			Attr	   *attr = lfirst(col);
+		e = makeNode(A_Expr);
+		e->oper = OP;
+		e->opname = "=";
+		e->lexpr = copyObject(lvar);
+		e->rexpr = copyObject(rvar);
 
-			if (strcmp(AttrString(attr), AttrString(uattr)) == 0)
-			{
-				lattr = attr;
-				break;
-			}
-		}
-		foreach(col, rightList)
+		if (result == NULL)
+			result = (Node *) e;
+		else
 		{
-			Attr	   *attr = lfirst(col);
+			A_Expr	   *a = makeNode(A_Expr);
 
-			if (strcmp(AttrString(attr), AttrString(uattr)) == 0)
-			{
-				rattr = attr;
-				break;
-			}
+			a->oper = AND;
+			a->opname = NULL;
+			a->lexpr = result;
+			a->rexpr = (Node *) e;
+			result = (Node *) a;
 		}
 
-		Assert((lattr != NULL) && (rattr != NULL));
+		rvars = lnext(rvars);
+	}
 
-		e = makeNode(A_Expr);
-		e->oper = OP;
-		e->opname = "=";
-		e->lexpr = (Node *) lattr;
-		e->rexpr = (Node *) rattr;
+	result = transformExpr(pstate, result, EXPR_COLUMN_FIRST);
 
-		result = lappend(result, e);
+	if (exprType(result) != BOOLOID)
+	{
+		/* This could only happen if someone defines a funny version of '=' */
+		elog(ERROR, "USING clause must return type bool, not type %s",
+			 typeidTypeName(exprType(result)));
 	}
 
 	return result;
 }	/* transformUsingClause() */
 
-#endif
-
 
-static RangeTblEntry *
+/*
+ * transformTableEntry --- transform a RangeVar (simple relation reference)
+ */
+static RangeTblRef *
 transformTableEntry(ParseState *pstate, RangeVar *r)
 {
-	RelExpr    *baserel = r->relExpr;
-	char	   *relname = baserel->relname;
-
-#if 0
-	char	   *refname;
-	List	   *columns;
-
-#endif
+	char	   *relname = r->relname;
 	RangeTblEntry *rte;
-
-#if 0
-	if (r->name != NULL)
-		refname = r->name->relname;
-	else
-		refname = NULL;
-
-	columns = ListTableAsAttrs(pstate, relname);
-
-	/* alias might be specified... */
-	if (r->name != NULL)
-	{
-#ifndef DISABLE_JOIN_SYNTAX
-		if (length(columns) > 0)
-		{
-			if (length(r->name->attrs) > 0)
-			{
-				if (length(columns) != length(r->name->attrs))
-					elog(ERROR, "'%s' has %d columns but %d %s specified",
-						 relname, length(columns), length(r->name->attrs),
-						 ((length(r->name->attrs) != 1) ? "aliases" : "alias"));
-
-				aliasList = nconc(aliasList, r->name->attrs);
-			}
-			else
-			{
-				r->name->attrs = columns;
-
-				aliasList = nconc(aliasList, r->name->attrs);
-			}
-		}
-		else
-			elog(NOTICE, "transformTableEntry: column aliases not handled (internal error)");
-#else
-		elog(ERROR, "Column aliases not yet supported");
-#endif
-	}
-	else
-	{
-		refname = relname;
-		aliasList = nconc(aliasList, columns);
-	}
-#endif
-
-	if (r->name == NULL)
-		r->name = makeAttr(relname, NULL);
+	RangeTblRef *rtr;
 
 	/*
-	 * marks this entry to indicate it comes from the FROM clause. In SQL,
+	 * mark this entry to indicate it comes from the FROM clause. In SQL,
 	 * the target list can only refer to range variables specified in the
 	 * from clause but we follow the more powerful POSTQUEL semantics and
 	 * automatically generate the range variable if not specified. However
 	 * there are times we need to know whether the entries are legitimate.
-	 *
-	 * eg. select * from foo f where f.x = 1; will generate wrong answer if
-	 * we expand * to foo.x.
 	 */
+	rte = addRangeTableEntry(pstate, relname, r->name, r->inh, true);
 
-	rte = addRangeTableEntry(pstate, relname, r->name,
-							 baserel->inh, TRUE, TRUE);
+	/*
+	 * We create a RangeTblRef, but we do not add it to the jointree here.
+	 * makeRangeTable will do so, if we are at top level of the FROM clause.
+	 */
+	rtr = makeNode(RangeTblRef);
+	/* assume new rte is at end */
+	rtr->rtindex = length(pstate->p_rtable);
+	Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
 
-	return rte;
-}	/* transformTableEntry() */
+	return rtr;
+}
 
 
 /*
- * parseFromClause -
- *	  turns the table references specified in the from-clause into a
- *	  range table. The range table may grow as we transform the expressions
- *	  in the target list. (Note that this happens because in POSTQUEL, we
- *	  allow references to relations not specified in the from-clause. We
- *	  also allow now as an extension.)
- *
- * The FROM clause can now contain JoinExpr nodes, which contain parsing info
- * for inner and outer joins. The USING clause must be expanded into a qualification
- * for an inner join at least, since that is compatible with the old syntax.
- * Not sure yet how to handle outer joins, but it will become clear eventually?
- * - thomas 1998-12-16
+ * transformRangeSubselect --- transform a sub-SELECT appearing in FROM
  */
-static void
-parseFromClause(ParseState *pstate, List *frmList)
+static RangeTblRef *
+transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 {
-	List	   *fl;
+	SelectStmt *subquery = (SelectStmt *) r->subquery;
+	List	   *parsetrees;
+	Query	   *query;
 
-	foreach(fl, frmList)
-	{
-		Node	   *n = lfirst(fl);
+	/*
+	 * subquery node might not be SelectStmt if user wrote something like
+	 * FROM (SELECT ... UNION SELECT ...).  Our current implementation of
+	 * UNION/INTERSECT/EXCEPT is too messy to deal with here, so punt until
+	 * we redesign querytrees to make it more reasonable.
+	 */
+	if (subquery == NULL || !IsA(subquery, SelectStmt))
+		elog(ERROR, "Set operations not yet supported in subselects in FROM");
 
-		/*
-		 * marks this entry to indicate it comes from the FROM clause. In
-		 * SQL, the target list can only refer to range variables
-		 * specified in the from clause but we follow the more powerful
-		 * POSTQUEL semantics and automatically generate the range
-		 * variable if not specified. However there are times we need to
-		 * know whether the entries are legitimate.
-		 *
-		 * eg. select * from foo f where f.x = 1; will generate wrong answer
-		 * if we expand * to foo.x.
-		 */
+	/*
+	 * Analyze and transform the subquery as if it were an independent
+	 * statement (we do NOT want it to see the outer query as a parent).
+	 */
+	parsetrees = parse_analyze(lcons(subquery, NIL), NULL);
 
-		/* Plain vanilla inner join, just like we've always had? */
-		if (IsA(n, RangeVar))
-			transformTableEntry(pstate, (RangeVar *) n);
+	/*
+	 * Check that we got something reasonable.  Some of these conditions
+	 * are probably impossible given restrictions of the grammar, but
+	 * check 'em anyway.
+	 */
+	if (length(parsetrees) != 1)
+		elog(ERROR, "Unexpected parse analysis result for subselect in FROM");
+	query = (Query *) lfirst(parsetrees);
+	if (query == NULL || !IsA(query, Query))
+		elog(ERROR, "Unexpected parse analysis result for subselect in FROM");
 
-		/* A newfangled join expression? */
-		else if (IsA(n, JoinExpr))
-		{
-#ifndef DISABLE_JOIN_SYNTAX
-			RangeTblEntry *l_rte,
-					   *r_rte;
-			Attr	   *l_name,
-					   *r_name = NULL;
-			JoinExpr   *j = (JoinExpr *) n;
-
-			if (j->alias != NULL)
-				elog(ERROR, "JOIN table aliases are not supported");
-
-			/* nested join? then handle the left one first... */
-			if (IsA(j->larg, JoinExpr))
-			{
-				parseFromClause(pstate, lcons(j->larg, NIL));
-				l_name = ((JoinExpr *) j->larg)->alias;
-			}
-			else
-			{
-				Assert(IsA(j->larg, RangeVar));
-				l_rte = transformTableEntry(pstate, (RangeVar *) j->larg);
-				l_name = expandTable(pstate, l_rte->eref->relname, TRUE);
-			}
+	if (query->commandType != CMD_SELECT)
+		elog(ERROR, "Expected SELECT query from subselect in FROM");
+	if (query->resultRelation != 0 || query->into != NULL)
+		elog(ERROR, "Subselect in FROM may not have SELECT INTO");
 
-			if (IsA(j->rarg, JoinExpr))
-			{
-				parseFromClause(pstate, lcons(j->rarg, NIL));
-				l_name = ((JoinExpr *) j->larg)->alias;
-			}
-			else
-			{
-				Assert(IsA(j->rarg, RangeVar));
-				r_rte = transformTableEntry(pstate, (RangeVar *) j->rarg);
-				r_name = expandTable(pstate, r_rte->eref->relname, TRUE);
-			}
 
-			/*
-			 * Natural join does not explicitly specify columns; must
-			 * generate columns to join. Need to run through the list of
-			 * columns from each table or join result and match up the
-			 * column names. Use the first table, and check every column
-			 * in the second table for a match.
-			 */
-			if (j->isNatural)
-			{
-				List	   *lx,
-						   *rx;
-				List	   *rlist = NULL;
+	elog(ERROR, "Subselect in FROM not done yet");
 
-				foreach(lx, l_name->attrs)
-				{
-					Ident	   *id = NULL;
-					Value	   *l_col = lfirst(lx);
+	return NULL;
+}
 
-					Assert(IsA(l_col, String));
 
-					foreach(rx, r_name->attrs)
-					{
-						Value	   *r_col = lfirst(rx);
+/*
+ * transformFromClauseItem -
+ *	  Transform a FROM-clause item, adding any required entries to the
+ *	  range table list being built in the ParseState, and return the
+ *	  transformed item ready to include in the jointree list.
+ *	  This routine can recurse to handle SQL92 JOIN expressions.
+ */
+static Node *
+transformFromClauseItem(ParseState *pstate, Node *n)
+{
+	if (IsA(n, RangeVar))
+	{
+		/* Plain relation reference */
+		return (Node *) transformTableEntry(pstate, (RangeVar *) n);
+	}
+	else if (IsA(n, RangeSubselect))
+	{
+		/* Plain relation reference */
+		return (Node *) transformRangeSubselect(pstate, (RangeSubselect *) n);
+	}
+	else if (IsA(n, JoinExpr))
+	{
+		/* A newfangled join expression */
+		JoinExpr   *j = (JoinExpr *) n;
+		List	   *l_colnames,
+				   *r_colnames,
+				   *res_colnames,
+				   *l_colvars,
+				   *r_colvars,
+				   *res_colvars;
 
-						Assert(IsA(r_col, String));
+		/*
+		 * Recursively process the left and right subtrees
+		 */
+		j->larg = transformFromClauseItem(pstate, j->larg);
+		j->rarg = transformFromClauseItem(pstate, j->rarg);
 
-						if (strcmp(strVal(l_col), strVal(r_col)) == 0)
-						{
-							id = (Ident *) makeNode(Ident);
-							id->name = strVal(l_col);
-							break;
-						}
-					}
+		/*
+		 * Extract column name and var lists from both subtrees
+		 */
+		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);
+		}
+		else
+		{
+			RangeTblEntry *rte;
 
-					/* right column matched? then keep as join column... */
-					if (id != NULL)
-						rlist = lappend(rlist, id);
-				}
-				j->quals = rlist;
+			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);
+		}
+		else
+		{
+			RangeTblEntry *rte;
 
-				printf("NATURAL JOIN columns are %s\n", nodeToString(rlist));
-			}
+			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 */
+		}
 
-			if (j->jointype == INNER_P)
+		/*
+		 * Natural join does not explicitly specify columns; must
+		 * generate columns to join. Need to run through the list of
+		 * columns from each table or join result and match up the
+		 * column names. Use the first table, and check every column
+		 * in the second table for a match.  (We'll check that the
+		 * matches were unique later on.)
+		 * The result of this step is a list of column names just like an
+		 * explicitly-written USING list.
+		 */
+		if (j->isNatural)
+		{
+			List	   *rlist = NIL;
+			List	   *lx,
+					   *rx;
+
+			Assert(j->using == NIL); /* shouldn't have USING() too */
+
+			foreach(lx, l_colnames)
 			{
-				/* CROSS JOIN */
-				if (j->quals == NULL)
-					printf("CROSS JOIN...\n");
+				char	   *l_colname = strVal(lfirst(lx));
+				Value	   *m_name = NULL;
 
-				/*
-				 * JOIN/USING This is an inner join, so rip apart the join
-				 * node and transform into a traditional FROM list.
-				 * NATURAL JOIN and JOIN USING both change the shape of
-				 * the result. Need to generate a list of result columns
-				 * to use for target list expansion and validation.
-				 */
-				else if (IsA(j->quals, List))
+				foreach(rx, r_colnames)
 				{
+					char	   *r_colname = strVal(lfirst(rx));
 
-					/*
-					 * List of Ident nodes means column names from a real
-					 * USING clause. Determine the shape of the joined
-					 * table.
-					 */
-					List	   *ucols,
-							   *ucol;
-					List	   *shape = NULL;
-					List	   *alias = NULL;
-					List	   *l_shape,
-							   *r_shape;
-
-					List	   *l_cols = makeAttrList(l_name);
-					List	   *r_cols = makeAttrList(r_name);
-
-					printf("USING input tables are:\n %s\n %s\n",
-						   nodeToString(l_name), nodeToString(r_name));
-
-					printf("USING expanded tables are:\n %s\n %s\n",
-						   nodeToString(l_cols), nodeToString(r_cols));
-
-					/* Columns from the USING clause... */
-					ucols = (List *) j->quals;
-					foreach(ucol, ucols)
+					if (strcmp(l_colname, r_colname) == 0)
 					{
-						List	   *col;
-						Attr	   *l_attr = NULL,
-								   *r_attr = NULL;
-						Ident	   *id = lfirst(ucol);
-
-						Attr	   *attr = makeAttr("", id->name);
-
-						foreach(col, l_cols)
-						{
-							attr = lfirst(col);
-							if (strcmp(AttrString(attr), id->name) == 0)
-							{
-								l_attr = attr;
-								break;
-							}
-						}
-
-						foreach(col, r_cols)
-						{
-							attr = lfirst(col);
-							if (strcmp(AttrString(attr), id->name) == 0)
-							{
-								r_attr = attr;
-								break;
-							}
-						}
-
-						if (l_attr == NULL)
-							elog(ERROR, "USING column '%s' not found in table '%s'",
-								 id->name, l_name->relname);
-						if (r_attr == NULL)
-							elog(ERROR, "USING column '%s' not found in table '%s'",
-								 id->name, r_name->relname);
-
-						shape = lappend(shape, l_attr);
-						alias = lappend(alias, makeAttr("", AttrString(l_attr)));
+						m_name = makeString(l_colname);
+						break;
 					}
-					printf("JOIN/USING join columns are %s\n", nodeToString(shape));
-
-					/* Remaining columns from the left side... */
-					l_shape = makeUniqueAttrList(makeAttrList(l_name), shape);
-
-					printf("JOIN/USING left columns are %s\n", nodeToString(l_shape));
-
-					r_shape = makeUniqueAttrList(makeAttrList(r_name), shape);
+				}
 
-					printf("JOIN/USING right columns are %s\n", nodeToString(r_shape));
+				/* matched a right column? then keep as join column... */
+				if (m_name != NULL)
+					rlist = lappend(rlist, m_name);
+			}
 
-					printf("JOIN/USING input quals are %s\n", nodeToString(j->quals));
+			j->using = rlist;
+		}
 
-					j->quals = transformUsingClause(pstate, shape, l_cols, r_cols);
+		/*
+		 * Now transform the join qualifications, if any.
+		 */
+		res_colnames = NIL;
+		res_colvars = NIL;
 
-					printf("JOIN/USING transformed quals are %s\n", nodeToString(j->quals));
+		if (j->using)
+		{
+			/*
+			 * JOIN/USING (or NATURAL JOIN, as transformed above).
+			 * Transform the list into an explicit ON-condition,
+			 * and generate a list of result columns.
+			 */
+			List	   *ucols = j->using;
+			List	   *l_usingvars = NIL;
+			List	   *r_usingvars = NIL;
+			List	   *ucol;
 
-					alias = nconc(nconc(alias, listCopy(l_shape)), listCopy(r_shape));
-					shape = nconc(nconc(shape, l_shape), r_shape);
+			Assert(j->quals == NULL); /* shouldn't have ON() too */
 
-					printf("JOIN/USING shaped table is %s\n", nodeToString(shape));
-					printf("JOIN/USING alias list is %s\n", nodeToString(alias));
+			foreach(ucol, ucols)
+			{
+				char	   *u_colname = strVal(lfirst(ucol));
+				List	   *col;
+				Node	   *l_colvar,
+						   *r_colvar,
+						   *colvar;
+				int			ndx;
+				int			l_index = -1;
+				int			r_index = -1;
+
+				ndx = 0;
+				foreach(col, l_colnames)
+				{
+					char	   *l_colname = strVal(lfirst(col));
 
-					pstate->p_shape = shape;
-					pstate->p_alias = alias;
+					if (strcmp(l_colname, u_colname) == 0)
+					{
+						if (l_index >= 0)
+							elog(ERROR, "Common column name \"%s\" appears more than once in left table", u_colname);
+						l_index = ndx;
+					}
+					ndx++;
 				}
+				if (l_index < 0)
+					elog(ERROR, "USING column \"%s\" not found in left table",
+						 u_colname);
 
-				/* otherwise, must be an expression from an ON clause... */
-				else
-					j->quals = (List *) lcons(j->quals, NIL);
-
-				/* listCopy may not be needed here --- will j->quals list
-				 * be used again anywhere?  The #ifdef'd code below may need
-				 * it, if it ever gets used...
-				 */
-				pstate->p_join_quals = nconc(pstate->p_join_quals,
-											 listCopy(j->quals));
-
-#if 0
-				if (qual == NULL)
-					elog(ERROR, "JOIN/ON not supported in this context");
-
-				printf("Table aliases are %s\n", nodeToString(*aliasList));
-#endif
-
-#if 0
-				/* XXX this code is WRONG because j->quals is a List
-				 * not a simple expression.  Perhaps *qual
-				 * ought also to be a List and we append to it,
-				 * similarly to the way p_join_quals is handled above?
-				 */
-				if (*qual == NULL)
+				ndx = 0;
+				foreach(col, r_colnames)
 				{
-					/* merge qualified join clauses... */
-					if (j->quals != NULL)
+					char	   *r_colname = strVal(lfirst(col));
+
+					if (strcmp(r_colname, u_colname) == 0)
 					{
-						if (*qual != NULL)
-						{
-							A_Expr	   *a = makeNode(A_Expr);
-
-							a->oper = AND;
-							a->opname = NULL;
-							a->lexpr = (Node *) *qual;
-							a->rexpr = (Node *) j->quals;
-
-							*qual = (Node *) a;
-						}
-						else
-							*qual = (Node *) j->quals;
+						if (r_index >= 0)
+							elog(ERROR, "Common column name \"%s\" appears more than once in right table", u_colname);
+						r_index = ndx;
 					}
+					ndx++;
 				}
-				else
+				if (r_index < 0)
+					elog(ERROR, "USING column \"%s\" not found in right table",
+						 u_colname);
+
+				l_colvar = nth(l_index, l_colvars);
+				l_usingvars = lappend(l_usingvars, l_colvar);
+				r_colvar = nth(r_index, r_colvars);
+				r_usingvars = lappend(r_usingvars, r_colvar);
+
+				res_colnames = lappend(res_colnames,
+									   nth(l_index, l_colnames));
+				switch (j->jointype)
 				{
-					elog(ERROR, "Multiple JOIN/ON clauses not handled (internal error)");
-					*qual = lappend(*qual, j->quals);
+					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);
+						A_Expr *a = makeNode(A_Expr);
+
+						a->oper = NOTNULL;
+						a->lexpr = l_colvar;
+						w->expr = (Node *) a;
+						w->result = l_colvar;
+						c->args = lcons(w, NIL);
+						c->defresult = r_colvar;
+						colvar = transformExpr(pstate, (Node *) c,
+											   EXPR_COLUMN_FIRST);
+						break;
+					}
 				}
-#endif
-
-				/*
-				 * if we are transforming this node back into a FROM list,
-				 * then we will need to replace the node with two nodes.
-				 * Will need access to the previous list item to change
-				 * the link pointer to reference these new nodes. Try
-				 * accumulating and returning a new list. - thomas
-				 * 1999-01-08 Not doing this yet though!
-				 */
+				res_colvars = lappend(res_colvars, colvar);
+			}
 
+			j->quals = transformUsingClause(pstate, l_usingvars, r_usingvars);
+		}
+		else if (j->quals)
+		{
+			/* User-written ON-condition; transform it */
+			j->quals = transformExpr(pstate, j->quals, EXPR_COLUMN_FIRST);
+			if (exprType(j->quals) != BOOLOID)
+			{
+				elog(ERROR, "ON clause must return type bool, not type %s",
+					 typeidTypeName(exprType(j->quals)));
 			}
-			else if ((j->jointype == LEFT)
-					 || (j->jointype == RIGHT)
-					 || (j->jointype == FULL))
-				elog(ERROR, "OUTER JOIN is not yet supported");
-			else
-				elog(ERROR, "Unrecognized JOIN clause; tag is %d (internal error)",
-					 j->jointype);
-#else
-			elog(ERROR, "JOIN expressions are not yet implemented");
-#endif
+			/* XXX should check that ON clause refers only to joined tbls */
 		}
 		else
-			elog(ERROR, "parseFromClause: unexpected FROM clause node (internal error)"
-				 "\n\t%s", nodeToString(n));
+		{
+			/* CROSS JOIN: no quals */
+		}
+
+		/* Add remaining columns from each side to the output columns */
+		extractUniqueColumns(res_colnames,
+							 l_colnames, l_colvars,
+							 &l_colnames, &l_colvars);
+		extractUniqueColumns(res_colnames,
+							 r_colnames, r_colvars,
+							 &r_colnames, &r_colvars);
+		res_colnames = nconc(res_colnames, l_colnames);
+		res_colvars = nconc(res_colvars, l_colvars);
+		res_colnames = nconc(res_colnames, r_colnames);
+		res_colvars = nconc(res_colvars, r_colvars);
+
+		/*
+		 * Process alias (AS clause), if any.
+		 *
+		 * The given table alias must be unique in the current nesting level,
+		 * ie it cannot match any RTE refname or jointable alias.  This is
+		 * a bit painful to check because my own child joins are not yet in
+		 * the pstate's jointree, so they have to be scanned separately.
+		 */
+		if (j->alias)
+		{
+			/* Check against previously created RTEs and jointree entries */
+			if (refnameRangeOrJoinEntry(pstate, j->alias->relname, NULL))
+				elog(ERROR, "Table name \"%s\" specified more than once",
+					 j->alias->relname);
+			/* Check children */
+			if (scanJoinTreeForRefname(j->larg, j->alias->relname) ||
+				scanJoinTreeForRefname(j->rarg, j->alias->relname))
+				elog(ERROR, "Table name \"%s\" specified more than once",
+					 j->alias->relname);
+			/*
+			 * 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;
+			}
+		}
+
+		j->colnames = res_colnames;
+		j->colvars = res_colvars;
+
+		return (Node *) j;
+	}
+	else
+		elog(ERROR, "transformFromClauseItem: unexpected node (internal error)"
+			 "\n\t%s", nodeToString(n));
+	return NULL;				/* can't get here, just keep compiler quiet */
+}
+
+
+/*
+ * transformWhereClause -
+ *	  transforms the qualification and make sure it is of type Boolean
+ */
+Node *
+transformWhereClause(ParseState *pstate, Node *clause)
+{
+	Node	   *qual;
+
+	if (clause == NULL)
+		return NULL;
+
+	qual = transformExpr(pstate, clause, EXPR_COLUMN_FIRST);
+
+	if (exprType(qual) != BOOLOID)
+	{
+		elog(ERROR, "WHERE clause must return type bool, not type %s",
+			 typeidTypeName(exprType(qual)));
 	}
-}	/* parseFromClause() */
+	return qual;
+}
 
 
 /*
@@ -786,10 +687,10 @@ findTargetlistEntry(ParseState *pstate, Node *node, List *tlist, int clause)
 			 * is a matching column.  If so, fall through to let
 			 * transformExpr() do the rest.  NOTE: if name could refer
 			 * ambiguously to more than one column name exposed by FROM,
-			 * colnameRangeTableEntry will elog(ERROR).  That's just what
+			 * colnameToVar will elog(ERROR).  That's just what
 			 * we want here.
 			 */
-			if (colnameRangeTableEntry(pstate, name) != NULL)
+			if (colnameToVar(pstate, name) != NULL)
 				name = NULL;
 		}
 
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7976f5e7795..a033ff4be22 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.82 2000/08/08 15:42:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.83 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -157,41 +157,51 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 				{
 					case OP:
 						{
-							Node	   *lexpr = transformExpr(pstate, a->lexpr, precedence);
-							Node	   *rexpr = transformExpr(pstate, a->rexpr, precedence);
+							Node	   *lexpr = transformExpr(pstate,
+															  a->lexpr,
+															  precedence);
+							Node	   *rexpr = transformExpr(pstate,
+															  a->rexpr,
+															  precedence);
 
 							result = (Node *) make_op(a->opname, lexpr, rexpr);
 						}
 						break;
 					case ISNULL:
 						{
-							Node	   *lexpr = transformExpr(pstate, a->lexpr, precedence);
+							Node	   *lexpr = transformExpr(pstate,
+															  a->lexpr,
+															  precedence);
 
 							result = ParseFuncOrColumn(pstate,
 													   "nullvalue",
 													   lcons(lexpr, NIL),
 													   false, false,
-												   &pstate->p_last_resno,
 													   precedence);
 						}
 						break;
 					case NOTNULL:
 						{
-							Node	   *lexpr = transformExpr(pstate, a->lexpr, precedence);
+							Node	   *lexpr = transformExpr(pstate,
+															  a->lexpr,
+															  precedence);
 
 							result = ParseFuncOrColumn(pstate,
 													   "nonnullvalue",
 													   lcons(lexpr, NIL),
 													   false, false,
-												   &pstate->p_last_resno,
 													   precedence);
 						}
 						break;
 					case AND:
 						{
+							Node	   *lexpr = transformExpr(pstate,
+															  a->lexpr,
+															  precedence);
+							Node	   *rexpr = transformExpr(pstate,
+															  a->rexpr,
+															  precedence);
 							Expr	   *expr = makeNode(Expr);
-							Node	   *lexpr = transformExpr(pstate, a->lexpr, precedence);
-							Node	   *rexpr = transformExpr(pstate, a->rexpr, precedence);
 
 							if (exprType(lexpr) != BOOLOID)
 								elog(ERROR, "left-hand side of AND is type '%s', not '%s'",
@@ -209,9 +219,13 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 						break;
 					case OR:
 						{
+							Node	   *lexpr = transformExpr(pstate,
+															  a->lexpr,
+															  precedence);
+							Node	   *rexpr = transformExpr(pstate,
+															  a->rexpr,
+															  precedence);
 							Expr	   *expr = makeNode(Expr);
-							Node	   *lexpr = transformExpr(pstate, a->lexpr, precedence);
-							Node	   *rexpr = transformExpr(pstate, a->rexpr, precedence);
 
 							if (exprType(lexpr) != BOOLOID)
 								elog(ERROR, "left-hand side of OR is type '%s', not '%s'",
@@ -227,8 +241,10 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 						break;
 					case NOT:
 						{
+							Node	   *rexpr = transformExpr(pstate,
+															  a->rexpr,
+															  precedence);
 							Expr	   *expr = makeNode(Expr);
-							Node	   *rexpr = transformExpr(pstate, a->rexpr, precedence);
 
 							if (exprType(rexpr) != BOOLOID)
 								elog(ERROR, "argument to NOT is type '%s', not '%s'",
@@ -254,13 +270,14 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 
 				/* transform the list of arguments */
 				foreach(args, fn->args)
-					lfirst(args) = transformExpr(pstate, (Node *) lfirst(args), precedence);
+					lfirst(args) = transformExpr(pstate,
+												 (Node *) lfirst(args),
+												 precedence);
 				result = ParseFuncOrColumn(pstate,
 										   fn->funcname,
 										   fn->args,
 										   fn->agg_star,
 										   fn->agg_distinct,
-										   &pstate->p_last_resno,
 										   precedence);
 				break;
 			}
@@ -609,8 +626,7 @@ transformAttr(ParseState *pstate, Attr *att, int precedence)
 {
 	Node	   *basenode;
 
-	basenode = ParseNestedFuncOrColumn(pstate, att, &pstate->p_last_resno,
-									   precedence);
+	basenode = ParseNestedFuncOrColumn(pstate, att, precedence);
 	return transformIndirection(pstate, basenode, att->indirection);
 }
 
@@ -618,7 +634,6 @@ static Node *
 transformIdent(ParseState *pstate, Ident *ident, int precedence)
 {
 	Node	   *result = NULL;
-	RangeTblEntry *rte;
 
 	/*
 	 * try to find the ident as a relation ... but not if subscripts
@@ -634,14 +649,10 @@ transformIdent(ParseState *pstate, Ident *ident, int precedence)
 	if (result == NULL || precedence == EXPR_COLUMN_FIRST)
 	{
 		/* try to find the ident as a column */
-		if ((rte = colnameRangeTableEntry(pstate, ident->name)) != NULL)
-		{
-			/* Convert it to a fully qualified Attr, and transform that */
-			Attr	   *att = makeAttr(rte->eref->relname, ident->name);
-
-			att->indirection = ident->indirection;
-			return transformAttr(pstate, att, precedence);
-		}
+		Node   *var = colnameToVar(pstate, ident->name);
+
+		if (var != NULL)
+			result = transformIndirection(pstate, var, ident->indirection);
 	}
 
 	if (result == NULL)
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index bad9401c609..1f19b1b949e 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.89 2000/08/24 03:29:05 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.90 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -64,19 +64,20 @@ static Oid	agg_select_candidate(Oid typeid, CandidateList candidates);
  ** a tree with of Iter and Func nodes.
  */
 Node *
-ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int precedence)
+ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int precedence)
 {
 	List	   *mutator_iter;
 	Node	   *retval = NULL;
 
 	if (attr->paramNo != NULL)
 	{
-		Param	   *param = (Param *) transformExpr(pstate, (Node *) attr->paramNo, EXPR_RELATION_FIRST);
+		Param	   *param = (Param *) transformExpr(pstate,
+													(Node *) attr->paramNo,
+													EXPR_RELATION_FIRST);
 
 		retval = ParseFuncOrColumn(pstate, strVal(lfirst(attr->attrs)),
 								   lcons(param, NIL),
 								   false, false,
-								   curr_resno,
 								   precedence);
 	}
 	else
@@ -88,7 +89,6 @@ ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int pre
 		retval = ParseFuncOrColumn(pstate, strVal(lfirst(attr->attrs)),
 								   lcons(ident, NIL),
 								   false, false,
-								   curr_resno,
 								   precedence);
 	}
 
@@ -98,7 +98,6 @@ ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr, int *curr_resno, int pre
 		retval = ParseFuncOrColumn(pstate, strVal(lfirst(mutator_iter)),
 								   lcons(retval, NIL),
 								   false, false,
-								   curr_resno,
 								   precedence);
 	}
 
@@ -241,17 +240,15 @@ agg_select_candidate(Oid typeid, CandidateList candidates)
 Node *
 ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 				  bool agg_star, bool agg_distinct,
-				  int *curr_resno, int precedence)
+				  int precedence)
 {
 	Oid			rettype = InvalidOid;
 	Oid			argrelid = InvalidOid;
 	Oid			funcid = InvalidOid;
 	List	   *i = NIL;
 	Node	   *first_arg = NULL;
-	char	   *relname = NULL;
-	char	   *refname = NULL;
+	char	   *refname;
 	Relation	rd;
-	Oid			relid;
 	int			nargs = length(fargs);
 	Func	   *funcnode;
 	Oid			oid_array[FUNC_MAX_ARGS];
@@ -283,81 +280,17 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 		if (IsA(first_arg, Ident) && ((Ident *) first_arg)->isRel)
 		{
 			Ident	   *ident = (Ident *) first_arg;
-			RangeTblEntry *rte;
-			AttrNumber	attnum;
 
 			/*
 			 * first arg is a relation. This could be a projection.
 			 */
 			refname = ident->name;
 
-			rte = refnameRangeTableEntry(pstate, refname);
-			if (rte == NULL)
-			{
-				rte = addRangeTableEntry(pstate, refname,
-										 makeAttr(refname, NULL),
-										 FALSE, FALSE, TRUE);
-				warnAutoRange(pstate, refname);
-			}
-
-			relname = rte->relname;
-			relid = rte->relid;
-			attnum = InvalidAttrNumber;
-
-			/*
-			 * If the attr isn't a set, just make a var for it.  If it is
-			 * a set, treat it like a function and drop through. Look
-			 * through the explicit column list first, since we now allow
-			 * column aliases. - thomas 2000-02-07
-			 */
-			if (rte->eref->attrs != NULL)
-			{
-				List	   *c;
-
-				/*
-				 * start counting attributes/columns from one. zero is
-				 * reserved for InvalidAttrNumber. - thomas 2000-01-27
-				 */
-				int			i = 1;
-
-				foreach(c, rte->eref->attrs)
-				{
-					char	   *colname = strVal(lfirst(c));
-
-					/* found a match? */
-					if (strcmp(colname, funcname) == 0)
-					{
-						char	   *basename = get_attname(relid, i);
-
-						if (basename != NULL)
-						{
-							funcname = basename;
-							attnum = i;
-						}
-
-						/*
-						 * attnum was initialized to InvalidAttrNumber
-						 * earlier, so no need to reset it if the above
-						 * test fails. - thomas 2000-02-07
-						 */
-						break;
-					}
-					i++;
-				}
-				if (attnum == InvalidAttrNumber)
-					attnum = specialAttNum(funcname);
-			}
-			else
-				attnum = get_attnum(relid, funcname);
+			retval = qualifiedNameToVar(pstate, refname, funcname, true);
+			if (retval)
+				return retval;
 
-			if (attnum != InvalidAttrNumber)
-			{
-				return (Node *) make_var(pstate,
-										 relid,
-										 refname,
-										 funcname);
-			}
-			/* else drop through - attr is a set */
+			/* else drop through - attr is a set or function */
 		}
 		else if (ISCOMPLEX(exprType(first_arg)))
 		{
@@ -376,10 +309,7 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 				toid = exprType(first_arg);
 				rd = heap_openr_nofail(typeidTypeName(toid));
 				if (RelationIsValid(rd))
-				{
-					relname = RelationGetRelationName(rd);
 					heap_close(rd, NoLock);
-				}
 				else
 					elog(ERROR, "Type '%s' is not a relation type",
 						 typeidTypeName(toid));
@@ -506,17 +436,9 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 
 			rte = refnameRangeTableEntry(pstate, refname);
 			if (rte == NULL)
-			{
-				rte = addRangeTableEntry(pstate, refname,
-										 makeAttr(refname, NULL),
-										 FALSE, FALSE, TRUE);
-				warnAutoRange(pstate, refname);
-			}
-
-			relname = rte->relname;
+				rte = addImplicitRTE(pstate, refname);
 
-			vnum = refnameRangeTablePosn(pstate, rte->eref->relname,
-										 &sublevels_up);
+			vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
 
 			/*
 			 * for func(relname), the param to the function is the tuple
@@ -525,7 +447,8 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 			 * but has varattno == 0 to signal that the whole tuple is the
 			 * argument.
 			 */
-			toid = typeTypeId(typenameType(relname));
+			toid = typeTypeId(typenameType(rte->relname));
+
 			/* replace it in the arg list */
 			lfirst(i) = makeVar(vnum, 0, toid, -1, sublevels_up);
 		}
@@ -666,16 +589,6 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 	/* perform the necessary typecasting of arguments */
 	make_arguments(pstate, nargs, fargs, oid_array, true_oid_array);
 
-	/*
-	 * Special checks to disallow sequence functions with side-effects
-	 * in WHERE clauses.  This is pretty much of a hack; why disallow these
-	 * when we have no way to check for side-effects of user-defined fns?
-	 */
-	if (funcid == F_NEXTVAL && pstate->p_in_where_clause)
-		elog(ERROR, "Sequence function nextval is not allowed in WHERE clauses");
-	if (funcid == F_SETVAL && pstate->p_in_where_clause)
-		elog(ERROR, "Sequence function setval is not allowed in WHERE clauses");
-
 	expr = makeNode(Expr);
 	expr->typeOid = rettype;
 	expr->opType = FUNC_EXPR;
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index 5d363ea3e69..85a56067bd2 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.45 2000/08/24 03:29:05 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.46 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,8 +49,8 @@ make_parsestate(ParseState *parentParseState)
 	pstate = palloc(sizeof(ParseState));
 	MemSet(pstate, 0, sizeof(ParseState));
 
-	pstate->p_last_resno = 1;
 	pstate->parentParseState = parentParseState;
+	pstate->p_last_resno = 1;
 
 	return pstate;
 }
@@ -164,35 +164,44 @@ make_op(char *opname, Node *ltree, Node *rtree)
 
 /*
  * make_var
- *		Build a Var node for an attribute identified by name
+ *		Build a Var node for an attribute identified by RTE and attrno
  */
 Var *
-make_var(ParseState *pstate, Oid relid, char *refname,
-		 char *attrname)
+make_var(ParseState *pstate, RangeTblEntry *rte, int attrno)
 {
-	HeapTuple	tp;
-	Form_pg_attribute att_tup;
 	int			vnum,
-				attid;
+				sublevels_up;
 	Oid			vartypeid;
 	int32		type_mod;
-	int			sublevels_up;
-
-	vnum = refnameRangeTablePosn(pstate, refname, &sublevels_up);
-
-	tp = SearchSysCacheTuple(ATTNAME,
-							 ObjectIdGetDatum(relid),
-							 PointerGetDatum(attrname),
-							 0, 0);
-	if (!HeapTupleIsValid(tp))
-		elog(ERROR, "Relation %s does not have attribute %s",
-			 refname, attrname);
-	att_tup = (Form_pg_attribute) GETSTRUCT(tp);
-	attid = att_tup->attnum;
-	vartypeid = att_tup->atttypid;
-	type_mod = att_tup->atttypmod;
-
-	return makeVar(vnum, attid, vartypeid, type_mod, sublevels_up);
+
+	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 = SearchSysCacheTuple(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;
+	}
+	else
+	{
+		/* Subselect RTE --- get type info from subselect's tlist */
+		elog(ERROR, "make_var: subselect in FROM not implemented yet");
+		vartypeid = type_mod = 0;
+	}
+
+	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 802299c8966..491cbc5ef08 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.46 2000/08/08 15:42:04 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.47 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,14 +20,25 @@
 #include "access/htup.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
+#include "parser/parsetree.h"
 #include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_type.h"
+#include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 
 
+static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
+							  char *colname);
+static Node *scanJoinForColumn(JoinExpr *join, char *colname,
+							   int sublevels_up);
+static List *expandNamesVars(ParseState *pstate, List *names, List *vars);
+static void warnAutoRange(ParseState *pstate, char *refname);
+
+
 /*
  * Information defining the "system" attributes of every relation.
  */
@@ -65,40 +76,96 @@ static struct
 #define SPECIALS ((int) (sizeof(special_attr)/sizeof(special_attr[0])))
 
 
-#ifdef NOT_USED
-/* refnameRangeTableEntries()
- * Given refname, return a list of range table entries
- * This is possible with JOIN syntax, where tables in a join
- * acquire the same reference name.
- * - thomas 2000-01-20
- * But at the moment we aren't carrying along a full list of
- * table/column aliases, so we don't have the full mechanism
- * to support outer joins in place yet.
- * - thomas 2000-03-04
+/*
+ * 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.
+ *	  Optionally get its nesting depth (0 = current).	If sublevels_up
+ *	  is NULL, only consider items at the current nesting level.
  */
-
-static List *
-refnameRangeTableEntries(ParseState *pstate, char *refname)
+Node *
+refnameRangeOrJoinEntry(ParseState *pstate,
+						char *refname,
+						int *sublevels_up)
 {
-	List	   *rteList = NULL;
-	List	   *temp;
+	if (sublevels_up)
+		*sublevels_up = 0;
 
 	while (pstate != NULL)
 	{
+		List	   *temp;
+		JoinExpr   *join;
+
+		/*
+		 * Check the rangetable for RTEs; if no match, recursively scan
+		 * the jointree for join tables.  We assume that no duplicate
+		 * entries have been made in any one nesting level.
+		 */
 		foreach(temp, pstate->p_rtable)
 		{
 			RangeTblEntry *rte = lfirst(temp);
 
 			if (strcmp(rte->eref->relname, refname) == 0)
-				rteList = lappend(rteList, rte);
+				return (Node *) rte;
 		}
+
+		join = scanJoinTreeForRefname((Node *) pstate->p_jointree, refname);
+		if (join)
+			return (Node *) join;
+
 		pstate = pstate->parentParseState;
+		if (sublevels_up)
+			(*sublevels_up)++;
+		else
+			break;
 	}
-	return rteList;
+	return NULL;
 }
-#endif
 
-/* given refname, return a pointer to the range table entry */
+/* Recursively search a jointree for a joinexpr with given refname */
+JoinExpr *
+scanJoinTreeForRefname(Node *jtnode, char *refname)
+{
+	JoinExpr   *result = NULL;
+
+	if (jtnode == NULL)
+		return NULL;
+	if (IsA(jtnode, List))
+	{
+		List	   *l;
+
+		foreach(l, (List *) jtnode)
+		{
+			result = scanJoinTreeForRefname(lfirst(l), refname);
+			if (result)
+				break;
+		}
+	}
+	else if (IsA(jtnode, RangeTblRef))
+	{
+		/* ignore ... */
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		if (j->alias && strcmp(j->alias->relname, refname) == 0)
+			return j;
+		result = scanJoinTreeForRefname(j->larg, refname);
+		if (! result)
+			result = scanJoinTreeForRefname(j->rarg, refname);
+	}
+	else
+		elog(ERROR, "scanJoinTreeForRefname: unexpected node type %d",
+			 nodeTag(jtnode));
+	return result;
+}
+
+/*
+ * given refname, return a pointer to the range table entry.
+ *
+ * NOTE that this routine will ONLY find RTEs, not join tables.
+ */
 RangeTblEntry *
 refnameRangeTableEntry(ParseState *pstate, char *refname)
 {
@@ -118,9 +185,13 @@ refnameRangeTableEntry(ParseState *pstate, char *refname)
 	return NULL;
 }
 
-/* given refname, return RT index (starting with 1) of the relation,
+/*
+ * given refname, return RT index (starting with 1) of the relation,
  * and optionally get its nesting depth (0 = current).	If sublevels_up
  * is NULL, only consider rels at the current nesting level.
+ * A zero result means name not found.
+ *
+ * NOTE that this routine will ONLY find RTEs, not join tables.
  */
 int
 refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up)
@@ -152,114 +223,264 @@ refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up)
 }
 
 /*
- * returns range entry if found, else NULL
+ * given an RTE, return RT index (starting with 1) of the entry,
+ * and optionally get its nesting depth (0 = current).	If sublevels_up
+ * is NULL, only consider rels at the current nesting level.
+ * Raises error if RTE not found.
  */
-RangeTblEntry *
-colnameRangeTableEntry(ParseState *pstate, char *colname)
+int
+RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
 {
-	List	   *et;
-	List	   *rtable;
-	RangeTblEntry *rte_result = NULL;
+	int			index;
+	List	   *temp;
+
+	if (sublevels_up)
+		*sublevels_up = 0;
 
 	while (pstate != NULL)
 	{
-		if (pstate->p_is_rule)
-			rtable = lnext(lnext(pstate->p_rtable));
+		index = 1;
+		foreach(temp, pstate->p_rtable)
+		{
+			if (rte == (RangeTblEntry *) lfirst(temp))
+				return index;
+			index++;
+		}
+		pstate = pstate->parentParseState;
+		if (sublevels_up)
+			(*sublevels_up)++;
 		else
-			rtable = pstate->p_rtable;
+			break;
+	}
+	elog(ERROR, "RTERangeTablePosn: RTE not found (internal error)");
+	return 0;					/* keep compiler quiet */
+}
+
+/*
+ * scanRTEForColumn
+ *	  Search the column names of a single RTE for the given name.
+ *	  If found, return an appropriate Var node, else return NULL.
+ *	  If the name proves ambiguous within this RTE, raise error.
+ */
+static Node *
+scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname)
+{
+	Node	   *result = NULL;
+	int			attnum = 0;
+	List	   *c;
 
-		foreach(et, rtable)
+	/*
+	 * Scan the user column names (or aliases) for a match.
+	 * Complain if multiple matches.
+	 */
+	foreach(c, rte->eref->attrs)
+	{
+		attnum++;
+		if (strcmp(strVal(lfirst(c)), colname) == 0)
 		{
-			RangeTblEntry *rte_candidate = NULL;
-			RangeTblEntry *rte = lfirst(et);
+			if (result)
+				elog(ERROR, "Column reference \"%s\" is ambiguous", colname);
+			result = (Node *) make_var(pstate, rte, attnum);
+		}
+	}
 
-			/* only consider RTEs mentioned in FROM or UPDATE/DELETE */
-			if (!rte->inFromCl && rte != pstate->p_target_rangetblentry)
-				continue;
+	/*
+	 * If we have a unique match, return it.  Note that this allows a user
+	 * alias to override a system column name (such as OID) without error.
+	 */
+	if (result)
+		return result;
 
-			if (rte->eref->attrs != NULL)
-			{
-				List	   *c;
-
-				foreach(c, rte->ref->attrs)
-				{
-					if (strcmp(strVal(lfirst(c)), colname) == 0)
-					{
-						if (rte_candidate != NULL)
-							elog(ERROR, "Column '%s' is ambiguous"
-								 " (internal error)", colname);
-						rte_candidate = rte;
-					}
-				}
-			}
+	/*
+	 * If the RTE represents a table (not a sub-select), consider system
+	 * column names.
+	 */
+	if (rte->relid != InvalidOid)
+	{
+		attnum = specialAttNum(colname);
+		if (attnum != InvalidAttrNumber)
+			result = (Node *) make_var(pstate, rte, attnum);
+	}
+
+	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.
+ */
+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));
 			/*
-			 * Even if we have an attribute list in the RTE, look for the
-			 * column here anyway. This is the only way we will find
-			 * implicit columns like "oid". - thomas 2000-02-07
+			 * If referencing an uplevel join item, we must adjust
+			 * sublevels settings in the copied expression.
 			 */
-			if ((rte_candidate == NULL)
-				&& (get_attnum(rte->relid, colname) != InvalidAttrNumber))
-				rte_candidate = rte;
+			if (sublevels_up > 0)
+				IncrementVarSublevelsUp(result, sublevels_up, 0);
+		}
+	}
+	return result;
+}
+
+/*
+ * colnameToVar
+ *	  Search for an unqualified column name.
+ *	  If found, return the appropriate Var node (or expression).
+ *	  If not found, return NULL.  If the name proves ambiguous, raise error.
+ */
+Node *
+colnameToVar(ParseState *pstate, char *colname)
+{
+	Node	   *result = NULL;
+	ParseState *orig_pstate = pstate;
+	int			levels_up = 0;
 
-			if (rte_candidate == NULL)
-				continue;
+	while (pstate != NULL)
+	{
+		List	   *jt;
 
-			if (rte_result != NULL)
+		/*
+		 * We want to look only at top-level jointree items, and even for
+		 * those, ignore RTEs that are marked as not inFromCl and not
+		 * the query's target relation.
+		 */
+		foreach(jt, pstate->p_jointree)
+		{
+			Node   *jtnode = (Node *) lfirst(jt);
+			Node   *newresult = NULL;
+
+			if (IsA(jtnode, RangeTblRef))
 			{
-				if (!pstate->p_is_insert ||
+				int			varno = ((RangeTblRef *) jtnode)->rtindex;
+				RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
+
+				if (! rte->inFromCl &&
 					rte != pstate->p_target_rangetblentry)
-					elog(ERROR, "Column '%s' is ambiguous", colname);
+					continue;
+
+				/* use orig_pstate here to get the right sublevels_up */
+				newresult = scanRTEForColumn(orig_pstate, rte, colname);
+			}
+			else if (IsA(jtnode, JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) jtnode;
+
+				newresult = scanJoinForColumn(j, colname, levels_up);
 			}
 			else
-				rte_result = rte;
+				elog(ERROR, "colnameToVar: unexpected node type %d",
+					 nodeTag(jtnode));
+
+			if (newresult)
+			{
+				if (result)
+					elog(ERROR, "Column reference \"%s\" is ambiguous",
+						 colname);
+				result = newresult;
+			}
 		}
 
-		if (rte_result != NULL)
+		if (result != NULL)
 			break;				/* found */
 
 		pstate = pstate->parentParseState;
+		levels_up++;
 	}
-	return rte_result;
+
+	return result;
 }
 
 /*
- * put new entry in pstate p_rtable structure, or return pointer
- * if pstate null
+ * qualifiedNameToVar
+ *	  Search for a qualified column name (refname + column name).
+ *	  If found, return the appropriate Var node (or expression).
+ *	  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;
+	int			sublevels_up;
+
+	rteorjoin = refnameRangeOrJoinEntry(pstate, refname, &sublevels_up);
+
+	if (rteorjoin == NULL)
+	{
+		if (! implicitRTEOK)
+			return NULL;
+		rteorjoin = (Node *) addImplicitRTE(pstate, refname);
+		sublevels_up = 0;
+	}
+
+	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;
+}
+
+/*
+ * Add an entry to the pstate's range table (p_rtable), unless the
+ * specified refname is already present, in which case raise error.
+ *
+ * If pstate is NULL, we just build an RTE and return it without worrying
+ * about membership in an rtable list.
  */
 RangeTblEntry *
 addRangeTableEntry(ParseState *pstate,
 				   char *relname,
-				   Attr *ref,
+				   Attr *alias,
 				   bool inh,
-				   bool inFromCl,
-				   bool inJoinSet)
+				   bool inFromCl)
 {
+	char	   *refname = alias ? alias->relname : relname;
 	Relation	rel;
 	RangeTblEntry *rte;
 	Attr	   *eref;
 	int			maxattrs;
-	int			sublevels_up;
+	int			numaliases;
 	int			varattno;
 
-	/* Look for an existing rte, if available... */
+	/* Check for conflicting RTE or jointable alias (at level 0 only) */
 	if (pstate != NULL)
 	{
-		int			rt_index = refnameRangeTablePosn(pstate, ref->relname,
-													 &sublevels_up);
+		Node   *rteorjoin = refnameRangeOrJoinEntry(pstate, refname, NULL);
 
-		if (rt_index != 0 && (!inFromCl || sublevels_up == 0))
-		{
-			if (!strcmp(ref->relname, "*OLD*") || !strcmp(ref->relname, "*NEW*"))
-				return (RangeTblEntry *) nth(rt_index - 1, pstate->p_rtable);
-			elog(ERROR, "Table name '%s' specified more than once", ref->relname);
-		}
+		if (rteorjoin)
+			elog(ERROR, "Table name \"%s\" specified more than once",
+				 refname);
 	}
 
 	rte = makeNode(RangeTblEntry);
 
 	rte->relname = relname;
-	rte->ref = ref;
+	rte->alias = alias;
 
 	/*
 	 * Get the rel's OID.  This access also ensures that we have an
@@ -271,30 +492,34 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relid = RelationGetRelid(rel);
 	maxattrs = RelationGetNumberOfAttributes(rel);
 
-	eref = copyObject(ref);
-	if (maxattrs < length(eref->attrs))
-		elog(ERROR, "Table '%s' has %d columns available but %d columns specified",
-			 relname, maxattrs, length(eref->attrs));
+	eref = alias ? copyObject(alias) : makeAttr(refname, NULL);
+	numaliases = length(eref->attrs);
+
+	if (maxattrs < numaliases)
+		elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
+			 refname, maxattrs, numaliases);
 
 	/* fill in any unspecified alias columns */
-	for (varattno = length(eref->attrs); varattno < maxattrs; varattno++)
+	for (varattno = numaliases; varattno < maxattrs; varattno++)
 	{
 		char	   *attrname;
 
 		attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
 		eref->attrs = lappend(eref->attrs, makeString(attrname));
 	}
-	heap_close(rel, AccessShareLock);
 	rte->eref = eref;
 
-	/*
-	 * Flags: - this RTE should be expanded to include descendant tables,
-	 * - this RTE is in the FROM clause, - this RTE should be included in
-	 * the planner's final join.
+	heap_close(rel, AccessShareLock);
+
+	/*----------
+	 * Flags:
+	 * - this RTE should be expanded to include descendant tables,
+	 * - this RTE is in the FROM clause,
+	 * - this RTE should not be checked for access rights.
+	 *----------
 	 */
 	rte->inh = inh;
 	rte->inFromCl = inFromCl;
-	rte->inJoinSet = inJoinSet;
 	rte->skipAcl = false;		/* always starts out false */
 
 	/*
@@ -306,118 +531,184 @@ addRangeTableEntry(ParseState *pstate,
 	return rte;
 }
 
-/* expandTable()
- * Populates an Attr with table name and column names
- * This is similar to expandAll(), but does not create an RTE
- * if it does not already exist.
- * - thomas 2000-01-19
+/*
+ * Add the given RTE as a top-level entry in the pstate's join tree,
+ * unless there already is an entry for it.
  */
-Attr *
-expandTable(ParseState *pstate, char *refname, bool getaliases)
+void
+addRTEtoJoinTree(ParseState *pstate, RangeTblEntry *rte)
+{
+	int			rtindex = RTERangeTablePosn(pstate, rte, NULL);
+	List	   *jt;
+	RangeTblRef *rtr;
+
+	foreach(jt, pstate->p_jointree)
+	{
+		Node	   *n = (Node *) lfirst(jt);
+
+		if (IsA(n, RangeTblRef))
+		{
+			if (rtindex == ((RangeTblRef *) n)->rtindex)
+				return;			/* it's already being joined to */
+		}
+	}
+
+	/* Not present, so add it */
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = rtindex;
+	pstate->p_jointree = lappend(pstate->p_jointree, rtr);
+}
+
+/*
+ * Add a POSTQUEL-style implicit RTE.
+ *
+ * We assume caller has already checked that there is no such RTE now.
+ */
+RangeTblEntry *
+addImplicitRTE(ParseState *pstate, char *relname)
 {
-	Attr	   *attr;
 	RangeTblEntry *rte;
+
+	rte = addRangeTableEntry(pstate, relname, NULL, false, false);
+	addRTEtoJoinTree(pstate, rte);
+	warnAutoRange(pstate, relname);
+
+	return rte;
+}
+
+/* expandRTE()
+ *
+ * Given a rangetable entry, create lists of its column names (aliases if
+ * provided, else real names) and Vars for each column.  Only user columns
+ * are considered, since this is primarily used to expand '*' and determine
+ * the contents of JOIN tables.
+ *
+ * If only one of the two kinds of output list is needed, pass NULL for the
+ * output pointer for the unwanted one.
+ */
+void
+expandRTE(ParseState *pstate, RangeTblEntry *rte,
+		  List **colnames, List **colvars)
+{
 	Relation	rel;
 	int			varattno,
-				maxattrs;
+				maxattrs,
+				rtindex,
+				sublevels_up;
 
-	rte = refnameRangeTableEntry(pstate, refname);
+	if (colnames)
+		*colnames = NIL;
+	if (colvars)
+		*colvars = NIL;
 
-	if (getaliases && (rte != NULL))
-		return rte->eref;
+	/* Need the RT index of the entry for creating Vars */
+	rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up);
 
-	if (rte != NULL)
-		rel = heap_open(rte->relid, AccessShareLock);
-	else
-		rel = heap_openr(refname, AccessShareLock);
-
-	if (rel == NULL)
-		elog(ERROR, "Relation '%s' not found", refname);
+	rel = heap_open(rte->relid, AccessShareLock);
 
 	maxattrs = RelationGetNumberOfAttributes(rel);
 
-	attr = makeAttr(refname, NULL);
-
 	for (varattno = 0; varattno < maxattrs; varattno++)
 	{
-		char	   *attrname;
+		Form_pg_attribute attr = rel->rd_att->attrs[varattno];
 
 #ifdef	_DROP_COLUMN_HACK__
-		if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno]))
+		if (COLUMN_IS_DROPPED(attr))
 			continue;
 #endif	 /* _DROP_COLUMN_HACK__ */
-		attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
-		attr->attrs = lappend(attr->attrs, makeString(attrname));
+
+		if (colnames)
+		{
+			char	   *label;
+
+			if (varattno < length(rte->eref->attrs))
+				label = strVal(nth(varattno, rte->eref->attrs));
+			else
+				label = NameStr(attr->attname);
+			*colnames = lappend(*colnames, makeString(pstrdup(label)));
+		}
+
+		if (colvars)
+		{
+			Var		   *varnode;
+
+			varnode = makeVar(rtindex, attr->attnum,
+							  attr->atttypid, attr->atttypmod,
+							  sublevels_up);
+
+			*colvars = lappend(*colvars, varnode);
+		}
 	}
 
 	heap_close(rel, AccessShareLock);
-
-	return attr;
 }
 
 /*
- * expandAll -
- *	  makes a list of attributes
+ * expandRelAttrs -
+ *	  makes a list of TargetEntry nodes for the attributes of the rel
  */
 List *
-expandAll(ParseState *pstate, char *relname, Attr *ref, int *this_resno)
+expandRelAttrs(ParseState *pstate, RangeTblEntry *rte)
 {
-	List	   *te_list = NIL;
-	RangeTblEntry *rte;
-	Relation	rel;
-	int			varattno,
-				maxattrs;
+	List	   *name_list,
+			   *var_list;
 
-	rte = refnameRangeTableEntry(pstate, ref->relname);
-	if (rte == NULL)
-	{
-		rte = addRangeTableEntry(pstate, relname, ref,
-								 FALSE, FALSE, TRUE);
-		warnAutoRange(pstate, ref->relname);
-	}
+	expandRTE(pstate, rte, &name_list, &var_list);
 
-	rel = heap_open(rte->relid, AccessShareLock);
+	return expandNamesVars(pstate, name_list, var_list);
+}
 
-	maxattrs = RelationGetNumberOfAttributes(rel);
+/*
+ * expandJoinAttrs -
+ *	  makes a list of TargetEntry nodes for the attributes of the join
+ */
+List *
+expandJoinAttrs(ParseState *pstate, JoinExpr *join, int sublevels_up)
+{
+	List	   *vars;
 
-	for (varattno = 0; varattno < maxattrs; varattno++)
-	{
-		char	   *attrname;
-		char	   *label;
-		Var		   *varnode;
-		TargetEntry *te = makeNode(TargetEntry);
+	vars = copyObject(join->colvars);
+	/*
+	 * 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);
 
-#ifdef	_DROP_COLUMN_HACK__
-		if (COLUMN_IS_DROPPED(rel->rd_att->attrs[varattno]))
-			continue;
-#endif	 /* _DROP_COLUMN_HACK__ */
-		attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
+	return expandNamesVars(pstate,
+						   copyObject(join->colnames),
+						   vars);
+}
 
-		/*
-		 * varattno is zero-based, so check that length() is always
-		 * greater
-		 */
-		if (length(rte->eref->attrs) > varattno)
-			label = pstrdup(strVal(nth(varattno, rte->eref->attrs)));
-		else
-			label = attrname;
-		varnode = make_var(pstate, rte->relid, relname, attrname);
+/*
+ * expandNamesVars -
+ *		Workhorse for "*" expansion: produce a list of targetentries
+ *		given lists of column names (as String nodes) and var references.
+ */
+static List *
+expandNamesVars(ParseState *pstate, List *names, List *vars)
+{
+	List	   *te_list = NIL;
 
-		/*
-		 * Even if the elements making up a set are complex, the set
-		 * itself is not.
-		 */
+	while (names)
+	{
+		char	   *label = strVal(lfirst(names));
+		Node	   *varnode = (Node *) lfirst(vars);
+		TargetEntry *te = makeNode(TargetEntry);
 
-		te->resdom = makeResdom((AttrNumber) (*this_resno)++,
-								varnode->vartype,
-								varnode->vartypmod,
+		te->resdom = makeResdom((AttrNumber) (pstate->p_last_resno)++,
+								exprType(varnode),
+								exprTypmod(varnode),
 								label,
 								false);
-		te->expr = (Node *) varnode;
+		te->expr = varnode;
 		te_list = lappend(te_list, te);
+
+		names = lnext(names);
+		vars = lnext(vars);
 	}
 
-	heap_close(rel, AccessShareLock);
+	Assert(vars == NIL);		/* lists not same length? */
 
 	return te_list;
 }
@@ -531,11 +822,17 @@ attnumTypeId(Relation rd, int attid)
 	return rd->rd_att->attrs[attid - 1]->atttypid;
 }
 
-void
+/*
+ * Generate a warning about an implicit RTE, if appropriate.
+ *
+ * Our current theory on this is that we should allow "SELECT foo.*"
+ * but warn about a mixture of explicit and implicit RTEs.
+ */
+static void
 warnAutoRange(ParseState *pstate, char *refname)
 {
-	List	   *temp;
 	bool		foundInFromCl = false;
+	List	   *temp;
 
 	foreach(temp, pstate->p_rtable)
 	{
@@ -548,8 +845,8 @@ warnAutoRange(ParseState *pstate, char *refname)
 		}
 	}
 	if (foundInFromCl)
-		elog(NOTICE, "Adding missing FROM-clause entry%s for table %s",
-			pstate->parentParseState != NULL ? " in subquery" : "",
-			refname);
+		elog(NOTICE, "Adding missing FROM-clause entry%s for table \"%s\"",
+			 pstate->parentParseState != NULL ? " in subquery" : "",
+			 refname);
 }
 
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 1564f976b04..b8e1570985f 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -8,13 +8,14 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.61 2000/08/08 15:42:04 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.62 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 #include "nodes/makefuncs.h"
+#include "parser/parsetree.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_func.h"
@@ -104,36 +105,8 @@ transformTargetList(ParseState *pstate, List *targetlist)
 				 * Target item is a single '*', expand all tables (eg.
 				 * SELECT * FROM emp)
 				 */
-				if (pstate->p_shape != NULL)
-				{
-					List	   *s,
-							   *a;
-					int			i;
-
-					Assert(length(pstate->p_shape) == length(pstate->p_alias));
-
-					s = pstate->p_shape;
-					a = pstate->p_alias;
-					for (i = 0; i < length(pstate->p_shape); i++)
-					{
-						TargetEntry *te;
-						char	   *colname;
-						Attr	   *shape = lfirst(s);
-						Attr	   *alias = lfirst(a);
-
-						Assert(IsA(shape, Attr) &&IsA(alias, Attr));
-
-						colname = strVal(lfirst(alias->attrs));
-						te = transformTargetEntry(pstate, (Node *) shape,
-												  NULL, colname, false);
-						p_target = lappend(p_target, te);
-						s = lnext(s);
-						a = lnext(a);
-					}
-				}
-				else
-					p_target = nconc(p_target,
-									 ExpandAllTables(pstate));
+				p_target = nconc(p_target,
+								 ExpandAllTables(pstate));
 			}
 			else if (att->attrs != NIL &&
 					 strcmp(strVal(lfirst(att->attrs)), "*") == 0)
@@ -143,10 +116,30 @@ transformTargetList(ParseState *pstate, List *targetlist)
 				 * Target item is relation.*, expand that table (eg.
 				 * SELECT emp.*, dname FROM emp, dept)
 				 */
-				p_target = nconc(p_target,
-								 expandAll(pstate, att->relname,
-										   makeAttr(att->relname, NULL),
-										   &pstate->p_last_resno));
+				Node	   *rteorjoin;
+				int			sublevels_up;
+
+				rteorjoin = refnameRangeOrJoinEntry(pstate, att->relname,
+													&sublevels_up);
+
+				if (rteorjoin == NULL)
+				{
+					rteorjoin = (Node *) addImplicitRTE(pstate, att->relname);
+					sublevels_up = 0;
+				}
+
+				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));
 			}
 			else
 			{
@@ -219,23 +212,12 @@ updateTargetListEntry(ParseState *pstate,
 	 */
 	if (indirection)
 	{
-#ifndef DISABLE_JOIN_SYNTAX
-		Attr	   *att = makeAttr(pstrdup(RelationGetRelationName(rd)), colname);
-
-#else
-		Attr	   *att = makeNode(Attr);
-
-#endif
+		Attr	   *att = makeAttr(pstrdup(RelationGetRelationName(rd)),
+								   colname);
 		Node	   *arrayBase;
 		ArrayRef   *aref;
 
-#ifdef DISABLE_JOIN_SYNTAX
-		att->relname = pstrdup(RelationGetRelationName(rd));
-		att->attrs = lcons(makeString(colname), NIL);
-#endif
-		arrayBase = ParseNestedFuncOrColumn(pstate, att,
-											&pstate->p_last_resno,
-											EXPR_COLUMN_FIRST);
+		arrayBase = ParseNestedFuncOrColumn(pstate, att, EXPR_COLUMN_FIRST);
 		aref = transformArraySubscripts(pstate, arrayBase,
 										indirection,
 										pstate->p_is_insert,
@@ -401,46 +383,54 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
 }
 
 /* ExpandAllTables()
- * Turns '*' (in the target list) into a list of attributes
- * (of all relations in the range table)
+ * Turns '*' (in the target list) into a list of targetlist entries.
+ *
+ * tlist entries are generated for each relation appearing in the FROM list,
+ * which by now has been expanded into a join tree.
  */
 static List *
 ExpandAllTables(ParseState *pstate)
 {
 	List	   *target = NIL;
-	List	   *rt,
-			   *rtable;
-
-	rtable = pstate->p_rtable;
-	if (pstate->p_is_rule)
-	{
-
-		/*
-		 * skip first two entries, "*new*" and "*current*"
-		 */
-		rtable = lnext(lnext(rtable));
-	}
+	List	   *jt;
 
 	/* SELECT *; */
-	if (rtable == NIL)
+	if (pstate->p_jointree == NIL)
 		elog(ERROR, "Wildcard with no tables specified not allowed");
 
-	foreach(rt, rtable)
+	foreach(jt, pstate->p_jointree)
 	{
-		RangeTblEntry *rte = lfirst(rt);
+		Node	   *n = (Node *) lfirst(jt);
 
-		/*
-		 * we only expand those listed in the from clause. (This will also
-		 * prevent us from using the wrong table in inserts: eg. tenk2 in
-		 * "insert into tenk2 select * from tenk1;")
-		 */
-		if (!rte->inFromCl)
-			continue;
+		if (IsA(n, RangeTblRef))
+		{
+			RangeTblEntry *rte;
 
-		target = nconc(target,
-					   expandAll(pstate, rte->eref->relname, rte->eref,
-								 &pstate->p_last_resno));
+			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));
+		}
+		else
+			elog(ERROR, "ExpandAllTables: unexpected node (internal error)"
+				 "\n\t%s", nodeToString(n));
 	}
+
 	return target;
 }
 
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index a7652407b73..4a6c825498a 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -1,32 +1,40 @@
 /*-------------------------------------------------------------------------
  *
  * parser.c
+ *		Main entry point/driver for PostgreSQL parser
+ *
  *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.45 2000/04/12 17:15:27 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.46 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
 #include "parser/analyze.h"
 #include "parser/gramparse.h"
+#include "parser/parse.h"
 #include "parser/parser.h"
 #include "parser/parse_expr.h"
 
+
 #if defined(FLEX_SCANNER)
 extern void DeleteBuffer(void);
-
 #endif	 /* FLEX_SCANNER */
 
 char	   *parseString;		/* the char* which holds the string to be
 								 * parsed */
 List	   *parsetree;			/* result of parsing is left here */
 
+static int	lookahead_token;	/* one-token lookahead */
+static bool have_lookahead;		/* lookahead_token set? */
+
 #ifdef SETS_FIXED
 static void fixupsets();
 static void define_sets();
@@ -42,11 +50,11 @@ parser(char *str, Oid *typev, int nargs)
 	List	   *queryList;
 	int			yyresult;
 
-	init_io();
-
-	parseString = pstrdup(str);
+	parseString = str;
 	parsetree = NIL;			/* in case parser forgets to set it */
+	have_lookahead = false;
 
+	scanner_init();
 	parser_init(typev, nargs);
 	parse_expr_init();
 
@@ -83,6 +91,52 @@ parser(char *str, Oid *typev, int nargs)
 	return queryList;
 }
 
+
+/*
+ * Intermediate filter between parser and base lexer (base_yylex in scan.l).
+ *
+ * The filter is needed because in some cases SQL92 requires more than one
+ * token lookahead.  We reduce these cases to one-token lookahead by combining
+ * tokens here, in order to keep the grammar LR(1).
+ *
+ * Using a filter is simpler than trying to recognize multiword tokens 
+ * directly in scan.l, because we'd have to allow for comments between the
+ * words ...
+ */
+int
+yylex(void)
+{
+	int			cur_token;
+
+	/* Get next token --- we might already have it */
+	if (have_lookahead)
+	{
+		cur_token = lookahead_token;
+		have_lookahead = false;
+	}
+	else
+		cur_token = base_yylex();
+
+	/* Do we need to look ahead for a possible multiword token? */
+	switch (cur_token)
+	{
+		case UNION:
+			/* UNION JOIN must be reduced to a single UNIONJOIN token */
+			lookahead_token = base_yylex();
+			if (lookahead_token == JOIN)
+				cur_token = UNIONJOIN;
+			else
+				have_lookahead = true;
+			break;
+
+		default:
+			break;
+	}
+
+	return cur_token;
+}
+
+
 #ifdef SETS_FIXED
 static void
 fixupsets(Query *parse)
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index f5597d1593e..5700915ad94 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.76 2000/08/22 13:01:20 ishii Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/scan.l,v 1.77 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -76,7 +76,7 @@ static char	   *literalbuf;		/* expandable buffer */
 static int		literallen;		/* actual current length */
 static int		literalalloc;	/* current allocated buffer size */
 
-static int		xcdepth = 0;
+static int		xcdepth = 0;	/* depth of nesting in slash-star comments */
 
 #define startlit()  (literalbuf[0] = '\0', literallen = 0)
 static void addlit(char *ytext, int yleng);
@@ -510,22 +510,24 @@ other			.
 
 %%
 
-void yyerror(const char * message)
+void
+yyerror(const char *message)
 {
 	elog(ERROR, "parser: %s at or near \"%s\"", message, yytext);
 }
 
-int yywrap()
+int
+yywrap(void)
 {
 	return(1);
 }
 
 /*
- init_io:
+ scanner_init:
 	called by postgres before any actual parsing is done
 */
 void
-init_io()
+scanner_init(void)
 {
 	/* it's important to set this to NULL
 	   because input()/myinput() checks the non-nullness of parseCh
diff --git a/src/backend/rewrite/locks.c b/src/backend/rewrite/locks.c
index a14e1b48684..78fdad8960a 100644
--- a/src/backend/rewrite/locks.c
+++ b/src/backend/rewrite/locks.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.31 2000/09/06 14:15:20 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/Attic/locks.c,v 1.32 2000/09/12 21:07:02 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -56,38 +56,16 @@ thisLockWasTriggered_walker(Node *node,
 			return true;
 		return false;
 	}
-	if (IsA(node, SubLink))
+	if (IsA(node, Query))
 	{
+		/* Recurse into subselects */
+		bool		result;
 
-		/*
-		 * Standard expression_tree_walker will not recurse into
-		 * subselect, but here we must do so.
-		 */
-		SubLink    *sub = (SubLink *) node;
-
-		if (thisLockWasTriggered_walker((Node *) (sub->lefthand), context))
-			return true;
 		context->sublevels_up++;
-		if (thisLockWasTriggered_walker((Node *) (sub->subselect), context))
-		{
-			context->sublevels_up--;	/* not really necessary */
-			return true;
-		}
+		result = query_tree_walker((Query *) node, thisLockWasTriggered_walker,
+								   (void *) context);
 		context->sublevels_up--;
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Reach here after recursing down into subselect above... */
-		Query	   *qry = (Query *) node;
-
-		if (thisLockWasTriggered_walker((Node *) (qry->targetList), context))
-			return true;
-		if (thisLockWasTriggered_walker((Node *) (qry->qual), context))
-			return true;
-		if (thisLockWasTriggered_walker((Node *) (qry->havingQual), context))
-			return true;
-		return false;
+		return result;
 	}
 	return expression_tree_walker(node, thisLockWasTriggered_walker,
 								  (void *) context);
@@ -175,7 +153,7 @@ matchLocks(CmdType event,
 
 typedef struct
 {
-	Oid	evowner;
+	Oid			evowner;
 } checkLockPerms_context;
 
 static bool
@@ -184,23 +162,8 @@ checkLockPerms_walker(Node *node,
 {
 	if (node == NULL)
 		return false;
-	if (IsA(node, SubLink))
-	{
-		/*
-		 * Standard expression_tree_walker will not recurse into
-		 * subselect, but here we must do so.
-		 */
-		SubLink    *sub = (SubLink *) node;
-
-		if (checkLockPerms_walker((Node *) (sub->lefthand), context))
-			return true;
-		if (checkLockPerms_walker((Node *) (sub->subselect), context))
-			return true;
-		return false;
-	}
 	if (IsA(node, Query))
 	{
-		/* Reach here after recursing down into subselect above... */
 		Query	   *qry = (Query *) node;
 		int			rtablength = length(qry->rtable);
 		int			i;
@@ -212,13 +175,10 @@ checkLockPerms_walker(Node *node,
 			int32		reqperm;
 			int32		aclcheck_res;
 
-			if (rte->ref != NULL)
-			{
-				if (strcmp(rte->ref->relname, "*NEW*") == 0)
-					continue;
-				if (strcmp(rte->ref->relname, "*OLD*") == 0)
-					continue;
-			}
+			if (strcmp(rte->eref->relname, "*NEW*") == 0)
+				continue;
+			if (strcmp(rte->eref->relname, "*OLD*") == 0)
+				continue;
 
 			if (i == qry->resultRelation)
 				switch (qry->commandType)
@@ -250,14 +210,8 @@ checkLockPerms_walker(Node *node,
 
 		/* If there are sublinks, search for them and check their RTEs */
 		if (qry->hasSubLinks)
-		{
-			if (checkLockPerms_walker((Node *) (qry->targetList), context))
-				return true;
-			if (checkLockPerms_walker((Node *) (qry->qual), context))
-				return true;
-			if (checkLockPerms_walker((Node *) (qry->havingQual), context))
-				return true;
-		}
+			return query_tree_walker(qry, checkLockPerms_walker,
+									 (void *) context);
 		return false;
 	}
 	return expression_tree_walker(node, checkLockPerms_walker,
@@ -278,7 +232,7 @@ checkLockPerms(List *locks, Query *parsetree, int rt_index)
 		return;					/* nothing to check */
 
 	/*
-	 * Get the usename of the rule's event relation owner
+	 * Get the userid of the rule's event relation owner
 	 */
 	rte = rt_fetch(rt_index, parsetree->rtable);
 	ev_rel = heap_openr(rte->relname, AccessShareLock);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 4362687f8b8..49dfae5b905 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.79 2000/09/06 14:15:20 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.80 2000/09/12 21:07:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -51,12 +51,11 @@ static RewriteInfo *gatherRewriteMeta(Query *parsetree,
 				  Node *rule_qual,
 				  int rt_index,
 				  CmdType event,
-				  bool *instead_flag);
-static bool rangeTableEntry_used(Node *node, int rt_index, int sublevels_up);
-static bool attribute_used(Node *node, int rt_index, int attno,
-			   int sublevels_up);
-static bool modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
-						   int sublevels_up, int new_sublevels_up);
+				  bool instead_flag);
+static List *adjustJoinTree(Query *parsetree, int rt_index, bool *found);
+static bool modifyAggrefChangeVarnodes(Query *query,
+									   int rt_index, int new_index,
+									   int sublevels_up, int new_sublevels_up);
 static Node *modifyAggrefDropQual(Node *node, Node *targetNode);
 static SubLink *modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree);
 static Node *modifyAggrefQual(Node *node, Query *parsetree);
@@ -80,16 +79,15 @@ gatherRewriteMeta(Query *parsetree,
 				  Node *rule_qual,
 				  int rt_index,
 				  CmdType event,
-				  bool *instead_flag)
+				  bool instead_flag)
 {
 	RewriteInfo *info;
 	int			rt_length;
-	int			result_reln;
 
 	info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
 	info->rt_index = rt_index;
 	info->event = event;
-	info->instead_flag = *instead_flag;
+	info->instead_flag = instead_flag;
 	info->rule_action = (Query *) copyObject(rule_action);
 	info->rule_qual = (Node *) copyObject(rule_qual);
 	if (info->rule_action == NULL)
@@ -99,21 +97,54 @@ gatherRewriteMeta(Query *parsetree,
 		info->nothing = FALSE;
 		info->action = info->rule_action->commandType;
 		info->current_varno = rt_index;
-		info->rt = parsetree->rtable;
-		rt_length = length(info->rt);
-		info->rt = nconc(info->rt, copyObject(info->rule_action->rtable));
+		rt_length = length(parsetree->rtable);
 
+		/* Adjust rule action and qual to offset its varnos */
 		info->new_varno = PRS2_NEW_VARNO + rt_length;
-		OffsetVarNodes(info->rule_action->qual, rt_length, 0);
-		OffsetVarNodes((Node *) info->rule_action->targetList, rt_length, 0);
+		OffsetVarNodes((Node *) info->rule_action, rt_length, 0);
 		OffsetVarNodes(info->rule_qual, rt_length, 0);
-		ChangeVarNodes((Node *) info->rule_action->qual,
-					   PRS2_OLD_VARNO + rt_length, rt_index, 0);
-		ChangeVarNodes((Node *) info->rule_action->targetList,
+		/* but its references to *OLD* should point at original rt_index */
+		ChangeVarNodes((Node *) info->rule_action,
 					   PRS2_OLD_VARNO + rt_length, rt_index, 0);
 		ChangeVarNodes(info->rule_qual,
 					   PRS2_OLD_VARNO + rt_length, rt_index, 0);
 
+		/*
+		 * We want the main parsetree's rtable to end up as the concatenation
+		 * of its original contents plus those of all the relevant rule
+		 * actions.  Also store same into all the rule_action rtables.
+		 * Some of the entries may be unused after we finish rewriting, but
+		 * if we tried to clean those out we'd have a much harder job to
+		 * adjust RT indexes in the query's Vars.  It's OK to have unused
+		 * RT entries, since planner will ignore them.
+		 *
+		 * NOTE KLUGY HACK: we assume the parsetree rtable had at least one
+		 * entry to begin with (OK enough, else where'd the rule come from?).
+		 * Because of this, if multiple rules nconc() their rtable additions
+		 * onto parsetree->rtable, they'll all see the same rtable because
+		 * they all have the same list head pointer.
+		 */
+		parsetree->rtable = nconc(parsetree->rtable,
+								  info->rule_action->rtable);
+		info->rule_action->rtable = parsetree->rtable;
+
+		/*
+		 * Each rule action's jointree should be the main parsetree's jointree
+		 * plus that rule's jointree, but *without* the original rtindex
+		 * that we're replacing (if present, which it won't be for INSERT).
+		 * Note that if the rule refers to OLD, its jointree will add back
+		 * a reference to rt_index.
+		 *
+		 * XXX This might be wrong for subselect-in-FROM?
+		 */
+		{
+			bool	found;
+			List   *newjointree = adjustJoinTree(parsetree, rt_index, &found);
+
+			info->rule_action->jointree = nconc(newjointree,
+												info->rule_action->jointree);
+		}
+
 		/*
 		 * bug here about replace CURRENT  -- sort of replace current is
 		 * deprecated now so this code shouldn't really need to be so
@@ -121,7 +152,8 @@ gatherRewriteMeta(Query *parsetree,
 		 */
 		if (info->action != CMD_SELECT)
 		{						/* i.e update XXXXX */
-			int			new_result_reln = 0;
+			int			result_reln;
+			int			new_result_reln;
 
 			result_reln = info->rule_action->resultRelation;
 			switch (result_reln)
@@ -140,152 +172,31 @@ gatherRewriteMeta(Query *parsetree,
 	return info;
 }
 
-
-/*
- * rangeTableEntry_used -
- *	we need to process a RTE for RIR rules only if it is
- *	referenced somewhere in var nodes of the query.
- */
-
-typedef struct
-{
-	int			rt_index;
-	int			sublevels_up;
-} rangeTableEntry_used_context;
-
-static bool
-rangeTableEntry_used_walker(Node *node,
-							rangeTableEntry_used_context *context)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		if (var->varlevelsup == context->sublevels_up &&
-			var->varno == context->rt_index)
-			return true;
-		return false;
-	}
-	if (IsA(node, SubLink))
-	{
-
-		/*
-		 * Standard expression_tree_walker will not recurse into
-		 * subselect, but here we must do so.
-		 */
-		SubLink    *sub = (SubLink *) node;
-
-		if (rangeTableEntry_used_walker((Node *) (sub->lefthand), context))
-			return true;
-		if (rangeTableEntry_used((Node *) (sub->subselect),
-								 context->rt_index,
-								 context->sublevels_up + 1))
-			return true;
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Reach here after recursing down into subselect above... */
-		Query	   *qry = (Query *) node;
-
-		if (rangeTableEntry_used_walker((Node *) (qry->targetList), context))
-			return true;
-		if (rangeTableEntry_used_walker((Node *) (qry->qual), context))
-			return true;
-		if (rangeTableEntry_used_walker((Node *) (qry->havingQual), context))
-			return true;
-		return false;
-	}
-	return expression_tree_walker(node, rangeTableEntry_used_walker,
-								  (void *) context);
-}
-
-static bool
-rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
-{
-	rangeTableEntry_used_context context;
-
-	context.rt_index = rt_index;
-	context.sublevels_up = sublevels_up;
-	return rangeTableEntry_used_walker(node, &context);
-}
-
-
 /*
- * attribute_used -
- *	Check if a specific attribute number of a RTE is used
- *	somewhere in the query
+ * Copy the query's jointree list, and attempt to remove any occurrence
+ * of the given rt_index as a top-level join item (we do not look for it
+ * within JoinExprs).  Returns modified jointree list --- original list
+ * is not changed.  *found is set to indicate if we found the rt_index.
  */
-
-typedef struct
-{
-	int			rt_index;
-	int			attno;
-	int			sublevels_up;
-} attribute_used_context;
-
-static bool
-attribute_used_walker(Node *node,
-					  attribute_used_context *context)
+static List *
+adjustJoinTree(Query *parsetree, int rt_index, bool *found)
 {
-	if (node == NULL)
-		return false;
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
+	List	   *newjointree = listCopy(parsetree->jointree);
+	List	   *jjt;
 
-		if (var->varlevelsup == context->sublevels_up &&
-			var->varno == context->rt_index &&
-			var->varattno == context->attno)
-			return true;
-		return false;
-	}
-	if (IsA(node, SubLink))
+	*found = false;
+	foreach(jjt, newjointree)
 	{
+		RangeTblRef *rtr = lfirst(jjt);
 
-		/*
-		 * Standard expression_tree_walker will not recurse into
-		 * subselect, but here we must do so.
-		 */
-		SubLink    *sub = (SubLink *) node;
-
-		if (attribute_used_walker((Node *) (sub->lefthand), context))
-			return true;
-		if (attribute_used((Node *) (sub->subselect),
-						   context->rt_index,
-						   context->attno,
-						   context->sublevels_up + 1))
-			return true;
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Reach here after recursing down into subselect above... */
-		Query	   *qry = (Query *) node;
-
-		if (attribute_used_walker((Node *) (qry->targetList), context))
-			return true;
-		if (attribute_used_walker((Node *) (qry->qual), context))
-			return true;
-		if (attribute_used_walker((Node *) (qry->havingQual), context))
-			return true;
-		return false;
+		if (IsA(rtr, RangeTblRef) && rtr->rtindex == rt_index)
+		{
+			newjointree = lremove(rtr, newjointree);
+			*found = true;
+			break;
+		}
 	}
-	return expression_tree_walker(node, attribute_used_walker,
-								  (void *) context);
-}
-
-static bool
-attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
-{
-	attribute_used_context context;
-
-	context.rt_index = rt_index;
-	context.attno = attno;
-	context.sublevels_up = sublevels_up;
-	return attribute_used_walker(node, &context);
+	return newjointree;
 }
 
 
@@ -330,48 +241,26 @@ modifyAggrefChangeVarnodes_walker(Node *node,
 		}
 		return false;
 	}
-	if (IsA(node, SubLink))
-	{
-
-		/*
-		 * Standard expression_tree_walker will not recurse into
-		 * subselect, but here we must do so.
-		 */
-		SubLink    *sub = (SubLink *) node;
-
-		if (modifyAggrefChangeVarnodes_walker((Node *) (sub->lefthand),
-											  context))
-			return true;
-		if (modifyAggrefChangeVarnodes((Node *) (sub->subselect),
-									   context->rt_index,
-									   context->new_index,
-									   context->sublevels_up + 1,
-									   context->new_sublevels_up + 1))
-			return true;
-		return false;
-	}
 	if (IsA(node, Query))
 	{
-		/* Reach here after recursing down into subselect above... */
-		Query	   *qry = (Query *) node;
-
-		if (modifyAggrefChangeVarnodes_walker((Node *) (qry->targetList),
-											  context))
-			return true;
-		if (modifyAggrefChangeVarnodes_walker((Node *) (qry->qual),
-											  context))
-			return true;
-		if (modifyAggrefChangeVarnodes_walker((Node *) (qry->havingQual),
-											  context))
-			return true;
-		return false;
+		/* Recurse into subselects */
+		bool		result;
+
+		context->sublevels_up++;
+		context->new_sublevels_up++;
+		result = query_tree_walker((Query *) node,
+								   modifyAggrefChangeVarnodes_walker,
+								   (void *) context);
+		context->sublevels_up--;
+		context->new_sublevels_up--;
+		return result;
 	}
 	return expression_tree_walker(node, modifyAggrefChangeVarnodes_walker,
 								  (void *) context);
 }
 
 static bool
-modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
+modifyAggrefChangeVarnodes(Query *query, int rt_index, int new_index,
 						   int sublevels_up, int new_sublevels_up)
 {
 	modifyAggrefChangeVarnodes_context context;
@@ -380,7 +269,8 @@ modifyAggrefChangeVarnodes(Node *node, int rt_index, int new_index,
 	context.new_index = new_index;
 	context.sublevels_up = sublevels_up;
 	context.new_sublevels_up = new_sublevels_up;
-	return modifyAggrefChangeVarnodes_walker(node, &context);
+	return query_tree_walker(query, modifyAggrefChangeVarnodes_walker,
+							 (void *) &context);
 }
 
 
@@ -453,6 +343,7 @@ modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree)
 	SubLink    *sublink;
 	TargetEntry *tle;
 	Resdom	   *resdom;
+	RangeTblRef *rtr;
 
 	aggVarNos = pull_varnos(aggref->target);
 	if (length(aggVarNos) != 1)
@@ -492,6 +383,9 @@ modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree)
 	subquery->distinctClause = NIL;
 	subquery->sortClause = NIL;
 	subquery->rtable = lcons(copyObject(rte), NIL);
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+	subquery->jointree = lcons(rtr, NIL);
 	subquery->targetList = lcons(tle, NIL);
 	subquery->qual = modifyAggrefDropQual((Node *) parsetree->qual,
 										  (Node *) aggref);
@@ -517,7 +411,7 @@ modifyAggrefMakeSublink(Aggref *aggref, Query *parsetree)
 	 * Note that because of previous line, these references have
 	 * varlevelsup = 1, which must be changed to 0.
 	 */
-	modifyAggrefChangeVarnodes((Node *) subquery,
+	modifyAggrefChangeVarnodes(subquery,
 							   lfirsti(aggVarNos), 1,
 							   1, 0);
 
@@ -675,6 +569,8 @@ apply_RIR_view_mutator(Node *node,
 			   apply_RIR_view_mutator, context);
 		MUTATE(newnode->havingQual, query->havingQual, Node *,
 			   apply_RIR_view_mutator, context);
+		MUTATE(newnode->jointree, query->jointree, List *,
+			   apply_RIR_view_mutator, context);
 		return (Node *) newnode;
 	}
 	return expression_tree_mutator(node, apply_RIR_view_mutator,
@@ -703,7 +599,7 @@ ApplyRetrieveRule(Query *parsetree,
 				  int rt_index,
 				  int relation_level,
 				  Relation relation,
-				  bool relWasInJoinSet)
+				  bool relIsUsed)
 {
 	Query	   *rule_action = NULL;
 	Node	   *rule_qual;
@@ -735,17 +631,34 @@ ApplyRetrieveRule(Query *parsetree,
 	addedrtable = copyObject(rule_action->rtable);
 
 	/*
-	 * If the original rel wasn't in the join set, none of its spawn is.
-	 * If it was, then leave the spawn's flags as they are.
+	 * If the original rel wasn't in the join set (which'd be the case
+	 * for the target of an INSERT, for example), none of its spawn is.
+	 * If it was, then the spawn has to be added to the join set.
 	 */
-	if (!relWasInJoinSet)
+	if (relIsUsed)
 	{
-		foreach(l, addedrtable)
-		{
-			RangeTblEntry *rte = lfirst(l);
-
-			rte->inJoinSet = false;
-		}
+		/*
+		 * QUICK HACK: this all needs to be replaced, but for now, find
+		 * the original rel in the jointree, remove it, and add the rule
+		 * action's jointree.  This will not work for views referenced
+		 * in JoinExprs!!
+		 *
+		 * Note: it is possible that the old rel is referenced in the query
+		 * but isn't present in the jointree; this should only happen for
+		 * *OLD* and *NEW*.  We must not fail if so, but add the rule's
+		 * jointree anyway.  (This is a major crock ... should fix rule
+		 * representation ...)
+		 */
+		bool	found;
+		List   *newjointree = adjustJoinTree(parsetree, rt_index, &found);
+		List   *addedjointree = (List *) copyObject(rule_action->jointree);
+
+		if (!found)
+			elog(DEBUG, "ApplyRetrieveRule: can't find old rel %s (%d) in jointree",
+				 rt_fetch(rt_index, rtable)->eref->relname, rt_index);
+		OffsetVarNodes((Node *) addedjointree, rt_length, 0);
+		newjointree = nconc(newjointree, addedjointree);
+		parsetree->jointree = newjointree;
 	}
 
 	rtable = nconc(rtable, addedrtable);
@@ -845,6 +758,10 @@ ApplyRetrieveRule(Query *parsetree,
  * NOTE: although this has the form of a walker, we cheat and modify the
  * SubLink nodes in-place.	It is caller's responsibility to ensure that
  * no unwanted side-effects occur!
+ *
+ * This is unlike most of the other routines that recurse into subselects,
+ * because we must take control at the SubLink node in order to replace
+ * the SubLink's subselect link with the possibly-rewritten subquery.
  */
 static bool
 fireRIRonSubselect(Node *node, void *context)
@@ -854,30 +771,15 @@ fireRIRonSubselect(Node *node, void *context)
 	if (IsA(node, SubLink))
 	{
 		SubLink    *sub = (SubLink *) node;
-		Query	   *qry;
 
-		/* Process lefthand args */
-		if (fireRIRonSubselect((Node *) (sub->lefthand), context))
-			return true;
 		/* Do what we came for */
-		qry = fireRIRrules((Query *) (sub->subselect));
-		sub->subselect = (Node *) qry;
-		/* Need not recurse into subselect, because fireRIRrules did it */
-		return false;
-	}
-	if (IsA(node, Query))
-	{
-		/* Reach here when called from fireRIRrules */
-		Query	   *qry = (Query *) node;
-
-		if (fireRIRonSubselect((Node *) (qry->targetList), context))
-			return true;
-		if (fireRIRonSubselect((Node *) (qry->qual), context))
-			return true;
-		if (fireRIRonSubselect((Node *) (qry->havingQual), context))
-			return true;
-		return false;
+		sub->subselect = (Node *) fireRIRrules((Query *) (sub->subselect));
+		/* Fall through to process lefthand args of SubLink */
 	}
+	/*
+	 * Do NOT recurse into Query nodes, because fireRIRrules already
+	 * processed subselects for us.
+	 */
 	return expression_tree_walker(node, fireRIRonSubselect,
 								  (void *) context);
 }
@@ -897,7 +799,7 @@ fireRIRrules(Query *parsetree)
 	RuleLock   *rules;
 	RewriteRule *rule;
 	RewriteRule RIRonly;
-	bool		relWasInJoinSet;
+	bool		relIsUsed;
 	int			i;
 	List	   *l;
 
@@ -916,11 +818,12 @@ fireRIRrules(Query *parsetree)
 		 * If the table is not referenced in the query, then we ignore it.
 		 * This prevents infinite expansion loop due to new rtable entries
 		 * inserted by expansion of a rule. A table is referenced if it is
-		 * part of the join set (a source table), or is the result table,
-		 * or is referenced by any Var nodes.
+		 * part of the join set (a source table), or is referenced by any
+		 * Var nodes, or is the result table.
 		 */
-		if (!rte->inJoinSet && rt_index != parsetree->resultRelation &&
-			!rangeTableEntry_used((Node *) parsetree, rt_index, 0))
+		relIsUsed = rangeTableEntry_used((Node *) parsetree, rt_index, 0);
+
+		if (!relIsUsed && rt_index != parsetree->resultRelation)
 			continue;
 
 		rel = heap_openr(rte->relname, AccessShareLock);
@@ -931,9 +834,6 @@ fireRIRrules(Query *parsetree)
 			continue;
 		}
 
-		relWasInJoinSet = rte->inJoinSet;		/* save before possibly
-												 * clearing */
-
 		/*
 		 * Collect the RIR rules that we must apply
 		 */
@@ -947,22 +847,10 @@ fireRIRrules(Query *parsetree)
 			if (rule->attrno > 0)
 			{
 				/* per-attr rule; do we need it? */
-				if (!attribute_used((Node *) parsetree,
-									rt_index,
+				if (!attribute_used((Node *) parsetree, rt_index,
 									rule->attrno, 0))
 					continue;
 			}
-			else
-			{
-
-				/*
-				 * Rel-wide ON SELECT DO INSTEAD means this is a view.
-				 * Remove the view from the planner's join target set, or
-				 * we'll get no rows out because view itself is empty!
-				 */
-				if (rule->isInstead)
-					rte->inJoinSet = false;
-			}
 
 			locks = lappend(locks, rule);
 		}
@@ -989,7 +877,7 @@ fireRIRrules(Query *parsetree)
 										  rt_index,
 										  RIRonly.attrno == -1,
 										  rel,
-										  relWasInJoinSet);
+										  relIsUsed);
 		}
 
 		heap_close(rel, AccessShareLock);
@@ -999,7 +887,7 @@ fireRIRrules(Query *parsetree)
 		parsetree->qual = modifyAggrefQual(parsetree->qual, parsetree);
 
 	if (parsetree->hasSubLinks)
-		fireRIRonSubselect((Node *) parsetree, NULL);
+		query_tree_walker(parsetree, fireRIRonSubselect, NULL);
 
 	return parsetree;
 }
@@ -1056,13 +944,20 @@ CopyAndAddQual(Query *parsetree,
 	{
 		List	   *rtable;
 		int			rt_length;
+		List	   *jointree;
 
 		rtable = new_tree->rtable;
 		rt_length = length(rtable);
 		rtable = nconc(rtable, copyObject(rule_action->rtable));
+		/* XXX above possibly wrong for subselect-in-FROM */
 		new_tree->rtable = rtable;
 		OffsetVarNodes(new_qual, rt_length, 0);
 		ChangeVarNodes(new_qual, PRS2_OLD_VARNO + rt_length, rt_index, 0);
+		jointree = copyObject(rule_action->jointree);
+		OffsetVarNodes((Node *) jointree, rt_length, 0);
+		ChangeVarNodes((Node *) jointree, PRS2_OLD_VARNO + rt_length,
+					   rt_index, 0);
+		new_tree->jointree = nconc(new_tree->jointree, jointree);
 	}
 	/* XXX -- where current doesn't work for instead nothing.... yet */
 	AddNotQual(new_tree, new_qual);
@@ -1103,8 +998,7 @@ fireRules(Query *parsetree,
 	foreach(i, locks)
 	{
 		RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
-		Node	   *qual,
-				   *event_qual;
+		Node	   *event_qual;
 		List	   *actions;
 		List	   *r;
 
@@ -1227,7 +1121,7 @@ fireRules(Query *parsetree,
 			 *--------------------------------------------------
 			 */
 			info = gatherRewriteMeta(parsetree, rule_action, rule_qual,
-									 rt_index, event, instead_flag);
+									 rt_index, event, *instead_flag);
 
 			/* handle escapable cases, or those handled by other code */
 			if (info->nothing)
@@ -1247,11 +1141,9 @@ fireRules(Query *parsetree,
 			 * splitting into two queries one w/rule_qual, one w/NOT
 			 * rule_qual. Also add user query qual onto rule action
 			 */
-			qual = parsetree->qual;
-			AddQual(info->rule_action, qual);
+			AddQual(info->rule_action, parsetree->qual);
 
-			if (info->rule_qual != NULL)
-				AddQual(info->rule_action, info->rule_qual);
+			AddQual(info->rule_action, info->rule_qual);
 
 			/*--------------------------------------------------
 			 * Step 2:
@@ -1264,18 +1156,6 @@ fireRules(Query *parsetree,
 
 			/*--------------------------------------------------
 			 * Step 3:
-			 *	  rewriting due to retrieve rules
-			 *--------------------------------------------------
-			 */
-			info->rule_action->rtable = info->rt;
-
-			/*
-			 * ProcessRetrieveQuery(info->rule_action, info->rt,
-			 * &orig_instead_flag, TRUE);
-			 */
-
-			/*--------------------------------------------------
-			 * Step 4
 			 *	  Simplify? hey, no algorithm for simplification... let
 			 *	  the planner do it.
 			 *--------------------------------------------------
@@ -1403,7 +1283,7 @@ deepRewriteQuery(Query *parsetree)
 		rewritten = nconc(rewritten, qual_products);
 
 	/* ----------
-	 * The original query is appended last if not instead
+	 * The original query is appended last (if no "instead" rule)
 	 * because update and delete rule actions might not do
 	 * anything if they are invoked after the update or
 	 * delete is performed. The command counter increment
@@ -1471,17 +1351,15 @@ BasicQueryRewrite(Query *parsetree)
 		 */
 		if (query->hasAggs)
 		{
-			query->hasAggs =
-				checkExprHasAggs((Node *) (query->targetList)) ||
-				checkExprHasAggs((Node *) (query->havingQual));
-			if (checkExprHasAggs((Node *) (query->qual)))
-				elog(ERROR, "BasicQueryRewrite: failed to remove aggs from qual");
+			query->hasAggs = checkExprHasAggs((Node *) query);
+			if (query->hasAggs)
+				if (checkExprHasAggs(query->qual))
+					elog(ERROR, "BasicQueryRewrite: failed to remove aggs from qual");
 		}
 		if (query->hasSubLinks)
-			query->hasSubLinks =
-				checkExprHasSubLink((Node *) (query->targetList)) ||
-				checkExprHasSubLink((Node *) (query->qual)) ||
-				checkExprHasSubLink((Node *) (query->havingQual));
+		{
+			query->hasSubLinks = checkExprHasSubLink((Node *) query);
+		}
 		results = lappend(results, query);
 	}
 
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index a8ec560c741..e83ac054853 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.47 2000/05/30 00:49:51 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.48 2000/09/12 21:07:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,7 +42,15 @@ static bool checkExprHasSubLink_walker(Node *node, void *context);
 bool
 checkExprHasAggs(Node *node)
 {
-	return checkExprHasAggs_walker(node, NULL);
+	/*
+	 * If a Query is passed, examine it --- but we will not recurse
+	 * into sub-Queries.
+	 */
+	if (node && IsA(node, Query))
+		return query_tree_walker((Query *) node, checkExprHasAggs_walker,
+								 NULL);
+	else
+		return checkExprHasAggs_walker(node, NULL);
 }
 
 static bool
@@ -64,7 +72,15 @@ checkExprHasAggs_walker(Node *node, void *context)
 bool
 checkExprHasSubLink(Node *node)
 {
-	return checkExprHasSubLink_walker(node, NULL);
+	/*
+	 * If a Query is passed, examine it --- but we will not recurse
+	 * into sub-Queries.
+	 */
+	if (node && IsA(node, Query))
+		return query_tree_walker((Query *) node, checkExprHasSubLink_walker,
+								 NULL);
+	else
+		return checkExprHasSubLink_walker(node, NULL);
 }
 
 static bool
@@ -84,10 +100,11 @@ 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.
+ * The varnoold fields are adjusted similarly.  Also, RangeTblRef nodes
+ * in join trees are adjusted.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
- * Var nodes in-place.	The given expression tree should have been copied
+ * nodes in-place.	The given expression tree should have been copied
  * earlier to ensure that no unwanted side-effects occur!
  */
 
@@ -113,38 +130,24 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
 		}
 		return false;
 	}
-	if (IsA(node, SubLink))
+	if (IsA(node, RangeTblRef))
 	{
+		RangeTblRef	   *rtr = (RangeTblRef *) node;
 
-		/*
-		 * Standard expression_tree_walker will not recurse into
-		 * subselect, but here we must do so.
-		 */
-		SubLink    *sub = (SubLink *) node;
-
-		if (OffsetVarNodes_walker((Node *) (sub->lefthand),
-								  context))
-			return true;
-		OffsetVarNodes((Node *) (sub->subselect),
-					   context->offset,
-					   context->sublevels_up + 1);
+		if (context->sublevels_up == 0)
+			rtr->rtindex += context->offset;
 		return false;
 	}
 	if (IsA(node, Query))
 	{
-		/* Reach here after recursing down into subselect above... */
-		Query	   *qry = (Query *) node;
+		/* Recurse into subselects */
+		bool		result;
 
-		if (OffsetVarNodes_walker((Node *) (qry->targetList),
-								  context))
-			return true;
-		if (OffsetVarNodes_walker((Node *) (qry->qual),
-								  context))
-			return true;
-		if (OffsetVarNodes_walker((Node *) (qry->havingQual),
-								  context))
-			return true;
-		return false;
+		context->sublevels_up++;
+		result = query_tree_walker((Query *) node, OffsetVarNodes_walker,
+								   (void *) context);
+		context->sublevels_up--;
+		return result;
 	}
 	return expression_tree_walker(node, OffsetVarNodes_walker,
 								  (void *) context);
@@ -157,7 +160,17 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
 
 	context.offset = offset;
 	context.sublevels_up = sublevels_up;
-	OffsetVarNodes_walker(node, &context);
+
+	/*
+	 * Must be prepared to start with a Query or a bare expression tree;
+	 * if it's a Query, go straight to query_tree_walker to make sure that
+	 * sublevels_up doesn't get incremented prematurely.
+	 */
+	if (node && IsA(node, Query))
+		query_tree_walker((Query *) node, OffsetVarNodes_walker,
+						  (void *) &context);
+	else
+		OffsetVarNodes_walker(node, &context);
 }
 
 /*
@@ -165,10 +178,11 @@ 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.
+ * to 'new_index'.	The varnoold fields are changed too.  Also, RangeTblRef
+ * nodes in join trees are adjusted.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
- * Var nodes in-place.	The given expression tree should have been copied
+ * nodes in-place.	The given expression tree should have been copied
  * earlier to ensure that no unwanted side-effects occur!
  */
 
@@ -196,39 +210,25 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 		}
 		return false;
 	}
-	if (IsA(node, SubLink))
+	if (IsA(node, RangeTblRef))
 	{
+		RangeTblRef	   *rtr = (RangeTblRef *) node;
 
-		/*
-		 * Standard expression_tree_walker will not recurse into
-		 * subselect, but here we must do so.
-		 */
-		SubLink    *sub = (SubLink *) node;
-
-		if (ChangeVarNodes_walker((Node *) (sub->lefthand),
-								  context))
-			return true;
-		ChangeVarNodes((Node *) (sub->subselect),
-					   context->rt_index,
-					   context->new_index,
-					   context->sublevels_up + 1);
+		if (context->sublevels_up == 0 &&
+			rtr->rtindex == context->rt_index)
+			rtr->rtindex = context->new_index;
 		return false;
 	}
 	if (IsA(node, Query))
 	{
-		/* Reach here after recursing down into subselect above... */
-		Query	   *qry = (Query *) node;
+		/* Recurse into subselects */
+		bool		result;
 
-		if (ChangeVarNodes_walker((Node *) (qry->targetList),
-								  context))
-			return true;
-		if (ChangeVarNodes_walker((Node *) (qry->qual),
-								  context))
-			return true;
-		if (ChangeVarNodes_walker((Node *) (qry->havingQual),
-								  context))
-			return true;
-		return false;
+		context->sublevels_up++;
+		result = query_tree_walker((Query *) node, ChangeVarNodes_walker,
+								   (void *) context);
+		context->sublevels_up--;
+		return result;
 	}
 	return expression_tree_walker(node, ChangeVarNodes_walker,
 								  (void *) context);
@@ -242,7 +242,17 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
 	context.rt_index = rt_index;
 	context.new_index = new_index;
 	context.sublevels_up = sublevels_up;
-	ChangeVarNodes_walker(node, &context);
+
+	/*
+	 * Must be prepared to start with a Query or a bare expression tree;
+	 * if it's a Query, go straight to query_tree_walker to make sure that
+	 * sublevels_up doesn't get incremented prematurely.
+	 */
+	if (node && IsA(node, Query))
+		query_tree_walker((Query *) node, ChangeVarNodes_walker,
+						  (void *) &context);
+	else
+		ChangeVarNodes_walker(node, &context);
 }
 
 /*
@@ -282,54 +292,181 @@ IncrementVarSublevelsUp_walker(Node *node,
 			var->varlevelsup += context->delta_sublevels_up;
 		return false;
 	}
-	if (IsA(node, SubLink))
+	if (IsA(node, Query))
 	{
+		/* Recurse into subselects */
+		bool		result;
 
-		/*
-		 * Standard expression_tree_walker will not recurse into
-		 * subselect, but here we must do so.
-		 */
-		SubLink    *sub = (SubLink *) node;
+		context->min_sublevels_up++;
+		result = query_tree_walker((Query *) node,
+								   IncrementVarSublevelsUp_walker,
+								   (void *) context);
+		context->min_sublevels_up--;
+		return result;
+	}
+	return expression_tree_walker(node, IncrementVarSublevelsUp_walker,
+								  (void *) context);
+}
+
+void
+IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
+						int min_sublevels_up)
+{
+	IncrementVarSublevelsUp_context context;
+
+	context.delta_sublevels_up = delta_sublevels_up;
+	context.min_sublevels_up = min_sublevels_up;
+
+	/*
+	 * Must be prepared to start with a Query or a bare expression tree;
+	 * if it's a Query, go straight to query_tree_walker to make sure that
+	 * sublevels_up doesn't get incremented prematurely.
+	 */
+	if (node && IsA(node, Query))
+		query_tree_walker((Query *) node, IncrementVarSublevelsUp_walker,
+						  (void *) &context);
+	else
+		IncrementVarSublevelsUp_walker(node, &context);
+}
+
+
+/*
+ * rangeTableEntry_used - detect whether an RTE is referenced somewhere
+ *	in var nodes or jointree nodes of a query or expression.
+ */
+
+typedef struct
+{
+	int			rt_index;
+	int			sublevels_up;
+} rangeTableEntry_used_context;
+
+static bool
+rangeTableEntry_used_walker(Node *node,
+							rangeTableEntry_used_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
 
-		if (IncrementVarSublevelsUp_walker((Node *) (sub->lefthand),
-										   context))
+		if (var->varlevelsup == context->sublevels_up &&
+			var->varno == context->rt_index)
 			return true;
-		IncrementVarSublevelsUp((Node *) (sub->subselect),
-								context->delta_sublevels_up,
-								context->min_sublevels_up + 1);
 		return false;
 	}
-	if (IsA(node, Query))
+	if (IsA(node, RangeTblRef))
 	{
-		/* Reach here after recursing down into subselect above... */
-		Query	   *qry = (Query *) node;
+		RangeTblRef *rtr = (RangeTblRef *) node;
 
-		if (IncrementVarSublevelsUp_walker((Node *) (qry->targetList),
-										   context))
+		if (rtr->rtindex == context->rt_index &&
+			context->sublevels_up == 0)
 			return true;
-		if (IncrementVarSublevelsUp_walker((Node *) (qry->qual),
-										   context))
-			return true;
-		if (IncrementVarSublevelsUp_walker((Node *) (qry->havingQual),
-										   context))
+		return false;
+	}
+	if (IsA(node, Query))
+	{
+		/* Recurse into subselects */
+		bool		result;
+
+		context->sublevels_up++;
+		result = query_tree_walker((Query *) node, rangeTableEntry_used_walker,
+								   (void *) context);
+		context->sublevels_up--;
+		return result;
+	}
+	return expression_tree_walker(node, rangeTableEntry_used_walker,
+								  (void *) context);
+}
+
+bool
+rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
+{
+	rangeTableEntry_used_context context;
+
+	context.rt_index = rt_index;
+	context.sublevels_up = sublevels_up;
+
+	/*
+	 * Must be prepared to start with a Query or a bare expression tree;
+	 * if it's a Query, go straight to query_tree_walker to make sure that
+	 * sublevels_up doesn't get incremented prematurely.
+	 */
+	if (node && IsA(node, Query))
+		return query_tree_walker((Query *) node, rangeTableEntry_used_walker,
+								 (void *) &context);
+	else
+		return rangeTableEntry_used_walker(node, &context);
+}
+
+
+/*
+ * attribute_used -
+ *	Check if a specific attribute number of a RTE is used
+ *	somewhere in the query or expression.
+ */
+
+typedef struct
+{
+	int			rt_index;
+	int			attno;
+	int			sublevels_up;
+} attribute_used_context;
+
+static bool
+attribute_used_walker(Node *node,
+					  attribute_used_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+
+		if (var->varlevelsup == context->sublevels_up &&
+			var->varno == context->rt_index &&
+			var->varattno == context->attno)
 			return true;
 		return false;
 	}
-	return expression_tree_walker(node, IncrementVarSublevelsUp_walker,
+	if (IsA(node, Query))
+	{
+		/* Recurse into subselects */
+		bool		result;
+
+		context->sublevels_up++;
+		result = query_tree_walker((Query *) node, attribute_used_walker,
+								   (void *) context);
+		context->sublevels_up--;
+		return result;
+	}
+	return expression_tree_walker(node, attribute_used_walker,
 								  (void *) context);
 }
 
-void
-IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
-						int min_sublevels_up)
+bool
+attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
 {
-	IncrementVarSublevelsUp_context context;
+	attribute_used_context context;
 
-	context.delta_sublevels_up = delta_sublevels_up;
-	context.min_sublevels_up = min_sublevels_up;
-	IncrementVarSublevelsUp_walker(node, &context);
+	context.rt_index = rt_index;
+	context.attno = attno;
+	context.sublevels_up = sublevels_up;
+
+	/*
+	 * Must be prepared to start with a Query or a bare expression tree;
+	 * if it's a Query, go straight to query_tree_walker to make sure that
+	 * sublevels_up doesn't get incremented prematurely.
+	 */
+	if (node && IsA(node, Query))
+		return query_tree_walker((Query *) node, attribute_used_walker,
+								 (void *) &context);
+	else
+		return attribute_used_walker(node, &context);
 }
 
+
 /*
  * Add the given qualifier condition to the query's WHERE clause
  */
@@ -615,11 +752,6 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
 		Query	   *query = (Query *) node;
 		Query	   *newnode;
 
-		/*
-		 * XXX original code for ResolveNew only recursed into qual field
-		 * of subquery.  I'm assuming that was an oversight ... tgl 9/99
-		 */
-
 		FLATCOPY(newnode, query, Query);
 		MUTATE(newnode->targetList, query->targetList, List *,
 			   ResolveNew_mutator, context);
@@ -627,6 +759,8 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
 			   ResolveNew_mutator, context);
 		MUTATE(newnode->havingQual, query->havingQual, Node *,
 			   ResolveNew_mutator, context);
+		MUTATE(newnode->jointree, query->jointree, List *,
+			   ResolveNew_mutator, context);
 		return (Node *) newnode;
 	}
 	return expression_tree_mutator(node, ResolveNew_mutator,
@@ -650,13 +784,15 @@ void
 FixNew(RewriteInfo *info, Query *parsetree)
 {
 	info->rule_action->targetList = (List *)
-	ResolveNew((Node *) info->rule_action->targetList,
-			   info, parsetree->targetList, 0);
+		ResolveNew((Node *) info->rule_action->targetList,
+				   info, parsetree->targetList, 0);
 	info->rule_action->qual = ResolveNew(info->rule_action->qual,
 										 info, parsetree->targetList, 0);
-	/* XXX original code didn't fix havingQual; presumably an oversight? */
 	info->rule_action->havingQual = ResolveNew(info->rule_action->havingQual,
-										 info, parsetree->targetList, 0);
+											   info, parsetree->targetList, 0);
+	info->rule_action->jointree = (List *)
+		ResolveNew((Node *) info->rule_action->jointree,
+				   info, parsetree->targetList, 0);
 }
 
 /*
@@ -758,11 +894,6 @@ HandleRIRAttributeRule_mutator(Node *node,
 		Query	   *query = (Query *) node;
 		Query	   *newnode;
 
-		/*
-		 * XXX original code for HandleRIRAttributeRule only recursed into
-		 * qual field of subquery.	I'm assuming that was an oversight ...
-		 */
-
 		FLATCOPY(newnode, query, Query);
 		MUTATE(newnode->targetList, query->targetList, List *,
 			   HandleRIRAttributeRule_mutator, context);
@@ -770,6 +901,8 @@ HandleRIRAttributeRule_mutator(Node *node,
 			   HandleRIRAttributeRule_mutator, context);
 		MUTATE(newnode->havingQual, query->havingQual, Node *,
 			   HandleRIRAttributeRule_mutator, context);
+		MUTATE(newnode->jointree, query->jointree, List *,
+			   HandleRIRAttributeRule_mutator, context);
 		return (Node *) newnode;
 	}
 	return expression_tree_mutator(node, HandleRIRAttributeRule_mutator,
@@ -798,9 +931,13 @@ HandleRIRAttributeRule(Query *parsetree,
 	parsetree->targetList = (List *)
 		HandleRIRAttributeRule_mutator((Node *) parsetree->targetList,
 									   &context);
-	parsetree->qual = HandleRIRAttributeRule_mutator(parsetree->qual,
-													 &context);
-	/* XXX original code did not fix havingQual ... oversight? */
-	parsetree->havingQual = HandleRIRAttributeRule_mutator(parsetree->havingQual,
-														   &context);
+	parsetree->qual =
+		HandleRIRAttributeRule_mutator(parsetree->qual,
+									   &context);
+	parsetree->havingQual =
+		HandleRIRAttributeRule_mutator(parsetree->havingQual,
+									   &context);
+	parsetree->jointree = (List *)
+		HandleRIRAttributeRule_mutator((Node *) parsetree->jointree,
+									   &context);
 }
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 571854d446c..26ebe21c4a2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1,9 +1,9 @@
 /**********************************************************************
- * get_ruledef.c	- Function to get a rules definition text
- *			  out of its tuple
+ * ruleutils.c	- Functions to convert stored expressions/querytrees
+ *				back to source text
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.60 2000/09/12 04:15:58 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.61 2000/09/12 21:07:05 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -43,6 +43,7 @@
 #include "catalog/pg_index.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_shadow.h"
+#include "commands/view.h"
 #include "executor/spi.h"
 #include "lib/stringinfo.h"
 #include "optimizer/clauses.h"
@@ -50,8 +51,8 @@
 #include "parser/keywords.h"
 #include "parser/parse_expr.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
 #include "utils/lsyscache.h"
-#include "commands/view.h"
 
 
 /* ----------
@@ -65,12 +66,6 @@ typedef struct
 	bool		varprefix;		/* TRUE to print prefixes on Vars */
 } deparse_context;
 
-typedef struct
-{
-	Index		rt_index;
-	int			levelsup;
-} check_if_rte_used_context;
-
 
 /* ----------
  * Global data
@@ -108,13 +103,13 @@ static void get_func_expr(Expr *expr, deparse_context *context);
 static void get_tle_expr(TargetEntry *tle, deparse_context *context);
 static void get_const_expr(Const *constval, deparse_context *context);
 static void get_sublink_expr(Node *node, deparse_context *context);
+static void get_from_clause(Query *query, deparse_context *context);
+static void get_from_clause_item(Node *jtnode, Query *query,
+								 deparse_context *context);
 static bool tleIsArrayAssign(TargetEntry *tle);
 static char *quote_identifier(char *ident);
 static char *get_relation_name(Oid relid);
 static char *get_attribute_name(Oid relid, int2 attnum);
-static bool check_if_rte_used(Node *node, Index rt_index, int levelsup);
-static bool check_if_rte_used_walker(Node *node,
-						 check_if_rte_used_context *context);
 
 #define only_marker(rte)  ((rte)->inh ? "" : "ONLY ")
 
@@ -230,13 +225,13 @@ pg_get_viewdef(PG_FUNCTION_ARGS)
 	Name		vname = PG_GETARG_NAME(0);
 	text	   *ruledef;
 	Datum		args[1];
-	char		nulls[2];
+	char		nulls[1];
 	int			spirc;
 	HeapTuple	ruletup;
 	TupleDesc	rulettc;
 	StringInfoData buf;
 	int			len;
-	char		*name;
+	char	   *name;
 
 	/* ----------
 	 * We need the view name somewhere deep down
@@ -276,7 +271,6 @@ pg_get_viewdef(PG_FUNCTION_ARGS)
 	name = MakeRetrieveViewRuleName(rulename);
 	args[0] = PointerGetDatum(name);
 	nulls[0] = ' ';
-	nulls[1] = '\0';
 	spirc = SPI_execp(plan_getview, args, nulls, 1);
 	if (spirc != SPI_OK_SELECT)
 		elog(ERROR, "failed to get pg_rewrite tuple for view %s", rulename);
@@ -883,60 +877,8 @@ get_select_query_def(Query *query, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
 	char	   *sep;
-	TargetEntry *tle;
-	RangeTblEntry *rte;
-	bool	   *rt_used;
-	int			rt_length;
-	int			rt_numused = 0;
-	bool		rt_constonly = TRUE;
-	int			i;
 	List	   *l;
 
-	/* ----------
-	 * First we need to know which and how many of the
-	 * range table entries in the query are used in the target list
-	 * or queries qualification
-	 * ----------
-	 */
-	rt_length = length(query->rtable);
-	rt_used = palloc(sizeof(bool) * rt_length);
-	for (i = 0; i < rt_length; i++)
-	{
-		if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
-			check_if_rte_used(query->qual, i + 1, 0) ||
-			check_if_rte_used(query->havingQual, i + 1, 0))
-		{
-			rt_used[i] = TRUE;
-			rt_numused++;
-		}
-		else
-			rt_used[i] = FALSE;
-	}
-
-	/* ----------
-	 * Now check if any of the used rangetable entries is different
-	 * from *NEW* and *OLD*. If so we must provide the FROM clause
-	 * later.
-	 * ----------
-	 */
-	i = 0;
-	foreach(l, query->rtable)
-	{
-		if (!rt_used[i++])
-			continue;
-
-		rte = (RangeTblEntry *) lfirst(l);
-		if (rte->ref == NULL)
-			continue;
-		if (strcmp(rte->ref->relname, "*NEW*") == 0)
-			continue;
-		if (strcmp(rte->ref->relname, "*OLD*") == 0)
-			continue;
-
-		rt_constonly = FALSE;
-		break;
-	}
-
 	/* ----------
 	 * Build up the query string - first we say SELECT
 	 * ----------
@@ -947,9 +889,9 @@ get_select_query_def(Query *query, deparse_context *context)
 	sep = " ";
 	foreach(l, query->targetList)
 	{
+		TargetEntry *tle = (TargetEntry *) lfirst(l);
 		bool		tell_as = false;
 
-		tle = (TargetEntry *) lfirst(l);
 		appendStringInfo(buf, sep);
 		sep = ", ";
 
@@ -962,6 +904,7 @@ get_select_query_def(Query *query, deparse_context *context)
 		else
 		{
 			Var		   *var = (Var *) (tle->expr);
+			RangeTblEntry *rte;
 			char	   *attname;
 
 			rte = get_rte_for_var(var, context);
@@ -975,60 +918,8 @@ get_select_query_def(Query *query, deparse_context *context)
 							 quote_identifier(tle->resdom->resname));
 	}
 
-	/* If we need other tables than *NEW* or *OLD* add the FROM clause */
-	if (!rt_constonly && rt_numused > 0)
-	{
-		sep = " FROM ";
-		i = 0;
-		foreach(l, query->rtable)
-		{
-			if (rt_used[i++])
-			{
-				rte = (RangeTblEntry *) lfirst(l);
-
-				if (rte->ref == NULL)
-					continue;
-				if (strcmp(rte->ref->relname, "*NEW*") == 0)
-					continue;
-				if (strcmp(rte->ref->relname, "*OLD*") == 0)
-					continue;
-
-				appendStringInfo(buf, sep);
-				sep = ", ";
-				appendStringInfo(buf, "%s%s",
-								 only_marker(rte),
-								 quote_identifier(rte->relname));
-
-				/*
-				 * NOTE: SQL92 says you can't write column aliases unless
-				 * you write a table alias --- so, if there's an alias
-				 * list, make sure we emit a table alias even if it's the
-				 * same as the table's real name.
-				 */
-				if ((rte->ref != NULL)
-					&& ((strcmp(rte->relname, rte->ref->relname) != 0)
-						|| (rte->ref->attrs != NIL)))
-				{
-					appendStringInfo(buf, " %s",
-									 quote_identifier(rte->ref->relname));
-					if (rte->ref->attrs != NIL)
-					{
-						List	   *col;
-
-						appendStringInfo(buf, " (");
-						foreach(col, rte->ref->attrs)
-						{
-							if (col != rte->ref->attrs)
-								appendStringInfo(buf, ", ");
-							appendStringInfo(buf, "%s",
-								  quote_identifier(strVal(lfirst(col))));
-						}
-						appendStringInfoChar(buf, ')');
-					}
-				}
-			}
-		}
-	}
+	/* Add the FROM clause if needed */
+	get_from_clause(query, context);
 
 	/* Add the WHERE clause if given */
 	if (query->qual != NULL)
@@ -1066,52 +957,32 @@ get_insert_query_def(Query *query, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
 	char	   *sep;
-	TargetEntry *tle;
-	RangeTblEntry *rte;
-	bool	   *rt_used;
-	int			rt_length;
-	int			rt_numused = 0;
 	bool		rt_constonly = TRUE;
+	RangeTblEntry *rte;
 	int			i;
 	List	   *l;
 
 	/* ----------
 	 * We need to know if other tables than *NEW* or *OLD*
 	 * are used in the query. If not, it's an INSERT ... VALUES,
-	 * otherwise an INSERT ... SELECT.
+	 * otherwise an INSERT ... SELECT.  (Pretty klugy ... fix this
+	 * when we redesign querytrees!)
 	 * ----------
 	 */
-	rt_length = length(query->rtable);
-	rt_used = palloc(sizeof(bool) * rt_length);
-	for (i = 0; i < rt_length; i++)
-	{
-		if (check_if_rte_used((Node *) (query->targetList), i + 1, 0) ||
-			check_if_rte_used(query->qual, i + 1, 0) ||
-			check_if_rte_used(query->havingQual, i + 1, 0))
-		{
-			rt_used[i] = TRUE;
-			rt_numused++;
-		}
-		else
-			rt_used[i] = FALSE;
-	}
-
 	i = 0;
 	foreach(l, query->rtable)
 	{
-		if (!rt_used[i++])
-			continue;
-
 		rte = (RangeTblEntry *) lfirst(l);
-		if (rte->ref == NULL)
-			continue;
-		if (strcmp(rte->ref->relname, "*NEW*") == 0)
+		i++;
+		if (strcmp(rte->eref->relname, "*NEW*") == 0)
 			continue;
-		if (strcmp(rte->ref->relname, "*OLD*") == 0)
+		if (strcmp(rte->eref->relname, "*OLD*") == 0)
 			continue;
-
-		rt_constonly = FALSE;
-		break;
+		if (rangeTableEntry_used((Node *) query, i, 0))
+		{
+			rt_constonly = FALSE;
+			break;
+		}
 	}
 
 	/* ----------
@@ -1122,11 +993,11 @@ get_insert_query_def(Query *query, deparse_context *context)
 	appendStringInfo(buf, "INSERT INTO %s",
 					 quote_identifier(rte->relname));
 
-	/* Add the target list */
+	/* Add the insert-column-names list */
 	sep = " (";
 	foreach(l, query->targetList)
 	{
-		tle = (TargetEntry *) lfirst(l);
+		TargetEntry *tle = (TargetEntry *) lfirst(l);
 
 		appendStringInfo(buf, sep);
 		sep = ", ";
@@ -1141,7 +1012,7 @@ get_insert_query_def(Query *query, deparse_context *context)
 		sep = "";
 		foreach(l, query->targetList)
 		{
-			tle = (TargetEntry *) lfirst(l);
+			TargetEntry *tle = (TargetEntry *) lfirst(l);
 
 			appendStringInfo(buf, sep);
 			sep = ", ";
@@ -1195,6 +1066,9 @@ get_update_query_def(Query *query, deparse_context *context)
 		get_tle_expr(tle, context);
 	}
 
+	/* Add the FROM clause if needed */
+	get_from_clause(query, context);
+
 	/* Finally add a WHERE clause if given */
 	if (query->qual != NULL)
 	{
@@ -1281,16 +1155,13 @@ get_rule_expr(Node *node, deparse_context *context)
 
 				if (context->varprefix)
 				{
-					if (rte->ref == NULL)
-						appendStringInfo(buf, "%s.",
-										 quote_identifier(rte->relname));
-					else if (strcmp(rte->ref->relname, "*NEW*") == 0)
+					if (strcmp(rte->eref->relname, "*NEW*") == 0)
 						appendStringInfo(buf, "new.");
-					else if (strcmp(rte->ref->relname, "*OLD*") == 0)
+					else if (strcmp(rte->eref->relname, "*OLD*") == 0)
 						appendStringInfo(buf, "old.");
 					else
 						appendStringInfo(buf, "%s.",
-									quote_identifier(rte->ref->relname));
+									quote_identifier(rte->eref->relname));
 				}
 				appendStringInfo(buf, "%s",
 						  quote_identifier(get_attribute_name(rte->relid,
@@ -1860,6 +1731,165 @@ get_sublink_expr(Node *node, deparse_context *context)
 		appendStringInfoChar(buf, ')');
 }
 
+
+/* ----------
+ * get_from_clause			- Parse back a FROM clause
+ * ----------
+ */
+static void
+get_from_clause(Query *query, deparse_context *context)
+{
+	StringInfo	buf = context->buf;
+	char	   *sep;
+	List	   *l;
+
+	/*
+	 * We use the query's jointree as a guide to what to print.  However,
+	 * we must ignore auto-added RTEs that are marked not inFromCl.
+	 * Also ignore the rule pseudo-RTEs for NEW and OLD.
+	 */
+	sep = " FROM ";
+
+	foreach(l, query->jointree)
+	{
+		Node   *jtnode = (Node *) lfirst(l);
+
+		if (IsA(jtnode, RangeTblRef))
+		{
+			int			varno = ((RangeTblRef *) jtnode)->rtindex;
+			RangeTblEntry *rte = rt_fetch(varno, query->rtable);
+
+			if (!rte->inFromCl)
+				continue;
+			if (strcmp(rte->eref->relname, "*NEW*") == 0)
+				continue;
+			if (strcmp(rte->eref->relname, "*OLD*") == 0)
+				continue;
+		}
+
+		appendStringInfo(buf, sep);
+		get_from_clause_item(jtnode, query, context);
+		sep = ", ";
+	}
+}
+
+static void
+get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
+{
+	StringInfo	buf = context->buf;
+
+	if (IsA(jtnode, RangeTblRef))
+	{
+		int			varno = ((RangeTblRef *) jtnode)->rtindex;
+		RangeTblEntry *rte = rt_fetch(varno, query->rtable);
+
+		appendStringInfo(buf, "%s%s",
+						 only_marker(rte),
+						 quote_identifier(rte->relname));
+		if (rte->alias != NULL)
+		{
+			appendStringInfo(buf, " %s",
+							 quote_identifier(rte->alias->relname));
+			if (rte->alias->attrs != NIL)
+			{
+				List	   *col;
+
+				appendStringInfo(buf, " (");
+				foreach(col, rte->alias->attrs)
+				{
+					if (col != rte->alias->attrs)
+						appendStringInfo(buf, ", ");
+					appendStringInfo(buf, "%s",
+									 quote_identifier(strVal(lfirst(col))));
+				}
+				appendStringInfoChar(buf, ')');
+			}
+		}
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		appendStringInfoChar(buf, '(');
+		get_from_clause_item(j->larg, query, context);
+		if (j->isNatural)
+			appendStringInfo(buf, " NATURAL");
+		switch (j->jointype)
+		{
+			case JOIN_INNER:
+				if (j->quals)
+					appendStringInfo(buf, " JOIN ");
+				else
+					appendStringInfo(buf, " CROSS JOIN ");
+				break;
+			case JOIN_LEFT:
+				appendStringInfo(buf, " LEFT JOIN ");
+				break;
+			case JOIN_FULL:
+				appendStringInfo(buf, " FULL JOIN ");
+				break;
+			case JOIN_RIGHT:
+				appendStringInfo(buf, " RIGHT JOIN ");
+				break;
+			case JOIN_UNION:
+				appendStringInfo(buf, " UNION JOIN ");
+				break;
+			default:
+				elog(ERROR, "get_from_clause_item: unknown join type %d",
+					 (int) j->jointype);
+		}
+		get_from_clause_item(j->rarg, query, context);
+		if (! j->isNatural)
+		{
+			if (j->using)
+			{
+				List	   *col;
+
+				appendStringInfo(buf, " USING (");
+				foreach(col, j->using)
+				{
+					if (col != j->using)
+						appendStringInfo(buf, ", ");
+					appendStringInfo(buf, "%s",
+									 quote_identifier(strVal(lfirst(col))));
+				}
+				appendStringInfoChar(buf, ')');
+			}
+			else if (j->quals)
+			{
+				appendStringInfo(buf, " ON (");
+				get_rule_expr(j->quals, context);
+				appendStringInfoChar(buf, ')');
+			}
+		}
+		appendStringInfoChar(buf, ')');
+		/* Yes, it's correct to put alias after the right paren ... */
+		if (j->alias != NULL)
+		{
+			appendStringInfo(buf, " %s",
+							 quote_identifier(j->alias->relname));
+			if (j->alias->attrs != NIL)
+			{
+				List	   *col;
+
+				appendStringInfo(buf, " (");
+				foreach(col, j->alias->attrs)
+				{
+					if (col != j->alias->attrs)
+						appendStringInfo(buf, ", ");
+					appendStringInfo(buf, "%s",
+									 quote_identifier(strVal(lfirst(col))));
+				}
+				appendStringInfoChar(buf, ')');
+			}
+		}
+	}
+	else
+		elog(ERROR, "get_from_clause_item: unexpected node type %d",
+			 nodeTag(jtnode));
+}
+
+
 /* ----------
  * tleIsArrayAssign			- check for array assignment
  * ----------
@@ -1990,56 +2020,3 @@ get_attribute_name(Oid relid, int2 attnum)
 	attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
 	return pstrdup(NameStr(attStruct->attname));
 }
-
-
-/* ----------
- * check_if_rte_used
- *		Check a targetlist or qual to see if a given rangetable entry
- *		is used in it
- * ----------
- */
-static bool
-check_if_rte_used(Node *node, Index rt_index, int levelsup)
-{
-	check_if_rte_used_context context;
-
-	context.rt_index = rt_index;
-	context.levelsup = levelsup;
-	return check_if_rte_used_walker(node, &context);
-}
-
-static bool
-check_if_rte_used_walker(Node *node,
-						 check_if_rte_used_context *context)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		return var->varno == context->rt_index &&
-			var->varlevelsup == context->levelsup;
-	}
-	if (IsA(node, SubLink))
-	{
-		SubLink    *sublink = (SubLink *) node;
-		Query	   *query = (Query *) sublink->subselect;
-
-		/* Recurse into subquery; expression_tree_walker will not */
-		if (check_if_rte_used((Node *) (query->targetList),
-							  context->rt_index, context->levelsup + 1) ||
-			check_if_rte_used(query->qual,
-							  context->rt_index, context->levelsup + 1) ||
-			check_if_rte_used(query->havingQual,
-							  context->rt_index, context->levelsup + 1))
-			return true;
-
-		/*
-		 * fall through to let expression_tree_walker examine lefthand
-		 * args
-		 */
-	}
-	return expression_tree_walker(node, check_if_rte_used_walker,
-								  (void *) context);
-}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index b73ea0e6136..eb067fe5057 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.44 2000/09/12 04:49:15 momjian Exp $
+ * $Id: catversion.h,v 1.45 2000/09/12 21:07:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200009111
+#define CATALOG_VERSION_NO	200009121
 
 #endif
diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h
index 7eb6667da03..c4ba3d1f5b6 100644
--- a/src/include/executor/execdebug.h
+++ b/src/include/executor/execdebug.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execdebug.h,v 1.13 2000/06/15 00:52:07 momjian Exp $
+ * $Id: execdebug.h,v 1.14 2000/09/12 21:07:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -128,16 +128,6 @@
 #undef EXEC_MERGEJOINDEBUG
  */
 
-/* ----------------
- *		EXEC_MERGEJOINPFREE is a flag which causes merge joins
- *		to pfree intermittant tuples (which is the proper thing)
- *		Not defining this means we avoid menory management problems
- *		at the cost of doing deallocation of stuff only at the
- *		end of the transaction
- * ----------------
-#undef EXEC_MERGEJOINPFREE
- */
-
 /* ----------------
  *		EXEC_DEBUGINTERACTIVE is a flag which enables the
  *		user to issue "DEBUG" commands from an interactive
@@ -170,11 +160,10 @@
  *			  only as necessary -cim 10/26/89
  * ----------------------------------------------------------------
  */
-#define T_OR_F(b)				(b ? "true" : "false")
+#define T_OR_F(b)				((b) ? "true" : "false")
 #define NULL_OR_TUPLE(slot)		(TupIsNull(slot) ? "null" : "a tuple")
 
 
-/* #define EXEC_TUPLECOUNT - XXX take out for now for executor stubbing -- jolly*/
 /* ----------------
  *		tuple count debugging defines
  * ----------------
@@ -326,28 +315,31 @@ extern int	NIndexTupleInserted;
 #define MJ1_printf(s, p)				printf(s, p)
 #define MJ2_printf(s, p1, p2)			printf(s, p1, p2)
 #define MJ_debugtup(tuple, type)		debugtup(tuple, type, NULL)
-#define MJ_dump(context, state)			ExecMergeTupleDump(econtext, state)
+#define MJ_dump(state)					ExecMergeTupleDump(state)
 #define MJ_DEBUG_QUAL(clause, res) \
   MJ2_printf("  ExecQual(%s, econtext) returns %s\n", \
 			 CppAsString(clause), T_OR_F(res));
 
 #define MJ_DEBUG_MERGE_COMPARE(qual, res) \
-  MJ2_printf("  MergeCompare(mergeclauses, %s, ..) returns %s\n", \
+  MJ2_printf("  MergeCompare(mergeclauses, %s, ...) returns %s\n", \
 			 CppAsString(qual), T_OR_F(res));
 
 #define MJ_DEBUG_PROC_NODE(slot) \
-  MJ2_printf("  %s = ExecProcNode(innerPlan) returns %s\n", \
+  MJ2_printf("  %s = ExecProcNode(...) returns %s\n", \
 			 CppAsString(slot), NULL_OR_TUPLE(slot));
+
 #else
+
 #define MJ_nodeDisplay(l)
 #define MJ_printf(s)
 #define MJ1_printf(s, p)
 #define MJ2_printf(s, p1, p2)
 #define MJ_debugtup(tuple, type)
-#define MJ_dump(context, state)
+#define MJ_dump(state)
 #define MJ_DEBUG_QUAL(clause, res)
 #define MJ_DEBUG_MERGE_COMPARE(qual, res)
 #define MJ_DEBUG_PROC_NODE(slot)
+
 #endif	 /* EXEC_MERGEJOINDEBUG */
 
 /* ----------------------------------------------------------------
diff --git a/src/include/executor/execdefs.h b/src/include/executor/execdefs.h
index 89fed192cdd..6b9457969b1 100644
--- a/src/include/executor/execdefs.h
+++ b/src/include/executor/execdefs.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execdefs.h,v 1.6 2000/01/26 05:58:05 momjian Exp $
+ * $Id: execdefs.h,v 1.7 2000/09/12 21:07:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,9 +42,13 @@
 #define EXEC_MJ_NEXTOUTER				5
 #define EXEC_MJ_TESTOUTER				6
 #define EXEC_MJ_NEXTINNER				7
-#define EXEC_MJ_SKIPINNER				8
-#define EXEC_MJ_SKIPOUTER				9
-#define EXEC_MJ_FILLINNER			   10
-#define EXEC_MJ_FILLOUTER			   11
+#define EXEC_MJ_SKIPOUTER_BEGIN			8
+#define EXEC_MJ_SKIPOUTER_TEST			9
+#define EXEC_MJ_SKIPOUTER_ADVANCE		10
+#define EXEC_MJ_SKIPINNER_BEGIN			11
+#define EXEC_MJ_SKIPINNER_TEST			12
+#define EXEC_MJ_SKIPINNER_ADVANCE		13
+#define EXEC_MJ_ENDOUTER				14
+#define EXEC_MJ_ENDINNER				15
 
 #endif	 /* EXECDEFS_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 5eb7cbb93ba..5c330915e75 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: executor.h,v 1.50 2000/08/24 23:34:09 tgl Exp $
+ * $Id: executor.h,v 1.51 2000/09/12 21:07:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -117,7 +117,9 @@ extern void ExecSetSlotDescriptorIsNew(TupleTableSlot *slot, bool isNew);
 extern void ExecInitResultTupleSlot(EState *estate, CommonState *commonstate);
 extern void ExecInitScanTupleSlot(EState *estate,
 					  CommonScanState *commonscanstate);
-extern void ExecInitOuterTupleSlot(EState *estate, HashJoinState *hashstate);
+extern TupleTableSlot *ExecInitExtraTupleSlot(EState *estate);
+extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
+											 TupleDesc tupType);
 
 extern TupleDesc ExecGetTupType(Plan *node);
 extern TupleDesc ExecTypeFromTL(List *targetList);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9626dbf8b1c..83ed6c5234b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.48 2000/08/24 03:29:13 tgl Exp $
+ * $Id: execnodes.h,v 1.49 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -469,11 +469,18 @@ typedef CommonState JoinState;
 
 /* ----------------
  *	 NestLoopState information
+ *
+ *		NeedNewOuter       true if need new outer tuple on next call
+ *		MatchedOuter       true if found a join match for current outer tuple
+ *		NullInnerTupleSlot prepared null tuple for left outer joins
  * ----------------
  */
 typedef struct NestLoopState
 {
 	JoinState	jstate;			/* its first field is NodeTag */
+	bool		nl_NeedNewOuter;
+	bool		nl_MatchedOuter;
+	TupleTableSlot *nl_NullInnerTupleSlot;
 } NestLoopState;
 
 /* ----------------
@@ -482,7 +489,13 @@ typedef struct NestLoopState
  *		OuterSkipQual	   outerKey1 < innerKey1 ...
  *		InnerSkipQual	   outerKey1 > innerKey1 ...
  *		JoinState		   current "state" of join. see executor.h
+ *		MatchedOuter       true if found a join match for current outer tuple
+ *		MatchedInner       true if found a join match for current inner tuple
+ *		OuterTupleSlot     pointer to slot in tuple table for cur outer tuple
+ *		InnerTupleSlot     pointer to slot in tuple table for cur inner tuple
  *		MarkedTupleSlot    pointer to slot in tuple table for marked tuple
+ *		NullOuterTupleSlot prepared null tuple for right outer joins
+ *		NullInnerTupleSlot prepared null tuple for left outer joins
  * ----------------
  */
 typedef struct MergeJoinState
@@ -491,7 +504,13 @@ typedef struct MergeJoinState
 	List	   *mj_OuterSkipQual;
 	List	   *mj_InnerSkipQual;
 	int			mj_JoinState;
+	bool		mj_MatchedOuter;
+	bool		mj_MatchedInner;
+	TupleTableSlot *mj_OuterTupleSlot;
+	TupleTableSlot *mj_InnerTupleSlot;
 	TupleTableSlot *mj_MarkedTupleSlot;
+	TupleTableSlot *mj_NullOuterTupleSlot;
+	TupleTableSlot *mj_NullInnerTupleSlot;
 } MergeJoinState;
 
 /* ----------------
@@ -506,6 +525,10 @@ typedef struct MergeJoinState
  *		hj_InnerHashKey			the inner hash key in the hashjoin condition
  *		hj_OuterTupleSlot		tuple slot for outer tuples
  *		hj_HashTupleSlot		tuple slot for hashed tuples
+ *		hj_NullInnerTupleSlot   prepared null tuple for left outer joins
+ *		hj_NeedNewOuter         true if need new outer tuple on next call
+ *		hj_MatchedOuter         true if found a join match for current outer
+ *		hj_hashdone				true if hash-table-build phase is done
  * ----------------
  */
 typedef struct HashJoinState
@@ -517,6 +540,10 @@ typedef struct HashJoinState
 	Node	   *hj_InnerHashKey;
 	TupleTableSlot *hj_OuterTupleSlot;
 	TupleTableSlot *hj_HashTupleSlot;
+	TupleTableSlot *hj_NullInnerTupleSlot;
+	bool		hj_NeedNewOuter;
+	bool		hj_MatchedOuter;
+	bool		hj_hashdone;
 } HashJoinState;
 
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d825c8fe395..f3929d8b2c6 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodes.h,v 1.75 2000/08/24 03:29:13 tgl Exp $
+ * $Id: nodes.h,v 1.76 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,6 +68,8 @@ typedef enum NodeTag
 	T_ArrayRef,
 	T_Iter,
 	T_RelabelType,
+	T_RangeTblRef,
+	T_JoinExpr,
 
 	/*---------------------
 	 * TAGS FOR PLANNER NODES (relation.h)
@@ -204,7 +206,7 @@ typedef enum NodeTag
 	T_A_Indices,
 	T_ResTarget,
 	T_TypeCast,
-	T_RelExpr,
+	T_RangeSubselect,
 	T_SortGroupBy,
 	T_RangeVar,
 	T_TypeName,
@@ -217,14 +219,14 @@ typedef enum NodeTag
 	T_SortClause,
 	T_GroupClause,
 	T_SubSelectXXX,				/* not used anymore; this tag# is available */
-	T_JoinExpr,
+	T_oldJoinExprXXX,			/* not used anymore; this tag# is available */
 	T_CaseExpr,
 	T_CaseWhen,
 	T_RowMark,
 	T_FkConstraint,
 
 	/*---------------------
-	 * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (cf. fmgr.h)
+	 * TAGS FOR FUNCTION-CALL CONTEXT AND RESULTINFO NODES (see fmgr.h)
 	 *---------------------
 	 */
 	T_TriggerData = 800,		/* in commands/trigger.h */
@@ -310,7 +312,7 @@ typedef double Cost;			/* execution cost (in page-access units) */
 
 /*
  * CmdType -
- *	  enums for type of operation to aid debugging
+ *	  enums for type of operation represented by a Query
  *
  * ??? could have put this in parsenodes.h but many files not in the
  *	  optimizer also need this...
@@ -329,4 +331,40 @@ typedef enum CmdType
 } CmdType;
 
 
+/*
+ * JoinType -
+ *	  enums for types of relation joins
+ *
+ * JoinType determines the exact semantics of joining two relations using
+ * a matching qualification.  For example, it tells what to do with a tuple
+ * that has no match in the other relation.
+ *
+ * This is needed in both parsenodes.h and plannodes.h, so put it here...
+ */
+typedef enum JoinType
+{
+	/*
+	 * The canonical kinds of joins
+	 */
+	JOIN_INNER,					/* matching tuple pairs only */
+	JOIN_LEFT,					/* pairs + unmatched outer tuples */
+	JOIN_FULL,					/* pairs + unmatched outer + unmatched inner */
+	JOIN_RIGHT,					/* pairs + unmatched inner tuples */
+	/*
+	 * SQL92 considers UNION JOIN to be a kind of join, so list it here for
+	 * parser convenience, even though it's not implemented like a join in
+	 * the executor.  (The planner must convert it to an Append plan.)
+	 */
+	JOIN_UNION
+	/*
+	 * Eventually we will have some additional join types for efficient
+	 * support of queries like WHERE foo IN (SELECT bar FROM ...).
+	 */
+} JoinType;
+
+#define IS_OUTER_JOIN(jointype) \
+	((jointype) == JOIN_LEFT || \
+	 (jointype) == JOIN_FULL || \
+	 (jointype) == JOIN_RIGHT)
+
 #endif	 /* NODES_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f6c75c19781..440a7609d83 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.112 2000/09/12 05:09:50 momjian Exp $
+ * $Id: parsenodes.h,v 1.113 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,7 +40,7 @@ typedef struct Query
 	Node	   *utilityStmt;	/* non-null if this is a non-optimizable
 								 * statement */
 
-	int			resultRelation; /* target relation (index to rtable) */
+	int			resultRelation; /* target relation (index into rtable) */
 	char	   *into;			/* portal (cursor) name */
 	bool		isPortal;		/* is this a retrieve into portal? */
 	bool		isBinary;		/* binary portal? */
@@ -50,6 +50,8 @@ typedef struct Query
 	bool		hasSubLinks;	/* has subquery SubLink */
 
 	List	   *rtable;			/* list of range table entries */
+	List	   *jointree;		/* table join tree (from the FROM clause) */
+
 	List	   *targetList;		/* target list (of TargetEntry) */
 	Node	   *qual;			/* qualifications applied to tuples */
 	List	   *rowMark;		/* list of RowMark entries */
@@ -1057,16 +1059,6 @@ typedef struct ResTarget
 								 * assign */
 } ResTarget;
 
-/*
- * RelExpr - relation expressions
- */
-typedef struct RelExpr
-{
-	NodeTag		type;
-	char	   *relname;		/* the relation name */
-	bool		inh;			/* inheritance query */
-} RelExpr;
-
 /*
  * SortGroupBy - for ORDER BY clause
  */
@@ -1083,10 +1075,21 @@ typedef struct SortGroupBy
 typedef struct RangeVar
 {
 	NodeTag		type;
-	RelExpr    *relExpr;		/* the relation expression */
-	Attr	   *name;			/* the name to be referenced (optional) */
+	char	   *relname;		/* the relation name */
+	bool		inh;			/* expand rel by inheritance? */
+	Attr	   *name;			/* optional table alias & column aliases */
 } RangeVar;
 
+/*
+ * RangeSubselect - subquery appearing in a FROM clause
+ */
+typedef struct RangeSubselect
+{
+	NodeTag		type;
+	Node	   *subquery;		/* the untransformed sub-select clause */
+	Attr	   *name;			/* optional table alias & column aliases */
+} RangeSubselect;
+
 /*
  * IndexElem - index parameters (used in CREATE INDEX)
  *
@@ -1114,20 +1117,6 @@ typedef struct DefElem
 	Node	   *arg;			/* a (Value *) or a (TypeName *) */
 } DefElem;
 
-/*
- * JoinExpr - for JOIN expressions
- */
-typedef struct JoinExpr
-{
-	NodeTag		type;
-	int			jointype;
-	bool		isNatural;		/* Natural join? Will need to shape table */
-	Node	   *larg;			/* RangeVar or join expression */
-	Node	   *rarg;			/* RangeVar or join expression */
-	Attr	   *alias;			/* table and column aliases, if any */
-	List	   *quals;			/* qualifiers on join, if any */
-} JoinExpr;
-
 
 /****************************************************************************
  *	Nodes for a Query tree
@@ -1155,11 +1144,12 @@ typedef struct TargetEntry
  *	  Some of the following are only used in one of
  *	  the parsing, optimizing, execution stages.
  *
- *	  eref is the expanded table name and columns for the underlying
- *	  relation. Note that for outer join syntax, allowed reference names
- *	  could be modified as one evaluates the nested clauses (e.g.
- *	  "SELECT ... FROM t1 NATURAL JOIN t2 WHERE ..." forbids explicit mention
- *	  of a table name in any reference to the join column.
+ *	  alias is an Attr node representing the AS alias-clause attached to the
+ *	  FROM expression, or NULL if no clause.
+ *
+ *	  eref is the table reference name and column reference names (either
+ *	  real or aliases).  This is filled in during parse analysis.  Note that
+ *	  system columns (OID etc) are not included in the column list.
  *
  *	  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
@@ -1170,29 +1160,17 @@ typedef struct TargetEntry
  *	  implicitly-added RTE shouldn't change the namespace for unqualified
  *	  column names processed later, and it also shouldn't affect the
  *	  expansion of '*'.
- *
- *	  inJoinSet marks those range variables that the planner should join
- *	  over even if they aren't explicitly referred to in the query.  For
- *	  example, "SELECT COUNT(1) FROM tx" should produce the number of rows
- *	  in tx.  A more subtle example uses a POSTQUEL implicit RTE:
- *			SELECT COUNT(1) FROM tx WHERE TRUE OR (tx.f1 = ty.f2)
- *	  Here we should get the product of the sizes of tx and ty.  However,
- *	  the query optimizer can simplify the WHERE clause to "TRUE", so
- *	  ty will no longer be referred to explicitly; without a flag forcing
- *	  it to be included in the join, we will get the wrong answer.	So,
- *	  a POSTQUEL implicit RTE must be marked inJoinSet but not inFromCl.
  *--------------------
  */
 typedef struct RangeTblEntry
 {
 	NodeTag		type;
 	char	   *relname;		/* real name of the relation */
-	Attr	   *ref;			/* reference names (given in FROM clause) */
-	Attr	   *eref;			/* expanded reference names */
 	Oid			relid;			/* OID of the relation */
+	Attr	   *alias;			/* user-written alias clause, if any */
+	Attr	   *eref;			/* expanded reference names */
 	bool		inh;			/* inheritance requested? */
 	bool		inFromCl;		/* present in FROM clause */
-	bool		inJoinSet;		/* planner must include this rel */
 	bool		skipAcl;		/* skip ACL check in executor */
 } RangeTblEntry;
 
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index a0e9881dc87..4e0bcfc7053 100644
--- a/src/include/nodes/pg_list.h
+++ b/src/include/nodes/pg_list.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_list.h,v 1.18 2000/06/09 01:44:26 momjian Exp $
+ * $Id: pg_list.h,v 1.19 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -101,6 +101,7 @@ extern List *nconc(List *list1, List *list2);
 extern List *lcons(void *datum, List *list);
 extern List *lconsi(int datum, List *list);
 extern bool member(void *datum, List *list);
+extern bool ptrMember(void *datum, List *list);
 extern bool intMember(int datum, List *list);
 extern Value *makeInteger(long i);
 extern Value *makeFloat(char *numericStr);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e348d25b2ba..cf93b9dee17 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: plannodes.h,v 1.41 2000/07/12 02:37:33 tgl Exp $
+ * $Id: plannodes.h,v 1.42 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -82,7 +82,7 @@ typedef struct Plan
 								 * individual nodes point to one EState
 								 * for the whole top-level plan */
 	List	   *targetlist;
-	List	   *qual;			/* Node* or List* ?? */
+	List	   *qual;			/* implicitly-ANDed qual conditions */
 	struct Plan *lefttree;
 	struct Plan *righttree;
 	List	   *extParam;		/* indices of _all_ _external_ PARAM_EXEC
@@ -210,9 +210,26 @@ typedef struct TidScan
 
 /* ----------------
  *		Join node
+ *
+ * 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)
+ *
+ * When jointype is INNER, joinqual and plan.qual are semantically
+ * interchangeable.  For OUTER jointypes, the two are *not* interchangeable;
+ * only joinqual is used to determine whether a match has been found for
+ * the purpose of deciding whether to generate null-extended tuples.
+ * (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.
  * ----------------
  */
-typedef Plan Join;
+typedef struct Join
+{
+	Plan		plan;
+	JoinType	jointype;
+	List	   *joinqual;		/* JOIN quals (in addition to plan.qual) */
+} Join;
 
 /* ----------------
  *		nest loop join node
@@ -245,7 +262,6 @@ typedef struct HashJoin
 	List	   *hashclauses;
 	Oid			hashjoinop;
 	HashJoinState *hashjoinstate;
-	bool		hashdone;
 } HashJoin;
 
 /* ---------------
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 0ef350687dc..bc17773642c 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1,13 +1,16 @@
 /*-------------------------------------------------------------------------
  *
  * primnodes.h
- *	  Definitions for parse tree/query tree ("primitive") nodes.
+ *	  Definitions for "primitive" node types, those that are used in more
+ *	  than one of the parse/plan/execute stages of the query pipeline.
+ *	  Currently, these are mostly nodes for executable expressions
+ *	  and join trees.
  *
  *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: primnodes.h,v 1.47 2000/08/24 03:29:13 tgl Exp $
+ * $Id: primnodes.h,v 1.48 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -98,6 +101,12 @@ typedef struct Fjoin
 	BoolPtr		fj_alwaysDone;
 } Fjoin;
 
+
+/* ----------------------------------------------------------------
+ *					node types for executable expressions
+ * ----------------------------------------------------------------
+ */
+
 /* ----------------
  * Expr
  *		typeOid			- oid of the type of this expression
@@ -155,7 +164,7 @@ typedef struct Var
 	AttrNumber	varattno;
 	Oid			vartype;
 	int32		vartypmod;
-	Index		varlevelsup;	/* erased by upper optimizer */
+	Index		varlevelsup;
 	Index		varnoold;		/* mainly for debugging --- see above */
 	AttrNumber	varoattno;
 } Var;
@@ -480,4 +489,76 @@ typedef struct RelabelType
 	int32		resulttypmod;
 } RelabelType;
 
+
+/* ----------------------------------------------------------------
+ *					node types for join trees
+ *
+ * The leaves of a join tree structure are RangeTblRef nodes.  Above
+ * these, JoinExpr nodes can appear to denote a specific kind of join
+ * or qualified join.  A join tree can also contain List nodes --- a list
+ * implies an unqualified cross-product join of its members.  The planner
+ * is allowed to combine the elements of a list using whatever join order
+ * seems good to it.  At present, JoinExpr nodes are always joined in
+ * exactly the order implied by the tree structure (except the planner
+ * may choose to swap inner and outer members of a join pair).
+ *
+ * NOTE: currently, the planner only supports a List at the top level of
+ * a join tree.  Should generalize this to allow Lists at lower levels.
+ *
+ * NOTE: the qualification expressions present in JoinExpr nodes are
+ * *in addition to* the query's main WHERE clause.  For outer joins there
+ * is a real semantic difference between a join qual and a WHERE clause,
+ * though if all joins are inner joins they are interchangeable.
+ *
+ * NOTE: in the raw output of gram.y, a join tree contains RangeVar and
+ * RangeSubselect nodes, which are both replaced by RangeTblRef nodes
+ * during the parse analysis phase.
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * RangeTblRef - reference to an entry in the query's rangetable
+ *
+ * We could use direct pointers to the RT entries and skip having these
+ * nodes, but multiple pointers to the same node in a querytree cause
+ * lots of headaches, so it seems better to store an index into the RT.
+ */
+typedef struct RangeTblRef
+{
+	NodeTag		type;
+	int			rtindex;
+} RangeTblRef;
+
+/*----------
+ * JoinExpr - for SQL JOIN expressions
+ *
+ * isNatural, using, and quals are interdependent.  The user can write only
+ * one of NATURAL, USING(), or ON() (this is enforced by the grammar).
+ * If he writes NATURAL then parse analysis generates the equivalent USING()
+ * list, and from that fills in "quals" with the right equality comparisons.
+ * If he writes USING() then "quals" is filled with equality comparisons.
+ * If he writes ON() then only "quals" is set.  Note that NATURAL/USING
+ * are not equivalent to ON() since they also affect the output column list.
+ *
+ * alias is an Attr node representing the AS alias-clause attached to the
+ * join expression, or NULL if no clause.  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.
+ *----------
+ */
+typedef struct JoinExpr
+{
+	NodeTag		type;
+	JoinType	jointype;		/* type of join */
+	bool		isNatural;		/* Natural join? Will need to shape table */
+	Node	   *larg;			/* left subtree */
+	Node	   *rarg;			/* right subtree */
+	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) */
+} JoinExpr;
+
 #endif	 /* PRIMNODES_H */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index b7d65131066..767e2e114e0 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: relation.h,v 1.47 2000/04/12 17:16:40 momjian Exp $
+ * $Id: relation.h,v 1.48 2000/09/12 21:07:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -73,6 +73,9 @@ typedef enum CostSelector
  *					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)
  *		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
@@ -94,6 +97,10 @@ typedef enum CostSelector
  * We store baserestrictcost in the RelOptInfo (for base relations) because
  * we know we will need it at least once (to price the sequential scan)
  * and may need it multiple times to price index scans.
+ *
+ * outerjoinset is used to ensure correct placement of WHERE clauses that
+ * apply to outer-joined relations; we must not apply such WHERE clauses
+ * until after the outer join is performed.
  */
 
 typedef struct RelOptInfo
@@ -124,6 +131,7 @@ typedef struct RelOptInfo
 	List	   *baserestrictinfo;		/* RestrictInfo structures (if
 										 * base rel) */
 	Cost		baserestrictcost;		/* cost of evaluating the above */
+	Relids		outerjoinset;			/* integer list of base relids */
 	List	   *joininfo;		/* JoinInfo structures */
 	List	   *innerjoin;		/* potential indexscans for nestloop joins */
 
@@ -263,6 +271,9 @@ typedef struct Path
  * that refer to values of other rels, so those other rels must be
  * included in the outer joinrel in order to make a usable join.
  *
+ * 'alljoinquals' is also used only for inner paths of nestloop joins.
+ * This flag is TRUE iff all the indexquals came from JOIN/ON conditions.
+ *
  * 'rows' is the estimated result tuple count for the indexscan.  This
  * is the same as path.parent->rows for a simple indexscan, but it is
  * different for a nestloop inner path, because the additional indexquals
@@ -277,6 +288,7 @@ typedef struct IndexPath
 	List	   *indexqual;
 	ScanDirection indexscandir;
 	Relids		joinrelids;		/* other rels mentioned in indexqual */
+	bool		alljoinquals;	/* all indexquals derived from JOIN conds? */
 	double		rows;			/* estimated number of result tuples */
 } IndexPath;
 
@@ -295,8 +307,11 @@ typedef struct JoinPath
 {
 	Path		path;
 
+	JoinType	jointype;
+
 	Path	   *outerjoinpath;	/* path for the outer side of the join */
 	Path	   *innerjoinpath;	/* path for the inner side of the join */
+
 	List	   *joinrestrictinfo;		/* RestrictInfos to apply to join */
 
 	/*
@@ -375,11 +390,12 @@ typedef struct HashPath
  * The clause cannot actually be applied until we have built a join rel
  * containing all the base rels it references, however.
  *
- * When we construct a join rel that describes exactly the set of base rels
- * referenced in a multi-relation restriction clause, we place that clause
- * into the joinrestrictinfo lists of paths for the join rel.  It will be
- * applied at that join level, and will not propagate any further up the
- * join tree.  (Note: the "predicate migration" code was once intended to
+ * When we construct a join rel that includes all the base rels referenced
+ * in a multi-relation restriction clause, we place that clause into the
+ * joinrestrictinfo lists of paths for the join rel, if neither left nor
+ * right sub-path includes all base rels referenced in the clause.  The clause
+ * will be applied at that join level, and will not propagate any further up
+ * the join tree.  (Note: the "predicate migration" code was once intended to
  * push restriction clauses up and down the plan tree based on evaluation
  * costs, but it's dead code and is unlikely to be resurrected in the
  * foreseeable future.)
@@ -394,18 +410,30 @@ typedef struct HashPath
  * or hashjoin clauses are fairly limited --- the code for each kind of
  * path is responsible for identifying the restrict clauses it can use
  * and ignoring the rest.  Clauses not implemented by an indexscan,
- * mergejoin, or hashjoin will be placed in the qpqual field of the
- * final Plan node, where they will be enforced by general-purpose
+ * mergejoin, or hashjoin will be placed in the plan qual or joinqual field
+ * of the final Plan node, where they will be enforced by general-purpose
  * qual-expression-evaluation code.  (But we are still entitled to count
  * their selectivity when estimating the result tuple count, if we
  * can guess what it is...)
+ *
+ * When dealing with outer joins we must distinguish between qual clauses
+ * that came from WHERE and those that came from JOIN/ON or JOIN/USING.
+ * (For inner joins there's no semantic difference and we can treat the
+ * clauses interchangeably.)  Both kinds of quals are stored as RestrictInfo
+ * nodes during planning, but there's a flag to indicate where they came from.
+ * Note also that when outer joins are present, a qual clause may be treated
+ * as referencing more rels than it really does.  This trick ensures that the
+ * qual will be evaluated at the right level of the join tree --- we don't
+ * want quals from WHERE to be evaluated until after the outer join is done.
  */
 
 typedef struct RestrictInfo
 {
 	NodeTag		type;
 
-	Expr	   *clause;			/* the represented clause of WHERE cond */
+	Expr	   *clause;			/* the represented clause of WHERE or JOIN */
+
+	bool		isjoinqual;		/* TRUE if clause came from JOIN/ON */
 
 	/* only used if clause is an OR clause: */
 	List	   *subclauseindices;		/* indexes matching subclauses */
@@ -437,7 +465,7 @@ typedef struct RestrictInfo
 typedef struct JoinInfo
 {
 	NodeTag		type;
-	Relids		unjoined_relids;/* some rels not yet part of my RelOptInfo */
+	Relids		unjoined_relids; /* some rels not yet part of my RelOptInfo */
 	List	   *jinfo_restrictinfo;		/* relevant RestrictInfos */
 } JoinInfo;
 
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 1b2bcd92055..62bb401193d 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: clauses.h,v 1.38 2000/08/13 02:50:26 tgl Exp $
+ * $Id: clauses.h,v 1.39 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -73,9 +73,11 @@ extern void CommuteClause(Expr *clause);
 extern Node *eval_const_expressions(Node *node);
 
 extern bool expression_tree_walker(Node *node, bool (*walker) (),
-											   void *context);
+								   void *context);
 extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (),
-												 void *context);
+									 void *context);
+extern bool query_tree_walker(Query *query, bool (*walker) (),
+							  void *context);
 
 #define is_subplan(clause)	((clause) != NULL && \
 							 IsA(clause, Expr) && \
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index b8788851e2b..0bf57ef0cc5 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pathnode.h,v 1.27 2000/04/12 17:16:42 momjian Exp $
+ * $Id: pathnode.h,v 1.28 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -34,26 +34,29 @@ extern IndexPath *create_index_path(Query *root, RelOptInfo *rel,
 extern TidPath *create_tidscan_path(RelOptInfo *rel, List *tideval);
 
 extern NestPath *create_nestloop_path(RelOptInfo *joinrel,
-					 Path *outer_path,
-					 Path *inner_path,
-					 List *restrict_clauses,
-					 List *pathkeys);
+									  JoinType jointype,
+									  Path *outer_path,
+									  Path *inner_path,
+									  List *restrict_clauses,
+									  List *pathkeys);
 
 extern MergePath *create_mergejoin_path(RelOptInfo *joinrel,
-					  Path *outer_path,
-					  Path *inner_path,
-					  List *restrict_clauses,
-					  List *pathkeys,
-					  List *mergeclauses,
-					  List *outersortkeys,
-					  List *innersortkeys);
+										JoinType jointype,
+										Path *outer_path,
+										Path *inner_path,
+										List *restrict_clauses,
+										List *pathkeys,
+										List *mergeclauses,
+										List *outersortkeys,
+										List *innersortkeys);
 
 extern HashPath *create_hashjoin_path(RelOptInfo *joinrel,
-					 Path *outer_path,
-					 Path *inner_path,
-					 List *restrict_clauses,
-					 List *hashclauses,
-					 Selectivity innerdisbursion);
+									  JoinType jointype,
+									  Path *outer_path,
+									  Path *inner_path,
+									  List *restrict_clauses,
+									  List *hashclauses,
+									  Selectivity innerdisbursion);
 
 /*
  * prototypes for relnode.c
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 66520d6a897..35eb3190f1c 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: paths.h,v 1.46 2000/07/24 03:10:54 tgl Exp $
+ * $Id: paths.h,v 1.47 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,21 +61,23 @@ extern void create_tidscan_paths(Query *root, RelOptInfo *rel);
  *	   routines to create join paths
  */
 extern void add_paths_to_joinrel(Query *root, RelOptInfo *joinrel,
-					 RelOptInfo *outerrel,
-					 RelOptInfo *innerrel,
-					 List *restrictlist);
+								 RelOptInfo *outerrel,
+								 RelOptInfo *innerrel,
+								 JoinType jointype,
+								 List *restrictlist);
 
 /*
  * joinrels.c
  *	  routines to determine which relations to join
  */
-extern void make_rels_by_joins(Query *root, int level);
-extern RelOptInfo *make_rels_by_clause_joins(Query *root,
-						  RelOptInfo *old_rel,
-						  List *other_rels);
-extern RelOptInfo *make_rels_by_clauseless_joins(Query *root,
-							  RelOptInfo *old_rel,
-							  List *other_rels);
+extern List *make_rels_by_joins(Query *root, int level, List **joinrels);
+extern List *make_rels_by_clause_joins(Query *root,
+									   RelOptInfo *old_rel,
+									   List *other_rels);
+extern List *make_rels_by_clauseless_joins(Query *root,
+										   RelOptInfo *old_rel,
+										   List *other_rels);
+extern RelOptInfo *make_rel_from_jointree(Query *root, Node *jtnode);
 
 /*
  * pathkeys.c
@@ -110,7 +112,7 @@ extern List *make_pathkeys_for_sortclauses(List *sortclauses,
 extern List *find_mergeclauses_for_pathkeys(List *pathkeys,
 							   List *restrictinfos);
 extern List *make_pathkeys_for_mergeclauses(Query *root,
-							   List *mergeclauses,
-							   List *tlist);
+											List *mergeclauses,
+											RelOptInfo *rel);
 
 #endif	 /* PATHS_H */
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 723543c437c..43c93978cdf 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: planmain.h,v 1.43 2000/07/24 03:10:54 tgl Exp $
+ * $Id: planmain.h,v 1.44 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,8 +20,7 @@
 /*
  * prototypes for plan/planmain.c
  */
-extern Plan *query_planner(Query *root, List *tlist, List *qual,
-			  double tuple_fraction);
+extern Plan *query_planner(Query *root, List *tlist, double tuple_fraction);
 
 /*
  * prototypes for plan/createplan.c
@@ -40,9 +39,10 @@ extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
 /*
  * prototypes for plan/initsplan.c
  */
-extern void make_var_only_tlist(Query *root, List *tlist);
+extern void build_base_rel_tlists(Query *root, List *tlist);
+extern Relids add_join_quals_to_rels(Query *root, Node *jtnode);
 extern void add_restrict_and_join_to_rels(Query *root, List *clauses);
-extern void add_missing_rels_to_query(Query *root);
+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);
 
@@ -58,6 +58,7 @@ extern void fix_opids(Node *node);
  * prep/prepkeyset.c
  */
 extern bool _use_keyset_query_optimizer;
+
 extern void transformKeySetQuery(Query *origNode);
 
 #endif	 /* PLANMAIN_H */
diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h
index 3d94854e03b..2e1d4d66f99 100644
--- a/src/include/optimizer/restrictinfo.h
+++ b/src/include/optimizer/restrictinfo.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: restrictinfo.h,v 1.8 2000/01/26 05:58:21 momjian Exp $
+ * $Id: restrictinfo.h,v 1.9 2000/09/12 21:07:11 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,5 +18,7 @@
 
 extern bool restriction_is_or_clause(RestrictInfo *restrictinfo);
 extern List *get_actual_clauses(List *restrictinfo_list);
+extern void get_actual_join_clauses(List *restrictinfo_list,
+									List **joinquals, List **otherquals);
 
 #endif	 /* RESTRICTINFO_H */
diff --git a/src/include/parser/gramparse.h b/src/include/parser/gramparse.h
index 02c95745fee..54d6e869ad9 100644
--- a/src/include/parser/gramparse.h
+++ b/src/include/parser/gramparse.h
@@ -1,28 +1,31 @@
 /*-------------------------------------------------------------------------
  *
  * gramparse.h
- *	  scanner support routines.  used by both the bootstrap lexer
- * as well as the normal lexer
+ *	  Declarations for routines exported from lexer and parser files.
+ *
  *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: gramparse.h,v 1.12 2000/04/12 17:16:44 momjian Exp $
+ * $Id: gramparse.h,v 1.13 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #ifndef GRAMPARSE_H
-#define GRAMPARSE_H				/* include once only */
+#define GRAMPARSE_H
 
-/* from scan.l */
-extern void init_io(void);
+/* from parser.c */
 extern int	yylex(void);
+
+/* from scan.l */
+extern void scanner_init(void);
+extern int	base_yylex(void);
 extern void yyerror(const char *message);
 
 /* from gram.y */
-extern Oid	param_type(int t);
 extern void parser_init(Oid *typev, int nargs);
+extern Oid	param_type(int t);
 extern int	yyparse(void);
 
 #endif	 /* GRAMPARSE_H */
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index b9a868d4420..fd1cfdb3604 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_clause.h,v 1.18 2000/06/09 01:44:29 momjian Exp $
+ * $Id: parse_clause.h,v 1.19 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +17,8 @@
 #include "parser/parse_node.h"
 
 extern void makeRangeTable(ParseState *pstate, List *frmList);
-extern void setTargetTable(ParseState *pstate, char *relname, bool inh);
+extern void setTargetTable(ParseState *pstate, char *relname,
+						   bool inh, bool inJoinSet);
 extern Node *transformWhereClause(ParseState *pstate, Node *where);
 extern List *transformGroupClause(ParseState *pstate, List *grouplist,
 					 List *targetlist);
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index be96652cfb2..d221c600c8e 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_func.h,v 1.26 2000/08/20 00:44:17 tgl Exp $
+ * $Id: parse_func.h,v 1.27 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -39,11 +39,11 @@ typedef struct _CandidateList
 }		   *CandidateList;
 
 extern Node *ParseNestedFuncOrColumn(ParseState *pstate, Attr *attr,
-						int *curr_resno, int precedence);
+									 int precedence);
 extern Node *ParseFuncOrColumn(ParseState *pstate,
-				  char *funcname, List *fargs,
-				  bool agg_star, bool agg_distinct,
-				  int *curr_resno, int precedence);
+							   char *funcname, List *fargs,
+							   bool agg_star, bool agg_distinct,
+							   int precedence);
 
 extern bool func_get_detail(char *funcname, int nargs, Oid *argtypes,
 							Oid *funcid, Oid *rettype,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d4231e8819d..002391d6530 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_node.h,v 1.20 2000/05/12 01:33:52 tgl Exp $
+ * $Id: parse_node.h,v 1.21 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,36 +16,28 @@
 #include "nodes/parsenodes.h"
 #include "utils/rel.h"
 
-/* State information used during parse analysis
- * p_join_quals is a list of untransformed qualification expressions
- * (implicitly ANDed together) found in the FROM clause.
- * Needs to be available later to merge with other qualifiers from the
- * WHERE clause.
+/*
+ * State information used during parse analysis
  */
 typedef struct ParseState
 {
-	int			p_last_resno;
-	List	   *p_rtable;
-	struct ParseState *parentParseState;
+	struct ParseState *parentParseState; /* stack link */
+	List	   *p_rtable;		/* range table so far */
+	List	   *p_jointree;		/* join tree so far */
+	int			p_last_resno;	/* last targetlist resno assigned */
 	bool		p_hasAggs;
 	bool		p_hasSubLinks;
 	bool		p_is_insert;
 	bool		p_is_update;
-	bool		p_is_rule;
-	bool		p_in_where_clause;
 	Relation	p_target_relation;
 	RangeTblEntry *p_target_rangetblentry;
-	List	   *p_shape;
-	List	   *p_alias;
-	List	   *p_join_quals;
 } ParseState;
 
 extern ParseState *make_parsestate(ParseState *parentParseState);
 extern Expr *make_op(char *opname, Node *ltree, Node *rtree);
 extern Node *make_operand(char *opname, Node *tree,
 			 Oid orig_typeId, Oid target_typeId);
-extern Var *make_var(ParseState *pstate, Oid relid, char *refname,
-		 char *attrname);
+extern Var *make_var(ParseState *pstate, RangeTblEntry *rte, int attrno);
 extern ArrayRef *transformArraySubscripts(ParseState *pstate,
 						 Node *arrayBase,
 						 List *indirection,
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 4f89bcc65c3..7c7a04844e4 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_relation.h,v 1.18 2000/06/08 22:37:53 momjian Exp $
+ * $Id: parse_relation.h,v 1.19 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,23 +16,35 @@
 
 #include "parser/parse_node.h"
 
-extern RangeTblEntry *refnameRangeTableEntry(ParseState *pstate, char *refname);
+extern Node *refnameRangeOrJoinEntry(ParseState *pstate,
+									 char *refname,
+									 int *sublevels_up);
+extern RangeTblEntry *refnameRangeTableEntry(ParseState *pstate,
+											 char *refname);
 extern int refnameRangeTablePosn(ParseState *pstate,
-					  char *refname,
-					  int *sublevels_up);
-extern RangeTblEntry *colnameRangeTableEntry(ParseState *pstate, char *colname);
+								 char *refname,
+								 int *sublevels_up);
+extern int RTERangeTablePosn(ParseState *pstate,
+							 RangeTblEntry *rte,
+							 int *sublevels_up);
+extern JoinExpr *scanJoinTreeForRefname(Node *jtnode, char *refname);
+extern Node *colnameToVar(ParseState *pstate, char *colname);
+extern Node *qualifiedNameToVar(ParseState *pstate, char *refname,
+								char *colname, bool implicitRTEOK);
 extern RangeTblEntry *addRangeTableEntry(ParseState *pstate,
-				   char *relname,
-				   Attr *ref,
-				   bool inh,
-				   bool inFromCl,
-				   bool inJoinSet);
-extern Attr *expandTable(ParseState *pstate, char *refname, bool getaliases);
-extern List *expandAll(ParseState *pstate, char *relname, Attr *ref,
-		  int *this_resno);
+										 char *relname,
+										 Attr *alias,
+										 bool inh,
+										 bool inFromCl);
+extern void addRTEtoJoinTree(ParseState *pstate, RangeTblEntry *rte);
+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 int	specialAttNum(char *a);
 extern Oid	attnumTypeId(Relation rd, int attid);
-extern void warnAutoRange(ParseState *pstate, char *refname);
 
 #endif	 /* PARSE_RELATION_H */
diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h
index 277bc32a504..ff727cfd07a 100644
--- a/src/include/parser/parsetree.h
+++ b/src/include/parser/parsetree.h
@@ -8,41 +8,26 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsetree.h,v 1.10 2000/06/12 19:40:51 momjian Exp $
+ * $Id: parsetree.h,v 1.11 2000/09/12 21:07:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef PARSETREE_H
-#define PARSETREE_H				/* include once only */
+#define PARSETREE_H
 
 #include "nodes/parsenodes.h"
 #include "nodes/pg_list.h"
 
 /* ----------------
- *		need pg_list.h for definitions of CAR(), etc. macros
+ *		need pg_list.h for definitions of nth(), etc.
  * ----------------
  */
 
 /* ----------------
  *		range table macros
- *
- *	parse tree:
- *		(root targetlist qual)
- *		 ^^^^
- *	parse root:
- *		(numlevels cmdtype resrel rangetable priority ruleinfo nestdotinfo)
- *								  ^^^^^^^^^^
- *	range table:
- *		(rtentry ...)
- *	rtentry:
  * ----------------
  */
 
-#define rt_relname(rt_entry) \
-	  ((!strcmp(((rt_entry)->ref->relname),"*OLD*") ||\
-		!strcmp(((rt_entry)->ref->relname),"*NEW*")) ? ((rt_entry)->ref->relname) : \
-		((char *)(rt_entry)->relname))
-
 /*
  *		rt_fetch
  *		rt_store
@@ -51,22 +36,18 @@
  *
  */
 #define rt_fetch(rangetable_index, rangetable) \
-	((RangeTblEntry*)nth((rangetable_index)-1, rangetable))
+	((RangeTblEntry*) nth((rangetable_index)-1, rangetable))
 
 #define rt_store(rangetable_index, rangetable, rt) \
 	set_nth(rangetable, (rangetable_index)-1, rt)
 
 /*
  *		getrelid
- *		getrelname
  *
  *		Given the range index of a relation, return the corresponding
- *		relation id or relation name.
+ *		relation OID.
  */
 #define getrelid(rangeindex,rangetable) \
-	((RangeTblEntry*)nth((rangeindex)-1, rangetable))->relid
-
-#define getrelname(rangeindex, rangetable) \
-	rt_relname((RangeTblEntry*)nth((rangeindex)-1, rangetable))
+	(rt_fetch(rangeindex, rangetable)->relid)
 
 #endif	 /* PARSETREE_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 7af0f3932ec..5271d78717c 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: rewriteHandler.h,v 1.12 2000/01/26 05:58:30 momjian Exp $
+ * $Id: rewriteHandler.h,v 1.13 2000/09/12 21:07:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,9 +16,8 @@
 
 #include "nodes/parsenodes.h"
 
-struct _rewrite_meta_knowledge
+typedef struct RewriteInfo
 {
-	List	   *rt;
 	int			rt_index;
 	bool		instead_flag;
 	int			event;
@@ -28,9 +27,7 @@ struct _rewrite_meta_knowledge
 	Query	   *rule_action;
 	Node	   *rule_qual;
 	bool		nothing;
-};
-
-typedef struct _rewrite_meta_knowledge RewriteInfo;
+} RewriteInfo;
 
 
 extern List *QueryRewrite(Query *parsetree);
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index af052a65510..c41519acb8c 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: rewriteManip.h,v 1.21 2000/04/12 17:16:50 momjian Exp $
+ * $Id: rewriteManip.h,v 1.22 2000/09/12 21:07:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,6 +22,12 @@ extern void ChangeVarNodes(Node *node, int old_varno, int new_varno,
 			   int sublevels_up);
 extern void IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
 						int min_sublevels_up);
+
+extern bool rangeTableEntry_used(Node *node, int rt_index,
+								 int sublevels_up);
+extern bool attribute_used(Node *node, int rt_index, int attno,
+						   int sublevels_up);
+
 extern void AddQual(Query *parsetree, Node *qual);
 extern void AddHavingQual(Query *parsetree, Node *havingQual);
 extern void AddNotQual(Query *parsetree, Node *qual);
diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out
index 0ad8be9eec4..2b53bc699dc 100644
--- a/src/test/regress/expected/case.out
+++ b/src/test/regress/expected/case.out
@@ -157,28 +157,28 @@ SELECT COALESCE(a.f, b.i, b.j)
  case  
 -------
   10.1
-  20.2
- -30.3
-     1
   10.1
-  20.2
- -30.3
-     2
   10.1
-  20.2
- -30.3
-     3
   10.1
-  20.2
- -30.3
-     2
   10.1
-  20.2
- -30.3
-     1
   10.1
   20.2
+  20.2
+  20.2
+  20.2
+  20.2
+  20.2
+ -30.3
+ -30.3
+ -30.3
  -30.3
+ -30.3
+ -30.3
+     1
+     2
+     3
+     2
+     1
     -6
 (24 rows)
 
@@ -197,28 +197,28 @@ SELECT '' AS Five, NULLIF(a.i,b.i) AS "NULLIF(a.i,b.i)",
  five | NULLIF(a.i,b.i) | NULLIF(b.i,4) 
 ------+-----------------+---------------
       |                 |             1
-      |               2 |             1
-      |               3 |             1
-      |               4 |             1
       |               1 |             2
-      |                 |             2
-      |               3 |             2
-      |               4 |             2
       |               1 |             3
-      |               2 |             3
-      |                 |             3
-      |               4 |             3
       |               1 |             2
-      |                 |             2
-      |               3 |             2
-      |               4 |             2
       |                 |             1
-      |               2 |             1
-      |               3 |             1
-      |               4 |             1
       |               1 |              
+      |               2 |             1
+      |                 |             2
+      |               2 |             3
+      |                 |             2
+      |               2 |             1
       |               2 |              
+      |               3 |             1
+      |               3 |             2
+      |                 |             3
+      |               3 |             2
+      |               3 |             1
       |               3 |              
+      |               4 |             1
+      |               4 |             2
+      |               4 |             3
+      |               4 |             2
+      |               4 |             1
       |               4 |              
 (24 rows)
 
diff --git a/src/test/regress/expected/geometry-cygwin-precision.out b/src/test/regress/expected/geometry-cygwin-precision.out
index 4e0651e46cd..67ab299c39e 100644
--- a/src/test/regress/expected/geometry-cygwin-precision.out
+++ b/src/test/regress/expected/geometry-cygwin-precision.out
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
diff --git a/src/test/regress/expected/geometry-i86-gnulibc.out b/src/test/regress/expected/geometry-i86-gnulibc.out
index c2e689dea96..6ad08de4154 100644
--- a/src/test/regress/expected/geometry-i86-gnulibc.out
+++ b/src/test/regress/expected/geometry-i86-gnulibc.out
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
diff --git a/src/test/regress/expected/geometry-positive-zeros-bsd.out b/src/test/regress/expected/geometry-positive-zeros-bsd.out
index 169709e37bc..78940c08468 100644
--- a/src/test/regress/expected/geometry-positive-zeros-bsd.out
+++ b/src/test/regress/expected/geometry-positive-zeros-bsd.out
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
diff --git a/src/test/regress/expected/geometry-positive-zeros.out b/src/test/regress/expected/geometry-positive-zeros.out
index 113733d9420..914d9e77528 100644
--- a/src/test/regress/expected/geometry-positive-zeros.out
+++ b/src/test/regress/expected/geometry-positive-zeros.out
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
diff --git a/src/test/regress/expected/geometry-powerpc-aix4.out b/src/test/regress/expected/geometry-powerpc-aix4.out
index 07017606bad..f7ab9e65096 100644
--- a/src/test/regress/expected/geometry-powerpc-aix4.out
+++ b/src/test/regress/expected/geometry-powerpc-aix4.out
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
diff --git a/src/test/regress/expected/geometry-powerpc-linux-gnulibc1.out b/src/test/regress/expected/geometry-powerpc-linux-gnulibc1.out
index 1b0714b6952..3c2e4557ffb 100644
--- a/src/test/regress/expected/geometry-powerpc-linux-gnulibc1.out
+++ b/src/test/regress/expected/geometry-powerpc-linux-gnulibc1.out
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
diff --git a/src/test/regress/expected/geometry-solaris-precision.out b/src/test/regress/expected/geometry-solaris-precision.out
index 4e0651e46cd..67ab299c39e 100644
--- a/src/test/regress/expected/geometry-solaris-precision.out
+++ b/src/test/regress/expected/geometry-solaris-precision.out
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 9dfe6081eb1..dcefbf78f54 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -163,28 +163,28 @@ SELECT '' AS twentyfour, b.f1 + p.f1 AS translation
  twentyfour |       translation       
 ------------+-------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (-8,2),(-10,0)
-            | (-7,3),(-9,1)
-            | (-7.5,3.5),(-7.5,2.5)
-            | (-7,3),(-7,3)
             | (-1,6),(-3,4)
-            | (0,7),(-2,5)
-            | (-0.5,7.5),(-0.5,6.5)
-            | (0,7),(0,7)
             | (7.1,36.5),(5.1,34.5)
-            | (8.1,37.5),(6.1,35.5)
-            | (7.6,38),(7.6,37)
-            | (8.1,37.5),(8.1,37.5)
             | (-3,-10),(-5,-12)
-            | (-2,-9),(-4,-11)
-            | (-2.5,-8.5),(-2.5,-9.5)
-            | (-2,-9),(-2,-9)
             | (12,12),(10,10)
+            | (3,3),(1,1)
+            | (-7,3),(-9,1)
+            | (0,7),(-2,5)
+            | (8.1,37.5),(6.1,35.5)
+            | (-2,-9),(-4,-11)
             | (13,13),(11,11)
+            | (2.5,3.5),(2.5,2.5)
+            | (-7.5,3.5),(-7.5,2.5)
+            | (-0.5,7.5),(-0.5,6.5)
+            | (7.6,38),(7.6,37)
+            | (-2.5,-8.5),(-2.5,-9.5)
             | (12.5,13.5),(12.5,12.5)
+            | (3,3),(3,3)
+            | (-7,3),(-7,3)
+            | (0,7),(0,7)
+            | (8.1,37.5),(8.1,37.5)
+            | (-2,-9),(-2,-9)
             | (13,13),(13,13)
 (24 rows)
 
@@ -193,28 +193,28 @@ SELECT '' AS twentyfour, b.f1 - p.f1 AS translation
  twentyfour |        translation        
 ------------+---------------------------
             | (2,2),(0,0)
-            | (3,3),(1,1)
-            | (2.5,3.5),(2.5,2.5)
-            | (3,3),(3,3)
             | (12,2),(10,0)
-            | (13,3),(11,1)
-            | (12.5,3.5),(12.5,2.5)
-            | (13,3),(13,3)
             | (5,-2),(3,-4)
-            | (6,-1),(4,-3)
-            | (5.5,-0.5),(5.5,-1.5)
-            | (6,-1),(6,-1)
             | (-3.1,-32.5),(-5.1,-34.5)
-            | (-2.1,-31.5),(-4.1,-33.5)
-            | (-2.6,-31),(-2.6,-32)
-            | (-2.1,-31.5),(-2.1,-31.5)
             | (7,14),(5,12)
-            | (8,15),(6,13)
-            | (7.5,15.5),(7.5,14.5)
-            | (8,15),(8,15)
             | (-8,-8),(-10,-10)
+            | (3,3),(1,1)
+            | (13,3),(11,1)
+            | (6,-1),(4,-3)
+            | (-2.1,-31.5),(-4.1,-33.5)
+            | (8,15),(6,13)
             | (-7,-7),(-9,-9)
+            | (2.5,3.5),(2.5,2.5)
+            | (12.5,3.5),(12.5,2.5)
+            | (5.5,-0.5),(5.5,-1.5)
+            | (-2.6,-31),(-2.6,-32)
+            | (7.5,15.5),(7.5,14.5)
             | (-7.5,-6.5),(-7.5,-7.5)
+            | (3,3),(3,3)
+            | (13,3),(13,3)
+            | (6,-1),(6,-1)
+            | (-2.1,-31.5),(-2.1,-31.5)
+            | (8,15),(8,15)
             | (-7,-7),(-7,-7)
 (24 rows)
 
@@ -223,29 +223,29 @@ SELECT '' AS twentyfour, b.f1 * p.f1 AS rotation
    FROM BOX_TBL b, POINT_TBL p;
  twentyfour |          rotation           
 ------------+-----------------------------
-            | (0,0),(0,0)
-            | (0,0),(0,0)
-            | (0,0),(0,0)
             | (0,0),(0,0)
             | (-0,0),(-20,-20)
-            | (-10,-10),(-30,-30)
-            | (-25,-25),(-25,-35)
-            | (-30,-30),(-30,-30)
             | (-0,2),(-14,0)
-            | (-7,3),(-21,1)
-            | (-17.5,2.5),(-21.5,-0.5)
-            | (-21,3),(-21,3)
             | (0,79.2),(-58.8,0)
-            | (-29.4,118.8),(-88.2,39.6)
-            | (-73.5,104.1),(-108,99)
-            | (-88.2,118.8),(-88.2,118.8)
             | (14,-0),(0,-34)
-            | (21,-17),(7,-51)
-            | (29.5,-42.5),(17.5,-47.5)
-            | (21,-51),(21,-51)
             | (0,40),(0,0)
+            | (0,0),(0,0)
+            | (-10,-10),(-30,-30)
+            | (-7,3),(-21,1)
+            | (-29.4,118.8),(-88.2,39.6)
+            | (21,-17),(7,-51)
             | (0,60),(0,20)
+            | (0,0),(0,0)
+            | (-25,-25),(-25,-35)
+            | (-17.5,2.5),(-21.5,-0.5)
+            | (-73.5,104.1),(-108,99)
+            | (29.5,-42.5),(17.5,-47.5)
             | (0,60),(-10,50)
+            | (0,0),(0,0)
+            | (-30,-30),(-30,-30)
+            | (-21,3),(-21,3)
+            | (-88.2,118.8),(-88.2,118.8)
+            | (21,-51),(21,-51)
             | (0,60),(0,60)
 (24 rows)
 
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 2b4f1b655aa..3c156ee46c1 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1,6 +1,6 @@
 --
 -- JOIN
--- Test join clauses
+-- Test JOIN clauses
 --
 CREATE TABLE J1_TBL (
   i integer,
@@ -28,6 +28,7 @@ INSERT INTO J2_TBL VALUES (1, -1);
 INSERT INTO J2_TBL VALUES (2, 2);
 INSERT INTO J2_TBL VALUES (3, -3);
 INSERT INTO J2_TBL VALUES (2, 4);
+INSERT INTO J2_TBL VALUES (5, -5);
 --
 -- CORRELATION NAMES
 -- Make sure that table/column aliases are supported
@@ -78,22 +79,26 @@ SELECT '' AS "xxx", *
  xxx | a | b |   c   | d | e  
 -----+---+---+-------+---+----
      | 1 | 3 | one   | 1 | -1
-     | 2 | 2 | two   | 1 | -1
-     | 3 | 1 | three | 1 | -1
-     | 4 | 0 | four  | 1 | -1
      | 1 | 3 | one   | 2 |  2
-     | 2 | 2 | two   | 2 |  2
-     | 3 | 1 | three | 2 |  2
-     | 4 | 0 | four  | 2 |  2
      | 1 | 3 | one   | 3 | -3
-     | 2 | 2 | two   | 3 | -3
-     | 3 | 1 | three | 3 | -3
-     | 4 | 0 | four  | 3 | -3
      | 1 | 3 | one   | 2 |  4
+     | 1 | 3 | one   | 5 | -5
+     | 2 | 2 | two   | 1 | -1
+     | 2 | 2 | two   | 2 |  2
+     | 2 | 2 | two   | 3 | -3
      | 2 | 2 | two   | 2 |  4
+     | 2 | 2 | two   | 5 | -5
+     | 3 | 1 | three | 1 | -1
+     | 3 | 1 | three | 2 |  2
+     | 3 | 1 | three | 3 | -3
      | 3 | 1 | three | 2 |  4
+     | 3 | 1 | three | 5 | -5
+     | 4 | 0 | four  | 1 | -1
+     | 4 | 0 | four  | 2 |  2
+     | 4 | 0 | four  | 3 | -3
      | 4 | 0 | four  | 2 |  4
-(16 rows)
+     | 4 | 0 | four  | 5 | -5
+(20 rows)
 
 SELECT '' AS "xxx", t1.a, t2.e
   FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e)
@@ -116,58 +121,218 @@ SELECT '' AS "xxx", *
  xxx | i | j |   t   | i | k  
 -----+---+---+-------+---+----
      | 1 | 3 | one   | 1 | -1
-     | 2 | 2 | two   | 1 | -1
-     | 3 | 1 | three | 1 | -1
-     | 4 | 0 | four  | 1 | -1
      | 1 | 3 | one   | 2 |  2
-     | 2 | 2 | two   | 2 |  2
-     | 3 | 1 | three | 2 |  2
-     | 4 | 0 | four  | 2 |  2
      | 1 | 3 | one   | 3 | -3
-     | 2 | 2 | two   | 3 | -3
-     | 3 | 1 | three | 3 | -3
-     | 4 | 0 | four  | 3 | -3
      | 1 | 3 | one   | 2 |  4
+     | 1 | 3 | one   | 5 | -5
+     | 2 | 2 | two   | 1 | -1
+     | 2 | 2 | two   | 2 |  2
+     | 2 | 2 | two   | 3 | -3
      | 2 | 2 | two   | 2 |  4
+     | 2 | 2 | two   | 5 | -5
+     | 3 | 1 | three | 1 | -1
+     | 3 | 1 | three | 2 |  2
+     | 3 | 1 | three | 3 | -3
      | 3 | 1 | three | 2 |  4
+     | 3 | 1 | three | 5 | -5
+     | 4 | 0 | four  | 1 | -1
+     | 4 | 0 | four  | 2 |  2
+     | 4 | 0 | four  | 3 | -3
      | 4 | 0 | four  | 2 |  4
-(16 rows)
+     | 4 | 0 | four  | 5 | -5
+(20 rows)
 
 -- ambiguous column
 SELECT '' AS "xxx", i, k, t
   FROM J1_TBL CROSS JOIN J2_TBL;
-ERROR:  Column 'i' is ambiguous
+ERROR:  Column reference "i" is ambiguous
 -- resolve previous ambiguity by specifying the table name
 SELECT '' AS "xxx", t1.i, k, t
   FROM J1_TBL t1 CROSS JOIN J2_TBL t2;
  xxx | i | k  |   t   
 -----+---+----+-------
      | 1 | -1 | one
-     | 2 | -1 | two
-     | 3 | -1 | three
-     | 4 | -1 | four
      | 1 |  2 | one
-     | 2 |  2 | two
-     | 3 |  2 | three
-     | 4 |  2 | four
      | 1 | -3 | one
-     | 2 | -3 | two
-     | 3 | -3 | three
-     | 4 | -3 | four
      | 1 |  4 | one
+     | 1 | -5 | one
+     | 2 | -1 | two
+     | 2 |  2 | two
+     | 2 | -3 | two
      | 2 |  4 | two
+     | 2 | -5 | two
+     | 3 | -1 | three
+     | 3 |  2 | three
+     | 3 | -3 | three
      | 3 |  4 | three
+     | 3 | -5 | three
+     | 4 | -1 | four
+     | 4 |  2 | four
+     | 4 | -3 | four
      | 4 |  4 | four
-(16 rows)
+     | 4 | -5 | four
+(20 rows)
 
 SELECT '' AS "xxx", ii, tt, kk
   FROM (J1_TBL CROSS JOIN J2_TBL)
     AS tx (ii, jj, tt, ii2, kk);
-ERROR:  JOIN table aliases are not supported
+ xxx | ii |  tt   | kk 
+-----+----+-------+----
+     |  1 | one   | -1
+     |  1 | one   |  2
+     |  1 | one   | -3
+     |  1 | one   |  4
+     |  1 | one   | -5
+     |  2 | two   | -1
+     |  2 | two   |  2
+     |  2 | two   | -3
+     |  2 | two   |  4
+     |  2 | two   | -5
+     |  3 | three | -1
+     |  3 | three |  2
+     |  3 | three | -3
+     |  3 | three |  4
+     |  3 | three | -5
+     |  4 | four  | -1
+     |  4 | four  |  2
+     |  4 | four  | -3
+     |  4 | four  |  4
+     |  4 | four  | -5
+(20 rows)
+
 SELECT '' AS "xxx", tx.ii, tx.jj, tx.kk
   FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e))
     AS tx (ii, jj, tt, ii2, kk);
-ERROR:  JOIN table aliases are not supported
+ xxx | ii | jj | kk 
+-----+----+----+----
+     |  1 |  3 | -1
+     |  1 |  3 |  2
+     |  1 |  3 | -3
+     |  1 |  3 |  4
+     |  1 |  3 | -5
+     |  2 |  2 | -1
+     |  2 |  2 |  2
+     |  2 |  2 | -3
+     |  2 |  2 |  4
+     |  2 |  2 | -5
+     |  3 |  1 | -1
+     |  3 |  1 |  2
+     |  3 |  1 | -3
+     |  3 |  1 |  4
+     |  3 |  1 | -5
+     |  4 |  0 | -1
+     |  4 |  0 |  2
+     |  4 |  0 | -3
+     |  4 |  0 |  4
+     |  4 |  0 | -5
+(20 rows)
+
+SELECT '' AS "xxx", *
+  FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b;
+ xxx | i | j |   t   | i | k  | i | k  
+-----+---+---+-------+---+----+---+----
+     | 1 | 3 | one   | 1 | -1 | 1 | -1
+     | 1 | 3 | one   | 1 | -1 | 2 |  2
+     | 1 | 3 | one   | 1 | -1 | 3 | -3
+     | 1 | 3 | one   | 1 | -1 | 2 |  4
+     | 1 | 3 | one   | 1 | -1 | 5 | -5
+     | 1 | 3 | one   | 2 |  2 | 1 | -1
+     | 1 | 3 | one   | 2 |  2 | 2 |  2
+     | 1 | 3 | one   | 2 |  2 | 3 | -3
+     | 1 | 3 | one   | 2 |  2 | 2 |  4
+     | 1 | 3 | one   | 2 |  2 | 5 | -5
+     | 1 | 3 | one   | 3 | -3 | 1 | -1
+     | 1 | 3 | one   | 3 | -3 | 2 |  2
+     | 1 | 3 | one   | 3 | -3 | 3 | -3
+     | 1 | 3 | one   | 3 | -3 | 2 |  4
+     | 1 | 3 | one   | 3 | -3 | 5 | -5
+     | 1 | 3 | one   | 2 |  4 | 1 | -1
+     | 1 | 3 | one   | 2 |  4 | 2 |  2
+     | 1 | 3 | one   | 2 |  4 | 3 | -3
+     | 1 | 3 | one   | 2 |  4 | 2 |  4
+     | 1 | 3 | one   | 2 |  4 | 5 | -5
+     | 1 | 3 | one   | 5 | -5 | 1 | -1
+     | 1 | 3 | one   | 5 | -5 | 2 |  2
+     | 1 | 3 | one   | 5 | -5 | 3 | -3
+     | 1 | 3 | one   | 5 | -5 | 2 |  4
+     | 1 | 3 | one   | 5 | -5 | 5 | -5
+     | 2 | 2 | two   | 1 | -1 | 1 | -1
+     | 2 | 2 | two   | 1 | -1 | 2 |  2
+     | 2 | 2 | two   | 1 | -1 | 3 | -3
+     | 2 | 2 | two   | 1 | -1 | 2 |  4
+     | 2 | 2 | two   | 1 | -1 | 5 | -5
+     | 2 | 2 | two   | 2 |  2 | 1 | -1
+     | 2 | 2 | two   | 2 |  2 | 2 |  2
+     | 2 | 2 | two   | 2 |  2 | 3 | -3
+     | 2 | 2 | two   | 2 |  2 | 2 |  4
+     | 2 | 2 | two   | 2 |  2 | 5 | -5
+     | 2 | 2 | two   | 3 | -3 | 1 | -1
+     | 2 | 2 | two   | 3 | -3 | 2 |  2
+     | 2 | 2 | two   | 3 | -3 | 3 | -3
+     | 2 | 2 | two   | 3 | -3 | 2 |  4
+     | 2 | 2 | two   | 3 | -3 | 5 | -5
+     | 2 | 2 | two   | 2 |  4 | 1 | -1
+     | 2 | 2 | two   | 2 |  4 | 2 |  2
+     | 2 | 2 | two   | 2 |  4 | 3 | -3
+     | 2 | 2 | two   | 2 |  4 | 2 |  4
+     | 2 | 2 | two   | 2 |  4 | 5 | -5
+     | 2 | 2 | two   | 5 | -5 | 1 | -1
+     | 2 | 2 | two   | 5 | -5 | 2 |  2
+     | 2 | 2 | two   | 5 | -5 | 3 | -3
+     | 2 | 2 | two   | 5 | -5 | 2 |  4
+     | 2 | 2 | two   | 5 | -5 | 5 | -5
+     | 3 | 1 | three | 1 | -1 | 1 | -1
+     | 3 | 1 | three | 1 | -1 | 2 |  2
+     | 3 | 1 | three | 1 | -1 | 3 | -3
+     | 3 | 1 | three | 1 | -1 | 2 |  4
+     | 3 | 1 | three | 1 | -1 | 5 | -5
+     | 3 | 1 | three | 2 |  2 | 1 | -1
+     | 3 | 1 | three | 2 |  2 | 2 |  2
+     | 3 | 1 | three | 2 |  2 | 3 | -3
+     | 3 | 1 | three | 2 |  2 | 2 |  4
+     | 3 | 1 | three | 2 |  2 | 5 | -5
+     | 3 | 1 | three | 3 | -3 | 1 | -1
+     | 3 | 1 | three | 3 | -3 | 2 |  2
+     | 3 | 1 | three | 3 | -3 | 3 | -3
+     | 3 | 1 | three | 3 | -3 | 2 |  4
+     | 3 | 1 | three | 3 | -3 | 5 | -5
+     | 3 | 1 | three | 2 |  4 | 1 | -1
+     | 3 | 1 | three | 2 |  4 | 2 |  2
+     | 3 | 1 | three | 2 |  4 | 3 | -3
+     | 3 | 1 | three | 2 |  4 | 2 |  4
+     | 3 | 1 | three | 2 |  4 | 5 | -5
+     | 3 | 1 | three | 5 | -5 | 1 | -1
+     | 3 | 1 | three | 5 | -5 | 2 |  2
+     | 3 | 1 | three | 5 | -5 | 3 | -3
+     | 3 | 1 | three | 5 | -5 | 2 |  4
+     | 3 | 1 | three | 5 | -5 | 5 | -5
+     | 4 | 0 | four  | 1 | -1 | 1 | -1
+     | 4 | 0 | four  | 1 | -1 | 2 |  2
+     | 4 | 0 | four  | 1 | -1 | 3 | -3
+     | 4 | 0 | four  | 1 | -1 | 2 |  4
+     | 4 | 0 | four  | 1 | -1 | 5 | -5
+     | 4 | 0 | four  | 2 |  2 | 1 | -1
+     | 4 | 0 | four  | 2 |  2 | 2 |  2
+     | 4 | 0 | four  | 2 |  2 | 3 | -3
+     | 4 | 0 | four  | 2 |  2 | 2 |  4
+     | 4 | 0 | four  | 2 |  2 | 5 | -5
+     | 4 | 0 | four  | 3 | -3 | 1 | -1
+     | 4 | 0 | four  | 3 | -3 | 2 |  2
+     | 4 | 0 | four  | 3 | -3 | 3 | -3
+     | 4 | 0 | four  | 3 | -3 | 2 |  4
+     | 4 | 0 | four  | 3 | -3 | 5 | -5
+     | 4 | 0 | four  | 2 |  4 | 1 | -1
+     | 4 | 0 | four  | 2 |  4 | 2 |  2
+     | 4 | 0 | four  | 2 |  4 | 3 | -3
+     | 4 | 0 | four  | 2 |  4 | 2 |  4
+     | 4 | 0 | four  | 2 |  4 | 5 | -5
+     | 4 | 0 | four  | 5 | -5 | 1 | -1
+     | 4 | 0 | four  | 5 | -5 | 2 |  2
+     | 4 | 0 | four  | 5 | -5 | 3 | -3
+     | 4 | 0 | four  | 5 | -5 | 2 |  4
+     | 4 | 0 | four  | 5 | -5 | 5 | -5
+(100 rows)
+
 --
 --
 -- Inner joins (equi-joins)
@@ -249,14 +414,6 @@ SELECT '' AS "xxx", *
      | 4 | 0 | four | 2
 (2 rows)
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
- xxx | a | b |  c   | d 
------+---+---+------+---
-     | 2 | 2 | two  | 2
-     | 4 | 0 | four | 2
-(2 rows)
-
 -- mismatch number of columns
 -- currently, Postgres will fill in with underlying names
 SELECT '' AS "xxx", *
@@ -290,28 +447,6 @@ SELECT '' AS "xxx", *
      | 4 | 0 | four | 2 | 4
 (2 rows)
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL CROSS JOIN J2_TBL;
- xxx | i | j |   t   | i | k  
------+---+---+-------+---+----
-     | 1 | 3 | one   | 1 | -1
-     | 2 | 2 | two   | 1 | -1
-     | 3 | 1 | three | 1 | -1
-     | 4 | 0 | four  | 1 | -1
-     | 1 | 3 | one   | 2 |  2
-     | 2 | 2 | two   | 2 |  2
-     | 3 | 1 | three | 2 |  2
-     | 4 | 0 | four  | 2 |  2
-     | 1 | 3 | one   | 3 | -3
-     | 2 | 2 | two   | 3 | -3
-     | 3 | 1 | three | 3 | -3
-     | 4 | 0 | four  | 3 | -3
-     | 1 | 3 | one   | 2 |  4
-     | 2 | 2 | two   | 2 |  4
-     | 3 | 1 | three | 2 |  4
-     | 4 | 0 | four  | 2 |  4
-(16 rows)
-
 --
 -- Non-equi-joins
 --
@@ -320,8 +455,8 @@ SELECT '' AS "xxx", *
  xxx | i | j |   t   | i | k 
 -----+---+---+-------+---+---
      | 1 | 3 | one   | 2 | 2
-     | 2 | 2 | two   | 2 | 2
      | 1 | 3 | one   | 2 | 4
+     | 2 | 2 | two   | 2 | 2
      | 2 | 2 | two   | 2 | 4
      | 3 | 1 | three | 2 | 4
      | 4 | 0 | four  | 2 | 4
@@ -330,21 +465,48 @@ SELECT '' AS "xxx", *
 --
 -- Outer joins
 --
-SELECT '' AS "xxx", *
-  FROM J1_TBL OUTER JOIN J2_TBL USING (i);
-ERROR:  OUTER JOIN is not yet supported
 SELECT '' AS "xxx", *
   FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i);
-ERROR:  OUTER JOIN is not yet supported
+ xxx | i | j |   t   | k  
+-----+---+---+-------+----
+     | 1 | 3 | one   | -1
+     | 2 | 2 | two   |  2
+     | 2 | 2 | two   |  4
+     | 3 | 1 | three | -3
+     | 4 | 0 | four  |   
+(5 rows)
+
 SELECT '' AS "xxx", *
   FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i);
-ERROR:  OUTER JOIN is not yet supported
+ xxx | i | j |   t   | k  
+-----+---+---+-------+----
+     | 1 | 3 | one   | -1
+     | 2 | 2 | two   |  2
+     | 2 | 2 | two   |  4
+     | 3 | 1 | three | -3
+     | 5 |   |       | -5
+(5 rows)
+
+-- Note that OUTER is a noise word
 SELECT '' AS "xxx", *
-  FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i);
-ERROR:  OUTER JOIN is not yet supported
+  FROM J1_TBL FULL JOIN J2_TBL USING (i);
+ xxx | i | j |   t   | k  
+-----+---+---+-------+----
+     | 1 | 3 | one   | -1
+     | 2 | 2 | two   |  2
+     | 2 | 2 | two   |  4
+     | 3 | 1 | three | -3
+     | 4 | 0 | four  |   
+     | 5 |   |       | -5
+(6 rows)
+
 --
 -- More complicated constructs
 --
+-- UNION JOIN isn't implemented yet
+SELECT '' AS "xxx", *
+  FROM J1_TBL UNION JOIN J2_TBL;
+ERROR:  UNION JOIN is not implemented yet
 --
 -- Clean up
 --
diff --git a/src/test/regress/expected/point.out b/src/test/regress/expected/point.out
index 9f347bce797..3eab1c5b5d5 100644
--- a/src/test/regress/expected/point.out
+++ b/src/test/regress/expected/point.out
@@ -154,36 +154,36 @@ SELECT '' AS thirty, p1.f1 AS point1, p2.f1 AS point2
    WHERE (p1.f1 <-> p2.f1) > 3;
  thirty |   point1   |   point2   
 --------+------------+------------
-        | (-10,0)    | (0,0)
-        | (-3,4)     | (0,0)
-        | (5.1,34.5) | (0,0)
-        | (-5,-12)   | (0,0)
-        | (10,10)    | (0,0)
         | (0,0)      | (-10,0)
-        | (-3,4)     | (-10,0)
-        | (5.1,34.5) | (-10,0)
-        | (-5,-12)   | (-10,0)
-        | (10,10)    | (-10,0)
         | (0,0)      | (-3,4)
-        | (-10,0)    | (-3,4)
-        | (5.1,34.5) | (-3,4)
-        | (-5,-12)   | (-3,4)
-        | (10,10)    | (-3,4)
         | (0,0)      | (5.1,34.5)
-        | (-10,0)    | (5.1,34.5)
-        | (-3,4)     | (5.1,34.5)
-        | (-5,-12)   | (5.1,34.5)
-        | (10,10)    | (5.1,34.5)
         | (0,0)      | (-5,-12)
-        | (-10,0)    | (-5,-12)
-        | (-3,4)     | (-5,-12)
-        | (5.1,34.5) | (-5,-12)
-        | (10,10)    | (-5,-12)
         | (0,0)      | (10,10)
+        | (-10,0)    | (0,0)
+        | (-10,0)    | (-3,4)
+        | (-10,0)    | (5.1,34.5)
+        | (-10,0)    | (-5,-12)
         | (-10,0)    | (10,10)
+        | (-3,4)     | (0,0)
+        | (-3,4)     | (-10,0)
+        | (-3,4)     | (5.1,34.5)
+        | (-3,4)     | (-5,-12)
         | (-3,4)     | (10,10)
+        | (5.1,34.5) | (0,0)
+        | (5.1,34.5) | (-10,0)
+        | (5.1,34.5) | (-3,4)
+        | (5.1,34.5) | (-5,-12)
         | (5.1,34.5) | (10,10)
+        | (-5,-12)   | (0,0)
+        | (-5,-12)   | (-10,0)
+        | (-5,-12)   | (-3,4)
+        | (-5,-12)   | (5.1,34.5)
         | (-5,-12)   | (10,10)
+        | (10,10)    | (0,0)
+        | (10,10)    | (-10,0)
+        | (10,10)    | (-3,4)
+        | (10,10)    | (5.1,34.5)
+        | (10,10)    | (-5,-12)
 (30 rows)
 
 -- put distance result into output to allow sorting with GEQ optimizer - tgl 97/05/10
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 34059b8a6e7..eb19122cbce 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1158,6 +1158,47 @@ SELECT count(*) FROM shoe;
      4
 (1 row)
 
+--
+-- Simple test of qualified ON INSERT ... this did not work in 7.0 ...
+--
+create table foo (f1 int);
+create table foo2 (f1 int);
+create rule foorule as on insert to foo where f1 < 100
+do instead nothing;
+insert into foo values(1);
+insert into foo values(1001);
+select * from foo;
+  f1  
+------
+ 1001
+(1 row)
+
+drop rule foorule;
+-- this should fail because f1 is not exposed for unqualified reference:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (f1);
+ERROR:  Attribute 'f1' not found
+-- this is the correct way:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (new.f1);
+insert into foo values(2);
+insert into foo values(100);
+select * from foo;
+  f1  
+------
+ 1001
+  100
+(2 rows)
+
+select * from foo2;
+ f1 
+----
+  2
+(1 row)
+
+drop rule foorule;
+drop table foo;
+drop table foo2;
 --
 -- Check that ruleutils are working
 --
@@ -1200,7 +1241,7 @@ SELECT tablename, rulename, definition FROM pg_rules
  rtest_order1  | rtest_order_r1  | CREATE RULE rtest_order_r1 AS ON INSERT TO rtest_order1 DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 1 - this should run 3rd or 4th'::text);
  rtest_order1  | rtest_order_r2  | CREATE RULE rtest_order_r2 AS ON INSERT TO rtest_order1 DO INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 2 - this should run 1st'::text);
  rtest_order1  | rtest_order_r3  | CREATE RULE rtest_order_r3 AS ON INSERT TO rtest_order1 DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 3 - this should run 3rd or 4th'::text);
- rtest_order1  | rtest_order_r4  | CREATE RULE rtest_order_r4 AS ON INSERT TO rtest_order1 WHERE (rtest_order2.a < 100) DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 4 - this should run 2nd'::text);
+ rtest_order1  | rtest_order_r4  | CREATE RULE rtest_order_r4 AS ON INSERT TO rtest_order1 WHERE (new.a < 100) DO INSTEAD INSERT INTO rtest_order2 (a, b, c) VALUES (new.a, nextval('rtest_seq'::text), 'rule 4 - this should run 2nd'::text);
  rtest_person  | rtest_pers_del  | CREATE RULE rtest_pers_del AS ON DELETE TO rtest_person DO DELETE FROM rtest_admin WHERE (rtest_admin.pname = old.pname);
  rtest_person  | rtest_pers_upd  | CREATE RULE rtest_pers_upd AS ON UPDATE TO rtest_person DO UPDATE rtest_admin SET pname = new.pname WHERE (rtest_admin.pname = old.pname);
  rtest_system  | rtest_sys_del   | CREATE RULE rtest_sys_del AS ON DELETE TO rtest_system DO (DELETE FROM rtest_interface WHERE (rtest_interface.sysname = old.sysname); DELETE FROM rtest_admin WHERE (rtest_admin.sysname = old.sysname); );
diff --git a/src/test/regress/expected/select_implicit.out b/src/test/regress/expected/select_implicit.out
index adf0f794774..e3d74e5daba 100644
--- a/src/test/regress/expected/select_implicit.out
+++ b/src/test/regress/expected/select_implicit.out
@@ -120,7 +120,7 @@ ERROR:  GROUP BY position 3 is not in target list
 SELECT count(*) FROM test_missing_target x, test_missing_target y 
 	WHERE x.a = y.a
 	GROUP BY b ORDER BY b;
-ERROR:  Column 'b' is ambiguous
+ERROR:  Column reference "b" is ambiguous
 --   order w/ target under ambiguous condition
 --   failure NOT expected
 SELECT a, a FROM test_missing_target
@@ -282,7 +282,7 @@ SELECT count(b) FROM test_missing_target
 SELECT count(x.a) FROM test_missing_target x, test_missing_target y 
 	WHERE x.a = y.a
 	GROUP BY b/2 ORDER BY b/2;
-ERROR:  Column 'b' is ambiguous
+ERROR:  Column reference "b" is ambiguous
 --   group w/ existing GROUP BY target under ambiguous condition
 SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y 
 	WHERE x.a = y.a
@@ -299,7 +299,7 @@ SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y
 SELECT count(b) FROM test_missing_target x, test_missing_target y 
 	WHERE x.a = y.a
 	GROUP BY x.b/2;
-ERROR:  Column 'b' is ambiguous
+ERROR:  Column reference "b" is ambiguous
 --   group w/o existing GROUP BY target under ambiguous condition
 --   into a table
 SELECT count(x.b) INTO TABLE test_missing_target3 
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index c63bd0596fb..88972de5ea9 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -1,6 +1,6 @@
 --
 -- JOIN
--- Test join clauses
+-- Test JOIN clauses
 --
 
 CREATE TABLE J1_TBL (
@@ -34,6 +34,7 @@ INSERT INTO J2_TBL VALUES (1, -1);
 INSERT INTO J2_TBL VALUES (2, 2);
 INSERT INTO J2_TBL VALUES (3, -3);
 INSERT INTO J2_TBL VALUES (2, 4);
+INSERT INTO J2_TBL VALUES (5, -5);
 
 --
 -- CORRELATION NAMES
@@ -86,6 +87,9 @@ SELECT '' AS "xxx", tx.ii, tx.jj, tx.kk
   FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e))
     AS tx (ii, jj, tt, ii2, kk);
 
+SELECT '' AS "xxx", *
+  FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b;
+
 
 --
 --
@@ -128,9 +132,6 @@ SELECT '' AS "xxx", *
 SELECT '' AS "xxx", *
   FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a);
-
 -- mismatch number of columns
 -- currently, Postgres will fill in with underlying names
 SELECT '' AS "xxx", *
@@ -147,9 +148,6 @@ SELECT '' AS "xxx", *
 SELECT '' AS "xxx", *
   FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k);
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL CROSS JOIN J2_TBL;
-
 
 --
 -- Non-equi-joins
@@ -163,23 +161,25 @@ SELECT '' AS "xxx", *
 -- Outer joins
 --
 
-SELECT '' AS "xxx", *
-  FROM J1_TBL OUTER JOIN J2_TBL USING (i);
-
 SELECT '' AS "xxx", *
   FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i);
 
 SELECT '' AS "xxx", *
   FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i);
 
+-- Note that OUTER is a noise word
 SELECT '' AS "xxx", *
-  FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i);
+  FROM J1_TBL FULL JOIN J2_TBL USING (i);
 
 
 --
 -- More complicated constructs
 --
 
+-- UNION JOIN isn't implemented yet
+SELECT '' AS "xxx", *
+  FROM J1_TBL UNION JOIN J2_TBL;
+
 --
 -- Clean up
 --
diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql
index 2b7aa33a8cf..2c99f2a3ccb 100644
--- a/src/test/regress/sql/rules.sql
+++ b/src/test/regress/sql/rules.sql
@@ -686,6 +686,39 @@ SELECT * FROM shoe ORDER BY shoename;
 SELECT count(*) FROM shoe;
 
 
+--
+-- Simple test of qualified ON INSERT ... this did not work in 7.0 ...
+--
+create table foo (f1 int);
+create table foo2 (f1 int);
+
+create rule foorule as on insert to foo where f1 < 100
+do instead nothing;
+
+insert into foo values(1);
+insert into foo values(1001);
+select * from foo;
+
+drop rule foorule;
+
+-- this should fail because f1 is not exposed for unqualified reference:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (f1);
+-- this is the correct way:
+create rule foorule as on insert to foo where f1 < 100
+do instead insert into foo2 values (new.f1);
+
+insert into foo values(2);
+insert into foo values(100);
+
+select * from foo;
+select * from foo2;
+
+drop rule foorule;
+drop table foo;
+drop table foo2;
+
+
 --
 -- Check that ruleutils are working
 --
-- 
GitLab