diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 25b7517e1ecbfa7881d371a23df34a7f1c1eb140..f98ca70514ff4684ec3def0a6253b26c6c87b053 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.59 2000/09/29 18:21:26 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.60 2000/10/05 19:11:26 tgl Exp $
  *
  */
 
@@ -197,6 +197,26 @@ explain_outNode(StringInfo str, Plan *plan, int indent, ExplainState *es)
 		case T_Unique:
 			pname = "Unique";
 			break;
+		case T_SetOp:
+			switch (((SetOp *) plan)->cmd)
+			{
+				case SETOPCMD_INTERSECT:
+					pname = "SetOp Intersect";
+					break;
+				case SETOPCMD_INTERSECT_ALL:
+					pname = "SetOp Intersect All";
+					break;
+				case SETOPCMD_EXCEPT:
+					pname = "SetOp Except";
+					break;
+				case SETOPCMD_EXCEPT_ALL:
+					pname = "SetOp Except All";
+					break;
+				default:
+					pname = "SetOp ???";
+					break;
+			}
+			break;
 		case T_Hash:
 			pname = "Hash";
 			break;
@@ -320,8 +340,6 @@ explain_outNode(StringInfo str, Plan *plan, int indent, ExplainState *es)
 				Assert(rtentry != NULL);
 				rt_store(appendplan->inheritrelid, es->rtable, rtentry);
 			}
-			else
-				es->rtable = nth(whichplan, appendplan->unionrtables);
 
 			for (i = 0; i < indent; i++)
 				appendStringInfo(str, "  ");
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index f26c35e9e1049f7a0ef263ed5c717a1f4ab169b0..7c79df5904de827de40423f5428f36243c95e453 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -4,7 +4,7 @@
 #    Makefile for executor
 #
 # IDENTIFICATION
-#    $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.14 2000/09/29 18:21:28 tgl Exp $
+#    $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.15 2000/10/05 19:11:26 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -16,7 +16,7 @@ OBJS = execAmi.o execFlatten.o execJunk.o execMain.o \
        execProcnode.o execQual.o execScan.o execTuples.o \
        execUtils.o functions.o nodeAppend.o nodeAgg.o nodeHash.o \
        nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \
-       nodeNestloop.o nodeResult.o nodeSeqscan.o nodeSort.o \
+       nodeNestloop.o nodeResult.o nodeSeqscan.o nodeSetOp.o nodeSort.o \
        nodeUnique.o nodeGroup.o spi.o nodeSubplan.o \
        nodeSubqueryscan.o nodeTidscan.o
 
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 31ad291236b3953b7c5e2be627c7a378acb39dd1..9d008494b30b15aa098a762c2ae36736c06288a0 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Id: execAmi.c,v 1.52 2000/09/29 18:21:28 tgl Exp $
+ *	$Id: execAmi.c,v 1.53 2000/10/05 19:11:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,6 +42,7 @@
 #include "executor/nodeNestloop.h"
 #include "executor/nodeResult.h"
 #include "executor/nodeSeqscan.h"
+#include "executor/nodeSetOp.h"
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
@@ -345,6 +346,10 @@ ExecReScan(Plan *node, ExprContext *exprCtxt, Plan *parent)
 			ExecReScanUnique((Unique *) node, exprCtxt, parent);
 			break;
 
+		case T_SetOp:
+			ExecReScanSetOp((SetOp *) node, exprCtxt, parent);
+			break;
+
 		case T_Sort:
 			ExecReScanSort((Sort *) node, exprCtxt, parent);
 			break;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index a202da98d2e7f2e7f9822a16456e1410572e9f6d..3393559d6302c52922395cbfff2c642e38cf3fb9 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.128 2000/09/29 18:21:28 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.129 2000/10/05 19:11:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -463,7 +463,6 @@ ExecCheckPlanPerms(Plan *plan, List *rangeTable, CmdType operation)
 					/* Append implements expansion of inheritance */
 					ExecCheckRTPerms(app->inheritrtable, operation);
 
-					/* Check appended plans w/outer rangetable */
 					foreach(appendplans, app->appendplans)
 					{
 						ExecCheckPlanPerms((Plan *) lfirst(appendplans),
@@ -474,15 +473,11 @@ ExecCheckPlanPerms(Plan *plan, List *rangeTable, CmdType operation)
 				else
 				{
 					/* Append implements UNION, which must be a SELECT */
-					List	   *rtables = app->unionrtables;
-
-					/* Check appended plans with their rangetables */
 					foreach(appendplans, app->appendplans)
 					{
 						ExecCheckPlanPerms((Plan *) lfirst(appendplans),
-										   (List *) lfirst(rtables),
+										   rangeTable,
 										   CMD_SELECT);
-						rtables = lnext(rtables);
 					}
 				}
 				break;
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index d6a1537131175c47d76ce7323f84e8545b7b2d74..6269a7caa10381898cdb3d4298e4620c2da79597 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -12,7 +12,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.20 2000/09/29 18:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.21 2000/10/05 19:11:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -88,6 +88,7 @@
 #include "executor/nodeNestloop.h"
 #include "executor/nodeResult.h"
 #include "executor/nodeSeqscan.h"
+#include "executor/nodeSetOp.h"
 #include "executor/nodeSort.h"
 #include "executor/nodeSubplan.h"
 #include "executor/nodeSubqueryscan.h"
@@ -199,6 +200,10 @@ ExecInitNode(Plan *node, EState *estate, Plan *parent)
 			result = ExecInitUnique((Unique *) node, estate, parent);
 			break;
 
+		case T_SetOp:
+			result = ExecInitSetOp((SetOp *) node, estate, parent);
+			break;
+
 		case T_Group:
 			result = ExecInitGroup((Group *) node, estate, parent);
 			break;
@@ -322,6 +327,10 @@ ExecProcNode(Plan *node, Plan *parent)
 			result = ExecUnique((Unique *) node);
 			break;
 
+		case T_SetOp:
+			result = ExecSetOp((SetOp *) node);
+			break;
+
 		case T_Group:
 			result = ExecGroup((Group *) node);
 			break;
@@ -401,6 +410,9 @@ ExecCountSlotsNode(Plan *node)
 		case T_Unique:
 			return ExecCountSlotsUnique((Unique *) node);
 
+		case T_SetOp:
+			return ExecCountSlotsSetOp((SetOp *) node);
+
 		case T_Group:
 			return ExecCountSlotsGroup((Group *) node);
 
@@ -519,6 +531,10 @@ ExecEndNode(Plan *node, Plan *parent)
 			ExecEndUnique((Unique *) node);
 			break;
 
+		case T_SetOp:
+			ExecEndSetOp((SetOp *) node);
+			break;
+
 		case T_Group:
 			ExecEndGroup((Group *) node);
 			break;
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 008105719f34ebac7049a6bd1a81cb3cb73732da..d1cdfabab3b2cc6793a041a3f00541524d5bc7ed 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.40 2000/09/29 18:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.41 2000/10/05 19:11:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -762,6 +762,14 @@ NodeGetResultTupleSlot(Plan *node)
 			}
 			break;
 
+		case T_SetOp:
+			{
+				SetOpState *setopstate = ((SetOp *) node)->setopstate;
+
+				slot = setopstate->cstate.cs_ResultTupleSlot;
+			}
+			break;
+
 		case T_MergeJoin:
 			{
 				MergeJoinState *mergestate = ((MergeJoin *) node)->mergestate;
@@ -783,8 +791,8 @@ NodeGetResultTupleSlot(Plan *node)
 			 *	  should never get here
 			 * ----------------
 			 */
-			elog(ERROR, "NodeGetResultTupleSlot: node not yet supported: %d ",
-				 nodeTag(node));
+			elog(ERROR, "NodeGetResultTupleSlot: node not yet supported: %d",
+				 (int) nodeTag(node));
 
 			return NULL;
 	}
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 2b1eceb15d909b2aaad1b475bc43b9c6ace524b9..6c547854b555fa3c2fbbfa16240815cde5a40058 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeAppend.c,v 1.35 2000/07/12 02:37:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeAppend.c,v 1.36 2000/10/05 19:11:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -80,11 +80,9 @@ exec_append_initialize_next(Append *node)
 	AppendState *appendstate;
 	TupleTableSlot *result_slot;
 	List	   *rangeTable;
-
 	int			whichplan;
 	int			nplans;
-	List	   *rtables;
-	List	   *rtable;
+	List	   *inheritrtable;
 	RangeTblEntry *rtentry;
 
 	/* ----------------
@@ -98,8 +96,7 @@ exec_append_initialize_next(Append *node)
 
 	whichplan = appendstate->as_whichplan;
 	nplans = appendstate->as_nplans;
-	rtables = node->unionrtables;
-	rtable = node->inheritrtable;
+	inheritrtable = node->inheritrtable;
 
 	if (whichplan < 0)
 	{
@@ -131,19 +128,17 @@ exec_append_initialize_next(Append *node)
 		/* ----------------
 		 *		initialize the scan
 		 *		(and update the range table appropriately)
-		 *		  (doesn't this leave the range table hosed for anybody upstream
-		 *		   of the Append node??? - jolly )
+		 *
+		 *		(doesn't this leave the range table hosed for anybody upstream
+		 *		 of the Append node??? - jolly )
 		 * ----------------
 		 */
 		if (node->inheritrelid > 0)
 		{
-			rtentry = nth(whichplan, rtable);
+			rtentry = nth(whichplan, inheritrtable);
 			Assert(rtentry != NULL);
-
 			rt_store(node->inheritrelid, rangeTable, rtentry);
 		}
-		else
-			estate->es_range_table = nth(whichplan, rtables);
 
 		if (appendstate->as_junkFilter_list)
 		{
@@ -181,7 +176,7 @@ ExecInitAppend(Append *node, EState *estate, Plan *parent)
 {
 	AppendState *appendstate;
 	int			nplans;
-	List	   *rtable;
+	List	   *inheritrtable;
 	List	   *appendplans;
 	bool	   *initialized;
 	int			i;
@@ -201,7 +196,7 @@ ExecInitAppend(Append *node, EState *estate, Plan *parent)
 
 	appendplans = node->appendplans;
 	nplans = length(appendplans);
-	rtable = node->inheritrtable;
+	inheritrtable = node->inheritrtable;
 
 	initialized = (bool *) palloc(nplans * sizeof(bool));
 	MemSet(initialized, 0, nplans * sizeof(bool));
@@ -214,7 +209,6 @@ ExecInitAppend(Append *node, EState *estate, Plan *parent)
 	appendstate->as_whichplan = 0;
 	appendstate->as_nplans = nplans;
 	appendstate->as_initialized = initialized;
-	appendstate->as_rtentries = rtable;
 
 	node->appendstate = appendstate;
 
@@ -250,7 +244,7 @@ ExecInitAppend(Append *node, EState *estate, Plan *parent)
 
 		inherited_result_rel = true;
 
-		foreach(rtentryP, rtable)
+		foreach(rtentryP, inheritrtable)
 		{
 			RangeTblEntry *rtentry = lfirst(rtentryP);
 			Oid			reloid = rtentry->relid;
@@ -522,8 +516,7 @@ ExecEndAppend(Append *node)
 	estate->es_result_relation_info = NULL;
 
 	/*
-	 * XXX should free appendstate->as_rtentries  and
-	 * appendstate->as_junkfilter_list here
+	 * XXX should free appendstate->as_junkfilter_list here
 	 */
 }
 void
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
new file mode 100644
index 0000000000000000000000000000000000000000..8c285525051a31daa27d95568c70974376bd075f
--- /dev/null
+++ b/src/backend/executor/nodeSetOp.c
@@ -0,0 +1,341 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeSetOp.c
+ *	  Routines to handle INTERSECT and EXCEPT selection
+ *
+ * The input of a SetOp node consists of tuples from two relations,
+ * which have been combined into one dataset and sorted on all the nonjunk
+ * attributes.  In addition there is a junk attribute that shows which
+ * relation each tuple came from.  The SetOp node scans each group of
+ * identical tuples to determine how many came from each input relation.
+ * Then it is a simple matter to emit the output demanded by the SQL spec
+ * for INTERSECT, INTERSECT ALL, EXCEPT, or EXCEPT ALL.
+ *
+ * This node type is not used for UNION or UNION ALL, since those can be
+ * implemented more cheaply (there's no need for the junk attribute to
+ * identify the source relation).
+ *
+ *
+ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeSetOp.c,v 1.1 2000/10/05 19:11:26 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ *		ExecSetOp		- filter input to generate INTERSECT/EXCEPT results
+ *		ExecInitSetOp	- initialize node and subnodes..
+ *		ExecEndSetOp	- shutdown node and subnodes
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "executor/executor.h"
+#include "executor/nodeGroup.h"
+#include "executor/nodeSetOp.h"
+
+/* ----------------------------------------------------------------
+ *		ExecSetOp
+ * ----------------------------------------------------------------
+ */
+TupleTableSlot *				/* return: a tuple or NULL */
+ExecSetOp(SetOp *node)
+{
+	SetOpState *setopstate;
+	TupleTableSlot *resultTupleSlot;
+	Plan	   *outerPlan;
+	TupleDesc	tupDesc;
+
+	/* ----------------
+	 *	get information from the node
+	 * ----------------
+	 */
+	setopstate = node->setopstate;
+	outerPlan = outerPlan((Plan *) node);
+	resultTupleSlot = setopstate->cstate.cs_ResultTupleSlot;
+	tupDesc = ExecGetResultType(&setopstate->cstate);
+
+	/* ----------------
+	 *	If the previously-returned tuple needs to be returned more than
+	 *	once, keep returning it.
+	 * ----------------
+	 */
+	if (setopstate->numOutput > 0)
+	{
+		setopstate->numOutput--;
+		return resultTupleSlot;
+	}
+
+	/* Flag that we have no current tuple */
+	ExecClearTuple(resultTupleSlot);
+
+	/* ----------------
+	 *	Absorb groups of duplicate tuples, counting them, and
+	 *	saving the first of each group as a possible return value.
+	 *	At the end of each group, decide whether to return anything.
+	 *
+	 *	We assume that the tuples arrive in sorted order
+	 *	so we can detect duplicates easily.
+	 * ----------------
+	 */
+	for (;;)
+	{
+		TupleTableSlot *inputTupleSlot;
+		bool		endOfGroup;
+
+		/* ----------------
+		 *	 fetch a tuple from the outer subplan, unless we already did.
+		 * ----------------
+		 */
+		if (setopstate->cstate.cs_OuterTupleSlot == NULL &&
+			! setopstate->subplan_done)
+		{
+			setopstate->cstate.cs_OuterTupleSlot =
+				ExecProcNode(outerPlan, (Plan *) node);
+			if (TupIsNull(setopstate->cstate.cs_OuterTupleSlot))
+				setopstate->subplan_done = true;
+		}
+		inputTupleSlot = setopstate->cstate.cs_OuterTupleSlot;
+
+		if (TupIsNull(resultTupleSlot))
+		{
+			/*
+			 * First of group: save a copy in result slot, and reset
+			 * duplicate-counters for new group.
+			 */
+			if (setopstate->subplan_done)
+				return NULL;	/* no more tuples */
+			ExecStoreTuple(heap_copytuple(inputTupleSlot->val),
+						   resultTupleSlot,
+						   InvalidBuffer,
+						   true); /* free copied tuple at ExecClearTuple */
+			setopstate->numLeft = 0;
+			setopstate->numRight = 0;
+			endOfGroup = false;
+		}
+		else if (setopstate->subplan_done)
+		{
+			/*
+			 * Reached end of input, so finish processing final group
+			 */
+			endOfGroup = true;
+		}
+		else
+		{
+			/*
+			 * Else test if the new tuple and the previously saved tuple match.
+			 */
+			if (execTuplesMatch(inputTupleSlot->val,
+								resultTupleSlot->val,
+								tupDesc,
+								node->numCols, node->dupColIdx,
+								setopstate->eqfunctions,
+								setopstate->tempContext))
+				endOfGroup = false;
+			else
+				endOfGroup = true;
+		}
+
+		if (endOfGroup)
+		{
+			/*
+			 * We've reached the end of the group containing resultTuple.
+			 * Decide how many copies (if any) to emit.  This logic is
+			 * straight from the SQL92 specification.
+			 */
+			switch (node->cmd)
+			{
+				case SETOPCMD_INTERSECT:
+					if (setopstate->numLeft > 0 && setopstate->numRight > 0)
+						setopstate->numOutput = 1;
+					else
+						setopstate->numOutput = 0;
+					break;
+				case SETOPCMD_INTERSECT_ALL:
+					setopstate->numOutput =
+						(setopstate->numLeft < setopstate->numRight) ?
+						setopstate->numLeft : setopstate->numRight;
+					break;
+				case SETOPCMD_EXCEPT:
+					if (setopstate->numLeft > 0 && setopstate->numRight == 0)
+						setopstate->numOutput = 1;
+					else
+						setopstate->numOutput = 0;
+					break;
+				case SETOPCMD_EXCEPT_ALL:
+					setopstate->numOutput =
+						(setopstate->numLeft < setopstate->numRight) ?
+						0 : (setopstate->numLeft - setopstate->numRight);
+					break;
+				default:
+					elog(ERROR, "ExecSetOp: bogus command code %d",
+						 (int) node->cmd);
+					break;
+			}
+			/* Fall out of for-loop if we have tuples to emit */
+			if (setopstate->numOutput > 0)
+				break;
+			/* Else flag that we have no current tuple, and loop around */
+			ExecClearTuple(resultTupleSlot);
+		}
+		else
+		{
+			/*
+			 * Current tuple is member of same group as resultTuple.
+			 * Count it in the appropriate counter.
+			 */
+			int		flag;
+			bool	isNull;
+
+			flag = DatumGetInt32(heap_getattr(inputTupleSlot->val,
+											  node->flagColIdx,
+											  tupDesc,
+											  &isNull));
+			Assert(!isNull);
+			if (flag)
+				setopstate->numRight++;
+			else
+				setopstate->numLeft++;
+			/* Set flag to fetch a new input tuple, and loop around */
+			setopstate->cstate.cs_OuterTupleSlot = NULL;
+		}
+	}
+
+	/*
+	 * If we fall out of loop, then we need to emit at least one copy
+	 * of resultTuple.
+	 */
+	Assert(setopstate->numOutput > 0);
+	setopstate->numOutput--;
+	return resultTupleSlot;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecInitSetOp
+ *
+ *		This initializes the setop node state structures and
+ *		the node's subplan.
+ * ----------------------------------------------------------------
+ */
+bool							/* return: initialization status */
+ExecInitSetOp(SetOp *node, EState *estate, Plan *parent)
+{
+	SetOpState *setopstate;
+	Plan	   *outerPlan;
+
+	/* ----------------
+	 *	assign execution state to node
+	 * ----------------
+	 */
+	node->plan.state = estate;
+
+	/* ----------------
+	 *	create new SetOpState for node
+	 * ----------------
+	 */
+	setopstate = makeNode(SetOpState);
+	node->setopstate = setopstate;
+	setopstate->cstate.cs_OuterTupleSlot = NULL;
+	setopstate->subplan_done = false;
+	setopstate->numOutput = 0;
+
+	/* ----------------
+	 *	Miscellaneous initialization
+	 *
+	 *	SetOp nodes have no ExprContext initialization because
+	 *	they never call ExecQual or ExecProject.  But they do need a
+	 *	per-tuple memory context anyway for calling execTuplesMatch.
+	 * ----------------
+	 */
+	setopstate->tempContext =
+		AllocSetContextCreate(CurrentMemoryContext,
+							  "SetOp",
+							  ALLOCSET_DEFAULT_MINSIZE,
+							  ALLOCSET_DEFAULT_INITSIZE,
+							  ALLOCSET_DEFAULT_MAXSIZE);
+
+#define SETOP_NSLOTS 1
+	/* ------------
+	 * Tuple table initialization
+	 * ------------
+	 */
+	ExecInitResultTupleSlot(estate, &setopstate->cstate);
+
+	/* ----------------
+	 *	then initialize outer plan
+	 * ----------------
+	 */
+	outerPlan = outerPlan((Plan *) node);
+	ExecInitNode(outerPlan, estate, (Plan *) node);
+
+	/* ----------------
+	 *	setop nodes do no projections, so initialize
+	 *	projection info for this node appropriately
+	 * ----------------
+	 */
+	ExecAssignResultTypeFromOuterPlan((Plan *) node, &setopstate->cstate);
+	setopstate->cstate.cs_ProjInfo = NULL;
+
+	/*
+	 * Precompute fmgr lookup data for inner loop
+	 */
+	setopstate->eqfunctions =
+		execTuplesMatchPrepare(ExecGetResultType(&setopstate->cstate),
+							   node->numCols,
+							   node->dupColIdx);
+
+	return TRUE;
+}
+
+int
+ExecCountSlotsSetOp(SetOp *node)
+{
+	return ExecCountSlotsNode(outerPlan(node)) +
+	ExecCountSlotsNode(innerPlan(node)) +
+	SETOP_NSLOTS;
+}
+
+/* ----------------------------------------------------------------
+ *		ExecEndSetOp
+ *
+ *		This shuts down the subplan and frees resources allocated
+ *		to this node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndSetOp(SetOp *node)
+{
+	SetOpState *setopstate = node->setopstate;
+
+	ExecEndNode(outerPlan((Plan *) node), (Plan *) node);
+
+	MemoryContextDelete(setopstate->tempContext);
+
+	/* clean up tuple table */
+	ExecClearTuple(setopstate->cstate.cs_ResultTupleSlot);
+	setopstate->cstate.cs_OuterTupleSlot = NULL;
+}
+
+
+void
+ExecReScanSetOp(SetOp *node, ExprContext *exprCtxt, Plan *parent)
+{
+	SetOpState *setopstate = node->setopstate;
+
+	ExecClearTuple(setopstate->cstate.cs_ResultTupleSlot);
+	setopstate->cstate.cs_OuterTupleSlot = NULL;
+	setopstate->subplan_done = false;
+	setopstate->numOutput = 0;
+
+	/*
+	 * if chgParam of subnode is not null then plan will be re-scanned by
+	 * first ExecProcNode.
+	 */
+	if (((Plan *) node)->lefttree->chgParam == NULL)
+		ExecReScan(((Plan *) node)->lefttree, exprCtxt, (Plan *) node);
+}
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index aee6911e5e424d5794d624db3473f9373c393e25..933dcc8342586c9164e4d7c4670ab1059a7b6578 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.27 2000/08/24 03:29:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeSubplan.c,v 1.28 2000/10/05 19:11:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -328,7 +328,14 @@ ExecInitSubPlan(SubPlan *node, EState *estate, Plan *parent)
 /* ----------------------------------------------------------------
  *		ExecSetParamPlan
  *
- *		Executes plan of node and sets parameters.
+ *		Executes an InitPlan subplan and sets its output parameters.
+ *
+ * This is called from ExecEvalParam() when the value of a PARAM_EXEC
+ * parameter is requested and the param's execPlan field is set (indicating
+ * that the param has not yet been evaluated).  This allows lazy evaluation
+ * of initplans: we don't run the subplan until/unless we need its output.
+ * Note that this routine MUST clear the execPlan fields of the plan's
+ * output parameters after evaluating them!
  * ----------------------------------------------------------------
  */
 void
@@ -424,13 +431,13 @@ ExecSetParamPlan(SubPlan *node, ExprContext *econtext)
 		}
 	}
 
-	MemoryContextSwitchTo(oldcontext);
-
 	if (plan->extParam == NULL) /* un-correlated ... */
 	{
 		ExecEndNode(plan, plan);
 		node->needShutdown = false;
 	}
+
+	MemoryContextSwitchTo(oldcontext);
 }
 
 /* ----------------------------------------------------------------
@@ -470,6 +477,9 @@ ExecReScanSetParamPlan(SubPlan *node, Plan *parent)
 	 * node->plan->chgParam is not NULL... ExecReScan (plan, NULL, plan);
 	 */
 
+	/*
+	 * Mark this subplan's output parameters as needing recalculation
+	 */
 	foreach(lst, node->setParam)
 	{
 		ParamExecData *prm = &(plan->state->es_param_exec_vals[lfirsti(lst)]);
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index 45f07a08b10e6f2cf4f1ae6311d72b8241852946..5593f71d0c60caf586700dd5f4d38e0e803741a4 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -3,12 +3,16 @@
  * nodeSubqueryscan.c
  *	  Support routines for scanning subqueries (subselects in rangetable).
  *
+ * This is just enough different from sublinks (nodeSubplan.c) to mean that
+ * we need two sets of code.  Ought to look at trying to unify the cases.
+ *
+ *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.1 2000/09/29 18:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeSubqueryscan.c,v 1.2 2000/10/05 19:11:26 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,9 +53,7 @@ SubqueryNext(SubqueryScan *node)
 	SubqueryScanState *subquerystate;
 	EState	   *estate;
 	ScanDirection direction;
-	int			execdir;
 	TupleTableSlot *slot;
-	Const		countOne;
 
 	/* ----------------
 	 *	get information from the estate and scan state
@@ -60,7 +62,6 @@ SubqueryNext(SubqueryScan *node)
 	estate = node->scan.plan.state;
 	subquerystate = (SubqueryScanState *) node->scan.scanstate;
 	direction = estate->es_direction;
-	execdir = ScanDirectionIsBackward(direction) ? EXEC_BACK : EXEC_FOR;
 	slot = subquerystate->csstate.css_ScanTupleSlot;
 
 	/*
@@ -85,25 +86,13 @@ SubqueryNext(SubqueryScan *node)
 		return (slot);
 	}
 
-	memset(&countOne, 0, sizeof(countOne));
-	countOne.type = T_Const;
-	countOne.consttype = INT4OID;
-	countOne.constlen = sizeof(int4);
-	countOne.constvalue = Int32GetDatum(1);
-	countOne.constisnull = false;
-	countOne.constbyval = true;
-	countOne.constisset = false;
-	countOne.constiscast = false;
-
 	/* ----------------
 	 *	get the next tuple from the sub-query
 	 * ----------------
 	 */
-	slot = ExecutorRun(subquerystate->sss_SubQueryDesc,
-					   subquerystate->sss_SubEState,
-					   execdir,
-					   NULL,	/* offset */
-					   (Node *) &countOne);
+	subquerystate->sss_SubEState->es_direction = direction;
+
+	slot = ExecProcNode(node->subplan, node->subplan);
 
 	subquerystate->csstate.css_ScanTupleSlot = slot;
 
@@ -139,6 +128,7 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, Plan *parent)
 {
 	SubqueryScanState *subquerystate;
 	RangeTblEntry *rte;
+	EState	   *sp_estate;
 
 	/* ----------------
 	 *	SubqueryScan should not have any "normal" children.
@@ -177,18 +167,25 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, Plan *parent)
 
 	/* ----------------
 	 *	initialize subquery
+	 *
+	 *	This should agree with ExecInitSubPlan
 	 * ----------------
 	 */
 	rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
 	Assert(rte->subquery != NULL);
 
-	subquerystate->sss_SubQueryDesc = CreateQueryDesc(rte->subquery,
-													  node->subplan,
-													  None);
-	subquerystate->sss_SubEState = CreateExecutorState();
+	sp_estate = CreateExecutorState();
+	subquerystate->sss_SubEState = sp_estate;
+
+	sp_estate->es_range_table = rte->subquery->rtable;
+	sp_estate->es_param_list_info = estate->es_param_list_info;
+	sp_estate->es_param_exec_vals = estate->es_param_exec_vals;
+	sp_estate->es_tupleTable =
+		ExecCreateTupleTable(ExecCountSlotsNode(node->subplan) + 10);
+	sp_estate->es_snapshot = estate->es_snapshot;
 
-	ExecutorStart(subquerystate->sss_SubQueryDesc,
-				  subquerystate->sss_SubEState);
+	if (!ExecInitNode(node->subplan, sp_estate, NULL))
+		return false;
 
 	subquerystate->csstate.css_ScanTupleSlot = NULL;
 	subquerystate->csstate.cstate.cs_TupFromTlist = false;
@@ -247,10 +244,9 @@ ExecEndSubqueryScan(SubqueryScan *node)
 	 * close down subquery
 	 * ----------------
 	 */
-	ExecutorEnd(subquerystate->sss_SubQueryDesc,
-				subquerystate->sss_SubEState);
+	ExecEndNode(node->subplan, node->subplan);
 
-	/* XXX we seem to be leaking the querydesc and sub-EState... */
+	/* XXX we seem to be leaking the sub-EState and tuple table... */
 
 	subquerystate->csstate.css_ScanTupleSlot = NULL;
 
@@ -284,6 +280,7 @@ ExecSubqueryReScan(SubqueryScan *node, ExprContext *exprCtxt, Plan *parent)
 		return;
 	}
 
-	ExecReScan(node->subplan, NULL, NULL);
+	ExecReScan(node->subplan, NULL, node->subplan);
+
 	subquerystate->csstate.css_ScanTupleSlot = NULL;
 }
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 808ffbd075a5a47c914235e3412ad6a3538e9af2..1a0f4623978fdf8e519707d214a7cfcec6a4680f 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.123 2000/09/29 18:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.124 2000/10/05 19:11:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -176,7 +176,6 @@ _copyAppend(Append *from)
 	 * ----------------
 	 */
 	Node_Copy(from, newnode, appendplans);
-	Node_Copy(from, newnode, unionrtables);
 	newnode->inheritrelid = from->inheritrelid;
 	Node_Copy(from, newnode, inheritrtable);
 
@@ -565,6 +564,33 @@ _copyUnique(Unique *from)
 	return newnode;
 }
 
+/* ----------------
+ *		_copySetOp
+ * ----------------
+ */
+static SetOp *
+_copySetOp(SetOp *from)
+{
+	SetOp	   *newnode = makeNode(SetOp);
+
+	/* ----------------
+	 *	copy node superclass fields
+	 * ----------------
+	 */
+	CopyPlanFields((Plan *) from, (Plan *) newnode);
+
+	/* ----------------
+	 *	copy remainder of node
+	 * ----------------
+	 */
+	newnode->cmd = from->cmd;
+	newnode->numCols = from->numCols;
+	newnode->dupColIdx = palloc(from->numCols * sizeof(AttrNumber));
+	memcpy(newnode->dupColIdx, from->dupColIdx, from->numCols * sizeof(AttrNumber));
+	newnode->flagColIdx = from->flagColIdx;
+
+	return newnode;
+}
 
 /* ----------------
  *		_copyHash
@@ -1696,28 +1722,26 @@ _copyQuery(Query *from)
 	newnode->isPortal = from->isPortal;
 	newnode->isBinary = from->isBinary;
 	newnode->isTemp = from->isTemp;
-	newnode->unionall = from->unionall;
 	newnode->hasAggs = from->hasAggs;
 	newnode->hasSubLinks = from->hasSubLinks;
 
 	Node_Copy(from, newnode, rtable);
 	Node_Copy(from, newnode, jointree);
 
-	Node_Copy(from, newnode, targetList);
-
 	newnode->rowMarks = listCopy(from->rowMarks);
 
-	Node_Copy(from, newnode, distinctClause);
-	Node_Copy(from, newnode, sortClause);
+	Node_Copy(from, newnode, targetList);
+
 	Node_Copy(from, newnode, groupClause);
 	Node_Copy(from, newnode, havingQual);
-
-	/* why is intersectClause missing? */
-	Node_Copy(from, newnode, unionClause);
+	Node_Copy(from, newnode, distinctClause);
+	Node_Copy(from, newnode, sortClause);
 
 	Node_Copy(from, newnode, limitOffset);
 	Node_Copy(from, newnode, limitCount);
 
+	Node_Copy(from, newnode, setOperations);
+
 	/*
 	 * We do not copy the planner internal fields: base_rel_list,
 	 * join_rel_list, equi_key_list, query_pathkeys. Not entirely clear if
@@ -1734,17 +1758,9 @@ _copyInsertStmt(InsertStmt *from)
 	
 	if (from->relname)
 		newnode->relname = pstrdup(from->relname);
-	Node_Copy(from, newnode, distinctClause);
 	Node_Copy(from, newnode, cols);
 	Node_Copy(from, newnode, targetList);
-	Node_Copy(from, newnode, fromClause);
-	Node_Copy(from, newnode, whereClause);
-	Node_Copy(from, newnode, groupClause);
-	Node_Copy(from, newnode, havingClause);
-	Node_Copy(from, newnode, unionClause);
-	newnode->unionall = from->unionall;
-	Node_Copy(from, newnode, intersectClause);
-	Node_Copy(from, newnode, forUpdate);
+	Node_Copy(from, newnode, selectStmt);
 
 	return newnode;
 }
@@ -1790,15 +1806,11 @@ _copySelectStmt(SelectStmt *from)
 	Node_Copy(from, newnode, whereClause);
 	Node_Copy(from, newnode, groupClause);
 	Node_Copy(from, newnode, havingClause);
-	Node_Copy(from, newnode, intersectClause);
-	Node_Copy(from, newnode, exceptClause);
-	Node_Copy(from, newnode, unionClause);
 	Node_Copy(from, newnode, sortClause);
 	if (from->portalname)
 		newnode->portalname = pstrdup(from->portalname);
 	newnode->binary = from->binary;
 	newnode->istemp = from->istemp;
-	newnode->unionall = from->unionall;
 	Node_Copy(from, newnode, limitOffset);
 	Node_Copy(from, newnode, limitCount);
 	Node_Copy(from, newnode, forUpdate);
@@ -1806,6 +1818,20 @@ _copySelectStmt(SelectStmt *from)
 	return newnode;
 }
 
+static SetOperationStmt *
+_copySetOperationStmt(SetOperationStmt *from)
+{
+	SetOperationStmt *newnode = makeNode(SetOperationStmt);
+	
+	newnode->op = from->op;
+	newnode->all = from->all;
+	Node_Copy(from, newnode, larg);
+	Node_Copy(from, newnode, rarg);
+	newnode->colTypes = listCopy(from->colTypes);
+
+	return newnode;
+}
+
 static AlterTableStmt *
 _copyAlterTableStmt(AlterTableStmt *from)
 {
@@ -2553,6 +2579,9 @@ copyObject(void *from)
 		case T_Unique:
 			retval = _copyUnique(from);
 			break;
+		case T_SetOp:
+			retval = _copySetOp(from);
+			break;
 		case T_Hash:
 			retval = _copyHash(from);
 			break;
@@ -2700,6 +2729,9 @@ copyObject(void *from)
 		case T_SelectStmt:
 			retval = _copySelectStmt(from);
 			break;
+		case T_SetOperationStmt:
+			retval = _copySetOperationStmt(from);
+			break;
 		case T_AlterTableStmt:
 			retval = _copyAlterTableStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index bcb8e396ede8bf2b7c00931e1fb4a16ff95d9f27..ab8779cb3755932d2a0643f3d3f819955670db4c 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.74 2000/09/29 18:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.75 2000/10/05 19:11:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -33,8 +33,6 @@
 #include "utils/datum.h"
 
 
-static bool equali(List *a, List *b);
-
 /* Macro for comparing string fields that might be NULL */
 #define equalstr(a, b)  \
 	(((a) != NULL && (b) != NULL) ? (strcmp(a, b) == 0) : (a) == (b))
@@ -600,8 +598,6 @@ _equalQuery(Query *a, Query *b)
 		return false;
 	if (a->isTemp != b->isTemp)
 		return false;
-	if (a->unionall != b->unionall)
-		return false;
 	if (a->hasAggs != b->hasAggs)
 		return false;
 	if (a->hasSubLinks != b->hasSubLinks)
@@ -610,26 +606,24 @@ _equalQuery(Query *a, Query *b)
 		return false;
 	if (!equal(a->jointree, b->jointree))
 		return false;
-	if (!equal(a->targetList, b->targetList))
-		return false;
 	if (!equali(a->rowMarks, b->rowMarks))
 		return false;
-	if (!equal(a->distinctClause, b->distinctClause))
-		return false;
-	if (!equal(a->sortClause, b->sortClause))
+	if (!equal(a->targetList, b->targetList))
 		return false;
 	if (!equal(a->groupClause, b->groupClause))
 		return false;
 	if (!equal(a->havingQual, b->havingQual))
 		return false;
-	if (!equal(a->intersectClause, b->intersectClause))
+	if (!equal(a->distinctClause, b->distinctClause))
 		return false;
-	if (!equal(a->unionClause, b->unionClause))
+	if (!equal(a->sortClause, b->sortClause))
 		return false;
 	if (!equal(a->limitOffset, b->limitOffset))
 		return false;
 	if (!equal(a->limitCount, b->limitCount))
 		return false;
+	if (!equal(a->setOperations, b->setOperations))
+		return false;
 
 	/*
 	 * We do not check the internal-to-the-planner fields: base_rel_list,
@@ -645,27 +639,11 @@ _equalInsertStmt(InsertStmt *a, InsertStmt *b)
 {
 	if (!equalstr(a->relname, b->relname))
 		return false;
-	if (!equal(a->distinctClause, b->distinctClause))
-		return false;
 	if (!equal(a->cols, b->cols))
 		return false;
 	if (!equal(a->targetList, b->targetList))
 		return false;
-	if (!equal(a->fromClause, b->fromClause))
-		return false;
-	if (!equal(a->whereClause, b->whereClause))
-		return false;
-	if (!equal(a->groupClause, b->groupClause))
-		return false;
-	if (!equal(a->havingClause, b->havingClause))
-		return false;
-	if (!equal(a->unionClause, b->unionClause))
-		return false;
-	if (a->unionall != b->unionall)
-		return false;
-	if (!equal(a->intersectClause, b->intersectClause))
-		return false;
-	if (!equal(a->forUpdate, b->forUpdate))
+	if (!equal(a->selectStmt, b->selectStmt))
 		return false;
 
 	return true;
@@ -718,12 +696,6 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
 		return false;
 	if (!equal(a->havingClause, b->havingClause))
 		return false;
-	if (!equal(a->intersectClause, b->intersectClause))
-		return false;
-	if (!equal(a->exceptClause, b->exceptClause))
-		return false;
-	if (!equal(a->unionClause, b->unionClause))
-		return false;
 	if (!equal(a->sortClause, b->sortClause))
 		return false;
 	if (!equalstr(a->portalname, b->portalname))
@@ -732,8 +704,6 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
 		return false;
 	if (a->istemp != b->istemp)
 		return false;
-	if (a->unionall != b->unionall)
-		return false;
 	if (!equal(a->limitOffset, b->limitOffset))
 		return false;
 	if (!equal(a->limitCount, b->limitCount))
@@ -744,6 +714,23 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
 	return true;
 }
 
+static bool
+_equalSetOperationStmt(SetOperationStmt *a, SetOperationStmt *b)
+{
+	if (a->op != b->op)
+		return false;
+	if (a->all != b->all)
+		return false;
+	if (!equal(a->larg, b->larg))
+		return false;
+	if (!equal(a->rarg, b->rarg))
+		return false;
+	if (!equali(a->colTypes, b->colTypes))
+		return false;
+
+	return true;
+}
+
 static bool
 _equalAlterTableStmt(AlterTableStmt *a, AlterTableStmt *b)
 {
@@ -1929,6 +1916,9 @@ equal(void *a, void *b)
 		case T_SelectStmt:
 			retval = _equalSelectStmt(a, b);
 			break;
+		case T_SetOperationStmt:
+			retval = _equalSetOperationStmt(a, b);
+			break;
 		case T_AlterTableStmt:
 			retval = _equalAlterTableStmt(a, b);
 			break;
@@ -2159,25 +2149,3 @@ equal(void *a, void *b)
 
 	return retval;
 }
-
-/*
- * equali
- *	  compares two lists of integers
- */
-static bool
-equali(List *a, List *b)
-{
-	List	   *l;
-
-	foreach(l, a)
-	{
-		if (b == NIL)
-			return false;
-		if (lfirsti(l) != lfirsti(b))
-			return false;
-		b = lnext(b);
-	}
-	if (b != NIL)
-		return false;
-	return true;
-}
diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c
index 358e6a7eb6f0615ba78fc2e8be60e66ef7a6f691..66674b5c364f5819036b1f7cf145146e61f3e123 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.34 2000/09/29 18:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/list.c,v 1.35 2000/10/05 19:11:27 tgl Exp $
  *
  * NOTES
  *	  XXX a few of the following functions are duplicated to handle
@@ -236,11 +236,33 @@ freeList(List *list)
 	}
 }
 
+/*
+ * equali
+ *	  compares two lists of integers
+ */
+bool
+equali(List *list1, List *list2)
+{
+	List	   *l;
+
+	foreach(l, list1)
+	{
+		if (list2 == NIL)
+			return false;
+		if (lfirsti(l) != lfirsti(list2))
+			return false;
+		list2 = lnext(list2);
+	}
+	if (list2 != NIL)
+		return false;
+	return true;
+}
+
 /*
  *		sameseti
  *
  *		Returns t if two integer lists contain the same elements
- *		(but unlike equal(), they need not be in the same order)
+ *		(but unlike equali(), they need not be in the same order)
  *
  *		Caution: this routine could be fooled if list1 contains
  *		duplicate elements.  It is intended to be used on lists
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 39bc497343b3296568e53619a770908475adae4b..cf8c90ecad64d1849d3160243d2f2a49f828c371 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.127 2000/09/29 18:21:29 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.128 2000/10/05 19:11:27 tgl Exp $
  *
  * NOTES
  *	  Every (plan) node in POSTGRES has an associated "out" routine which
@@ -147,6 +147,7 @@ _outIndexStmt(StringInfo str, IndexStmt *node)
 static void
 _outSelectStmt(StringInfo str, SelectStmt *node)
 {
+	/* XXX this is pretty durn incomplete */
 	appendStringInfo(str, "SELECT :where ");
 	_outNode(str, node->whereClause);
 }
@@ -258,11 +259,10 @@ _outQuery(StringInfo str, Query *node)
 	_outToken(str, node->into);
 
 	appendStringInfo(str, " :isPortal %s :isBinary %s :isTemp %s"
-					 " :unionall %s :hasAggs %s :hasSubLinks %s :rtable ",
+					 " :hasAggs %s :hasSubLinks %s :rtable ",
 					 node->isPortal ? "true" : "false",
 					 node->isBinary ? "true" : "false",
 					 node->isTemp ? "true" : "false",
-					 node->unionall ? "true" : "false",
 					 node->hasAggs ? "true" : "false",
 					 node->hasSubLinks ? "true" : "false");
 	_outNode(str, node->rtable);
@@ -270,17 +270,11 @@ _outQuery(StringInfo str, Query *node)
 	appendStringInfo(str, " :jointree ");
 	_outNode(str, node->jointree);
 
-	appendStringInfo(str, " :targetList ");
-	_outNode(str, node->targetList);
-
 	appendStringInfo(str, " :rowMarks ");
 	_outIntList(str, node->rowMarks);
 
-	appendStringInfo(str, " :distinctClause ");
-	_outNode(str, node->distinctClause);
-
-	appendStringInfo(str, " :sortClause ");
-	_outNode(str, node->sortClause);
+	appendStringInfo(str, " :targetList ");
+	_outNode(str, node->targetList);
 
 	appendStringInfo(str, " :groupClause ");
 	_outNode(str, node->groupClause);
@@ -288,17 +282,20 @@ _outQuery(StringInfo str, Query *node)
 	appendStringInfo(str, " :havingQual ");
 	_outNode(str, node->havingQual);
 
-	appendStringInfo(str, " :intersectClause ");
-	_outNode(str, node->intersectClause);
+	appendStringInfo(str, " :distinctClause ");
+	_outNode(str, node->distinctClause);
 
-	appendStringInfo(str, " :unionClause ");
-	_outNode(str, node->unionClause);
+	appendStringInfo(str, " :sortClause ");
+	_outNode(str, node->sortClause);
 
 	appendStringInfo(str, " :limitOffset ");
 	_outNode(str, node->limitOffset);
 
 	appendStringInfo(str, " :limitCount ");
 	_outNode(str, node->limitCount);
+
+	appendStringInfo(str, " :setOperations ");
+	_outNode(str, node->setOperations);
 }
 
 static void
@@ -315,6 +312,19 @@ _outGroupClause(StringInfo str, GroupClause *node)
 					 node->tleSortGroupRef, node->sortop);
 }
 
+static void
+_outSetOperationStmt(StringInfo str, SetOperationStmt *node)
+{
+	appendStringInfo(str, " SETOPERATIONSTMT :op %d :all %s :larg ",
+					 (int) node->op,
+					 node->all ? "true" : "false");
+	_outNode(str, node->larg);
+	appendStringInfo(str, " :rarg ");
+	_outNode(str, node->rarg);
+	appendStringInfo(str, " :colTypes ");
+	_outIntList(str, node->colTypes);
+}
+
 /*
  * print the basic stuff of all nodes that inherit from Plan
  */
@@ -384,11 +394,7 @@ _outAppend(StringInfo str, Append *node)
 	appendStringInfo(str, " :appendplans ");
 	_outNode(str, node->appendplans);
 
-	appendStringInfo(str, " :unionrtables ");
-	_outNode(str, node->unionrtables);
-
-	appendStringInfo(str,
-					 " :inheritrelid %u :inheritrtable ",
+	appendStringInfo(str, " :inheritrelid %u :inheritrtable ",
 					 node->inheritrelid);
 	_outNode(str, node->inheritrtable);
 }
@@ -601,6 +607,22 @@ _outUnique(StringInfo str, Unique *node)
 		appendStringInfo(str, "%d ", (int) node->uniqColIdx[i]);
 }
 
+static void
+_outSetOp(StringInfo str, SetOp *node)
+{
+	int		i;
+
+	appendStringInfo(str, " SETOP ");
+	_outPlanInfo(str, (Plan *) node);
+
+	appendStringInfo(str, " :cmd %d :numCols %d :dupColIdx ",
+					 (int) node->cmd, node->numCols);
+	for (i = 0; i < node->numCols; i++)
+		appendStringInfo(str, "%d ", (int) node->dupColIdx[i]);
+	appendStringInfo(str, " :flagColIdx %d ",
+					 (int) node->flagColIdx);
+}
+
 /*
  *	Hash is a subclass of Plan
  */
@@ -1480,6 +1502,9 @@ _outNode(StringInfo str, void *obj)
 			case T_GroupClause:
 				_outGroupClause(str, obj);
 				break;
+			case T_SetOperationStmt:
+				_outSetOperationStmt(str, obj);
+				break;
 			case T_Plan:
 				_outPlan(str, obj);
 				break;
@@ -1531,6 +1556,9 @@ _outNode(StringInfo str, void *obj)
 			case T_Unique:
 				_outUnique(str, obj);
 				break;
+			case T_SetOp:
+				_outSetOp(str, obj);
+				break;
 			case T_Hash:
 				_outHash(str, obj);
 				break;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index a0417f8108b77bffbe1015a3bc21a3c51906ca55..c507cea3c45b84f57e72fef84b5ff5bc6d9c747c 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.42 2000/09/29 18:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.43 2000/10/05 19:11:27 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -338,6 +338,9 @@ plannode_type(Plan *p)
 		case T_Unique:
 			return "UNIQUE";
 			break;
+		case T_SetOp:
+			return "SETOP";
+			break;
 		case T_Hash:
 			return "HASH";
 			break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index da0ba22684f321656c36cb757d8000bfd78f414f..57174bfb60e7b58b3f9507ee804341a90f0b18fc 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.97 2000/09/29 18:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.98 2000/10/05 19:11:27 tgl Exp $
  *
  * NOTES
  *	  Most of the read functions for plan nodes are tested. (In fact, they
@@ -109,10 +109,6 @@ _readQuery()
 	token = lsptok(NULL, &length);		/* get isTemp */
 	local_node->isTemp = (token[0] == 't') ? true : false;
 
-	token = lsptok(NULL, &length);		/* skip :unionall */
-	token = lsptok(NULL, &length);		/* get unionall */
-	local_node->unionall = (token[0] == 't') ? true : false;
-
 	token = lsptok(NULL, &length);		/* skip the :hasAggs */
 	token = lsptok(NULL, &length);		/* get hasAggs */
 	local_node->hasAggs = (token[0] == 't') ? true : false;
@@ -127,17 +123,11 @@ _readQuery()
 	token = lsptok(NULL, &length);		/* skip :jointree */
 	local_node->jointree = nodeRead(true);
 
-	token = lsptok(NULL, &length);		/* skip :targetlist */
-	local_node->targetList = nodeRead(true);
-
 	token = lsptok(NULL, &length);		/* skip :rowMarks */
 	local_node->rowMarks = toIntList(nodeRead(true));
 
-	token = lsptok(NULL, &length);		/* skip :distinctClause */
-	local_node->distinctClause = nodeRead(true);
-
-	token = lsptok(NULL, &length);		/* skip :sortClause */
-	local_node->sortClause = nodeRead(true);
+	token = lsptok(NULL, &length);		/* skip :targetlist */
+	local_node->targetList = nodeRead(true);
 
 	token = lsptok(NULL, &length);		/* skip :groupClause */
 	local_node->groupClause = nodeRead(true);
@@ -145,11 +135,11 @@ _readQuery()
 	token = lsptok(NULL, &length);		/* skip :havingQual */
 	local_node->havingQual = nodeRead(true);
 
-	token = lsptok(NULL, &length);		/* skip :intersectClause */
-	local_node->intersectClause = nodeRead(true);
+	token = lsptok(NULL, &length);		/* skip :distinctClause */
+	local_node->distinctClause = nodeRead(true);
 
-	token = lsptok(NULL, &length);		/* skip :unionClause */
-	local_node->unionClause = nodeRead(true);
+	token = lsptok(NULL, &length);		/* skip :sortClause */
+	local_node->sortClause = nodeRead(true);
 
 	token = lsptok(NULL, &length);		/* skip :limitOffset */
 	local_node->limitOffset = nodeRead(true);
@@ -157,6 +147,9 @@ _readQuery()
 	token = lsptok(NULL, &length);		/* skip :limitCount */
 	local_node->limitCount = nodeRead(true);
 
+	token = lsptok(NULL, &length);		/* skip :setOperations */
+	local_node->setOperations = nodeRead(true);
+
 	return local_node;
 }
 
@@ -208,6 +201,39 @@ _readGroupClause()
 	return local_node;
 }
 
+/* ----------------
+ *		_readSetOperationStmt
+ * ----------------
+ */
+static SetOperationStmt *
+_readSetOperationStmt()
+{
+	SetOperationStmt *local_node;
+	char	   *token;
+	int			length;
+
+	local_node = makeNode(SetOperationStmt);
+
+	token = lsptok(NULL, &length);		/* eat :op */
+	token = lsptok(NULL, &length);		/* get op */
+	local_node->op = (SetOperation) atoi(token);
+
+	token = lsptok(NULL, &length);		/* eat :all */
+	token = lsptok(NULL, &length);		/* get all */
+	local_node->all = (token[0] == 't') ? true : false;
+
+	token = lsptok(NULL, &length);		/* eat :larg */
+	local_node->larg = nodeRead(true);	/* get larg */
+
+	token = lsptok(NULL, &length);		/* eat :rarg */
+	local_node->rarg = nodeRead(true);	/* get rarg */
+
+	token = lsptok(NULL, &length);		/* eat :colTypes */
+	local_node->colTypes = toIntList(nodeRead(true));
+
+	return local_node;
+}
+
 /* ----------------
  *		_getPlan
  * ----------------
@@ -322,9 +348,6 @@ _readAppend()
 	token = lsptok(NULL, &length);		/* eat :appendplans */
 	local_node->appendplans = nodeRead(true);	/* now read it */
 
-	token = lsptok(NULL, &length);		/* eat :unionrtables */
-	local_node->unionrtables = nodeRead(true);	/* now read it */
-
 	token = lsptok(NULL, &length);		/* eat :inheritrelid */
 	token = lsptok(NULL, &length);		/* get inheritrelid */
 	local_node->inheritrelid = strtoul(token, NULL, 10);
@@ -1995,6 +2018,8 @@ parsePlanString(void)
 		return_value = _readSortClause();
 	else if (length == 11 && strncmp(token, "GROUPCLAUSE", length) == 0)
 		return_value = _readGroupClause();
+	else if (length == 16 && strncmp(token, "SETOPERATIONSTMT", length) == 0)
+		return_value = _readSetOperationStmt();
 	else if (length == 4 && strncmp(token, "CASE", length) == 0)
 		return_value = _readCaseExpr();
 	else if (length == 4 && strncmp(token, "WHEN", length) == 0)
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 8ab2aeec91853f33503db0ceeae5fe048f51a9c4..7e017a746f1d5b35da8c1317c8c2971506a4d5a7 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.65 2000/09/29 18:21:31 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/path/allpaths.c,v 1.66 2000/10/05 19:11:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -98,7 +98,8 @@ set_base_rel_pathlist(Query *root)
 			 */
 
 			/* Generate the plan for the subquery */
-			rel->subplan = planner(rte->subquery);
+			rel->subplan = subquery_planner(rte->subquery,
+											-1.0 /* default case */ );
 
 			/* Copy number of output rows from subplan */
 			rel->tuples = rel->subplan->plan_rows;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index cc308b4fc96869617c23ee9caa02b65b8b91f491..eb005121cd55f05cd0aa6eb95e6ce295493af7b4 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.97 2000/09/29 18:21:33 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.98 2000/10/05 19:11:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,8 +68,6 @@ static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
 			   ScanDirection indexscandir);
 static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid,
 			 List *tideval);
-static SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
-									   Index scanrelid, Plan *subplan);
 static NestLoop *make_nestloop(List *tlist,
 							   List *joinclauses, List *otherclauses,
 							   Plan *lefttree, Plan *righttree,
@@ -86,7 +84,6 @@ static MergeJoin *make_mergejoin(List *tlist,
 								 Plan *lefttree, Plan *righttree,
 								 JoinType jointype);
 static void copy_path_costsize(Plan *dest, Path *src);
-static void copy_plan_costsize(Plan *dest, Plan *src);
 
 /*
  * create_plan
@@ -1109,7 +1106,7 @@ copy_path_costsize(Plan *dest, Path *src)
  * but it helps produce more reasonable-looking EXPLAIN output.
  * (Some callers alter the info after copying it.)
  */
-static void
+void
 copy_plan_costsize(Plan *dest, Plan *src)
 {
 	if (src)
@@ -1206,7 +1203,7 @@ make_tidscan(List *qptlist,
 	return node;
 }
 
-static SubqueryScan *
+SubqueryScan *
 make_subqueryscan(List *qptlist,
 				  List *qpqual,
 				  Index scanrelid,
@@ -1593,6 +1590,67 @@ make_unique(List *tlist, Plan *lefttree, List *distinctList)
 	return node;
 }
 
+/*
+ * distinctList is a list of SortClauses, identifying the targetlist items
+ * that should be considered by the SetOp filter.
+ */
+
+SetOp *
+make_setop(SetOpCmd cmd, List *tlist, Plan *lefttree,
+		   List *distinctList, AttrNumber flagColIdx)
+{
+	SetOp	   *node = makeNode(SetOp);
+	Plan	   *plan = &node->plan;
+	int			numCols = length(distinctList);
+	int			keyno = 0;
+	AttrNumber *dupColIdx;
+	List	   *slitem;
+
+	copy_plan_costsize(plan, lefttree);
+
+	/*
+	 * Charge one cpu_operator_cost per comparison per input tuple. We
+	 * assume all columns get compared at most of the tuples.
+	 */
+	plan->total_cost += cpu_operator_cost * plan->plan_rows * numCols;
+
+	/*
+	 * As for Group, we make the unsupported assumption that there will be
+	 * 10% as many tuples out as in.
+	 */
+	plan->plan_rows *= 0.1;
+	if (plan->plan_rows < 1)
+		plan->plan_rows = 1;
+
+	plan->state = (EState *) NULL;
+	plan->targetlist = tlist;
+	plan->qual = NIL;
+	plan->lefttree = lefttree;
+	plan->righttree = NULL;
+
+	/*
+	 * convert SortClause list into array of attr indexes, as wanted by
+	 * exec
+	 */
+	Assert(numCols > 0);
+	dupColIdx = (AttrNumber *) palloc(sizeof(AttrNumber) * numCols);
+
+	foreach(slitem, distinctList)
+	{
+		SortClause *sortcl = (SortClause *) lfirst(slitem);
+		TargetEntry *tle = get_sortgroupclause_tle(sortcl, tlist);
+
+		dupColIdx[keyno++] = tle->resdom->resno;
+	}
+
+	node->cmd = cmd;
+	node->numCols = numCols;
+	node->dupColIdx = dupColIdx;
+	node->flagColIdx = flagColIdx;
+
+	return node;
+}
+
 Result *
 make_result(List *tlist,
 			Node *resconstantqual,
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 29cfccfef7b56ba525418b9993f8b2cb6122e016..a9747b3279941d58ff1fbb440b79f4d84a2d50e8 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.60 2000/09/29 18:21:33 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planmain.c,v 1.61 2000/10/05 19:11:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -174,8 +174,6 @@ subplanner(Query *root,
 	List	   *brel;
 	RelOptInfo *final_rel;
 	Plan	   *resultplan;
-	MemoryContext mycontext;
-	MemoryContext oldcxt;
 	Path	   *cheapestpath;
 	Path	   *presortedpath;
 
@@ -227,24 +225,6 @@ subplanner(Query *root,
 	 */
 	root->query_pathkeys = canonicalize_pathkeys(root, root->query_pathkeys);
 
-	/*
-	 * We might allocate quite a lot of storage during planning (due to
-	 * constructing lots of Paths), but all of it can be reclaimed after
-	 * we generate the finished Plan tree.  Work in a temporary context
-	 * to let that happen.  We make the context a child of
-	 * TransactionCommandContext so it will be freed if error abort.
-	 *
-	 * Note: beware of trying to move this up to the start of this routine.
-	 * Some of the data structures built above --- notably the pathkey
-	 * equivalence sets --- will still be needed after this routine exits.
-	 */
-	mycontext = AllocSetContextCreate(TransactionCommandContext,
-									  "Planner",
-									  ALLOCSET_DEFAULT_MINSIZE,
-									  ALLOCSET_DEFAULT_INITSIZE,
-									  ALLOCSET_DEFAULT_MAXSIZE);
-	oldcxt = MemoryContextSwitchTo(mycontext);
-
 	/*
 	 * Ready to do the primary planning.
 	 */
@@ -355,25 +335,5 @@ subplanner(Query *root,
 
 plan_built:
 
-	/*
-	 * Must copy the completed plan tree and its pathkeys out of temporary
-	 * context.  We also have to copy the rtable in case it contains any
-	 * subqueries.  (If it does, they'll have been modified during the
-	 * recursive invocation of planner.c, and hence will contain substructure
-	 * allocated in my temporary context...)
-	 */
-	MemoryContextSwitchTo(oldcxt);
-
-	resultplan = copyObject(resultplan);
-
-	root->query_pathkeys = copyObject(root->query_pathkeys);
-
-	root->rtable = copyObject(root->rtable);
-
-	/*
-	 * Now we can release the Path storage.
-	 */
-	MemoryContextDelete(mycontext);
-
 	return resultplan;
 }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 937628121b363ccd0c8d29fdeeaf81bb97ad902f..d73ca9a34ac1b4f4121f1a8779c3b1ddf1e3ff07 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.91 2000/09/29 18:21:33 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.92 2000/10/05 19:11:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,7 +48,6 @@ static List *make_subplanTargetList(Query *parse, List *tlist,
 static Plan *make_groupplan(List *group_tlist, bool tuplePerGroup,
 			   List *groupClause, AttrNumber *grpColIdx,
 			   bool is_presorted, Plan *subplan);
-static Plan *make_sortplan(List *tlist, Plan *plannode, List *sortcls);
 
 /*****************************************************************************
  *
@@ -60,43 +59,32 @@ planner(Query *parse)
 {
 	Plan	   *result_plan;
 	Index		save_PlannerQueryLevel;
-	List	   *save_PlannerInitPlan;
 	List	   *save_PlannerParamVar;
-	int			save_PlannerPlanId;
 
 	/*
-	 * The outer planner can be called recursively, for example to process
-	 * a subquery in the rangetable.  (A less obvious example occurs when
-	 * eval_const_expressions tries to simplify an SQL function.)
-	 * So, global state variables must be saved and restored.
+	 * The planner can be called recursively (an example is when
+	 * eval_const_expressions tries to simplify an SQL function).
+	 * So, these global state variables must be saved and restored.
 	 *
-	 * (Perhaps these should be moved into the Query structure instead?)
+	 * These vars cannot be moved into the Query structure since their
+	 * whole purpose is communication across multiple sub-Queries.
+	 *
+	 * Note we do NOT save and restore PlannerPlanId: it exists to assign
+	 * unique IDs to SubPlan nodes, and we want those IDs to be unique
+	 * for the life of a backend.  Also, PlannerInitPlan is saved/restored
+	 * in subquery_planner, not here.
 	 */
 	save_PlannerQueryLevel = PlannerQueryLevel;
-	save_PlannerInitPlan = PlannerInitPlan;
 	save_PlannerParamVar = PlannerParamVar;
-	save_PlannerPlanId = PlannerPlanId;
 
-	/* Initialize state for subselects */
-	PlannerQueryLevel = 1;
-	PlannerInitPlan = NULL;
-	PlannerParamVar = NULL;
-	PlannerPlanId = 0;
+	/* Initialize state for handling outer-level references and params */
+	PlannerQueryLevel = 0;		/* will be 1 in top-level subquery_planner */
+	PlannerParamVar = NIL;
 
-	/* this should go away sometime soon */
-	transformKeySetQuery(parse);
-
-	/* primary planning entry point (may recurse for sublinks) */
+	/* primary planning entry point (may recurse for subqueries) */
 	result_plan = subquery_planner(parse, -1.0 /* default case */ );
 
-	Assert(PlannerQueryLevel == 1);
-
-	/* if top-level query had subqueries, do housekeeping for them */
-	if (PlannerPlanId > 0)
-	{
-		(void) SS_finalize_plan(result_plan);
-		result_plan->initPlan = PlannerInitPlan;
-	}
+	Assert(PlannerQueryLevel == 0);
 
 	/* executor wants to know total number of Params used overall */
 	result_plan->nParamExec = length(PlannerParamVar);
@@ -106,9 +94,7 @@ planner(Query *parse)
 
 	/* restore state for outer planner, if any */
 	PlannerQueryLevel = save_PlannerQueryLevel;
-	PlannerInitPlan = save_PlannerInitPlan;
 	PlannerParamVar = save_PlannerParamVar;
-	PlannerPlanId = save_PlannerPlanId;
 
 	return result_plan;
 }
@@ -125,14 +111,9 @@ planner(Query *parse)
  *
  * Basically, this routine does the stuff that should only be done once
  * per Query object.  It then calls union_planner, which may be called
- * recursively on the same Query node in order to handle UNIONs and/or
- * inheritance.  subquery_planner is called recursively from subselect.c
- * to handle sub-Query nodes found within the query's expressions.
- *
- * prepunion.c uses an unholy combination of calling union_planner when
- * recursing on the primary Query node, or subquery_planner when recursing
- * on a UNION'd Query node that hasn't previously been seen by
- * subquery_planner.  That whole chunk of code needs rewritten from scratch.
+ * recursively on the same Query node in order to handle inheritance.
+ * subquery_planner will be called recursively to handle sub-Query nodes
+ * found within the query's expressions and rangetable.
  *
  * Returns a query plan.
  *--------------------
@@ -140,6 +121,20 @@ planner(Query *parse)
 Plan *
 subquery_planner(Query *parse, double tuple_fraction)
 {
+	List	   *saved_initplan = PlannerInitPlan;
+	int			saved_planid = PlannerPlanId;
+	Plan	   *plan;
+	List	   *lst;
+
+	/* Set up for a new level of subquery */
+	PlannerQueryLevel++;
+	PlannerInitPlan = NIL;
+
+#ifdef ENABLE_KEY_SET_QUERY
+	/* this should go away sometime soon */
+	transformKeySetQuery(parse);
+#endif
+
 	/*
 	 * Check to see if any subqueries in the rangetable can be merged into
 	 * this query.
@@ -179,9 +174,9 @@ subquery_planner(Query *parse, double tuple_fraction)
 											  EXPRKIND_HAVING);
 
 	/*
-	 * Do the main planning (potentially recursive)
+	 * Do the main planning (potentially recursive for inheritance)
 	 */
-	return union_planner(parse, tuple_fraction);
+	plan = union_planner(parse, tuple_fraction);
 
 	/*
 	 * XXX should any more of union_planner's activity be moved here?
@@ -190,6 +185,35 @@ subquery_planner(Query *parse, double tuple_fraction)
 	 * but I suspect it would pay off in simplicity and avoidance of
 	 * wasted cycles.
 	 */
+
+	/*
+	 * If any subplans were generated, or if we're inside a subplan,
+	 * build subPlan, extParam and locParam lists for plan nodes.
+	 */
+	if (PlannerPlanId != saved_planid || PlannerQueryLevel > 1)
+	{
+		(void) SS_finalize_plan(plan);
+		/*
+		 * At the moment, SS_finalize_plan doesn't handle initPlans
+		 * and so we assign them to the topmost plan node.
+		 */
+		plan->initPlan = PlannerInitPlan;
+		/* Must add the initPlans' extParams to the topmost node's, too */
+		foreach(lst, plan->initPlan)
+		{
+			SubPlan *subplan = (SubPlan *) lfirst(lst);
+
+			plan->extParam = set_unioni(plan->extParam,
+										subplan->plan->extParam);
+		}
+	}
+
+	/* Return to outer subquery context */
+	PlannerQueryLevel--;
+	PlannerInitPlan = saved_initplan;
+	/* we do NOT restore PlannerPlanId; that's not an oversight! */
+
+	return plan;
 }
 
 /*
@@ -320,9 +344,10 @@ is_simple_subquery(Query *subquery)
 	if (subquery->limitOffset || subquery->limitCount)
 		elog(ERROR, "LIMIT is not supported in subselects");
 	/*
-	 * Can't currently pull up a union query.  Maybe after querytree redesign.
+	 * Can't currently pull up a query with setops.
+	 * Maybe after querytree redesign...
 	 */
-	if (subquery->unionClause)
+	if (subquery->setOperations)
 		return false;
 	/*
 	 * Can't pull up a subquery involving grouping, aggregation, or sorting.
@@ -573,7 +598,7 @@ preprocess_qual_conditions(Query *parse, Node *jtnode)
 
 /*--------------------
  * union_planner
- *	  Invokes the planner on union-type queries (both regular UNIONs and
+ *	  Invokes the planner on union-type queries (both set operations and
  *	  appends produced by inheritance), recursing if necessary to get them
  *	  all, then processes normal plans.
  *
@@ -606,24 +631,31 @@ union_planner(Query *parse,
 	Index		rt_index;
 	List	   *inheritors;
 
-	if (parse->unionClause)
+	if (parse->setOperations)
 	{
-		result_plan = plan_union_queries(parse);
-		/* XXX do we need to do this? bjm 12/19/97 */
-		tlist = preprocess_targetlist(tlist,
-									  parse->commandType,
-									  parse->resultRelation,
-									  parse->rtable);
+		/*
+		 * Construct the plan for set operations.  The result will
+		 * not need any work except perhaps a top-level sort.
+		 */
+		result_plan = plan_set_operations(parse);
+
+		/*
+		 * We should not need to call preprocess_targetlist, since we must
+		 * be in a SELECT query node.
+		 */
+		Assert(parse->commandType == CMD_SELECT);
 
 		/*
 		 * We leave current_pathkeys NIL indicating we do not know sort
-		 * order.  This is correct for the appended-together subplan
-		 * results, even if the subplans themselves produced sorted results.
+		 * order.  This is correct when the top set operation is UNION ALL,
+		 * since the appended-together results are unsorted even if the
+		 * subplans were sorted.  For other set operations we could be
+		 * smarter --- future improvement!
 		 */
 
 		/*
 		 * Calculate pathkeys that represent grouping/ordering
-		 * requirements
+		 * requirements (grouping should always be null, but...)
 		 */
 		group_pathkeys = make_pathkeys_for_sortclauses(parse->groupClause,
 													   tlist);
@@ -886,7 +918,7 @@ union_planner(Query *parse,
 				tuple_fraction = 0.25;
 		}
 
-		/* Generate the (sub) plan */
+		/* Generate the basic plan for this Query */
 		result_plan = query_planner(parse,
 									sub_tlist,
 									tuple_fraction);
@@ -1176,7 +1208,7 @@ make_groupplan(List *group_tlist,
  * make_sortplan
  *	  Add a Sort node to implement an explicit ORDER BY clause.
  */
-static Plan *
+Plan *
 make_sortplan(List *tlist, Plan *plannode, List *sortcls)
 {
 	List	   *sort_tlist;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index ad1b47aaeb66590d9933d6f67f8e26a9e91e2a06..14c9dad3ef321cdd7968614ef1b573cda178850f 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.66 2000/09/29 18:21:33 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.67 2000/10/05 19:11:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -103,9 +103,15 @@ set_plan_references(Plan *plan)
 			fix_expr_references(plan, (Node *) plan->qual);
 			break;
 		case T_SubqueryScan:
+			/*
+			 * We do not do set_uppernode_references() here, because
+			 * a SubqueryScan will always have been created with correct
+			 * references to its subplan's outputs to begin with.
+			 */
 			fix_expr_references(plan, (Node *) plan->targetlist);
 			fix_expr_references(plan, (Node *) plan->qual);
-			/* No need to recurse into the subplan, it's fixed already */
+			/* Recurse into subplan too */
+			set_plan_references(((SubqueryScan *) plan)->subplan);
 			break;
 		case T_NestLoop:
 			set_join_references((Join *) plan);
@@ -132,6 +138,7 @@ set_plan_references(Plan *plan)
 		case T_Material:
 		case T_Sort:
 		case T_Unique:
+		case T_SetOp:
 		case T_Hash:
 
 			/*
@@ -170,6 +177,7 @@ set_plan_references(Plan *plan)
 			 * Append, like Sort et al, doesn't actually evaluate its
 			 * targetlist or quals, and we haven't bothered to give it
 			 * its own tlist copy.  So, don't fix targetlist/qual.
+			 * But do recurse into subplans.
 			 */
 			foreach(pl, ((Append *) plan)->appendplans)
 				set_plan_references((Plan *) lfirst(pl));
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 182d1384aa1764c8547c7128675504858e823896..03e38371df5e7bec60ba03234ea6ec152f32444e 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.42 2000/09/29 18:21:33 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.43 2000/10/05 19:11:29 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,7 +29,8 @@
 Index		PlannerQueryLevel;	/* level of current query */
 List	   *PlannerInitPlan;	/* init subplans for current query */
 List	   *PlannerParamVar;	/* to get Var from Param->paramid */
-int			PlannerPlanId;		/* to assign unique ID to subquery plans */
+
+int			PlannerPlanId = 0;	/* to assign unique ID to subquery plans */
 
 /*--------------------
  * PlannerParamVar is a list of Var nodes, wherein the n'th entry
@@ -81,7 +82,7 @@ replace_var(Var *var)
 
 	/*
 	 * If there's already a PlannerParamVar entry for this same Var, just
-	 * use it.	NOTE: in situations involving UNION or inheritance, it is
+	 * use it.	NOTE: in sufficiently complex querytrees, it is
 	 * possible for the same varno/varlevel to refer to different RTEs in
 	 * different parts of the parsetree, so that different fields might
 	 * end up sharing the same Param number.  As long as we check the
@@ -128,11 +129,6 @@ make_subplan(SubLink *slink)
 	Plan	   *plan;
 	List	   *lst;
 	Node	   *result;
-	List	   *saved_ip = PlannerInitPlan;
-
-	PlannerInitPlan = NULL;
-
-	PlannerQueryLevel++;		/* we become child */
 
 	/*
 	 * Check to see if this node was already processed; if so we have
@@ -181,45 +177,30 @@ make_subplan(SubLink *slink)
 	else
 		tuple_fraction = -1.0;	/* default behavior */
 
-	node->plan = plan = subquery_planner(subquery, tuple_fraction);
-
 	/*
-	 * Assign subPlan, extParam and locParam to plan nodes. At the moment,
-	 * SS_finalize_plan doesn't handle initPlan-s and so we assign them to
-	 * the topmost plan node and take care about its extParam too.
+	 * Generate the plan for the subquery.
 	 */
-	(void) SS_finalize_plan(plan);
-	plan->initPlan = PlannerInitPlan;
-
-	/* Create extParam list as union of InitPlan-s' lists */
-	foreach(lst, PlannerInitPlan)
-	{
-		List	   *lp;
-
-		foreach(lp, ((SubPlan *) lfirst(lst))->plan->extParam)
-		{
-			if (!intMember(lfirsti(lp), plan->extParam))
-				plan->extParam = lappendi(plan->extParam, lfirsti(lp));
-		}
-	}
+	node->plan = plan = subquery_planner(subquery, tuple_fraction);
 
-	/* and now we are parent again */
-	PlannerInitPlan = saved_ip;
-	PlannerQueryLevel--;
+	node->plan_id = PlannerPlanId++; /* Assign unique ID to this SubPlan */
 
-	node->plan_id = PlannerPlanId++;
 	node->rtable = subquery->rtable;
 	node->sublink = slink;
+
 	slink->subselect = NULL;	/* cool ?! see error check above! */
 
-	/* make parParam list of params coming from current query level */
+	/*
+	 * Make parParam list of params that current query level will pass
+	 * to this child plan.
+	 */
 	foreach(lst, plan->extParam)
 	{
-		Var		   *var = nth(lfirsti(lst), PlannerParamVar);
+		int			paramid = lfirsti(lst);
+		Var		   *var = nth(paramid, PlannerParamVar);
 
 		/* note varlevelsup is absolute level number */
 		if (var->varlevelsup == PlannerQueryLevel)
-			node->parParam = lappendi(node->parParam, lfirsti(lst));
+			node->parParam = lappendi(node->parParam, paramid);
 	}
 
 	/*
@@ -625,6 +606,11 @@ SS_finalize_plan(Plan *plan)
 								 SS_finalize_plan((Plan *) lfirst(lst)));
 			break;
 
+		case T_SubqueryScan:
+			results.paramids = set_unioni(results.paramids,
+						SS_finalize_plan(((SubqueryScan *) plan)->subplan));
+			break;
+
 		case T_IndexScan:
 			finalize_primnode((Node *) ((IndexScan *) plan)->indxqual,
 							  &results);
@@ -667,10 +653,10 @@ SS_finalize_plan(Plan *plan)
 
 		case T_Agg:
 		case T_SeqScan:
-		case T_SubqueryScan:
 		case T_Material:
 		case T_Sort:
 		case T_Unique:
+		case T_SetOp:
 		case T_Group:
 			break;
 
@@ -689,17 +675,18 @@ SS_finalize_plan(Plan *plan)
 
 	foreach(lst, results.paramids)
 	{
-		Var		   *var = nth(lfirsti(lst), PlannerParamVar);
+		int			paramid = lfirsti(lst);
+		Var		   *var = nth(paramid, PlannerParamVar);
 
 		/* note varlevelsup is absolute level number */
 		if (var->varlevelsup < PlannerQueryLevel)
-			extParam = lappendi(extParam, lfirsti(lst));
+			extParam = lappendi(extParam, paramid);
 		else if (var->varlevelsup > PlannerQueryLevel)
 			elog(ERROR, "SS_finalize_plan: plan shouldn't reference subplan's variable");
 		else
 		{
 			Assert(var->varno == 0 && var->varattno == 0);
-			locParam = lappendi(locParam, lfirsti(lst));
+			locParam = lappendi(locParam, paramid);
 		}
 	}
 
diff --git a/src/backend/optimizer/prep/prepkeyset.c b/src/backend/optimizer/prep/prepkeyset.c
index 60166289a59a9e15700b4337c119abd8e1574262..764697636c7f5eb7f45312e76e4d3763e85ee37a 100644
--- a/src/backend/optimizer/prep/prepkeyset.c
+++ b/src/backend/optimizer/prep/prepkeyset.c
@@ -20,6 +20,8 @@
 
 bool		_use_keyset_query_optimizer = FALSE;
 
+#ifdef ENABLE_KEY_SET_QUERY
+
 static int	inspectOpNode(Expr *expr);
 static int	inspectAndNode(Expr *expr);
 static int	inspectOrNode(Expr *expr);
@@ -213,3 +215,5 @@ inspectOpNode(Expr *expr)
 	secondExpr = lsecond(expr->args);
 	return (firstExpr && secondExpr && nodeTag(firstExpr) == T_Var && nodeTag(secondExpr) == T_Const);
 }
+
+#endif /* ENABLE_KEY_SET_QUERY */
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index e3b9803d0ab0c19232b0be7904700b265c532342..822c0c79f0533e253663bd280a01bf00e31381a4 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.38 2000/08/08 15:41:48 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.39 2000/10/05 19:11:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,6 +49,17 @@ preprocess_targetlist(List *tlist,
 					  Index result_relation,
 					  List *range_table)
 {
+	/*
+	 * Sanity check: if there is a result relation, it'd better be a
+	 * real relation not a subquery.  Else parser or rewriter messed up.
+	 */
+	if (result_relation)
+	{
+		RangeTblEntry *rte = rt_fetch(result_relation, range_table);
+
+		if (rte->subquery != NULL || rte->relid == InvalidOid)
+			elog(ERROR, "preprocess_targetlist: subquery cannot be result relation");
+	}
 
 	/*
 	 * for heap_formtuple to work, the targetlist must match the exact
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index d3df88632700658e317ee14480e330dd75290aa9..0c91631563d89d0c1074d7ad0ce422c9278548e9 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1,27 +1,32 @@
 /*-------------------------------------------------------------------------
  *
  * prepunion.c
- *	  Routines to plan inheritance, union, and version queries
+ *	  Routines to plan set-operation and inheritance queries.  The filename
+ *	  is a leftover from a time when only UNIONs were handled.
  *
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.53 2000/09/29 18:21:34 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/prepunion.c,v 1.54 2000/10/05 19:11:30 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
-#include <sys/types.h>
-
 #include "postgres.h"
 
+#include <sys/types.h>
+
+#include "catalog/pg_type.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/plancat.h"
+#include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
 #include "optimizer/tlist.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
 
@@ -33,221 +38,398 @@ typedef struct
 	Oid			new_relid;
 } fix_parsetree_attnums_context;
 
+static Plan *recurse_set_operations(Node *setOp, Query *parse,
+									List *colTypes, int flag,
+									List *refnames_tlist);
+static Plan *generate_union_plan(SetOperationStmt *op, Query *parse,
+								 List *refnames_tlist);
+static Plan *generate_nonunion_plan(SetOperationStmt *op, Query *parse,
+									List *refnames_tlist);
+static List *recurse_union_children(Node *setOp, Query *parse,
+									SetOperationStmt *top_union,
+									List *refnames_tlist);
+static List *generate_setop_tlist(List *colTypes, int flag,
+								  List *input_tlist,
+								  List *refnames_tlist);
+static bool tlist_same_datatypes(List *tlist, List *colTypes);
 static void fix_parsetree_attnums(Index rt_index, Oid old_relid,
 					  Oid new_relid, Query *parsetree);
 static bool fix_parsetree_attnums_walker(Node *node,
 							 fix_parsetree_attnums_context *context);
 static RangeTblEntry *new_rangetable_entry(Oid new_relid,
 					 RangeTblEntry *old_entry);
-static Append *make_append(List *appendplans, List *unionrtables,
-			Index rt_index,
-			List *inheritrtable, List *tlist);
+static Append *make_append(List *appendplans, Index rt_index,
+						   List *inheritrtable, List *tlist);
 
 
 /*
- * plan_union_queries
+ * plan_set_operations
  *
- *	  Plans the queries for a given UNION.
+ *	  Plans the queries for a tree of set operations (UNION/INTERSECT/EXCEPT)
  *
- * Returns an Append plan that combines the results of the unioned queries.
- * Note that Append output is correct for UNION ALL, but caller still needs
- * to take care of sort/unique processing if it's a plain UNION.  We set or
- * clear the Query's fields so that the right things will happen back in
- * union_planner.  (This control structure is an unholy mess...)
+ * This routine only deals with the setOperations tree of the given query.
+ * Any top-level ORDER BY requested in parse->sortClause will be added on
+ * back in union_planner.
  */
 Plan *
-plan_union_queries(Query *parse)
+plan_set_operations(Query *parse)
 {
-	List	   *union_plans = NIL,
-			   *ulist,
-			   *union_all_queries,
-			   *union_rts,
-			   *last_union = NIL,
-			   *hold_sortClause = parse->sortClause;
-	bool		union_all_found = false,
-				union_found = false,
-				last_union_all_flag = false;
-
-	/*------------------------------------------------------------------
-	 *
-	 * Do we need to split up our unions because we have UNION and UNION
-	 * ALL?
-	 *
-	 * We are checking for the case of: SELECT 1 UNION SELECT 2 UNION SELECT
-	 * 3 UNION ALL SELECT 4 UNION ALL SELECT 5
-	 *
-	 * where we have to do a DISTINCT on the output of the first three
-	 * queries, then add the rest.	If they have used UNION and UNION ALL,
-	 * we grab all queries up to the last UNION query, make them their own
-	 * UNION with the owner as the first query in the list.  Then, we take
-	 * the remaining queries, which is UNION ALL, and add them to the list
-	 * of union queries.
-	 *
-	 * So the above query becomes:
-	 *
-	 *	Append Node
-	 *	{
-	 *		Sort and Unique
-	 *		{
-	 *			Append Node
-	 *			{
-	 *				SELECT 1		This is really a sub-UNION.
-	 *				unionClause		We run a DISTINCT on these.
-	 *				{
-	 *					SELECT 2
-	 *					SELECT 3
-	 *				}
-	 *			}
-	 *		}
-	 *		SELECT 4
-	 *		SELECT 5
-	 *	}
-	 *
-	 *---------------------------------------------------------------------
+	SetOperationStmt *topop = (SetOperationStmt *) parse->setOperations;
+	Node	   *node;
+	Query	   *leftmostQuery;
+
+	Assert(topop && IsA(topop, SetOperationStmt));
+
+	/*
+	 * Find the leftmost component Query.  We need to use its column names
+	 * for all generated tlists (else SELECT INTO won't work right).
 	 */
+	node = topop->larg;
+	while (node && IsA(node, SetOperationStmt))
+		node = ((SetOperationStmt *) node)->larg;
+	Assert(node && IsA(node, RangeTblRef));
+	leftmostQuery = rt_fetch(((RangeTblRef *) node)->rtindex,
+							 parse->rtable)->subquery;
+	Assert(leftmostQuery != NULL);
 
-	foreach(ulist, parse->unionClause)
+	/*
+	 * Recurse on setOperations tree to generate plans for set ops.
+	 * The final output plan should have just the column types shown
+	 * as the output from the top-level node.
+	 */
+	return recurse_set_operations((Node *) topop, parse,
+								  topop->colTypes, -1,
+								  leftmostQuery->targetList);
+}
+
+/*
+ * recurse_set_operations
+ *	  Recursively handle one step in a tree of set operations
+ *
+ * colTypes: integer list of type OIDs of expected output columns
+ * flag: if >= 0, add a resjunk output column indicating value of flag
+ * refnames_tlist: targetlist to take column names from
+ */
+static Plan *
+recurse_set_operations(Node *setOp, Query *parse,
+					   List *colTypes, int flag,
+					   List *refnames_tlist)
+{
+	if (IsA(setOp, RangeTblRef))
 	{
-		Query	   *union_query = lfirst(ulist);
+		RangeTblRef *rtr = (RangeTblRef *) setOp;
+		RangeTblEntry *rte = rt_fetch(rtr->rtindex, parse->rtable);
+		Query  *subquery = rte->subquery;
+		Plan   *subplan,
+			   *plan;
 
-		if (union_query->unionall)
-			union_all_found = true;
-		else
-		{
-			union_found = true;
-			last_union = ulist;
-		}
-		last_union_all_flag = union_query->unionall;
+		Assert(subquery != NULL);
+		/*
+		 * Generate plan for primitive subquery
+		 */
+		subplan = subquery_planner(subquery,
+								   -1.0 /* default case */ );
+		/*
+		 * Add a SubqueryScan with the caller-requested targetlist
+		 */
+		plan = (Plan *)
+			make_subqueryscan(generate_setop_tlist(colTypes, flag,
+												   subplan->targetlist,
+												   refnames_tlist),
+							  NIL,
+							  rtr->rtindex,
+							  subplan);
+		copy_plan_costsize(plan, subplan);
+		return plan;
 	}
-
-	/* Is this a simple one */
-	if (!union_all_found ||
-		!union_found ||
-	/* A trailing UNION negates the effect of earlier UNION ALLs */
-		!last_union_all_flag)
+	else if (IsA(setOp, SetOperationStmt))
 	{
-		List	   *hold_unionClause = parse->unionClause;
-		double		tuple_fraction = -1.0;		/* default processing */
-
-		/* we will do sorting later, so don't do it now */
-		if (!union_all_found ||
-			!last_union_all_flag)
-		{
-			parse->sortClause = NIL;
-			parse->distinctClause = NIL;
-
-			/*
-			 * force lower-level planning to assume that all tuples will
-			 * be retrieved, even if it sees a LIMIT in the query node.
-			 */
-			tuple_fraction = 0.0;
-		}
-
-		parse->unionClause = NIL;		/* prevent recursion */
-		union_plans = lcons(union_planner(parse, tuple_fraction), NIL);
-		union_rts = lcons(parse->rtable, NIL);
+		SetOperationStmt *op = (SetOperationStmt *) setOp;
+		Plan   *plan;
 
-		foreach(ulist, hold_unionClause)
+		/* UNIONs are much different from INTERSECT/EXCEPT */
+		if (op->op == SETOP_UNION)
+			plan = generate_union_plan(op, parse, refnames_tlist);
+		else
+			plan = generate_nonunion_plan(op, parse, refnames_tlist);
+		/*
+		 * If necessary, add a Result node to project the caller-requested
+		 * output columns.
+		 *
+		 * XXX you don't really want to know about this: setrefs.c will apply
+		 * replace_vars_with_subplan_refs() to the Result node's tlist.
+		 * This would fail if the input plan's non-resjunk tlist entries were
+		 * not all simple Vars equal() to the referencing Vars generated by
+		 * generate_setop_tlist().  However, since the input plan was
+		 * generated by generate_union_plan() or generate_nonunion_plan(),
+		 * the referencing Vars will equal the tlist entries they reference.
+		 * Ugly but I don't feel like making that code more general right now.
+		 */
+		if (flag >= 0 || ! tlist_same_datatypes(plan->targetlist, colTypes))
 		{
-			Query	   *union_query = lfirst(ulist);
-
-			/*
-			 * use subquery_planner here because the union'd queries have
-			 * not been preprocessed yet.  My goodness this is messy...
-			 */
-			union_plans = lappend(union_plans,
-								  subquery_planner(union_query,
-												   tuple_fraction));
-			union_rts = lappend(union_rts, union_query->rtable);
+			plan = (Plan *)
+				make_result(generate_setop_tlist(colTypes, flag,
+												 plan->targetlist,
+												 refnames_tlist),
+							NULL,
+							plan);
 		}
+		return plan;
 	}
 	else
 	{
+		elog(ERROR, "recurse_set_operations: unexpected node %d",
+			 (int) nodeTag(setOp));
+		return NULL;			/* keep compiler quiet */
+	}
+}
 
-		/*
-		 * We have mixed unions and non-unions
-		 *
-		 * We need to restructure this to put the UNIONs on their own so we
-		 * can do a DISTINCT.
-		 */
+/*
+ * Generate plan for a UNION or UNION ALL node
+ */
+static Plan *
+generate_union_plan(SetOperationStmt *op, Query *parse,
+					List *refnames_tlist)
+{
+	List   *planlist;
+	Plan   *plan;
+
+	/*
+	 * If any of my children are identical UNION nodes (same op, all-flag,
+	 * and colTypes) then they can be merged into this node so that we
+	 * generate only one Append and Sort for the lot.  Recurse to find
+	 * such nodes and compute their children's plans.
+	 */
+	planlist = nconc(recurse_union_children(op->larg, parse,
+											op, refnames_tlist),
+					 recurse_union_children(op->rarg, parse,
+											op, refnames_tlist));
+	/*
+	 * Append the child results together.
+	 *
+	 * The tlist for an Append plan isn't important as far as the Append
+	 * is concerned, but we must make it look real anyway for the benefit
+	 * of the next plan level up.
+	 */
+	plan = (Plan *)
+		make_append(planlist,
+					0,
+					NIL,
+					generate_setop_tlist(op->colTypes, -1,
+									((Plan *) lfirst(planlist))->targetlist,
+									refnames_tlist));
+	/*
+	 * For UNION ALL, we just need the Append plan.  For UNION,
+	 * need to add Sort and Unique nodes to produce unique output.
+	 */
+	if (! op->all)
+	{
+		List   *tlist,
+			   *sortList;
 
-		/* save off everthing past the last UNION */
-		union_all_queries = lnext(last_union);
+		tlist = new_unsorted_tlist(plan->targetlist);
+		sortList = addAllTargetsToSortList(NIL, tlist);
+		plan = make_sortplan(tlist, plan, sortList);
+		plan = (Plan *) make_unique(tlist, plan, copyObject(sortList));
+	}
+	return plan;
+}
 
-		/* clip off the list to remove the trailing UNION ALLs */
-		lnext(last_union) = NIL;
+/*
+ * Generate plan for an INTERSECT, INTERSECT ALL, EXCEPT, or EXCEPT ALL node
+ */
+static Plan *
+generate_nonunion_plan(SetOperationStmt *op, Query *parse,
+					   List *refnames_tlist)
+{
+	Plan   *lplan,
+		   *rplan,
+		   *plan;
+	List   *tlist,
+		   *sortList;
+	SetOpCmd cmd;
+
+	/* Recurse on children, ensuring their outputs are marked */
+	lplan = recurse_set_operations(op->larg, parse,
+								   op->colTypes, 0,
+								   refnames_tlist);
+	rplan = recurse_set_operations(op->rarg, parse,
+								   op->colTypes, 1,
+								   refnames_tlist);
+	/*
+	 * Append the child results together.
+	 *
+	 * The tlist for an Append plan isn't important as far as the Append
+	 * is concerned, but we must make it look real anyway for the benefit
+	 * of the next plan level up.
+	 */
+	plan = (Plan *)
+		make_append(makeList2(lplan, rplan),
+					0,
+					NIL,
+					generate_setop_tlist(op->colTypes, 0,
+										 lplan->targetlist,
+										 refnames_tlist));
+	/*
+	 * Sort the child results, then add a SetOp plan node to
+	 * generate the correct output.
+	 */
+	tlist = new_unsorted_tlist(plan->targetlist);
+	sortList = addAllTargetsToSortList(NIL, tlist);
+	plan = make_sortplan(tlist, plan, sortList);
+	switch (op->op)
+	{
+		case SETOP_INTERSECT:
+			cmd = op->all ? SETOPCMD_INTERSECT_ALL : SETOPCMD_INTERSECT;
+			break;
+		case SETOP_EXCEPT:
+			cmd = op->all ? SETOPCMD_EXCEPT_ALL : SETOPCMD_EXCEPT;
+			break;
+		default:
+			elog(ERROR, "generate_nonunion_plan: bogus operation code");
+			cmd = SETOPCMD_INTERSECT; /* keep compiler quiet */
+			break;
+	}
+	plan = (Plan *) make_setop(cmd, tlist, plan, sortList,
+							   length(op->colTypes)+1);
+	return plan;
+}
 
-		/*
-		 * Recursion, but UNION only. The last one is a UNION, so it will
-		 * not come here in recursion.
-		 *
-		 * XXX is it OK to pass default -1 to union_planner in this path, or
-		 * should we force a tuple_fraction value?
-		 */
-		union_plans = lcons(union_planner(parse, -1.0), NIL);
-		union_rts = lcons(parse->rtable, NIL);
+/*
+ * Pull up children of a UNION node that are identically-propertied UNIONs.
+ *
+ * NOTE: we can also pull a UNION ALL up into a UNION, since the distinct
+ * output rows will be lost anyway.
+ */
+static List *
+recurse_union_children(Node *setOp, Query *parse,
+					   SetOperationStmt *top_union,
+					   List *refnames_tlist)
+{
+	if (IsA(setOp, SetOperationStmt))
+	{
+		SetOperationStmt *op = (SetOperationStmt *) setOp;
 
-		/* Append the remaining UNION ALLs */
-		foreach(ulist, union_all_queries)
+		if (op->op == top_union->op &&
+			(op->all == top_union->all || op->all) &&
+			equali(op->colTypes, top_union->colTypes))
 		{
-			Query	   *union_all_query = lfirst(ulist);
-
-			/*
-			 * use subquery_planner here because the union'd queries have
-			 * not been preprocessed yet.  My goodness this is messy...
-			 */
-			union_plans = lappend(union_plans,
-								subquery_planner(union_all_query, -1.0));
-			union_rts = lappend(union_rts, union_all_query->rtable);
+			/* Same UNION, so fold children into parent's subplan list */
+			return nconc(recurse_union_children(op->larg, parse,
+												top_union, refnames_tlist),
+						 recurse_union_children(op->rarg, parse,
+												top_union, refnames_tlist));
 		}
 	}
+	/* Not same, so plan this child separately */
+	return makeList1(recurse_set_operations(setOp, parse,
+											top_union->colTypes, -1,
+											refnames_tlist));
+}
 
-	/* We have already split UNION and UNION ALL and we made it consistent */
-	if (!last_union_all_flag)
-	{
+/*
+ * Generate targetlist for a set-operation plan node
+ */
+static List *
+generate_setop_tlist(List *colTypes, int flag,
+					 List *input_tlist,
+					 List *refnames_tlist)
+{
+	List	   *tlist = NIL;
+	int			resno = 1;
+	List	   *i;
+	Resdom	   *resdom;
+	Node	   *expr;
 
+	foreach(i, colTypes)
+	{
+		Oid		colType = (Oid) lfirsti(i);
+		TargetEntry *inputtle = (TargetEntry *) lfirst(input_tlist);
+		TargetEntry *reftle = (TargetEntry *) lfirst(refnames_tlist);
+
+		Assert(inputtle->resdom->resno == resno);
+		Assert(reftle->resdom->resno == resno);
+		Assert(!inputtle->resdom->resjunk);
+		Assert(!reftle->resdom->resjunk);
 		/*
-		 * Need SELECT DISTINCT behavior to implement UNION. Put back the
-		 * held sortClause, add any missing columns to the sort clause,
-		 * and set distinctClause properly.
+		 * Generate columns referencing input columns and having
+		 * appropriate data types and column names.  Insert datatype
+		 * coercions where necessary.
+		 *
+		 * HACK: constants in the input's targetlist are copied up as-is
+		 * rather than being referenced as subquery outputs.  This is mainly
+		 * to ensure that when we try to coerce them to the output column's
+		 * datatype, the right things happen for UNKNOWN constants.
 		 */
-		List	   *slitem;
-
-		parse->sortClause = addAllTargetsToSortList(hold_sortClause,
-													parse->targetList);
-		parse->distinctClause = NIL;
-		foreach(slitem, parse->sortClause)
-		{
-			SortClause *scl = (SortClause *) lfirst(slitem);
-			TargetEntry *tle = get_sortgroupclause_tle(scl, parse->targetList);
-
-			if (!tle->resdom->resjunk)
-				parse->distinctClause = lappend(parse->distinctClause,
-												copyObject(scl));
-		}
+		resdom = makeResdom((AttrNumber) resno++,
+							colType,
+							-1,
+							pstrdup(reftle->resdom->resname),
+							false);
+		if (inputtle->expr && IsA(inputtle->expr, Const))
+			expr = inputtle->expr;
+		else
+			expr = (Node *) makeVar(0,
+									inputtle->resdom->resno,
+									inputtle->resdom->restype,
+									inputtle->resdom->restypmod,
+									0);
+		expr = coerce_to_common_type(NULL,
+									 expr,
+									 colType,
+									 "UNION/INTERSECT/EXCEPT");
+		tlist = lappend(tlist, makeTargetEntry(resdom, expr));
+		input_tlist = lnext(input_tlist);
+		refnames_tlist = lnext(refnames_tlist);
 	}
-	else
+
+	if (flag >= 0)
 	{
-		/* needed so we don't take SELECT DISTINCT from the first query */
-		parse->distinctClause = NIL;
+		/* Add a resjunk column yielding specified flag value */
+		resdom = makeResdom((AttrNumber) resno++,
+							INT4OID,
+							-1,
+							pstrdup("flag"),
+							true);
+		expr = (Node *) makeConst(INT4OID,
+								  sizeof(int4),
+								  Int32GetDatum(flag),
+								  false,
+								  true,
+								  false,
+								  false);
+		tlist = lappend(tlist, makeTargetEntry(resdom, expr));
 	}
 
-	/*
-	 * Make sure we don't try to apply the first query's grouping stuff to
-	 * the Append node, either.  Basically we don't want union_planner to
-	 * do anything when we return control, except add the top sort/unique
-	 * nodes for DISTINCT processing if this wasn't UNION ALL, or the top
-	 * sort node if it was UNION ALL with a user-provided sort clause.
-	 */
-	parse->groupClause = NULL;
-	parse->havingQual = NULL;
-	parse->hasAggs = false;
+	return tlist;
+}
 
-	return (Plan *) make_append(union_plans,
-								union_rts,
-								0,
-								NIL,
-								parse->targetList);
+/*
+ * Does tlist have same datatypes as requested colTypes?
+ *
+ * Resjunk columns are ignored.
+ */
+static bool
+tlist_same_datatypes(List *tlist, List *colTypes)
+{
+	List	   *i;
+
+	foreach(i, tlist)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(i);
+
+		if (!tle->resdom->resjunk)
+		{
+			if (colTypes == NIL)
+				return false;
+			if (tle->resdom->restype != (Oid) lfirsti(colTypes))
+				return false;
+			colTypes = lnext(colTypes);
+		}
+	}
+	if (colTypes != NIL)
+		return false;
+	return true;
 }
 
 
@@ -372,7 +554,6 @@ plan_inherit_queries(Query *root, List *tlist,
 
 	/* Construct the finished Append plan. */
 	return (Plan *) make_append(union_plans,
-								NIL,
 								rt_index,
 								union_rtentries,
 								((Plan *) lfirst(union_plans))->targetlist);
@@ -530,7 +711,8 @@ fix_parsetree_attnums(Index rt_index,
 
 	query_tree_walker(parsetree,
 					  fix_parsetree_attnums_walker,
-					  (void *) &context);
+					  (void *) &context,
+					  true);
 }
 
 /*
@@ -570,7 +752,8 @@ fix_parsetree_attnums_walker(Node *node,
 		context->sublevels_up++;
 		result = query_tree_walker((Query *) node,
 								   fix_parsetree_attnums_walker,
-								   (void *) context);
+								   (void *) context,
+								   true);
 		context->sublevels_up--;
 		return result;
 	}
@@ -580,7 +763,6 @@ fix_parsetree_attnums_walker(Node *node,
 
 static Append *
 make_append(List *appendplans,
-			List *unionrtables,
 			Index rt_index,
 			List *inheritrtable,
 			List *tlist)
@@ -589,7 +771,6 @@ make_append(List *appendplans,
 	List	   *subnode;
 
 	node->appendplans = appendplans;
-	node->unionrtables = unionrtables;
 	node->inheritrelid = rt_index;
 	node->inheritrtable = inheritrtable;
 	node->plan.startup_cost = 0;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 06c67daebfc7a1498b4f528fde6f7cae43351e58..f52ec6d2d874874d391185cd3d9455705641cf3e 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.76 2000/09/29 18:21:23 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.77 2000/10/05 19:11:32 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -1636,8 +1636,8 @@ simplify_op_or_func(Expr *expr, List *args)
  * 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.)  Also, RangeTblRef,
- * FromExpr, and JoinExpr nodes are handled, so that qual expressions in a
- * jointree can be processed without additional code.
+ * FromExpr, JoinExpr, and SetOperationStmt nodes are handled, so that query
+ * jointrees and setOperation trees 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).
@@ -1654,7 +1654,8 @@ simplify_op_or_func(Expr *expr, List *args)
  *		if (IsA(node, Query))
  *		{
  *			adjust context for subquery;
- *			result = query_tree_walker((Query *) node, my_walker, context);
+ *			result = query_tree_walker((Query *) node, my_walker, context,
+ *									   true); // to visit subquery RTEs too
  *			restore context if needed;
  *			return result;
  *		}
@@ -1827,6 +1828,16 @@ expression_tree_walker(Node *node,
 				 */
 			}
 			break;
+		case T_SetOperationStmt:
+			{
+				SetOperationStmt *setop = (SetOperationStmt *) node;
+
+				if (walker(setop->larg, context))
+					return true;
+				if (walker(setop->rarg, context))
+					return true;
+			}
+			break;
 		default:
 			elog(ERROR, "expression_tree_walker: Unexpected node type %d",
 				 nodeTag(node));
@@ -1843,11 +1854,17 @@ expression_tree_walker(Node *node,
  * 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.
+ *
+ * If visitQueryRTEs is true, the walker will also be called on sub-Query
+ * nodes present in subquery rangetable entries of the given Query.  This
+ * is optional since some callers handle those sub-queries separately,
+ * or don't really want to see subqueries anyway.
  */
 bool
 query_tree_walker(Query *query,
 				  bool (*walker) (),
-				  void *context)
+				  void *context,
+				  bool visitQueryRTEs)
 {
 	Assert(query != NULL && IsA(query, Query));
 
@@ -1855,11 +1872,23 @@ query_tree_walker(Query *query,
 		return true;
 	if (walker((Node *) query->jointree, context))
 		return true;
+	if (walker(query->setOperations, context))
+		return true;
 	if (walker(query->havingQual, context))
 		return true;
-	/*
-	 * XXX for subselect-in-FROM, may need to examine rtable as well?
-	 */
+	if (visitQueryRTEs)
+	{
+		List   *rt;
+
+		foreach(rt, query->rtable)
+		{
+			RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+
+			if (rte->subquery)
+				if (walker(rte->subquery, context))
+					return true;
+		}
+	}
 	return false;
 }
 
@@ -2158,6 +2187,17 @@ expression_tree_mutator(Node *node,
 				return (Node *) newnode;
 			}
 			break;
+		case T_SetOperationStmt:
+			{
+				SetOperationStmt *setop = (SetOperationStmt *) node;
+				SetOperationStmt *newnode;
+
+				FLATCOPY(newnode, setop, SetOperationStmt);
+				MUTATE(newnode->larg, setop->larg, Node *);
+				MUTATE(newnode->rarg, setop->rarg, Node *);
+				return (Node *) newnode;
+			}
+			break;
 		default:
 			elog(ERROR, "expression_tree_mutator: Unexpected node type %d",
 				 nodeTag(node));
@@ -2166,3 +2206,58 @@ expression_tree_mutator(Node *node,
 	/* can't get here, but keep compiler happy */
 	return NULL;
 }
+
+
+/*
+ * query_tree_mutator --- initiate modification 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
+ * mutator intends to descend into subqueries.  It is also useful for
+ * descending into subqueries within a mutator.
+ *
+ * The specified Query node is modified-in-place; do a FLATCOPY() beforehand
+ * if you don't want to change the original.  All substructure is safely
+ * copied, however.
+ *
+ * If visitQueryRTEs is true, the mutator will also be called on sub-Query
+ * nodes present in subquery rangetable entries of the given Query.  This
+ * is optional since some callers handle those sub-queries separately,
+ * or don't really want to see subqueries anyway.
+ */
+void
+query_tree_mutator(Query *query,
+				   Node *(*mutator) (),
+				   void *context,
+				   bool visitQueryRTEs)
+{
+	Assert(query != NULL && IsA(query, Query));
+
+	MUTATE(query->targetList, query->targetList, List *);
+	MUTATE(query->jointree, query->jointree, FromExpr *);
+	MUTATE(query->setOperations, query->setOperations, Node *);
+	MUTATE(query->havingQual, query->havingQual, Node *);
+	if (visitQueryRTEs)
+	{
+		List   *newrt = NIL;
+		List   *rt;
+
+		foreach(rt, query->rtable)
+		{
+			RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+
+			if (rte->subquery)
+			{
+				RangeTblEntry *newrte;
+
+				FLATCOPY(newrte, rte, RangeTblEntry);
+				CHECKFLATCOPY(newrte->subquery, rte->subquery, Query);
+				MUTATE(newrte->subquery, newrte->subquery, Query *);
+				rte = newrte;
+			}
+			newrt = lappend(newrt, rte);
+		}
+		query->rtable = newrt;
+	}
+}
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index ec9f9dafd0b4f92d84905f3ba7e6f4f36511d27a..a18db71ec556353f380e5fe78257bd8b4aed5681 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.27 2000/09/12 21:06:59 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/util/var.c,v 1.28 2000/10/05 19:11:32 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -67,7 +67,7 @@ pull_varnos(Node *node)
 	 */
 	if (node && IsA(node, Query))
 		query_tree_walker((Query *) node, pull_varnos_walker,
-						  (void *) &context);
+						  (void *) &context, true);
 	else
 		pull_varnos_walker(node, &context);
 
@@ -108,12 +108,12 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
 	}
 	if (IsA(node, Query))
 	{
-		/* Recurse into not-yet-planned subquery */
+		/* Recurse into RTE subquery or not-yet-planned sublink subquery */
 		bool		result;
 
 		context->sublevels_up++;
 		result = query_tree_walker((Query *) node, pull_varnos_walker,
-								   (void *) context);
+								   (void *) context, true);
 		context->sublevels_up--;
 		return result;
 	}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 169075c47e29079eb2260e503c755e744768c4c2..ffedca05ed997062176f6fb56152c4c6b95c1633 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.159 2000/09/29 18:21:36 tgl Exp $
+ *	$Id: analyze.c,v 1.160 2000/10/05 19:11:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,8 +20,10 @@
 #include "nodes/makefuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse.h"
+#include "parser/parsetree.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
 #include "parser/parse_type.h"
@@ -44,11 +46,13 @@ static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt);
 static Query *transformExtendStmt(ParseState *pstate, ExtendStmt *stmt);
 static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt);
 static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
+static Query *transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt);
+static Node *transformSetOperationTree(ParseState *pstate, Node *node);
 static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
-static Query *transformCursorStmt(ParseState *pstate, SelectStmt *stmt);
 static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt);
 static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt);
 
+static List *getSetColTypes(ParseState *pstate, Node *node);
 static void transformForUpdate(Query *qry, List *forUpdate);
 static void transformFkeyGetPrimaryKey(FkConstraint *fkconstraint);
 static void transformConstraintAttrs(List *constraintList);
@@ -156,7 +160,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			{
 				ViewStmt   *n = (ViewStmt *) parseTree;
 
-				n->query = (Query *) transformStmt(pstate, (Node *) n->query);
+				n->query = transformStmt(pstate, (Node *) n->query);
 
 				/*
 				 * If a list of column names was given, run through and
@@ -258,20 +262,17 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			break;
 
 		case T_SelectStmt:
-			if (!((SelectStmt *) parseTree)->portalname)
-			{
-				result = transformSelectStmt(pstate, (SelectStmt *) parseTree);
-				result->limitOffset = ((SelectStmt *) parseTree)->limitOffset;
-				result->limitCount = ((SelectStmt *) parseTree)->limitCount;
-			}
-			else
-				result = transformCursorStmt(pstate, (SelectStmt *) parseTree);
+			result = transformSelectStmt(pstate, (SelectStmt *) parseTree);
+			break;
+
+		case T_SetOperationStmt:
+			result = transformSetOperationStmt(pstate, (SetOperationStmt *) parseTree);
 			break;
 
 		default:
 
 			/*
-			 * other statments don't require any transformation-- just
+			 * other statements don't require any transformation-- just
 			 * return the original parsetree, yea!
 			 */
 			result = makeNode(Query);
@@ -313,7 +314,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	if (pstate->p_hasAggs)
 		parseCheckAggregates(pstate, qry, qual);
 
-	return (Query *) qry;
+	return qry;
 }
 
 /*
@@ -324,7 +325,6 @@ static Query *
 transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
-	Node	   *qual;
 	List	   *icolumns;
 	List	   *attrnos;
 	List	   *attnos;
@@ -335,59 +335,89 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	qry->commandType = CMD_INSERT;
 	pstate->p_is_insert = true;
 
-	/*----------
-	 * Initial processing steps are just like SELECT, which should not
-	 * be surprising, since we may be handling an INSERT ... SELECT.
-	 * It is important that we finish processing all the SELECT subclauses
-	 * before we start doing any INSERT-specific processing; otherwise
-	 * the behavior of SELECT within INSERT might be different from a
-	 * stand-alone SELECT.	(Indeed, Postgres up through 6.5 had bugs of
-	 * just that nature...)
-	 *----------
-	 */
-
-	/* set up a range table --- note INSERT target is not in it yet */
-	makeRangeTable(pstate, stmt->fromClause);
-
-	qry->targetList = transformTargetList(pstate, stmt->targetList);
-
-	qual = transformWhereClause(pstate, stmt->whereClause);
-
-	/*
-	 * Initial processing of HAVING clause is just like WHERE clause.
-	 * Additional work will be done in optimizer/plan/planner.c.
-	 */
-	qry->havingQual = transformWhereClause(pstate, stmt->havingClause);
-
-	qry->groupClause = transformGroupClause(pstate,
-											stmt->groupClause,
-											qry->targetList);
-
-	/* An InsertStmt has no sortClause */
-	qry->sortClause = NIL;
-
-	qry->distinctClause = transformDistinctClause(pstate,
-												  stmt->distinctClause,
-												  qry->targetList,
-												  &qry->sortClause);
-
-	qry->hasSubLinks = pstate->p_hasSubLinks;
-	qry->hasAggs = pstate->p_hasAggs;
-	if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
-		parseCheckAggregates(pstate, qry, qual);
-
 	/*
-	 * The INSERT INTO ... SELECT ... could have a UNION in child, so
-	 * unionClause may be false
+	 * Is it INSERT ... SELECT or INSERT ... VALUES?
 	 */
-	qry->unionall = stmt->unionall;
+	if (stmt->selectStmt)
+	{
+		List   *selectList;
+		Query  *selectQuery;
+		RangeTblEntry *rte;
+		RangeTblRef *rtr;
 
-	/*
-	 * Just hand through the unionClause and intersectClause. We will
-	 * handle it in the function Except_Intersect_Rewrite()
-	 */
-	qry->unionClause = stmt->unionClause;
-	qry->intersectClause = stmt->intersectClause;
+		/*
+		 * Process the source SELECT.
+		 *
+		 * It is important that this be handled just like a standalone SELECT;
+		 * otherwise the behavior of SELECT within INSERT might be different
+		 * from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
+		 * bugs of just that nature...)
+		 */
+		selectList = parse_analyze(makeList1(stmt->selectStmt), pstate);
+		Assert(length(selectList) == 1);
+
+		selectQuery = (Query *) lfirst(selectList);
+		Assert(IsA(selectQuery, Query));
+		Assert(selectQuery->commandType == CMD_SELECT);
+		if (selectQuery->into || selectQuery->isPortal)
+			elog(ERROR, "INSERT ... SELECT may not specify INTO");
+		/*
+		 * Make the source be a subquery in the INSERT's rangetable,
+		 * and add it to the joinlist.
+		 */
+		rte = addRangeTableEntryForSubquery(pstate,
+											selectQuery,
+											makeAttr("*SELECT*", NULL),
+											true);
+		rtr = makeNode(RangeTblRef);
+		/* assume new rte is at end */
+		rtr->rtindex = length(pstate->p_rtable);
+		Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
+		pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+		/*
+		 * Generate a targetlist for the INSERT that selects all
+		 * the non-resjunk columns from the subquery.  (We need this to
+		 * be separate from the subquery's tlist because we may add
+		 * columns, insert datatype coercions, etc.)
+		 *
+		 * HACK: constants in the INSERT's targetlist are copied up as-is
+		 * rather than being referenced as subquery outputs.  This is mainly
+		 * to ensure that when we try to coerce them to the target column's
+		 * datatype, the right things happen for UNKNOWN constants.
+		 * Otherwise this fails:
+		 *		INSERT INTO foo SELECT 'bar', ... FROM baz
+		 */
+		qry->targetList = NIL;
+		foreach(tl, selectQuery->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(tl);
+			Resdom	   *resnode = tle->resdom;
+			Node	   *expr;
+
+			if (resnode->resjunk)
+				continue;
+			if (tle->expr && IsA(tle->expr, Const))
+				expr = tle->expr;
+			else
+				expr = (Node *) makeVar(rtr->rtindex,
+										resnode->resno,
+										resnode->restype,
+										resnode->restypmod,
+										0);
+			resnode = copyObject(resnode);
+			resnode->resno = (AttrNumber) pstate->p_last_resno++;
+			qry->targetList = lappend(qry->targetList,
+									  makeTargetEntry(resnode, expr));
+		}
+	}
+	else
+	{
+		/*
+		 * For INSERT ... VALUES, transform the given list of values
+		 * to form a targetlist for the INSERT.
+		 */
+		qry->targetList = transformTargetList(pstate, stmt->targetList);
+	}
 
 	/*
 	 * Now we are done with SELECT-like processing, and can get on with
@@ -400,11 +430,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 */
 	setTargetTable(pstate, stmt->relname, false, false);
 
-	/* now the range table and jointree will not change */
-	qry->rtable = pstate->p_rtable;
-	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
-	qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
-
 	/* Prepare to assign non-conflicting resnos to resjunk attributes */
 	if (pstate->p_last_resno <= pstate->p_target_relation->rd_rel->relnatts)
 		pstate->p_last_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
@@ -412,7 +437,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	/* Validate stmt->cols list, or build default list if no list given */
 	icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
 
-	/* Prepare non-junk columns for assignment to target table */
+	/*
+	 * Prepare columns for assignment to target table.
+	 */
 	numuseratts = 0;
 	attnos = attrnos;
 	foreach(tl, qry->targetList)
@@ -421,18 +448,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		Resdom	   *resnode = tle->resdom;
 		Ident	   *id;
 
-		if (resnode->resjunk)
-		{
-
-			/*
-			 * Resjunk nodes need no additional processing, but be sure
-			 * they have names and resnos that do not match any target
-			 * columns; else rewriter or planner might get confused.
-			 */
-			resnode->resname = "?resjunk?";
-			resnode->resno = (AttrNumber) pstate->p_last_resno++;
-			continue;
-		}
+		Assert(!resnode->resjunk);
 		if (icolumns == NIL || attnos == NIL)
 			elog(ERROR, "INSERT has more expressions than target columns");
 		id = (Ident *) lfirst(icolumns);
@@ -458,7 +474,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * have defaults and were not assigned to by the user.
 	 *
 	 * XXX wouldn't it make more sense to do this further downstream, after
-	 * the rule rewriter?
+	 * the rule rewriter?  As is, altering a column default will not change
+	 * the behavior of INSERTs in already-defined rules.
 	 */
 	rd_att = pstate->p_target_relation->rd_att;
 	if (rd_att->constr && rd_att->constr->num_defval > 0)
@@ -498,13 +515,17 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		}
 	}
 
-	if (stmt->forUpdate != NULL)
-		transformForUpdate(qry, stmt->forUpdate);
+	/* done building the range table and jointree */
+	qry->rtable = pstate->p_rtable;
+	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+	qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
-	/* in case of subselects in default clauses... */
 	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasAggs = pstate->p_hasAggs;
+	if (pstate->p_hasAggs)
+		parseCheckAggregates(pstate, qry, NULL);
 
-	return (Query *) qry;
+	return qry;
 }
 
 /*
@@ -1608,6 +1629,7 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
  * transformSelectStmt -
  *	  transforms a Select Statement
  *
+ * Note: this is also used for DECLARE CURSOR statements.
  */
 static Query *
 transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
@@ -1617,13 +1639,42 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
 	qry->commandType = CMD_SELECT;
 
+	if (stmt->portalname)
+	{
+		/* DECLARE CURSOR */
+		if (stmt->into)
+			elog(ERROR, "DECLARE CURSOR must not specify INTO");
+		if (stmt->forUpdate)
+			elog(ERROR, "DECLARE/UPDATE is not supported"
+				 "\n\tCursors must be READ ONLY");
+		/*
+		 *	15 august 1991 -- since 3.0 postgres does locking
+		 *	right, we discovered that portals were violating
+		 *	locking protocol.  portal locks cannot span xacts.
+		 *	as a short-term fix, we installed the check here.
+		 *							-- mao
+		 */
+		if (!IsTransactionBlock())
+			elog(ERROR, "DECLARE CURSOR may only be used in begin/end transaction blocks");
+
+		qry->into = stmt->portalname;
+		qry->isTemp = stmt->istemp;
+		qry->isPortal = TRUE;
+		qry->isBinary = stmt->binary; /* internal portal */
+	}
+	else
+	{
+		/* SELECT */
+		qry->into = stmt->into;
+		qry->isTemp = stmt->istemp;
+		qry->isPortal = FALSE;
+		qry->isBinary = FALSE;
+	}
+
 	/* set up a range table */
 	makeRangeTable(pstate, stmt->fromClause);
 
-	qry->into = stmt->into;
-	qry->isTemp = stmt->istemp;
-	qry->isPortal = FALSE;
-
+	/* transform targetlist and WHERE */
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
 	qual = transformWhereClause(pstate, stmt->whereClause);
@@ -1647,33 +1698,356 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 												  qry->targetList,
 												  &qry->sortClause);
 
+	qry->limitOffset = stmt->limitOffset;
+	qry->limitCount = stmt->limitCount;
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasAggs = pstate->p_hasAggs;
 	if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
 		parseCheckAggregates(pstate, qry, qual);
 
+	qry->rtable = pstate->p_rtable;
+	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+	if (stmt->forUpdate != NULL)
+		transformForUpdate(qry, stmt->forUpdate);
+
+	return qry;
+}
+
+/*
+ * transformSetOperationsStmt -
+ *	  transforms a SetOperations Statement
+ *
+ * SetOperations is actually just a SELECT, but with UNION/INTERSECT/EXCEPT
+ * structure to it.  We must transform each leaf SELECT and build up a top-
+ * level Query that contains the leaf SELECTs as subqueries in its rangetable.
+ * The SetOperations tree (with leaf SelectStmts replaced by RangeTblRef nodes)
+ * becomes the setOperations field of the top-level Query.
+ */
+static Query *
+transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt)
+{
+	Query	   *qry = makeNode(Query);
+	Node	   *node;
+	SelectStmt *leftmostSelect;
+	Query	   *leftmostQuery;
+	char	   *into;
+	char	   *portalname;
+	bool		binary;
+	bool		istemp;
+	List	   *sortClause;
+	Node	   *limitOffset;
+	Node	   *limitCount;
+	List	   *forUpdate;
+	List	   *lefttl,
+			   *dtlist;
+	int			tllen;
+
+	qry->commandType = CMD_SELECT;
+
 	/*
-	 * The INSERT INTO ... SELECT ... could have a UNION in child, so
-	 * unionClause may be false
+	 * Find leftmost leaf SelectStmt and extract the one-time-only items
+	 * from it.
 	 */
-	qry->unionall = stmt->unionall;
+	node = stmt->larg;
+	while (node && IsA(node, SetOperationStmt))
+		node = ((SetOperationStmt *) node)->larg;
+	Assert(node && IsA(node, SelectStmt));
+	leftmostSelect = (SelectStmt *) node;
+
+	into = leftmostSelect->into;
+	portalname = leftmostSelect->portalname;
+	binary = leftmostSelect->binary;
+	istemp = leftmostSelect->istemp;
+	sortClause = leftmostSelect->sortClause;
+	limitOffset = leftmostSelect->limitOffset;
+	limitCount = leftmostSelect->limitCount;
+	forUpdate = leftmostSelect->forUpdate;
+
+	/* clear them to prevent complaints in transformSetOperationTree() */
+	leftmostSelect->into = NULL;
+	leftmostSelect->portalname = NULL;
+	leftmostSelect->binary = false;
+	leftmostSelect->istemp = false;
+	leftmostSelect->sortClause = NIL;
+	leftmostSelect->limitOffset = NULL;
+	leftmostSelect->limitCount = NULL;
+	leftmostSelect->forUpdate = NIL;
+
+	/* We don't actually support forUpdate with set ops at the moment. */
+	if (forUpdate)
+		elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT");
 
 	/*
-	 * Just hand through the unionClause and intersectClause. We will
-	 * handle it in the function Except_Intersect_Rewrite()
+	 * Recursively transform the components of the tree.
 	 */
-	qry->unionClause = stmt->unionClause;
-	qry->intersectClause = stmt->intersectClause;
+	stmt = (SetOperationStmt *)
+		transformSetOperationTree(pstate, (Node *) stmt);
+	Assert(stmt && IsA(stmt, SetOperationStmt));
+	qry->setOperations = (Node *) stmt;
+
+	/*
+	 * Re-find leftmost SELECT (now it's a sub-query in rangetable)
+	 */
+	node = stmt->larg;
+	while (node && IsA(node, SetOperationStmt))
+		node = ((SetOperationStmt *) node)->larg;
+	Assert(node && IsA(node, RangeTblRef));
+	leftmostQuery = rt_fetch(((RangeTblRef *) node)->rtindex,
+							 pstate->p_rtable)->subquery;
+	Assert(leftmostQuery != NULL);
+	/*
+	 * Generate dummy targetlist for outer query using column names of
+	 * leftmost select and common datatypes of topmost set operation
+	 */
+	qry->targetList = NIL;
+	lefttl = leftmostQuery->targetList;
+	foreach(dtlist, stmt->colTypes)
+	{
+		Oid		colType = (Oid) lfirsti(dtlist);
+		char   *colName = ((TargetEntry *) lfirst(lefttl))->resdom->resname;
+		Resdom *resdom;
+		Node   *expr;
+
+		resdom = makeResdom((AttrNumber) pstate->p_last_resno++,
+							colType,
+							-1,
+							pstrdup(colName),
+							false);
+		expr = (Node *) makeVar(1,
+								resdom->resno,
+								colType,
+								-1,
+								0);
+		qry->targetList = lappend(qry->targetList,
+								  makeTargetEntry(resdom, expr));
+		lefttl = lnext(lefttl);
+	}
+	/*
+	 * Insert one-time items into top-level query
+	 *
+	 * This needs to agree with transformSelectStmt!
+	 */
+	if (portalname)
+	{
+		/* DECLARE CURSOR */
+		if (into)
+			elog(ERROR, "DECLARE CURSOR must not specify INTO");
+		if (forUpdate)
+			elog(ERROR, "DECLARE/UPDATE is not supported"
+				 "\n\tCursors must be READ ONLY");
+		/*
+		 *	15 august 1991 -- since 3.0 postgres does locking
+		 *	right, we discovered that portals were violating
+		 *	locking protocol.  portal locks cannot span xacts.
+		 *	as a short-term fix, we installed the check here.
+		 *							-- mao
+		 */
+		if (!IsTransactionBlock())
+			elog(ERROR, "DECLARE CURSOR may only be used in begin/end transaction blocks");
+
+		qry->into = portalname;
+		qry->isTemp = istemp;
+		qry->isPortal = TRUE;
+		qry->isBinary = binary; /* internal portal */
+	}
+	else
+	{
+		/* SELECT */
+		qry->into = into;
+		qry->isTemp = istemp;
+		qry->isPortal = FALSE;
+		qry->isBinary = FALSE;
+	}
+
+	/*
+	 * For now, we don't support resjunk sort clauses on the output of a
+	 * setOperation tree --- you can only use the SQL92-spec options of
+	 * selecting an output column by name or number.  Enforce by checking
+	 * that transformSortClause doesn't add any items to tlist.
+	 */
+	tllen = length(qry->targetList);
+
+	qry->sortClause = transformSortClause(pstate,
+										  sortClause,
+										  qry->targetList);
+
+	if (tllen != length(qry->targetList))
+		elog(ERROR, "ORDER BY on a UNION/INTERSECT/EXCEPT result must be on one of the result columns");
+
+	qry->limitOffset = limitOffset;
+	qry->limitCount = limitCount;
+
+	qry->hasSubLinks = pstate->p_hasSubLinks;
+	qry->hasAggs = pstate->p_hasAggs;
+	if (pstate->p_hasAggs || qry->groupClause || qry->havingQual)
+		parseCheckAggregates(pstate, qry, NULL);
 
 	qry->rtable = pstate->p_rtable;
-	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
 
-	if (stmt->forUpdate != NULL)
-		transformForUpdate(qry, stmt->forUpdate);
+	if (forUpdate != NULL)
+		transformForUpdate(qry, forUpdate);
 
-	return (Query *) qry;
+	return qry;
+}
+
+/*
+ * transformSetOperationTree
+ *		Recursively transform leaves and internal nodes of a set-op tree
+ */
+static Node *
+transformSetOperationTree(ParseState *pstate, Node *node)
+{
+	if (IsA(node, SelectStmt))
+	{
+		SelectStmt *stmt = (SelectStmt *) node;
+		List   *save_rtable;
+		List   *selectList;
+		Query  *selectQuery;
+		char	selectName[32];
+		RangeTblEntry *rte;
+		RangeTblRef *rtr;
+
+		/*
+		 * Validity-check leaf SELECTs for disallowed ops.  INTO check is
+		 * necessary, the others should have been disallowed by grammar.
+		 */
+		if (stmt->into)
+			elog(ERROR, "INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT");
+		if (stmt->portalname)
+			elog(ERROR, "Portal is only allowed on first SELECT of UNION/INTERSECT/EXCEPT");
+		if (stmt->sortClause)
+			elog(ERROR, "ORDER BY is only allowed at end of UNION/INTERSECT/EXCEPT");
+		if (stmt->limitOffset || stmt->limitCount)
+			elog(ERROR, "LIMIT is only allowed at end of UNION/INTERSECT/EXCEPT");
+		if (stmt->forUpdate)
+			elog(ERROR, "FOR UPDATE is only allowed at end of UNION/INTERSECT/EXCEPT");
+		/*
+		 * Transform SelectStmt into a Query.  We do not want any previously
+		 * transformed leaf queries to be visible in the outer context of
+		 * this sub-query, so temporarily make the top-level pstate have an
+		 * empty rtable.  (We needn't do the same with the joinlist because
+		 * we aren't entering anything in the top-level joinlist.)
+		 */
+		save_rtable = pstate->p_rtable;
+		pstate->p_rtable = NIL;
+		selectList = parse_analyze(makeList1(stmt), pstate);
+		pstate->p_rtable = save_rtable;
+
+		Assert(length(selectList) == 1);
+		selectQuery = (Query *) lfirst(selectList);
+		/*
+		 * Make the leaf query be a subquery in the top-level rangetable.
+		 */
+		sprintf(selectName, "*SELECT* %d", length(pstate->p_rtable) + 1);
+		rte = addRangeTableEntryForSubquery(pstate,
+											selectQuery,
+											makeAttr(pstrdup(selectName),
+													 NULL),
+											false);
+		/*
+		 * Return a RangeTblRef to replace the SelectStmt in the set-op tree.
+		 */
+		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 (Node *) rtr;
+	}
+	else if (IsA(node, SetOperationStmt))
+	{
+		SetOperationStmt *op = (SetOperationStmt *) node;
+		List   *lcoltypes;
+		List   *rcoltypes;
+		const char *context;
+
+		context = (op->op == SETOP_UNION ? "UNION" :
+				   (op->op == SETOP_INTERSECT ? "INTERSECT" :
+					"EXCEPT"));
+		/*
+		 * Recursively transform the child nodes.
+		 */
+		op->larg = transformSetOperationTree(pstate, op->larg);
+		op->rarg = transformSetOperationTree(pstate, op->rarg);
+		/*
+		 * Verify that the two children have the same number of non-junk
+		 * columns, and determine the types of the merged output columns.
+		 */
+		lcoltypes = getSetColTypes(pstate, op->larg);
+		rcoltypes = getSetColTypes(pstate, op->rarg);
+		if (length(lcoltypes) != length(rcoltypes))
+			elog(ERROR, "Each %s query must have the same number of columns",
+				 context);
+		op->colTypes = NIL;
+		while (lcoltypes != NIL)
+		{
+			Oid		lcoltype = (Oid) lfirsti(lcoltypes);
+			Oid		rcoltype = (Oid) lfirsti(rcoltypes);
+			Oid		rescoltype;
+
+			rescoltype = select_common_type(makeListi2(lcoltype, rcoltype),
+											context);
+			op->colTypes = lappendi(op->colTypes, rescoltype);
+			lcoltypes = lnext(lcoltypes);
+			rcoltypes = lnext(rcoltypes);
+		}
+		return (Node *) op;
+	}
+	else
+	{
+		elog(ERROR, "transformSetOperationTree: unexpected node %d",
+			 (int) nodeTag(node));
+		return NULL;			/* keep compiler quiet */
+	}
 }
 
+/*
+ * getSetColTypes
+ *		Get output column types of an (already transformed) set-op node
+ */
+static List *
+getSetColTypes(ParseState *pstate, Node *node)
+{
+	if (IsA(node, RangeTblRef))
+	{
+		RangeTblRef *rtr = (RangeTblRef *) node;
+		RangeTblEntry *rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
+		Query  *selectQuery = rte->subquery;
+		List   *result = NIL;
+		List   *tl;
+
+		Assert(selectQuery != NULL);
+		/* Get types of non-junk columns */
+		foreach(tl, selectQuery->targetList)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(tl);
+			Resdom	   *resnode = tle->resdom;
+
+			if (resnode->resjunk)
+				continue;
+			result = lappendi(result, resnode->restype);
+		}
+		return result;
+	}
+	else if (IsA(node, SetOperationStmt))
+	{
+		SetOperationStmt *op = (SetOperationStmt *) node;
+
+		/* Result already computed during transformation of node */
+		Assert(op->colTypes != NIL);
+		return op->colTypes;
+	}
+	else
+	{
+		elog(ERROR, "getSetColTypes: unexpected node %d",
+			 (int) nodeTag(node));
+		return NIL;				/* keep compiler quiet */
+	}
+}
+
+
 /*
  * transformUpdateStmt -
  *	  transforms an update statement
@@ -1756,26 +2130,6 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	if (origTargetList != NIL)
 		elog(ERROR, "UPDATE target count mismatch --- internal error");
 
-	return (Query *) qry;
-}
-
-/*
- * transformCursorStmt -
- *	  transform a Create Cursor Statement
- *
- */
-static Query *
-transformCursorStmt(ParseState *pstate, SelectStmt *stmt)
-{
-	Query	   *qry;
-
-	qry = transformSelectStmt(pstate, stmt);
-
-	qry->into = stmt->portalname;
-	qry->isTemp = stmt->istemp;
-	qry->isPortal = TRUE;
-	qry->isBinary = stmt->binary;		/* internal portal */
-
 	return qry;
 }
 
@@ -2039,106 +2393,11 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt)
 	return qry;
 }
 
-
-/* This function steps through the tree
- * built up by the select_w_o_sort rule
- * and builds a list of all SelectStmt Nodes found
- * The built up list is handed back in **select_list.
- * If one of the SelectStmt Nodes has the 'unionall' flag
- * set to true *unionall_present hands back 'true' */
-void
-create_select_list(Node *ptr, List **select_list, bool *unionall_present)
-{
-	if (IsA(ptr, SelectStmt))
-	{
-		*select_list = lappend(*select_list, ptr);
-		if (((SelectStmt *) ptr)->unionall == TRUE)
-			*unionall_present = TRUE;
-		return;
-	}
-
-	/* Recursively call for all arguments. A NOT expr has no lexpr! */
-	if (((A_Expr *) ptr)->lexpr != NULL)
-		create_select_list(((A_Expr *) ptr)->lexpr, select_list, unionall_present);
-	create_select_list(((A_Expr *) ptr)->rexpr, select_list, unionall_present);
-}
-
-/* Changes the A_Expr Nodes to Expr Nodes and exchanges ANDs and ORs.
- * The reason for the exchange is easy: We implement INTERSECTs and EXCEPTs
- * by rewriting these queries to semantically equivalent queries that use
- * IN and NOT IN subselects. To be able to use all three operations
- * (UNIONs INTERSECTs and EXCEPTs) in one complex query we have to
- * translate the queries into Disjunctive Normal Form (DNF). Unfortunately
- * there is no function 'dnfify' but there is a function 'cnfify'
- * which produces DNF when we exchange ANDs and ORs before calling
- * 'cnfify' and exchange them back in the result.
- *
- * If an EXCEPT or INTERSECT is present *intersect_present
- * hands back 'true' */
-Node *
-A_Expr_to_Expr(Node *ptr, bool *intersect_present)
-{
-	Node	   *result = NULL;
-
-	switch (nodeTag(ptr))
-	{
-		case T_A_Expr:
-			{
-				A_Expr	   *a = (A_Expr *) ptr;
-
-				switch (a->oper)
-				{
-					case AND:
-						{
-							Expr	   *expr = makeNode(Expr);
-							Node	   *lexpr = A_Expr_to_Expr(((A_Expr *) ptr)->lexpr, intersect_present);
-							Node	   *rexpr = A_Expr_to_Expr(((A_Expr *) ptr)->rexpr, intersect_present);
-
-							*intersect_present = TRUE;
-
-							expr->typeOid = BOOLOID;
-							expr->opType = OR_EXPR;
-							expr->args = makeList2(lexpr, rexpr);
-							result = (Node *) expr;
-							break;
-						}
-					case OR:
-						{
-							Expr	   *expr = makeNode(Expr);
-							Node	   *lexpr = A_Expr_to_Expr(((A_Expr *) ptr)->lexpr, intersect_present);
-							Node	   *rexpr = A_Expr_to_Expr(((A_Expr *) ptr)->rexpr, intersect_present);
-
-							expr->typeOid = BOOLOID;
-							expr->opType = AND_EXPR;
-							expr->args = makeList2(lexpr, rexpr);
-							result = (Node *) expr;
-							break;
-						}
-					case NOT:
-						{
-							Expr	   *expr = makeNode(Expr);
-							Node	   *rexpr = A_Expr_to_Expr(((A_Expr *) ptr)->rexpr, intersect_present);
-
-							expr->typeOid = BOOLOID;
-							expr->opType = NOT_EXPR;
-							expr->args = makeList1(rexpr);
-							result = (Node *) expr;
-							break;
-						}
-				}
-				break;
-			}
-		default:
-			result = ptr;
-	}
-	return result;
-}
-
 void
 CheckSelectForUpdate(Query *qry)
 {
-	if (qry->unionClause || qry->intersectClause)
-		elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT clause");
+	if (qry->setOperations)
+		elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT");
 	if (qry->distinctClause != NIL)
 		elog(ERROR, "SELECT FOR UPDATE is not allowed with DISTINCT clause");
 	if (qry->groupClause != NIL)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31aea152fa6d4d21335f3d80d710882f3ace501d..f13b942abd87b3d51b032fd017e85ae8a60a0ec9 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.193 2000/09/29 18:21:36 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.194 2000/10/05 19:11:33 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -78,6 +78,7 @@ 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 SelectStmt *findLeftmostSelect(Node *node);
 static bool exprIsNullConstant(Node *arg);
 static Node *doNegate(Node *n);
 static void doNegateFloat(Value *v);
@@ -112,25 +113,26 @@ static void doNegateFloat(Value *v);
 	VersionStmt			*vstmt;
 	DefineStmt			*dstmt;
 	RuleStmt			*rstmt;
-	InsertStmt			*astmt;
+	InsertStmt			*istmt;
 }
 
 %type <node>	stmt,
-		AlterSchemaStmt, AlterTableStmt, ClosePortalStmt,
-		CopyStmt, CreateStmt, CreateAsStmt, CreateSchemaStmt, CreateSeqStmt, DefineStmt, DropStmt,
-		TruncateStmt, CommentStmt,
-		ExtendStmt, FetchStmt,	GrantStmt, CreateTrigStmt, DropSchemaStmt, DropTrigStmt,
-		CreatePLangStmt, DropPLangStmt,
-		IndexStmt, ListenStmt, UnlistenStmt, LockStmt, OptimizableStmt,
-		ProcedureStmt, ReindexStmt, RemoveAggrStmt, RemoveOperStmt,
-		RemoveFuncStmt, RemoveStmt,
-		RenameStmt, RevokeStmt, RuleStmt, SetSessionStmt, TransactionStmt, ViewStmt, LoadStmt,
-		CreatedbStmt, DropdbStmt, VacuumStmt, CursorStmt, SubSelect,
-		UpdateStmt, InsertStmt, select_clause, SelectStmt, NotifyStmt, DeleteStmt, 
-		ClusterStmt, ExplainStmt, VariableSetStmt, VariableShowStmt, VariableResetStmt,
-		CreateUserStmt, AlterUserStmt, DropUserStmt, RuleActionStmt,
-		RuleActionStmtOrEmpty, ConstraintsSetStmt,
-		CreateGroupStmt, AlterGroupStmt, DropGroupStmt
+		AlterGroupStmt, AlterSchemaStmt, AlterTableStmt, AlterUserStmt,
+		ClosePortalStmt, ClusterStmt, CommentStmt, ConstraintsSetStmt,
+		CopyStmt, CreateAsStmt, CreateGroupStmt, CreatePLangStmt,
+		CreateSchemaStmt, CreateSeqStmt, CreateStmt, CreateTrigStmt,
+		CreateUserStmt, CreatedbStmt, CursorStmt, DefineStmt, DeleteStmt,
+		DropGroupStmt, DropPLangStmt, DropSchemaStmt, DropStmt, DropTrigStmt,
+		DropUserStmt, DropdbStmt, ExplainStmt, ExtendStmt, FetchStmt,
+		GrantStmt, IndexStmt, InsertStmt, ListenStmt, LoadStmt, LockStmt,
+		NotifyStmt, OptimizableStmt, ProcedureStmt, ReindexStmt,
+		RemoveAggrStmt, RemoveFuncStmt, RemoveOperStmt, RemoveStmt,
+		RenameStmt, RevokeStmt, RuleActionStmt, RuleActionStmtOrEmpty,
+		RuleStmt, SelectStmt, SetSessionStmt, TransactionStmt, TruncateStmt,
+		UnlistenStmt, UpdateStmt, VacuumStmt, VariableResetStmt,
+		VariableSetStmt, VariableShowStmt, ViewStmt
+
+%type <node>	select_clause, select_subclause
 
 %type <list>	SessionList
 %type <node>	SessionClause
@@ -212,7 +214,7 @@ static void doNegateFloat(Value *v);
 %type <list>	OptSeqList
 %type <defelt>	OptSeqElem
 
-%type <astmt>	insert_rest
+%type <istmt>	insert_rest
 
 %type <node>	OptTableElement, ConstraintElem
 %type <node>	columnDef
@@ -1533,16 +1535,16 @@ OptInherit:  INHERITS '(' relation_name_list ')'		{ $$ = $3; }
 
 CreateAsStmt:  CREATE OptTemp TABLE relation_name OptUnder OptCreateAs AS SelectStmt
 				{
-					SelectStmt *n = (SelectStmt *)$8;
-                    if ($5 != NIL)
-						yyerror("CREATE TABLE/AS SELECT does not support UNDER");
-					if ($6 != NIL)
-						mapTargetColumns($6, n->targetList);
+					SelectStmt *n = findLeftmostSelect($8);
 					if (n->into != NULL)
 						elog(ERROR,"CREATE TABLE/AS SELECT may not specify INTO");
 					n->istemp = $2;
 					n->into = $4;
-					$$ = (Node *)n;
+                    if ($5 != NIL)
+						yyerror("CREATE TABLE/AS SELECT does not support UNDER");
+					if ($6 != NIL)
+						mapTargetColumns($6, n->targetList);
+					$$ = $8;
 				}
 		;
 
@@ -2909,11 +2911,7 @@ ViewStmt:  CREATE VIEW name opt_column_list AS SelectStmt
 					ViewStmt *n = makeNode(ViewStmt);
 					n->viewname = $3;
 					n->aliases = $4;
-					n->query = (Query *)$6;
-					if (((SelectStmt *)n->query)->unionClause != NULL)
-						elog(ERROR,"UNION in views is not implemented");
-					if (((SelectStmt *)n->query)->forUpdate != NULL)
-						elog(ERROR, "SELECT FOR UPDATE is not allowed in CREATE VIEW");
+					n->query = (Query *) $6;
 					$$ = (Node *)n;
 				}
 		;
@@ -3156,78 +3154,37 @@ InsertStmt:  INSERT INTO relation_name insert_rest
 insert_rest:  VALUES '(' target_list ')'
 				{
 					$$ = makeNode(InsertStmt);
-					$$->cols = NULL;
-					$$->distinctClause = NIL;
+					$$->cols = NIL;
 					$$->targetList = $3;
-					$$->fromClause = NIL;
-					$$->whereClause = NULL;
-					$$->groupClause = NIL;
-					$$->havingClause = NULL;
-					$$->unionClause = NIL;
+					$$->selectStmt = NULL;
 				}
 		| DEFAULT VALUES
 				{
 					$$ = makeNode(InsertStmt);
-					$$->distinctClause = NIL;
+					$$->cols = NIL;
 					$$->targetList = NIL;
-					$$->fromClause = NIL;
-					$$->whereClause = NULL;
-					$$->groupClause = NIL;
-					$$->havingClause = NULL;
-					$$->unionClause = NIL;
-				 	$$->intersectClause = NIL;
-				}
-/* We want the full power of SelectStatements including INTERSECT and EXCEPT
- * for insertion.  However, we can't support sort or limit clauses.
- */
+					$$->selectStmt = NULL;
+				}
 		| SelectStmt
 				{
-					SelectStmt *n = (SelectStmt *) $1;
-					if (n->sortClause)
-						elog(ERROR, "ORDER BY is not allowed in INSERT/SELECT");
 					$$ = makeNode(InsertStmt);
 					$$->cols = NIL;
-					$$->distinctClause = n->distinctClause;
-					$$->targetList = n->targetList;
-					$$->fromClause = n->fromClause;
-					$$->whereClause = n->whereClause;
-					$$->groupClause = n->groupClause;
-					$$->havingClause = n->havingClause;
-					$$->unionClause = n->unionClause;
-					$$->intersectClause = n->intersectClause;
-					$$->unionall = n->unionall;
-					$$->forUpdate = n->forUpdate;
+					$$->targetList = NIL;
+					$$->selectStmt = $1;
 				}
 		| '(' columnList ')' VALUES '(' target_list ')'
 				{
 					$$ = makeNode(InsertStmt);
 					$$->cols = $2;
-					$$->distinctClause = NIL;
 					$$->targetList = $6;
-					$$->fromClause = NIL;
-					$$->whereClause = NULL;
-					$$->groupClause = NIL;
-					$$->havingClause = NULL;
-					$$->unionClause = NIL; 
-				 	$$->intersectClause = NIL;
+					$$->selectStmt = NULL;
 				}
 		| '(' columnList ')' SelectStmt
 				{
-					SelectStmt *n = (SelectStmt *) $4;
-					if (n->sortClause)
-						elog(ERROR, "ORDER BY is not allowed in INSERT/SELECT");
 					$$ = makeNode(InsertStmt);
 					$$->cols = $2;
-					$$->distinctClause = n->distinctClause;
-					$$->targetList = n->targetList;
-					$$->fromClause = n->fromClause;
-					$$->whereClause = n->whereClause;
-					$$->groupClause = n->groupClause;
-					$$->havingClause = n->havingClause;
-					$$->unionClause = n->unionClause;
-					$$->intersectClause = n->intersectClause;
-					$$->unionall = n->unionall;
-					$$->forUpdate = n->forUpdate;
+					$$->targetList = NIL;
+					$$->selectStmt = $4;
 				}
 		;
 
@@ -3324,26 +3281,10 @@ UpdateStmt:  UPDATE opt_only relation_name
  *****************************************************************************/
 CursorStmt:  DECLARE name opt_cursor CURSOR FOR SelectStmt
   				{
- 					SelectStmt *n;
-  
- 					n= (SelectStmt *)$6;
-  					/* from PORTAL name */
-  					/*
-  					 *	15 august 1991 -- since 3.0 postgres does locking
-					 *	right, we discovered that portals were violating
-					 *	locking protocol.  portal locks cannot span xacts.
-					 *	as a short-term fix, we installed the check here.
-					 *							-- mao
-					 */
-					if (!IsTransactionBlock())
-						elog(ERROR,"Named portals may only be used in begin/end transaction blocks");
-
+ 					SelectStmt *n = findLeftmostSelect($6);
 					n->portalname = $2;
 					n->binary = $3;
-					if (n->forUpdate != NULL)
-							elog(ERROR,"DECLARE/UPDATE is not supported"
-								 		"\n\tCursors must be READ ONLY");
-					$$ = (Node *)n;
+					$$ = $6;
 				}
 		;
 
@@ -3364,92 +3305,22 @@ opt_cursor:  BINARY						{ $$ = TRUE; }
 /* A complete SELECT statement looks like this.  Note sort, for_update,
  * and limit clauses can only appear once, not in each set operation.
  * 
- * The rule returns a SelectStmt Node having the set operations attached to 
- * unionClause and intersectClause (NIL if no set operations were present)
+ * The rule returns either a SelectStmt node or a SetOperationStmt tree.
+ * One-time clauses are attached to the leftmost SelectStmt leaf.
+ *
+ * NOTE: only the leftmost SelectStmt leaf should have INTO, either.
+ * However, this is not checked by the grammar; parse analysis must check it.
  */
 
 SelectStmt:	  select_clause sort_clause for_update_clause opt_select_limit
 			{
-				if IsA($1, SelectStmt)
-				{
-					/* There were no set operations, so just attach the
-					 * one-time clauses.
-					 */
-					SelectStmt *n = (SelectStmt *) $1;
-					n->sortClause = $2;
-					n->forUpdate = $3;
-					n->limitOffset = nth(0, $4);
-					n->limitCount = nth(1, $4);
-					$$ = (Node *) n;
-                }
-				else
-				{
-					/* There were set operations.  The root of the operator
-					 * tree is delivered by $1, but we must hand back a
-					 * SelectStmt node not an A_Expr Node.
-					 * So we find the leftmost 'SelectStmt' in the operator
-					 * tree $1 (which is the first Select Statement in the
-					 * query), which will be the returned node.
-					 * Then we attach the whole operator tree to that node's
-					 * 'intersectClause', and a list of all 'SelectStmt' Nodes
-					 * in the tree to its 'unionClause'. (NOTE that this means
-					 * the top node has indirect recursive pointers to itself!
-					 * This would cause trouble if we tried copyObject!!)
-					 * The intersectClause and unionClause subtrees will be
-					 * left untouched by the main parser, and will only be
-					 * processed when control gets to the function
-					 * Except_Intersect_Rewrite() (in rewriteHandler.c).
-					 */
-					Node *op = (Node *) $1;
-					List *select_list = NIL;
-					SelectStmt *first_select;
-					bool	intersect_present = FALSE,
-						unionall_present = FALSE;
-
-					/* Take the operator tree as an argument and create a
-					 * list of all SelectStmt Nodes found in the tree.
-					 *
-					 * If one of the SelectStmt Nodes has the 'unionall' flag
-					 * set to true the 'unionall_present' flag is also set to
-					 * true.
-					 */
-					create_select_list(op, &select_list, &unionall_present);
-
-					/* Replace all the A_Expr Nodes in the operator tree by
-					 * Expr Nodes.
-					 *
-					 * If an INTERSECT or an EXCEPT is present, the 
-					 * 'intersect_present' flag is set to true
-					 */
-					op = A_Expr_to_Expr(op, &intersect_present);
+				SelectStmt *n = findLeftmostSelect($1);
 
-					/* If both flags are set to true we have a UNION ALL
-					 * statement mixed up with INTERSECT or EXCEPT 
-					 * which can not be handled at the moment.
-					 */
-					if (intersect_present && unionall_present)
-						elog(ERROR, "UNION ALL not allowed in mixed set operations");
-
-					/* Get the leftmost SeletStmt Node (which automatically
-					 * represents the first Select Statement of the query!)
-					 */
-					first_select = (SelectStmt *) lfirst(select_list);
-
-					/* Attach the list of all SeletStmt Nodes to unionClause */
-					first_select->unionClause = select_list;
-
-					/* Attach the whole operator tree to intersectClause */
-					first_select->intersectClause = (List *) op;
-
-					/* finally attach the sort clause &etc */
-					first_select->sortClause = $2;
-					first_select->forUpdate = $3;
-					first_select->limitOffset = nth(0, $4);
-					first_select->limitCount = nth(1, $4);
-					$$ = (Node *) first_select;
-				}		
-				if (((SelectStmt *)$$)->forUpdate != NULL && QueryIsRule)
-					elog(ERROR, "SELECT/FOR UPDATE is not allowed in CREATE RULE");
+				n->sortClause = $2;
+				n->forUpdate = $3;
+				n->limitOffset = nth(0, $4);
+				n->limitCount = nth(1, $4);
+				$$ = $1;
 			}
 		;
 
@@ -3458,82 +3329,69 @@ SelectStmt:	  select_clause sort_clause for_update_clause opt_select_limit
  * the ordering of the set operations.  Without '(' and ')' we want the
  * operations to be ordered per the precedence specs at the head of this file.
  *
+ * Since parentheses around SELECTs also appear in the expression grammar,
+ * there is a parse ambiguity if parentheses are allowed at the top level of a
+ * select_clause: are the parens part of the expression or part of the select?
+ * We separate select_clause into two levels to resolve this: select_clause
+ * can have top-level parentheses, select_subclause cannot.
+ *
  * 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
  * by the topmost SelectStmt rule.  Likewise FOR UPDATE and LIMIT.
- * 
- * The rule builds up an operator tree using A_Expr Nodes. AND Nodes represent
- * INTERSECTs, OR Nodes represent UNIONs, and AND NOT nodes represent EXCEPTs. 
- * The SelectStatements to be connected are the left and right arguments to
- * the A_Expr Nodes.
- * If no set operations appear in the query, the tree consists only of one
- * SelectStmt Node.
  */
-select_clause: '(' select_clause ')'
+select_clause: '(' select_subclause ')'
 			{
 				$$ = $2; 
 			}
-		| SubSelect
+		| select_subclause
 			{
 				$$ = $1; 
 			}
-		| select_clause EXCEPT opt_all select_clause
-			{
-				$$ = (Node *)makeA_Expr(AND,NULL,$1,
-										makeA_Expr(NOT,NULL,NULL,$4));
-				if ($3)
-					elog(ERROR, "EXCEPT ALL is not implemented yet");
-			}
-		| select_clause UNION opt_all select_clause
-			{	
-				if (IsA($4, SelectStmt))
-				{
-					SelectStmt *n = (SelectStmt *)$4;
-					n->unionall = $3;
-					/* NOTE: if UNION ALL appears with a parenthesized set
-					 * operation to its right, the ALL is silently discarded.
-					 * Should we generate an error instead?  I think it may
-					 * be OK since ALL with UNION to its right is ignored
-					 * anyway...
-					 */
-				}
-				$$ = (Node *)makeA_Expr(OR,NULL,$1,$4);
-			}
-		| select_clause INTERSECT opt_all select_clause
-			{
-				$$ = (Node *)makeA_Expr(AND,NULL,$1,$4);
-				if ($3)
-					elog(ERROR, "INTERSECT ALL is not implemented yet");
-			}
-		; 
+		;
 
-SubSelect:	SELECT opt_distinct target_list
+select_subclause: SELECT opt_distinct target_list
 			 result from_clause where_clause
 			 group_clause having_clause
 				{
 					SelectStmt *n = makeNode(SelectStmt);
 					n->distinctClause = $2;
-					n->unionall = FALSE;
 					n->targetList = $3;
-					/* This is new: Subselects support the INTO clause
-					 * which allows queries that are not part of the
-					 * SQL92 standard and should not be formulated!
-					 * We need it for INTERSECT and EXCEPT and I did not
-					 * want to create a new rule 'SubSelect1' including the
-					 * feature. If it makes troubles we will have to add 
-					 * a new rule and change this to prevent INTOs in 
-					 * Subselects again.
-					 */
 					n->istemp = (bool) ((Value *) lfirst($4))->val.ival;
 					n->into = (char *) lnext($4);
-
 					n->fromClause = $5;
 					n->whereClause = $6;
 					n->groupClause = $7;
 					n->havingClause = $8;
 					$$ = (Node *)n;
 				}
-		;
+		| select_clause UNION opt_all select_clause
+			{	
+				SetOperationStmt *n = makeNode(SetOperationStmt);
+				n->op = SETOP_UNION;
+				n->all = $3;
+				n->larg = $1;
+				n->rarg = $4;
+				$$ = (Node *) n;
+			}
+		| select_clause INTERSECT opt_all select_clause
+			{
+				SetOperationStmt *n = makeNode(SetOperationStmt);
+				n->op = SETOP_INTERSECT;
+				n->all = $3;
+				n->larg = $1;
+				n->rarg = $4;
+				$$ = (Node *) n;
+			}
+		| select_clause EXCEPT opt_all select_clause
+			{
+				SetOperationStmt *n = makeNode(SetOperationStmt);
+				n->op = SETOP_EXCEPT;
+				n->all = $3;
+				n->larg = $1;
+				n->rarg = $4;
+				$$ = (Node *) n;
+			}
+		; 
 
 		/* easy way to return two values. Can someone improve this?  bjm */
 result:  INTO OptTempTableName			{ $$ = $2; }
@@ -3763,7 +3621,7 @@ table_ref:  relation_expr
 					$1->name = $2;
 					$$ = (Node *) $1;
 				}
-		| '(' select_clause ')' alias_clause
+		| '(' select_subclause ')' alias_clause
 				{
 					RangeSubselect *n = makeNode(RangeSubselect);
 					n->subquery = $2;
@@ -4316,7 +4174,7 @@ opt_interval:  datetime							{ $$ = makeList1($1); }
  * Define row_descriptor to allow yacc to break the reduce/reduce conflict
  *  with singleton expressions.
  */
-row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
+row_expr: '(' row_descriptor ')' IN '(' select_subclause ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = $2;
@@ -4326,7 +4184,7 @@ row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
 					n->subselect = $6;
 					$$ = (Node *)n;
 				}
-		| '(' row_descriptor ')' NOT IN '(' SubSelect ')'
+		| '(' row_descriptor ')' NOT IN '(' select_subclause ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = $2;
@@ -4336,7 +4194,7 @@ row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
 					n->subselect = $7;
 					$$ = (Node *)n;
 				}
-		| '(' row_descriptor ')' all_Op sub_type '(' SubSelect ')'
+		| '(' row_descriptor ')' all_Op sub_type '(' select_subclause ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = $2;
@@ -4349,7 +4207,7 @@ row_expr: '(' row_descriptor ')' IN '(' SubSelect ')'
 					n->subselect = $7;
 					$$ = (Node *)n;
 				}
-		| '(' row_descriptor ')' all_Op '(' SubSelect ')'
+		| '(' row_descriptor ')' all_Op '(' select_subclause ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = $2;
@@ -4680,7 +4538,7 @@ a_expr:  c_expr
 						$$ = n;
 					}
 				}
-		| a_expr all_Op sub_type '(' SubSelect ')'
+		| a_expr all_Op sub_type '(' select_subclause ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = makeList1($1);
@@ -5076,7 +4934,7 @@ c_expr:  attr
 					n->agg_distinct = FALSE;
 					$$ = (Node *)n;
 				}
-		| '(' SubSelect ')'
+		| '(' select_subclause ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = NIL;
@@ -5086,7 +4944,7 @@ c_expr:  attr
 					n->subselect = $2;
 					$$ = (Node *)n;
 				}
-		| EXISTS '(' SubSelect ')'
+		| EXISTS '(' select_subclause ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = NIL;
@@ -5185,7 +5043,7 @@ trim_list:  a_expr FROM expr_list
 				{ $$ = $1; }
 		;
 
-in_expr:  SubSelect
+in_expr:  select_subclause
 				{
 					SubLink *n = makeNode(SubLink);
 					n->subselect = $1;
@@ -5912,6 +5770,19 @@ mapTargetColumns(List *src, List *dst)
 } /* mapTargetColumns() */
 
 
+/* findLeftmostSelect()
+ *		Find the leftmost SelectStmt in a SetOperationStmt parsetree.
+ */
+static SelectStmt *
+findLeftmostSelect(Node *node)
+{
+	while (node && IsA(node, SetOperationStmt))
+		node = ((SetOperationStmt *) node)->larg;
+	Assert(node && IsA(node, SelectStmt));
+	return (SelectStmt *) node;
+}
+
+
 /* xlateSqlFunc()
  * Convert alternate function names to internal Postgres functions.
  *
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index cc849ebf07b5d1b16bd441abfc16a732171540b7..20233ed1950b38c29ad2ae346f12e41c4d87c3b6 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.68 2000/09/29 18:21:36 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.69 2000/10/05 19:11:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -347,7 +347,8 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
 static RangeTblRef *
 transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 {
-	SelectStmt *subquery = (SelectStmt *) r->subquery;
+	List	   *save_rtable;
+	List	   *save_joinlist;
 	List	   *parsetrees;
 	Query	   *query;
 	RangeTblEntry *rte;
@@ -362,19 +363,21 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 		elog(ERROR, "sub-select in FROM must have an alias");
 
 	/*
-	 * 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.
+	 * Analyze and transform the subquery.  This is a bit tricky because
+	 * we don't want the subquery to be able to see any FROM items already
+	 * created in the current query (per SQL92, the scope of a FROM item
+	 * does not include other FROM items).  But it does need to be able to
+	 * see any further-up parent states, so we can't just pass a null parent
+	 * pstate link.  So, temporarily make the current query level have an
+	 * empty rtable and joinlist.
 	 */
-	if (subquery == NULL || !IsA(subquery, SelectStmt))
-		elog(ERROR, "Set operations not yet supported in subselects in FROM");
-
-	/*
-	 * 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(makeList1(subquery), NULL);
+	save_rtable = pstate->p_rtable;
+	save_joinlist = pstate->p_joinlist;
+	pstate->p_rtable = NIL;
+	pstate->p_joinlist = NIL;
+	parsetrees = parse_analyze(makeList1(r->subquery), pstate);
+	pstate->p_rtable = save_rtable;
+	pstate->p_joinlist = save_joinlist;
 
 	/*
 	 * Check that we got something reasonable.  Some of these conditions
@@ -1181,108 +1184,3 @@ exprIsInSortList(Node *expr, List *sortList, List *targetList)
 	}
 	return false;
 }
-
-/* transformUnionClause()
- * Transform a UNION clause.
- * Note that the union clause is actually a fully-formed select structure.
- * So, it is evaluated as a select, then the resulting target fields
- *	are matched up to ensure correct types in the results.
- * The select clause parsing is done recursively, so the unions are evaluated
- *	right-to-left. One might want to look at all columns from all clauses before
- *	trying to coerce, but unless we keep track of the call depth we won't know
- *	when to do this because of the recursion.
- * Let's just try matching in pairs for now (right to left) and see if it works.
- * - thomas 1998-05-22
- */
-#ifdef NOT_USED
-static List *
-transformUnionClause(List *unionClause, List *targetlist)
-{
-	List	   *union_list = NIL;
-	List	   *qlist,
-			   *qlist_item;
-
-	if (unionClause)
-	{
-		/* recursion */
-		qlist = parse_analyze(unionClause, NULL);
-
-		foreach(qlist_item, qlist)
-		{
-			Query	   *query = (Query *) lfirst(qlist_item);
-			List	   *prev_target = targetlist;
-			List	   *next_target;
-			int			prev_len = 0,
-						next_len = 0;
-
-			foreach(prev_target, targetlist)
-				if (!((TargetEntry *) lfirst(prev_target))->resdom->resjunk)
-				prev_len++;
-
-			foreach(next_target, query->targetList)
-				if (!((TargetEntry *) lfirst(next_target))->resdom->resjunk)
-				next_len++;
-
-			if (prev_len != next_len)
-				elog(ERROR, "Each UNION clause must have the same number of columns");
-
-			foreach(next_target, query->targetList)
-			{
-				Oid			itype;
-				Oid			otype;
-
-				otype = ((TargetEntry *) lfirst(prev_target))->resdom->restype;
-				itype = ((TargetEntry *) lfirst(next_target))->resdom->restype;
-
-				/* one or both is a NULL column? then don't convert... */
-				if (otype == InvalidOid)
-				{
-					/* propagate a known type forward, if available */
-					if (itype != InvalidOid)
-						((TargetEntry *) lfirst(prev_target))->resdom->restype = itype;
-#if FALSE
-					else
-					{
-						((TargetEntry *) lfirst(prev_target))->resdom->restype = UNKNOWNOID;
-						((TargetEntry *) lfirst(next_target))->resdom->restype = UNKNOWNOID;
-					}
-#endif
-				}
-				else if (itype == InvalidOid)
-				{
-				}
-				/* they don't match in type? then convert... */
-				else if (itype != otype)
-				{
-					Node	   *expr;
-
-					expr = ((TargetEntry *) lfirst(next_target))->expr;
-					expr = CoerceTargetExpr(NULL, expr, itype, otype, -1);
-					if (expr == NULL)
-					{
-						elog(ERROR, "Unable to transform %s to %s"
-							 "\n\tEach UNION clause must have compatible target types",
-							 typeidTypeName(itype),
-							 typeidTypeName(otype));
-					}
-					((TargetEntry *) lfirst(next_target))->expr = expr;
-					((TargetEntry *) lfirst(next_target))->resdom->restype = otype;
-				}
-
-				/* both are UNKNOWN? then evaluate as text... */
-				else if (itype == UNKNOWNOID)
-				{
-					((TargetEntry *) lfirst(next_target))->resdom->restype = TEXTOID;
-					((TargetEntry *) lfirst(prev_target))->resdom->restype = TEXTOID;
-				}
-				prev_target = lnext(prev_target);
-			}
-			union_list = lappend(union_list, query);
-		}
-		return union_list;
-	}
-	else
-		return NIL;
-}
-
-#endif
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index bd098fb6c68294d613b3ee733ef28dae6dfde658..ef13d67cf1c63afb864abe2862ea0890a4afac87 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.46 2000/07/30 22:13:50 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_coerce.c,v 2.47 2000/10/05 19:11:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -314,6 +314,100 @@ coerce_type_typmod(ParseState *pstate, Node *node,
 }
 
 
+/* select_common_type()
+ *		Determine the common supertype of a list of input expression types.
+ *		This is used for determining the output type of CASE and UNION
+ *		constructs.
+ *
+ * typeids is a nonempty integer list of type OIDs.  Note that earlier items
+ * in the list will be preferred if there is doubt.
+ * 'context' is a phrase to use in the error message if we fail to select
+ * a usable type.
+ *
+ * XXX this code is WRONG, since (for example) given the input (int4,int8)
+ * it will select int4, whereas according to SQL92 clause 9.3 the correct
+ * answer is clearly int8.  To fix this we need a notion of a promotion
+ * hierarchy within type categories --- something more complete than
+ * just a single preferred type.
+ */
+Oid
+select_common_type(List *typeids, const char *context)
+{
+	Oid			ptype;
+	CATEGORY	pcategory;
+	List	   *l;
+
+	Assert(typeids != NIL);
+	ptype = (Oid) lfirsti(typeids);
+	pcategory = TypeCategory(ptype);
+	foreach(l, lnext(typeids))
+	{
+		Oid		ntype = (Oid) lfirsti(l);
+
+		/* move on to next one if no new information... */
+		if (ntype && (ntype != UNKNOWNOID) && (ntype != ptype))
+		{
+			if (!ptype || ptype == UNKNOWNOID)
+			{
+				/* so far, only nulls so take anything... */
+				ptype = ntype;
+				pcategory = TypeCategory(ptype);
+			}
+			else if (TypeCategory(ntype) != pcategory)
+			{
+				/*
+				 * both types in different categories? then
+				 * not much hope...
+				 */
+				elog(ERROR, "%s types \"%s\" and \"%s\" not matched",
+					 context, typeidTypeName(ptype), typeidTypeName(ntype));
+			}
+			else if (IsPreferredType(pcategory, ntype)
+					 && can_coerce_type(1, &ptype, &ntype))
+			{
+				/*
+				 * new one is preferred and can convert? then
+				 * take it...
+				 */
+				ptype = ntype;
+				pcategory = TypeCategory(ptype);
+			}
+		}
+	}
+	return ptype;
+}
+
+/* coerce_to_common_type()
+ *		Coerce an expression to the given type.
+ *
+ * This is used following select_common_type() to coerce the individual
+ * expressions to the desired type.  'context' is a phrase to use in the
+ * error message if we fail to coerce.
+ *
+ * NOTE: pstate may be NULL.
+ */
+Node *
+coerce_to_common_type(ParseState *pstate, Node *node,
+					  Oid targetTypeId,
+					  const char *context)
+{
+	Oid			inputTypeId = exprType(node);
+
+	if (inputTypeId == targetTypeId)
+		return node;			/* no work */
+	if (can_coerce_type(1, &inputTypeId, &targetTypeId))
+	{
+		node = coerce_type(pstate, node, inputTypeId, targetTypeId, -1);
+	}
+	else
+	{
+		elog(ERROR, "%s unable to convert to type \"%s\"",
+			 context, typeidTypeName(targetTypeId));
+	}
+	return node;
+}
+
+
 /* TypeCategory()
  * Assign a category to the specified OID.
  */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 591fdab87827e93b38f0ced47b556f2a908fa82e..7b647124d1f2abfe98edb79d7250bcaaa36a2627 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.84 2000/09/29 18:21:36 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.85 2000/10/05 19:11:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -412,9 +412,9 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 			{
 				CaseExpr   *c = (CaseExpr *) expr;
 				CaseWhen   *w;
+				List	   *typeids = NIL;
 				List	   *args;
 				Oid			ptype;
-				CATEGORY	pcategory;
 
 				/* transform the list of arguments */
 				foreach(args, c->args)
@@ -432,6 +432,7 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 						w->expr = (Node *) a;
 					}
 					lfirst(args) = transformExpr(pstate, (Node *) w, precedence);
+					typeids = lappendi(typeids, exprType(w->result));
 				}
 
 				/*
@@ -452,104 +453,26 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 					c->defresult = (Node *) n;
 				}
 				c->defresult = transformExpr(pstate, c->defresult, precedence);
+				/*
+				 * Note: default result is considered the most significant
+				 * type in determining preferred type.  This is how the code
+				 * worked before, but it seems a little bogus to me --- tgl
+				 */
+				typeids = lconsi(exprType(c->defresult), typeids);
 
-				/* now check types across result clauses... */
-				c->casetype = exprType(c->defresult);
-				ptype = c->casetype;
-				pcategory = TypeCategory(ptype);
-				foreach(args, c->args)
-				{
-					Oid			wtype;
-
-					w = lfirst(args);
-					wtype = exprType(w->result);
-					/* move on to next one if no new information... */
-					if (wtype && (wtype != UNKNOWNOID)
-						&& (wtype != ptype))
-					{
-						if (!ptype || ptype == UNKNOWNOID)
-						{
-							/* so far, only nulls so take anything... */
-							ptype = wtype;
-							pcategory = TypeCategory(ptype);
-						}
-						else if ((TypeCategory(wtype) != pcategory)
-								 || ((TypeCategory(wtype) == USER_TYPE)
-							&& (TypeCategory(c->casetype) == USER_TYPE)))
-						{
-
-							/*
-							 * both types in different categories? then
-							 * not much hope...
-							 */
-							elog(ERROR, "CASE/WHEN types '%s' and '%s' not matched",
-								 typeidTypeName(c->casetype), typeidTypeName(wtype));
-						}
-						else if (IsPreferredType(pcategory, wtype)
-								 && can_coerce_type(1, &ptype, &wtype))
-						{
-
-							/*
-							 * new one is preferred and can convert? then
-							 * take it...
-							 */
-							ptype = wtype;
-							pcategory = TypeCategory(ptype);
-						}
-					}
-				}
+				ptype = select_common_type(typeids, "CASE");
+				c->casetype = ptype;
 
 				/* Convert default result clause, if necessary */
-				if (c->casetype != ptype)
-				{
-					if (!c->casetype || c->casetype == UNKNOWNOID)
-					{
+				c->defresult = coerce_to_common_type(pstate, c->defresult,
+													 ptype, "CASE/ELSE");
 
-						/*
-						 * default clause is NULL, so assign preferred
-						 * type from WHEN clauses...
-						 */
-						c->casetype = ptype;
-					}
-					else if (can_coerce_type(1, &c->casetype, &ptype))
-					{
-						c->defresult = coerce_type(pstate, c->defresult,
-												 c->casetype, ptype, -1);
-						c->casetype = ptype;
-					}
-					else
-					{
-						elog(ERROR, "CASE/ELSE unable to convert to type '%s'",
-							 typeidTypeName(ptype));
-					}
-				}
-
-				/* Convert when clauses, if not null and if necessary */
+				/* Convert when-clause results, if necessary */
 				foreach(args, c->args)
 				{
-					Oid			wtype;
-
 					w = lfirst(args);
-					wtype = exprType(w->result);
-
-					/*
-					 * only bother with conversion if not NULL and
-					 * different type...
-					 */
-					if (wtype && (wtype != UNKNOWNOID)
-						&& (wtype != ptype))
-					{
-						if (can_coerce_type(1, &wtype, &ptype))
-						{
-							w->result = coerce_type(pstate, w->result, wtype,
-													ptype, -1);
-						}
-						else
-						{
-							elog(ERROR, "CASE/WHEN unable to convert to type '%s'",
-								 typeidTypeName(ptype));
-						}
-					}
+					w->result = coerce_to_common_type(pstate, w->result,
+													  ptype, "CASE/WHEN");
 				}
 
 				result = expr;
@@ -560,7 +483,7 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 			{
 				CaseWhen   *w = (CaseWhen *) expr;
 
-				w->expr = transformExpr(pstate, (Node *) w->expr, precedence);
+				w->expr = transformExpr(pstate, w->expr, precedence);
 				if (exprType(w->expr) != BOOLOID)
 					elog(ERROR, "WHEN clause must have a boolean result");
 
@@ -575,7 +498,7 @@ transformExpr(ParseState *pstate, Node *expr, int precedence)
 					n->val.type = T_Null;
 					w->result = (Node *) n;
 				}
-				w->result = transformExpr(pstate, (Node *) w->result, precedence);
+				w->result = transformExpr(pstate, w->result, precedence);
 				result = expr;
 				break;
 			}
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index e06896d58bc6f6066996dde190dcfa0b918d904a..c08ddbc6782b6888f72116b0705b8b0bc01fc7a0 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.53 2000/09/29 18:21:24 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.54 2000/10/05 19:11:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -449,7 +449,8 @@ setRuleCheckAsUser(Query *qry, Oid userid)
 
 	/* If there are sublinks, search for them and process their RTEs */
 	if (qry->hasSubLinks)
-		query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid);
+		query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
+						  false /* already did the ones in rtable */);
 }
 
 /*
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 5d1e3a4070518cc8a1e80d589885c7ec40fbb23c..d0fe6a5ee10d4145519c28b8ccbceab488917993 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.81 2000/09/29 18:21:24 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.82 2000/10/05 19:11:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,12 +44,7 @@ static List *adjustJoinTreeList(Query *parsetree, int rt_index, bool *found);
 static List *matchLocks(CmdType event, RuleLock *rulelocks,
 						int varno, Query *parsetree);
 static Query *fireRIRrules(Query *parsetree);
-static Query *Except_Intersect_Rewrite(Query *parsetree);
-static void check_targetlists_are_compatible(List *prev_target,
-								 List *current_target);
-static void create_intersect_list(Node *ptr, List **intersect_list);
-static Node *intersect_tree_analyze(Node *tree, Node *first_select,
-					   Node *parsetree);
+
 
 /*
  * gatherRewriteMeta -
@@ -462,7 +457,8 @@ fireRIRrules(Query *parsetree)
 	 * Recurse into sublink subqueries, too.
 	 */
 	if (parsetree->hasSubLinks)
-		query_tree_walker(parsetree, fireRIRonSubLink, NULL);
+		query_tree_walker(parsetree, fireRIRonSubLink, NULL,
+						  false /* already handled the ones in rtable */);
 
 	/*
 	 * If the query was marked having aggregates, check if this is
@@ -856,7 +852,7 @@ deepRewriteQuery(Query *parsetree)
 
 
 /*
- * QueryOneRewrite -
+ * QueryRewriteOne -
  *	  rewrite one query
  */
 static List *
@@ -872,17 +868,20 @@ QueryRewriteOne(Query *parsetree)
 
 
 /*
- * BasicQueryRewrite -
- *	  rewrite one query via query rewrite system, possibly returning 0
- *	  or many queries
+ * QueryRewrite -
+ *	  Primary entry point to the query rewriter.
+ *	  Rewrite one query via query rewrite system, possibly returning 0
+ *	  or many queries.
+ *
+ * NOTE: The code in QueryRewrite was formerly in pg_parse_and_plan(), and was
+ * moved here so that it would be invoked during EXPLAIN.
  */
-static List *
-BasicQueryRewrite(Query *parsetree)
+List *
+QueryRewrite(Query *parsetree)
 {
 	List	   *querylist;
 	List	   *results = NIL;
 	List	   *l;
-	Query	   *query;
 
 	/*
 	 * Step 1
@@ -898,550 +897,41 @@ BasicQueryRewrite(Query *parsetree)
 	 */
 	foreach(l, querylist)
 	{
-		query = fireRIRrules((Query *) lfirst(l));
-		results = lappend(results, query);
-	}
-
-	return results;
-}
-
-/*
- * QueryRewrite -
- *	  Primary entry point to the query rewriter.
- *	  Rewrite one query via query rewrite system, possibly returning 0
- *	  or many queries.
- *
- * NOTE: The code in QueryRewrite was formerly in pg_parse_and_plan(), and was
- * moved here so that it would be invoked during EXPLAIN.  The division of
- * labor between this routine and BasicQueryRewrite is not obviously correct
- * ... at least not to me ... tgl 5/99.
- */
-List *
-QueryRewrite(Query *parsetree)
-{
-	List	   *rewritten,
-			   *rewritten_item;
-
-	/*
-	 * Rewrite Union, Intersect and Except Queries to normal Union Queries
-	 * using IN and NOT IN subselects
-	 */
-	if (parsetree->intersectClause)
-		parsetree = Except_Intersect_Rewrite(parsetree);
-
-	/* Rewrite basic queries (retrieve, append, delete, replace) */
-	rewritten = BasicQueryRewrite(parsetree);
-
-	/*
-	 * Rewrite the UNIONS.
-	 */
-	foreach(rewritten_item, rewritten)
-	{
-		Query	   *qry = (Query *) lfirst(rewritten_item);
-		List	   *union_result = NIL;
-		List	   *union_item;
-
-		foreach(union_item, qry->unionClause)
-		{
-			union_result = nconc(union_result,
-						BasicQueryRewrite((Query *) lfirst(union_item)));
-		}
-		qry->unionClause = union_result;
-	}
-
-	return rewritten;
-}
-
-/* This function takes two targetlists as arguments and checks if the
- * targetlists are compatible (i.e. both select for the same number of
- * attributes and the types are compatible */
-static void
-check_targetlists_are_compatible(List *prev_target, List *current_target)
-{
-	List	   *tl;
-	int			prev_len = 0,
-				next_len = 0;
-
-	foreach(tl, prev_target)
-		if (!((TargetEntry *) lfirst(tl))->resdom->resjunk)
-			prev_len++;
-
-	foreach(tl, current_target)
-		if (!((TargetEntry *) lfirst(tl))->resdom->resjunk)
-			next_len++;
-
-	if (prev_len != next_len)
-		elog(ERROR, "Each UNION | EXCEPT | INTERSECT query must have the same number of columns.");
-
-	foreach(tl, current_target)
-	{
-		TargetEntry	   *next_tle = (TargetEntry *) lfirst(tl);
-		TargetEntry	   *prev_tle;
-		Oid				itype;
-		Oid				otype;
-
-		if (next_tle->resdom->resjunk)
-			continue;
-
-		/* This loop must find an entry, since we counted them above. */
-		do
-		{
-			prev_tle = (TargetEntry *) lfirst(prev_target);
-			prev_target = lnext(prev_target);
-		} while (prev_tle->resdom->resjunk);
-
-		itype = next_tle->resdom->restype;
-		otype = prev_tle->resdom->restype;
-
-		/* one or both is a NULL column? then don't convert... */
-		if (otype == InvalidOid)
-		{
-			/* propagate a known type forward, if available */
-			if (itype != InvalidOid)
-				prev_tle->resdom->restype = itype;
-#ifdef NOT_USED
-			else
-			{
-				prev_tle->resdom->restype = UNKNOWNOID;
-				next_tle->resdom->restype = UNKNOWNOID;
-			}
-#endif
-		}
-		else if (itype == InvalidOid)
-		{
-		}
-		/* they don't match in type? then convert... */
-		else if (itype != otype)
-		{
-			Node	   *expr;
-
-			expr = next_tle->expr;
-			expr = CoerceTargetExpr(NULL, expr, itype, otype, -1);
-			if (expr == NULL)
-			{
-				elog(ERROR, "Unable to transform %s to %s"
-					 "\n\tEach UNION | EXCEPT | INTERSECT clause must have compatible target types",
-					 typeidTypeName(itype),
-					 typeidTypeName(otype));
-			}
-			next_tle->expr = expr;
-			next_tle->resdom->restype = otype;
-		}
-
-		/* both are UNKNOWN? then evaluate as text... */
-		else if (itype == UNKNOWNOID)
-		{
-			next_tle->resdom->restype = TEXTOID;
-			prev_tle->resdom->restype = TEXTOID;
-		}
-	}
-}
-
-/*
- * Rewrites UNION INTERSECT and EXCEPT queries to semantically equivalent
- * queries that use IN and NOT IN subselects.
- *
- * The operator tree is attached to 'intersectClause' (see rule
- * 'SelectStmt' in gram.y) of the 'parsetree' given as an
- * argument. First we remember some clauses (the sortClause, the
- * distinctClause etc.)  Then we translate the operator tree to DNF
- * (disjunctive normal form) by 'cnfify'. (Note that 'cnfify' produces
- * CNF but as we exchanged ANDs with ORs in function A_Expr_to_Expr()
- * earlier we get DNF after exchanging ANDs and ORs again in the
- * result.) Now we create a new query by evaluating the new operator
- * tree which is in DNF now. For every AND we create an entry in the
- * union list and for every OR we create an IN subselect. (NOT IN
- * subselects are created for OR NOT nodes). The first entry of the
- * union list is handed back but before that the remembered clauses
- * (sortClause etc) are attached to the new top Node (Note that the
- * new top Node can differ from the parsetree given as argument because of
- * the translation to DNF. That's why we have to remember the sortClause
- * and so on!)
- */
-static Query *
-Except_Intersect_Rewrite(Query *parsetree)
-{
-
-	SubLink    *n;
-	Query	   *result,
-			   *intersect_node;
-	List	   *elist,
-			   *intersect_list = NIL,
-			   *intersect,
-			   *intersectClause;
-	List	   *union_list = NIL,
-			   *sortClause,
-			   *distinctClause;
-	List	   *left_expr,
-			   *resnames = NIL;
-	char	   *op,
-			   *into;
-	bool		isBinary,
-				isPortal,
-				isTemp;
-	Node	   *limitOffset,
-			   *limitCount;
-	CmdType		commandType = CMD_SELECT;
-	RangeTblEntry *rtable_insert = NULL;
-	List	   *prev_target = NIL;
-
-	/*
-	 * Remember the Resnames of the given parsetree's targetlist (these
-	 * are the resnames of the first Select Statement of the query
-	 * formulated by the user and he wants the columns named by these
-	 * strings. The transformation to DNF can cause another Select
-	 * Statment to be the top one which uses other names for its columns.
-	 * Therefore we remember the original names and attach them to the
-	 * targetlist of the new topmost Node at the end of this function
-	 */
-	foreach(elist, parsetree->targetList)
-	{
-		TargetEntry *tent = (TargetEntry *) lfirst(elist);
+		Query   *query = (Query *) lfirst(l);
 
-		if (! tent->resdom->resjunk)
-			resnames = lappend(resnames, tent->resdom->resname);
-	}
-
-	/*
-	 * If the Statement is an INSERT INTO ... (SELECT...) statement using
-	 * UNIONs, INTERSECTs or EXCEPTs and the transformation to DNF makes
-	 * another Node to the top node we have to transform the new top node
-	 * to an INSERT node and the original INSERT node to a SELECT node
-	 */
-	if (parsetree->commandType == CMD_INSERT)
-	{
+		query = fireRIRrules(query);
 
 		/*
-		 * The result relation ( = the one to insert into) has to be
-		 * attached to the rtable list of the new top node
+		 * If the query target was rewritten as a view, complain.
 		 */
-		rtable_insert = rt_fetch(parsetree->resultRelation, parsetree->rtable);
-
-		parsetree->commandType = CMD_SELECT;
-		commandType = CMD_INSERT;
-		parsetree->resultRelation = 0;
-	}
-
-	/*
-	 * Save some items, to be able to attach them to the resulting top
-	 * node at the end of the function
-	 */
-	sortClause = parsetree->sortClause;
-	distinctClause = parsetree->distinctClause;
-	into = parsetree->into;
-	isBinary = parsetree->isBinary;
-	isPortal = parsetree->isPortal;
-	isTemp = parsetree->isTemp;
-	limitOffset = parsetree->limitOffset;
-	limitCount = parsetree->limitCount;
-
-	/*
-	 * The operator tree attached to parsetree->intersectClause is still
-	 * 'raw' ( = the leaf nodes are still SelectStmt nodes instead of
-	 * Query nodes) So step through the tree and transform the nodes using
-	 * parse_analyze().
-	 *
-	 * The parsetree (given as an argument to Except_Intersect_Rewrite()) has
-	 * already been transformed and transforming it again would cause
-	 * troubles.  So we give the 'raw' version (of the cooked parsetree)
-	 * to the function to prevent an additional transformation. Instead we
-	 * hand back the 'cooked' version also given as an argument to
-	 * intersect_tree_analyze()
-	 */
-	intersectClause =
-		(List *) intersect_tree_analyze((Node *) parsetree->intersectClause,
-								 (Node *) lfirst(parsetree->unionClause),
-										(Node *) parsetree);
-
-	/* intersectClause is no longer needed so set it to NIL */
-	parsetree->intersectClause = NIL;
-
-	/*
-	 * unionClause will be needed later on but the list it delivered is no
-	 * longer needed, so set it to NIL
-	 */
-	parsetree->unionClause = NIL;
-
-	/*
-	 * Transform the operator tree to DNF (remember ANDs and ORs have been
-	 * exchanged, that's why we get DNF by using cnfify)
-	 *
-	 * After the call, explicit ANDs are removed and all AND operands are
-	 * simply items in the intersectClause list
-	 */
-	intersectClause = cnfify((Expr *) intersectClause, true);
-
-	/*
-	 * For every entry of the intersectClause list we generate one entry
-	 * in the union_list
-	 */
-	foreach(intersect, intersectClause)
-	{
-
-		/*
-		 * for every OR we create an IN subselect and for every OR NOT we
-		 * create a NOT IN subselect, so first extract all the Select
-		 * Query nodes from the tree (that contains only OR or OR NOTs any
-		 * more because we did a transformation to DNF
-		 *
-		 * There must be at least one node that is not negated (i.e. just OR
-		 * and not OR NOT) and this node will be the first in the list
-		 * returned
-		 */
-		intersect_list = NIL;
-		create_intersect_list((Node *) lfirst(intersect), &intersect_list);
-
-		/*
-		 * This one will become the Select Query node, all other nodes are
-		 * transformed into subselects under this node!
-		 */
-		intersect_node = (Query *) lfirst(intersect_list);
-		intersect_list = lnext(intersect_list);
-
-		/*
-		 * Check if all Select Statements use the same number of
-		 * attributes and if all corresponding attributes are of the same
-		 * type
-		 */
-		if (prev_target)
-			check_targetlists_are_compatible(prev_target, intersect_node->targetList);
-		prev_target = intersect_node->targetList;
-
-		/*
-		 * Transform all nodes remaining into subselects and add them to
-		 * the qualifications of the Select Query node
-		 */
-		while (intersect_list != NIL)
+		if (query->resultRelation)
 		{
+			RangeTblEntry *rte = rt_fetch(query->resultRelation,
+										  query->rtable);
 
-			n = makeNode(SubLink);
-
-			/* Here we got an OR so transform it to an IN subselect */
-			if (IsA(lfirst(intersect_list), Query))
-			{
-
-				/*
-				 * Check if all Select Statements use the same number of
-				 * attributes and if all corresponding attributes are of
-				 * the same type
-				 */
-				check_targetlists_are_compatible(prev_target,
-						 ((Query *) lfirst(intersect_list))->targetList);
-
-				n->subselect = lfirst(intersect_list);
-				op = "=";
-				n->subLinkType = ANY_SUBLINK;
-				n->useor = false;
-			}
-
-			/*
-			 * Here we got an OR NOT node so transform it to a NOT IN
-			 * subselect
-			 */
-			else
-			{
-
-				/*
-				 * Check if all Select Statements use the same number of
-				 * attributes and if all corresponding attributes are of
-				 * the same type
-				 */
-				check_targetlists_are_compatible(prev_target,
-												 ((Query *) lfirst(((Expr *) lfirst(intersect_list))->args))->targetList);
-
-				n->subselect = (Node *) lfirst(((Expr *) lfirst(intersect_list))->args);
-				op = "<>";
-				n->subLinkType = ALL_SUBLINK;
-				n->useor = true;
-			}
-
-			/*
-			 * Prepare the lefthand side of the Sublinks: All the entries
-			 * of the targetlist must be (IN) or must not be (NOT IN) the
-			 * subselect
-			 */
-			n->lefthand = NIL;
-			foreach(elist, intersect_node->targetList)
-			{
-				TargetEntry *tent = (TargetEntry *) lfirst(elist);
-
-				if (! tent->resdom->resjunk)
-					n->lefthand = lappend(n->lefthand, tent->expr);
-			}
-
-			/*
-			 * Also prepare the list of Opers that must be used for the
-			 * comparisons (they depend on the specific datatypes
-			 * involved!)
-			 */
-			left_expr = n->lefthand;
-			n->oper = NIL;
-
-			foreach(elist, ((Query *) (n->subselect))->targetList)
+			if (rte->subquery)
 			{
-				TargetEntry *tent = (TargetEntry *) lfirst(elist);
-				Node	   *lexpr;
-				Operator	optup;
-				Form_pg_operator opform;
-				Oper	   *newop;
-
-				if (tent->resdom->resjunk)
-					continue;
-
-				lexpr = lfirst(left_expr);
-
-				optup = oper(op,
-							 exprType(lexpr),
-							 exprType(tent->expr),
-							 FALSE);
-				opform = (Form_pg_operator) GETSTRUCT(optup);
-
-				if (opform->oprresult != BOOLOID)
-					elog(ERROR, "parser: '%s' must return 'bool' to be used with quantified predicate subquery", op);
-
-				newop = makeOper(oprid(optup),	/* opno */
-								 InvalidOid,	/* opid */
-								 opform->oprresult);
-
-				n->oper = lappend(n->oper, newop);
-
-				left_expr = lnext(left_expr);
+				switch (query->commandType)
+				{
+					case CMD_INSERT:
+						elog(ERROR, "Cannot insert into a view without an appropriate rule");
+						break;
+					case CMD_UPDATE:
+						elog(ERROR, "Cannot update a view without an appropriate rule");
+						break;
+					case CMD_DELETE:
+						elog(ERROR, "Cannot delete from a view without an appropriate rule");
+						break;
+					default:
+						elog(ERROR, "QueryRewrite: unexpected commandType %d",
+							 (int) query->commandType);
+						break;
+				}
 			}
-
-			Assert(left_expr == NIL); /* should have used 'em all */
-
-			/*
-			 * If the Select Query node has aggregates in use add all the
-			 * subselects to the HAVING qual else to the WHERE qual
-			 */
-			if (intersect_node->hasAggs)
-				AddHavingQual(intersect_node, (Node *) n);
-			else
-				AddQual(intersect_node, (Node *) n);
-
-			/* Now we got sublinks */
-			intersect_node->hasSubLinks = true;
-			intersect_list = lnext(intersect_list);
 		}
-		intersect_node->intersectClause = NIL;
-		union_list = lappend(union_list, intersect_node);
-	}
-
-	/* The first entry to union_list is our new top node */
-	result = (Query *) lfirst(union_list);
-	/* attach the rest to unionClause */
-	result->unionClause = lnext(union_list);
-	/* Attach all the items remembered in the beginning of the function */
-	result->sortClause = sortClause;
-	result->distinctClause = distinctClause;
-	result->into = into;
-	result->isPortal = isPortal;
-	result->isBinary = isBinary;
-	result->isTemp = isTemp;
-	result->limitOffset = limitOffset;
-	result->limitCount = limitCount;
-
-	/*
-	 * The relation to insert into is attached to the range table of the
-	 * new top node
-	 */
-	if (commandType == CMD_INSERT)
-	{
-		result->rtable = lappend(result->rtable, rtable_insert);
-		result->resultRelation = length(result->rtable);
-		result->commandType = commandType;
-	}
-
-	/*
-	 * The resnames of the originally first SelectStatement are attached
-	 * to the new first SelectStatement
-	 */
-	foreach(elist, result->targetList)
-	{
-		TargetEntry *tent = (TargetEntry *) lfirst(elist);
-
-		if (tent->resdom->resjunk)
-			continue;
-
-		tent->resdom->resname = lfirst(resnames);
-		resnames = lnext(resnames);
-	}
-
-	return result;
-}
-
-/*
- * Create a list of nodes that are either Query nodes of NOT Expr
- * nodes followed by a Query node. The tree given in ptr contains at
- * least one non negated Query node. This node is attached to the
- * beginning of the list.
- */
-static void
-create_intersect_list(Node *ptr, List **intersect_list)
-{
-	List	   *arg;
-
-	if (IsA(ptr, Query))
-	{
-		/* The non negated node is attached at the beginning (lcons) */
-		*intersect_list = lcons(ptr, *intersect_list);
-		return;
-	}
 
-	if (IsA(ptr, Expr))
-	{
-		if (((Expr *) ptr)->opType == NOT_EXPR)
-		{
-			/* negated nodes are appended to the end (lappend) */
-			*intersect_list = lappend(*intersect_list, ptr);
-			return;
-		}
-		else
-		{
-			foreach(arg, ((Expr *) ptr)->args)
-				create_intersect_list(lfirst(arg), intersect_list);
-			return;
-		}
-		return;
-	}
-}
-
-/*
- * The nodes given in 'tree' are still 'raw' so 'cook' them using
- * parse_analyze().  The node given in first_select has already been cooked,
- * so don't transform it again but return a pointer to the previously cooked
- * version given in 'parsetree' instead.
- */
-static Node *
-intersect_tree_analyze(Node *tree, Node *first_select, Node *parsetree)
-{
-	Node	   *result = (Node *) NIL;
-	List	   *arg;
-
-	if (IsA(tree, SelectStmt))
-	{
-
-		/*
-		 * If we get to the tree given in first_select return parsetree
-		 * instead of performing parse_analyze()
-		 */
-		if (tree == first_select)
-			result = parsetree;
-		else
-		{
-			/* transform the 'raw' nodes to 'cooked' Query nodes */
-			List	   *qtree = parse_analyze(makeList1(tree), NULL);
-
-			result = (Node *) lfirst(qtree);
-		}
+		results = lappend(results, query);
 	}
 
-	if (IsA(tree, Expr))
-	{
-		/* Call recursively for every argument of the node */
-		foreach(arg, ((Expr *) tree)->args)
-			lfirst(arg) = intersect_tree_analyze(lfirst(arg), first_select, parsetree);
-		result = tree;
-	}
-	return result;
+	return results;
 }
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 0b07b5e9c46d30018edf8fadb7c9340d38eb8365..cffe624deb4f60ac062f6767c5f29ab8d2ddfc92 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.49 2000/09/29 18:21:24 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.50 2000/10/05 19:11:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,7 +48,7 @@ checkExprHasAggs(Node *node)
 	 */
 	if (node && IsA(node, Query))
 		return query_tree_walker((Query *) node, checkExprHasAggs_walker,
-								 NULL);
+								 NULL, false);
 	else
 		return checkExprHasAggs_walker(node, NULL);
 }
@@ -78,7 +78,7 @@ checkExprHasSubLink(Node *node)
 	 */
 	if (node && IsA(node, Query))
 		return query_tree_walker((Query *) node, checkExprHasSubLink_walker,
-								 NULL);
+								 NULL, false);
 	else
 		return checkExprHasSubLink_walker(node, NULL);
 }
@@ -101,7 +101,7 @@ checkExprHasSubLink_walker(Node *node, void *context)
  * Find all Var nodes in the given tree with varlevelsup == sublevels_up,
  * and increment their varno fields (rangetable indexes) by 'offset'.
  * The varnoold fields are adjusted similarly.  Also, RangeTblRef nodes
- * in join trees are adjusted.
+ * in join trees and setOp trees are adjusted.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
  * nodes in-place.	The given expression tree should have been copied
@@ -136,6 +136,7 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
 
 		if (context->sublevels_up == 0)
 			rtr->rtindex += context->offset;
+		/* the subquery itself is visited separately */
 		return false;
 	}
 	if (IsA(node, Query))
@@ -145,7 +146,7 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
 
 		context->sublevels_up++;
 		result = query_tree_walker((Query *) node, OffsetVarNodes_walker,
-								   (void *) context);
+								   (void *) context, true);
 		context->sublevels_up--;
 		return result;
 	}
@@ -168,7 +169,7 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
 	 */
 	if (node && IsA(node, Query))
 		query_tree_walker((Query *) node, OffsetVarNodes_walker,
-						  (void *) &context);
+						  (void *) &context, true);
 	else
 		OffsetVarNodes_walker(node, &context);
 }
@@ -179,7 +180,7 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
  * Find all Var nodes in the given tree belonging to a specific relation
  * (identified by sublevels_up and rt_index), and change their varno fields
  * to 'new_index'.	The varnoold fields are changed too.  Also, RangeTblRef
- * nodes in join trees are adjusted.
+ * nodes in join trees and setOp trees are adjusted.
  *
  * NOTE: although this has the form of a walker, we cheat and modify the
  * nodes in-place.	The given expression tree should have been copied
@@ -217,6 +218,7 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 		if (context->sublevels_up == 0 &&
 			rtr->rtindex == context->rt_index)
 			rtr->rtindex = context->new_index;
+		/* the subquery itself is visited separately */
 		return false;
 	}
 	if (IsA(node, Query))
@@ -226,7 +228,7 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
 
 		context->sublevels_up++;
 		result = query_tree_walker((Query *) node, ChangeVarNodes_walker,
-								   (void *) context);
+								   (void *) context, true);
 		context->sublevels_up--;
 		return result;
 	}
@@ -250,7 +252,7 @@ ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
 	 */
 	if (node && IsA(node, Query))
 		query_tree_walker((Query *) node, ChangeVarNodes_walker,
-						  (void *) &context);
+						  (void *) &context, true);
 	else
 		ChangeVarNodes_walker(node, &context);
 }
@@ -300,7 +302,7 @@ IncrementVarSublevelsUp_walker(Node *node,
 		context->min_sublevels_up++;
 		result = query_tree_walker((Query *) node,
 								   IncrementVarSublevelsUp_walker,
-								   (void *) context);
+								   (void *) context, true);
 		context->min_sublevels_up--;
 		return result;
 	}
@@ -324,7 +326,7 @@ IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
 	 */
 	if (node && IsA(node, Query))
 		query_tree_walker((Query *) node, IncrementVarSublevelsUp_walker,
-						  (void *) &context);
+						  (void *) &context, true);
 	else
 		IncrementVarSublevelsUp_walker(node, &context);
 }
@@ -332,7 +334,7 @@ IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
 
 /*
  * rangeTableEntry_used - detect whether an RTE is referenced somewhere
- *	in var nodes or jointree nodes of a query or expression.
+ *	in var nodes or join or setOp trees of a query or expression.
  */
 
 typedef struct
@@ -363,6 +365,7 @@ rangeTableEntry_used_walker(Node *node,
 		if (rtr->rtindex == context->rt_index &&
 			context->sublevels_up == 0)
 			return true;
+		/* the subquery itself is visited separately */
 		return false;
 	}
 	if (IsA(node, Query))
@@ -372,7 +375,7 @@ rangeTableEntry_used_walker(Node *node,
 
 		context->sublevels_up++;
 		result = query_tree_walker((Query *) node, rangeTableEntry_used_walker,
-								   (void *) context);
+								   (void *) context, true);
 		context->sublevels_up--;
 		return result;
 	}
@@ -395,7 +398,7 @@ rangeTableEntry_used(Node *node, int rt_index, int sublevels_up)
 	 */
 	if (node && IsA(node, Query))
 		return query_tree_walker((Query *) node, rangeTableEntry_used_walker,
-								 (void *) &context);
+								 (void *) &context, true);
 	else
 		return rangeTableEntry_used_walker(node, &context);
 }
@@ -437,7 +440,7 @@ attribute_used_walker(Node *node,
 
 		context->sublevels_up++;
 		result = query_tree_walker((Query *) node, attribute_used_walker,
-								   (void *) context);
+								   (void *) context, true);
 		context->sublevels_up--;
 		return result;
 	}
@@ -461,7 +464,7 @@ attribute_used(Node *node, int rt_index, int attno, int sublevels_up)
 	 */
 	if (node && IsA(node, Query))
 		return query_tree_walker((Query *) node, attribute_used_walker,
-								 (void *) &context);
+								 (void *) &context, true);
 	else
 		return attribute_used_walker(node, &context);
 }
@@ -681,10 +684,8 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
 		FLATCOPY(newnode, sublink, SubLink);
 		MUTATE(newnode->lefthand, sublink->lefthand, List *,
 			   ResolveNew_mutator, context);
-		context->sublevels_up++;
 		MUTATE(newnode->subselect, sublink->subselect, Node *,
 			   ResolveNew_mutator, context);
-		context->sublevels_up--;
 		return (Node *) newnode;
 	}
 	if (IsA(node, Query))
@@ -693,12 +694,9 @@ ResolveNew_mutator(Node *node, ResolveNew_context *context)
 		Query	   *newnode;
 
 		FLATCOPY(newnode, query, Query);
-		MUTATE(newnode->targetList, query->targetList, List *,
-			   ResolveNew_mutator, context);
-		MUTATE(newnode->jointree, query->jointree, FromExpr *,
-			   ResolveNew_mutator, context);
-		MUTATE(newnode->havingQual, query->havingQual, Node *,
-			   ResolveNew_mutator, context);
+		context->sublevels_up++;
+		query_tree_mutator(newnode, ResolveNew_mutator, context, true);
+		context->sublevels_up--;
 		return (Node *) newnode;
 	}
 	return expression_tree_mutator(node, ResolveNew_mutator,
@@ -718,11 +716,22 @@ ResolveNew(Node *node, int target_varno, int sublevels_up,
 	context.update_varno = update_varno;
 
 	/*
-	 * Note: if an entire Query is passed, the right things will happen,
-	 * because ResolveNew_mutator increments sublevels_up when it sees
-	 * a SubLink, not a Query.
+	 * Must be prepared to start with a Query or a bare expression tree;
+	 * if it's a Query, go straight to query_tree_mutator to make sure that
+	 * sublevels_up doesn't get incremented prematurely.
 	 */
-	return ResolveNew_mutator(node, &context);
+	if (node && IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		Query	   *newnode;
+
+		FLATCOPY(newnode, query, Query);
+		query_tree_mutator(newnode, ResolveNew_mutator,
+						   (void *) &context, true);
+		return (Node *) newnode;
+	}
+	else
+		return ResolveNew_mutator(node, &context);
 }
 
 /*
@@ -740,12 +749,8 @@ FixNew(RewriteInfo *info, Query *parsetree)
 	context.event = info->event;
 	context.update_varno = info->current_varno;
 
-	info->rule_action->targetList = (List *)
-		ResolveNew_mutator((Node *) info->rule_action->targetList, &context);
-	info->rule_action->jointree = (FromExpr *)
-		ResolveNew_mutator((Node *) info->rule_action->jointree, &context);
-	info->rule_action->havingQual =
-		ResolveNew_mutator(info->rule_action->havingQual, &context);
+	query_tree_mutator(info->rule_action, ResolveNew_mutator,
+					   (void *) &context, true);
 }
 
 
@@ -837,10 +842,8 @@ HandleRIRAttributeRule_mutator(Node *node,
 		FLATCOPY(newnode, sublink, SubLink);
 		MUTATE(newnode->lefthand, sublink->lefthand, List *,
 			   HandleRIRAttributeRule_mutator, context);
-		context->sublevels_up++;
 		MUTATE(newnode->subselect, sublink->subselect, Node *,
 			   HandleRIRAttributeRule_mutator, context);
-		context->sublevels_up--;
 		return (Node *) newnode;
 	}
 	if (IsA(node, Query))
@@ -849,14 +852,10 @@ HandleRIRAttributeRule_mutator(Node *node,
 		Query	   *newnode;
 
 		FLATCOPY(newnode, query, Query);
-		MUTATE(newnode->targetList, query->targetList, List *,
-			   HandleRIRAttributeRule_mutator, context);
-		MUTATE(newnode->jointree, query->jointree, FromExpr *,
-			   HandleRIRAttributeRule_mutator, context);
-		MUTATE(newnode->havingQual, query->havingQual, Node *,
-			   HandleRIRAttributeRule_mutator, context);
-
-
+		context->sublevels_up++;
+		query_tree_mutator(newnode, HandleRIRAttributeRule_mutator,
+						   context, true);
+		context->sublevels_up--;
 		return (Node *) newnode;
 	}
 	return expression_tree_mutator(node, HandleRIRAttributeRule_mutator,
@@ -882,15 +881,8 @@ HandleRIRAttributeRule(Query *parsetree,
 	context.badsql = badsql;
 	context.sublevels_up = 0;
 
-	parsetree->targetList = (List *)
-		HandleRIRAttributeRule_mutator((Node *) parsetree->targetList,
-									   &context);
-	parsetree->jointree = (FromExpr *)
-		HandleRIRAttributeRule_mutator((Node *) parsetree->jointree,
-									   &context);
-	parsetree->havingQual =
-		HandleRIRAttributeRule_mutator(parsetree->havingQual,
-									   &context);
+	query_tree_mutator(parsetree, HandleRIRAttributeRule_mutator,
+					   (void *) &context, true);
 }
 
 #endif /* NOT_USED */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 64d0c3a820a2c0123bdf4baeb5b962d74fd673be..4d0fa04bdf3a880f2e5c2a57f13e6cdedfbeafec 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
  *				back to source text
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.64 2000/09/29 18:21:37 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.65 2000/10/05 19:11:34 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -97,6 +97,10 @@ static void get_select_query_def(Query *query, deparse_context *context);
 static void get_insert_query_def(Query *query, deparse_context *context);
 static void get_update_query_def(Query *query, deparse_context *context);
 static void get_delete_query_def(Query *query, deparse_context *context);
+static void get_basic_select_query(Query *query, deparse_context *context);
+static void get_setop_query(Node *setOp, Query *query,
+							deparse_context *context, bool toplevel);
+static bool simple_distinct(List *distinctClause, List *targetList);
 static RangeTblEntry *get_rte_for_var(Var *var, deparse_context *context);
 static void get_rule_expr(Node *node, deparse_context *context);
 static void get_func_expr(Expr *expr, deparse_context *context);
@@ -874,6 +878,63 @@ get_query_def(Query *query, StringInfo buf, List *parentrtables)
  */
 static void
 get_select_query_def(Query *query, deparse_context *context)
+{
+	StringInfo	buf = context->buf;
+	bool		shortform_orderby;
+	char	   *sep;
+	List	   *l;
+
+	/* ----------
+	 * If the Query node has a setOperations tree, then it's the top
+	 * level of a UNION/INTERSECT/EXCEPT query; only the ORDER BY field
+	 * is interesting in the top query itself.
+	 * ----------
+	 */
+	if (query->setOperations)
+	{
+		get_setop_query(query->setOperations, query, context, true);
+		/* ORDER BY clauses must be simple in this case */
+		shortform_orderby = true;
+	}
+	else
+	{
+		get_basic_select_query(query, context);
+		shortform_orderby = false;
+	}
+
+	/* Add the ORDER BY clause if given */
+	if (query->sortClause != NIL)
+	{
+		appendStringInfo(buf, " ORDER BY ");
+		sep = "";
+		foreach(l, query->sortClause)
+		{
+			SortClause *srt = (SortClause *) lfirst(l);
+			TargetEntry *sorttle;
+			char	   *opname;
+
+			sorttle = get_sortgroupclause_tle(srt,
+											  query->targetList);
+			appendStringInfo(buf, sep);
+			if (shortform_orderby)
+				appendStringInfo(buf, "%d", sorttle->resdom->resno);
+			else
+				get_rule_expr(sorttle->expr, context);
+			opname = get_opname(srt->sortop);
+			if (strcmp(opname, "<") != 0)
+			{
+				if (strcmp(opname, ">") == 0)
+					appendStringInfo(buf, " DESC");
+				else
+					appendStringInfo(buf, " USING %s", opname);
+			}
+			sep = ", ";
+		}
+	}
+}
+
+static void
+get_basic_select_query(Query *query, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
 	char	   *sep;
@@ -885,6 +946,32 @@ get_select_query_def(Query *query, deparse_context *context)
 	 */
 	appendStringInfo(buf, "SELECT");
 
+	/* Add the DISTINCT clause if given */
+	if (query->distinctClause != NIL)
+	{
+		if (simple_distinct(query->distinctClause, query->targetList))
+		{
+			appendStringInfo(buf, " DISTINCT");
+		}
+		else
+		{
+			appendStringInfo(buf, " DISTINCT ON (");
+			sep = "";
+			foreach(l, query->distinctClause)
+			{
+				SortClause *srt = (SortClause *) lfirst(l);
+				Node	   *sortexpr;
+
+				sortexpr = get_sortgroupclause_expr(srt,
+													query->targetList);
+				appendStringInfo(buf, sep);
+				get_rule_expr(sortexpr, context);
+				sep = ", ";
+			}
+			appendStringInfo(buf, ")");
+		}
+	}
+
 	/* Then we tell what to select (the targetlist) */
 	sep = " ";
 	foreach(l, query->targetList)
@@ -931,7 +1018,7 @@ get_select_query_def(Query *query, deparse_context *context)
 		get_rule_expr(query->jointree->quals, context);
 	}
 
-	/* Add the GROUP BY CLAUSE */
+	/* Add the GROUP BY clause if given */
 	if (query->groupClause != NULL)
 	{
 		appendStringInfo(buf, " GROUP BY ");
@@ -948,6 +1035,94 @@ get_select_query_def(Query *query, deparse_context *context)
 			sep = ", ";
 		}
 	}
+
+	/* Add the HAVING clause if given */
+	if (query->havingQual != NULL)
+	{
+		appendStringInfo(buf, " HAVING ");
+		get_rule_expr(query->havingQual, context);
+	}
+}
+
+static void
+get_setop_query(Node *setOp, Query *query, deparse_context *context,
+				bool toplevel)
+{
+	StringInfo	buf = context->buf;
+
+	if (IsA(setOp, RangeTblRef))
+	{
+		RangeTblRef *rtr = (RangeTblRef *) setOp;
+		RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
+		Query  *subquery = rte->subquery;
+
+		Assert(subquery != NULL);
+		get_query_def(subquery, buf, context->rangetables);
+	}
+	else if (IsA(setOp, SetOperationStmt))
+	{
+		SetOperationStmt *op = (SetOperationStmt *) setOp;
+
+		/* Must suppress parens at top level of a setop tree because
+		 * of grammar limitations...
+		 */
+		if (! toplevel)
+			appendStringInfo(buf, "(");
+		get_setop_query(op->larg, query, context, false);
+		switch (op->op)
+		{
+			case SETOP_UNION:
+				appendStringInfo(buf, " UNION ");
+				break;
+			case SETOP_INTERSECT:
+				appendStringInfo(buf, " INTERSECT ");
+				break;
+			case SETOP_EXCEPT:
+				appendStringInfo(buf, " EXCEPT ");
+				break;
+			default:
+				elog(ERROR, "get_setop_query: unexpected set op %d",
+					 (int) op->op);
+		}
+		if (op->all)
+			appendStringInfo(buf, "ALL ");
+		get_setop_query(op->rarg, query, context, false);
+		if (! toplevel)
+			appendStringInfo(buf, ")");
+	}
+	else
+	{
+		elog(ERROR, "get_setop_query: unexpected node %d",
+			 (int) nodeTag(setOp));
+	}
+}
+
+/*
+ * Detect whether a DISTINCT list can be represented as just DISTINCT
+ * or needs DISTINCT ON.  It's simple if it contains exactly the nonjunk
+ * targetlist items.
+ */
+static bool
+simple_distinct(List *distinctClause, List *targetList)
+{
+	while (targetList)
+	{
+		TargetEntry *tle = (TargetEntry *) lfirst(targetList);
+
+		if (! tle->resdom->resjunk)
+		{
+			if (distinctClause == NIL)
+				return false;
+			if (((SortClause *) lfirst(distinctClause))->tleSortGroupRef !=
+				tle->resdom->ressortgroupref)
+				return false;
+			distinctClause = lnext(distinctClause);
+		}
+		targetList = lnext(targetList);
+	}
+	if (distinctClause != NIL)
+		return false;
+	return true;
 }
 
 
@@ -959,33 +1134,24 @@ static void
 get_insert_query_def(Query *query, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
-	char	   *sep;
-	bool		rt_constonly = TRUE;
+	RangeTblEntry *select_rte = NULL;
 	RangeTblEntry *rte;
-	int			i;
+	char	   *sep;
 	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.  (Pretty klugy ... fix this
-	 * when we redesign querytrees!)
+	 * If it's an INSERT ... SELECT there will be a single subquery RTE
+	 * for the SELECT.
 	 * ----------
 	 */
-	i = 0;
 	foreach(l, query->rtable)
 	{
 		rte = (RangeTblEntry *) lfirst(l);
-		i++;
-		if (strcmp(rte->eref->relname, "*NEW*") == 0)
-			continue;
-		if (strcmp(rte->eref->relname, "*OLD*") == 0)
+		if (rte->subquery == NULL)
 			continue;
-		if (rangeTableEntry_used((Node *) query, i, 0))
-		{
-			rt_constonly = FALSE;
-			break;
-		}
+		if (select_rte)
+			elog(ERROR, "get_insert_query_def: too many RTEs in INSERT!");
+		select_rte = rte;
 	}
 
 	/* ----------
@@ -1012,7 +1178,7 @@ get_insert_query_def(Query *query, deparse_context *context)
 	appendStringInfo(buf, ") ");
 
 	/* Add the VALUES or the SELECT */
-	if (rt_constonly && query->jointree->quals == NULL)
+	if (select_rte == NULL)
 	{
 		appendStringInfo(buf, "VALUES (");
 		sep = "";
@@ -1030,7 +1196,9 @@ get_insert_query_def(Query *query, deparse_context *context)
 		appendStringInfoChar(buf, ')');
 	}
 	else
-		get_select_query_def(query, context);
+	{
+		get_query_def(select_rte->subquery, buf, NIL);
+	}
 }
 
 
@@ -1809,7 +1977,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			/* Subquery RTE */
 			Assert(rte->subquery != NULL);
 			appendStringInfoChar(buf, '(');
-			get_query_def(rte->subquery, buf, NIL);
+			get_query_def(rte->subquery, buf, context->rangetables);
 			appendStringInfoChar(buf, ')');
 		}
 		if (rte->alias != NULL)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index c64b5a9a9de23e912a8d363ab568b2822f9d999b..8310bc3c01ab20577b7385847ef055222469d043 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.48 2000/09/29 18:21:37 tgl Exp $
+ * $Id: catversion.h,v 1.49 2000/10/05 19:11:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200009281
+#define CATALOG_VERSION_NO	200010041
 
 #endif
diff --git a/src/include/executor/nodeSetOp.h b/src/include/executor/nodeSetOp.h
new file mode 100644
index 0000000000000000000000000000000000000000..0414cc46ef0530be5a0eba58965db4862023413b
--- /dev/null
+++ b/src/include/executor/nodeSetOp.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeSetOp.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $Id: nodeSetOp.h,v 1.1 2000/10/05 19:11:36 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODESETOP_H
+#define NODESETOP_H
+
+#include "nodes/plannodes.h"
+
+extern TupleTableSlot *ExecSetOp(SetOp *node);
+extern bool ExecInitSetOp(SetOp *node, EState *estate, Plan *parent);
+extern int	ExecCountSlotsSetOp(SetOp *node);
+extern void ExecEndSetOp(SetOp *node);
+extern void ExecReScanSetOp(SetOp *node, ExprContext *exprCtxt, Plan *parent);
+
+#endif	 /* NODESETOP_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 65d35a2977eff9d40fb778d1c3b4403de72faa55..06de4be54cb5c805bfb21e64627ac194ef107a0c 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.50 2000/09/29 18:21:38 tgl Exp $
+ * $Id: execnodes.h,v 1.51 2000/10/05 19:11:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -348,7 +348,6 @@ typedef struct ResultState
  *		whichplan		which plan is being executed
  *		nplans			how many plans are in the list
  *		initialized		array of ExecInitNode() results
- *		rtentries		range table for the current plan
  *		result_relation_info_list  array of each subplan's result relation info
  *		junkFilter_list  array of each subplan's junk filter
  * ----------------
@@ -359,7 +358,6 @@ typedef struct AppendState
 	int			as_whichplan;
 	int			as_nplans;
 	bool	   *as_initialized;
-	List	   *as_rtentries;
 	List	   *as_result_relation_info_list;
 	List	   *as_junkFilter_list;
 } AppendState;
@@ -460,14 +458,12 @@ typedef struct TidScanState
  *		The sub-query will have its own EState, which we save here.
  *		ScanTupleSlot references the current output tuple of the sub-query.
  *
- *		SubQueryDesc	   queryDesc for sub-query
  *		SubEState		   exec state for sub-query
  * ----------------
  */
 typedef struct SubqueryScanState
 {
 	CommonScanState csstate;	/* its first field is NodeTag */
-	struct QueryDesc *sss_SubQueryDesc;
 	EState	   *sss_SubEState;
 } SubqueryScanState;
 
@@ -659,6 +655,26 @@ typedef struct UniqueState
 	MemoryContext tempContext;	/* short-term context for comparisons */
 } UniqueState;
 
+/* ----------------
+ *	 SetOpState information
+ *
+ *		SetOp nodes are used "on top of" sort nodes to discard
+ *		duplicate tuples returned from the sort phase.  These are
+ *		more complex than a simple Unique since we have to count
+ *		how many duplicates to return.
+ * ----------------
+ */
+typedef struct SetOpState
+{
+	CommonState cstate;			/* its first field is NodeTag */
+	FmgrInfo   *eqfunctions;	/* per-field lookup data for equality fns */
+	bool		subplan_done;	/* has subplan returned EOF? */
+	long		numLeft;		/* number of left-input dups of cur group */
+	long		numRight;		/* number of right-input dups of cur group */
+	long		numOutput;		/* number of dups left to output */
+	MemoryContext tempContext;	/* short-term context for comparisons */
+} SetOpState;
+
 
 /* ----------------
  *	 HashState information
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c72516477dc642e0c101751f9af32f151fe5187f..fe798492c787aa627118338bb9b2e44f05cd6dbf 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.77 2000/09/29 18:21:38 tgl Exp $
+ * $Id: nodes.h,v 1.78 2000/10/05 19:11:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,7 +45,7 @@ typedef enum NodeTag
 	T_Agg,
 	T_Unique,
 	T_Hash,
-	T_Choose_XXX,				/* not used anymore; this tag# is available */
+	T_SetOp,
 	T_Group,
 	T_SubPlan,
 	T_TidScan,
@@ -121,6 +121,7 @@ typedef enum NodeTag
 	T_HashState,
 	T_TidScanState,
 	T_SubqueryScanState,
+	T_SetOpState,
 
 	/*---------------------
 	 * TAGS FOR MEMORY NODES (memnodes.h)
@@ -141,7 +142,7 @@ typedef enum NodeTag
 	T_Null,
 
 	/*---------------------
-	 * TAGS FOR PARSE TREE NODES (parsenode.h)
+	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
 	 *---------------------
 	 */
 	T_Query = 600,
@@ -150,7 +151,7 @@ typedef enum NodeTag
 	T_UpdateStmt,
 	T_SelectStmt,
 	T_AlterTableStmt,
-	T_AggregateStmtXXX,			/* not used anymore; this tag# is available */
+	T_SetOperationStmt,
 	T_ChangeACLStmt,
 	T_ClosePortalStmt,
 	T_ClusterStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ab805406eca8126a930d28de90efd5ab1f6c3fff..41309426e8b04fea273146e52c9b70d1d6a89a6b 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.114 2000/09/29 18:21:38 tgl Exp $
+ * $Id: parsenodes.h,v 1.115 2000/10/05 19:11:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,7 +29,6 @@
  *
  * we need the isPortal flag because portal names can be null too; can
  * get rid of it if we support CURSOR as a commandType.
- *
  */
 typedef struct Query
 {
@@ -45,33 +44,32 @@ typedef struct Query
 	bool		isPortal;		/* is this a retrieve into portal? */
 	bool		isBinary;		/* binary portal? */
 	bool		isTemp;			/* is 'into' a temp table? */
-	bool		unionall;		/* union without unique sort */
+
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasSubLinks;	/* has subquery SubLink */
 
 	List	   *rtable;			/* list of range table entries */
 	FromExpr   *jointree;		/* table join tree (FROM and WHERE clauses) */
 
-	List	   *targetList;		/* target list (of TargetEntry) */
-
 	List	   *rowMarks;		/* integer list of RT indexes of relations
 								 * that are selected FOR UPDATE */
 
-	List	   *distinctClause; /* a list of SortClause's */
-
-	List	   *sortClause;		/* a list of SortClause's */
+	List	   *targetList;		/* target list (of TargetEntry) */
 
 	List	   *groupClause;	/* a list of GroupClause's */
 
 	Node	   *havingQual;		/* qualifications applied to groups */
 
-	List	   *intersectClause;
-	List	   *unionClause;	/* unions are linked under the previous
-								 * query */
+	List	   *distinctClause; /* a list of SortClause's */
+
+	List	   *sortClause;		/* a list of SortClause's */
 
 	Node	   *limitOffset;	/* # of result tuples to skip */
 	Node	   *limitCount;		/* # of result tuples to return */
 
+	Node	   *setOperations;	/* set-operation tree if this is top level
+								 * of a UNION/INTERSECT/EXCEPT query */
+
 	/* internal to planner */
 	List	   *base_rel_list;	/* list of base-relation RelOptInfos */
 	List	   *join_rel_list;	/* list of join-relation RelOptInfos */
@@ -785,19 +783,14 @@ typedef struct InsertStmt
 {
 	NodeTag		type;
 	char	   *relname;		/* relation to insert into */
-	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
-								 * lcons(NIL,NIL) for all (SELECT
-								 * DISTINCT) */
-	List	   *cols;			/* names of the columns */
+	List	   *cols;			/* optional: names of the target columns */
+	/*
+	 * An INSERT statement has *either* VALUES or SELECT, never both.
+	 * If VALUES, a targetList is supplied (empty for DEFAULT VALUES).
+	 * If SELECT, a complete SelectStmt (or SetOperation tree) is supplied.
+	 */
 	List	   *targetList;		/* the target list (of ResTarget) */
-	List	   *fromClause;		/* the from clause */
-	Node	   *whereClause;	/* qualifications */
-	List	   *groupClause;	/* GROUP BY clauses */
-	Node	   *havingClause;	/* having conditional-expression */
-	List	   *unionClause;	/* union subselect parameters */
-	bool		unionall;		/* union without unique sort */
-	List	   *intersectClause;
-	List	   *forUpdate;		/* FOR UPDATE clause */
+	Node	   *selectStmt;		/* the source SELECT */
 } InsertStmt;
 
 /* ----------------------
@@ -842,20 +835,50 @@ typedef struct SelectStmt
 	Node	   *whereClause;	/* qualifications */
 	List	   *groupClause;	/* GROUP BY clauses */
 	Node	   *havingClause;	/* having conditional-expression */
-	List	   *intersectClause;
-	List	   *exceptClause;
-
-	List	   *unionClause;	/* union subselect parameters */
 	List	   *sortClause;		/* sort clause (a list of SortGroupBy's) */
 	char	   *portalname;		/* the portal (cursor) to create */
 	bool		binary;			/* a binary (internal) portal? */
 	bool		istemp;			/* into is a temp table */
-	bool		unionall;		/* union without unique sort */
 	Node	   *limitOffset;	/* # of result tuples to skip */
 	Node	   *limitCount;		/* # of result tuples to return */
 	List	   *forUpdate;		/* FOR UPDATE clause */
 } SelectStmt;
 
+/* ----------------------
+ *		Select Statement with Set Operations
+ *
+ * UNION/INTERSECT/EXCEPT operations are represented in the output of gram.y
+ * as a tree whose leaves are SelectStmts and internal nodes are
+ * SetOperationStmts.  The statement-wide info (ORDER BY, etc clauses)
+ * is placed in the leftmost SelectStmt leaf.
+ *
+ * After parse analysis, there is a top-level Query node containing the leaf
+ * SELECTs as subqueries in its range table.  Its setOperations field is the
+ * SetOperationStmt tree with leaf SelectStmt nodes replaced by RangeTblRef
+ * nodes.  The statement-wide options such as ORDER BY are attached to this
+ * top-level Query.
+ * ----------------------
+ */
+typedef enum SetOperation
+{
+	SETOP_UNION,
+	SETOP_INTERSECT,
+	SETOP_EXCEPT
+} SetOperation;
+
+typedef struct SetOperationStmt
+{
+	NodeTag		type;
+	SetOperation op;			/* type of set op */
+	bool		all;			/* ALL specified? */
+	Node	   *larg;			/* left child */
+	Node	   *rarg;			/* right child */
+	/* Eventually add fields for CORRESPONDING spec here */
+
+	/* This field is filled in during parse analysis: */
+	List	   *colTypes;		/* integer list of OIDs of output column types */
+} SetOperationStmt;
+
 /****************************************************************************
  *	Supporting data structures for Parse Trees
  *
diff --git a/src/include/nodes/pg_list.h b/src/include/nodes/pg_list.h
index 953f6edebeef8737dc45e53615965d0c62e28bba..07c58348e07d065893a0078af466769f5a81e2c9 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.20 2000/09/29 18:21:39 tgl Exp $
+ * $Id: pg_list.h,v 1.21 2000/10/05 19:11:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -135,6 +135,7 @@ extern List *lreverse(List *l);
 extern List *set_union(List *list1, List *list2);
 extern List *set_unioni(List *list1, List *list2);
 
+extern bool equali(List *list1, List *list2);
 extern bool sameseti(List *list1, List *list2);
 extern bool nonoverlap_setsi(List *list1, List *list2);
 extern bool is_subseti(List *list1, List *list2);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index ca5727f01524230b67369362c46bf310feaafecd..d8e3df4829aaf1c223ee958c946a00d0172d5946 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.43 2000/09/29 18:21:39 tgl Exp $
+ * $Id: plannodes.h,v 1.44 2000/10/05 19:11:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,6 +46,7 @@
  *		Material				MaterialState			matstate;
  *		Sort					SortState				sortstate;
  *		Unique					UniqueState				uniquestate;
+ *		SetOp					SetOpState				setopstate;
  *		Hash					HashState				hashstate;
  *
  * ----------------------------------------------------------------
@@ -145,16 +146,19 @@ typedef struct Result
 
 /* ----------------
  *		append node
+ *
+ * Append nodes can modify the query's rtable during execution.
+ * If inheritrelid > 0, then the RTE with index inheritrelid is replaced
+ * by the i'th element of inheritrtable to execute the i'th subplan.
+ * We assume that this RTE is not used in any other part of the
+ * query plan tree, else confusion may result...
  * ----------------
  */
 typedef struct Append
 {
 	Plan		plan;
 	List	   *appendplans;
-	List	   *unionrtables;	/* List of range tables, one for each
-								 * union query. */
-	Index		inheritrelid;	/* The range table has to be changed for
-								 * inheritance. */
+	Index		inheritrelid;
 	List	   *inheritrtable;
 	AppendState *appendstate;
 } Append;
@@ -348,6 +352,29 @@ typedef struct Unique
 	UniqueState *uniquestate;
 } Unique;
 
+/* ----------------
+ *		setop node
+ * ----------------
+ */
+typedef enum SetOpCmd
+{
+	SETOPCMD_INTERSECT,
+	SETOPCMD_INTERSECT_ALL,
+	SETOPCMD_EXCEPT,
+	SETOPCMD_EXCEPT_ALL
+} SetOpCmd;
+
+typedef struct SetOp
+{
+	Plan		plan;
+	SetOpCmd	cmd;			/* what to do */
+	int			numCols;		/* number of columns to check for
+								 * duplicate-ness */
+	AttrNumber *dupColIdx;		/* indexes into the target list */
+	AttrNumber	flagColIdx;
+	SetOpState *setopstate;
+} SetOp;
+
 /* ----------------
  *		hash build node
  * ----------------
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index 881940f9a185af60bc82471587052760b850fb2c..612750dfaf3c169ae64b2c3e2a03aa2080cf7a1c 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.40 2000/09/29 18:21:40 tgl Exp $
+ * $Id: clauses.h,v 1.41 2000/10/05 19:11:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -75,7 +75,9 @@ extern bool expression_tree_walker(Node *node, bool (*walker) (),
 extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (),
 									 void *context);
 extern bool query_tree_walker(Query *query, bool (*walker) (),
-							  void *context);
+							  void *context, bool visitQueryRTEs);
+extern void query_tree_mutator(Query *query, Node *(*mutator) (),
+							   void *context, bool visitQueryRTEs);
 
 #define is_subplan(clause)	((clause) != NULL && \
 							 IsA(clause, Expr) && \
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index e46d0bdbd84d1f29ee43bf4391e1b7afac652d7c..015590a5ee2a6214f0c4e11672f7eaa3baec1dd4 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.45 2000/09/29 18:21:40 tgl Exp $
+ * $Id: planmain.h,v 1.46 2000/10/05 19:11:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -26,6 +26,8 @@ extern Plan *query_planner(Query *root, List *tlist, double tuple_fraction);
  * prototypes for plan/createplan.c
  */
 extern Plan *create_plan(Query *root, Path *best_path);
+extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
+									   Index scanrelid, Plan *subplan);
 extern Sort *make_sort(List *tlist, Plan *lefttree, int keycount);
 extern Sort *make_sort_from_pathkeys(List *tlist, Plan *lefttree,
 									 List *pathkeys);
@@ -34,7 +36,10 @@ extern Group *make_group(List *tlist, bool tuplePerGroup, int ngrp,
 		   AttrNumber *grpColIdx, Plan *lefttree);
 extern Material *make_material(List *tlist, Plan *lefttree);
 extern Unique *make_unique(List *tlist, Plan *lefttree, List *distinctList);
+extern SetOp *make_setop(SetOpCmd cmd, List *tlist, Plan *lefttree,
+						 List *distinctList, AttrNumber flagColIdx);
 extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);
+extern void copy_plan_costsize(Plan *dest, Plan *src);
 
 /*
  * prototypes for plan/initsplan.c
diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h
index da099940e6ed8d068af7b02e541fa1c4f6f06ae7..3fc0f435dc33e76ff2cc530c426816bfb8f88b75 100644
--- a/src/include/optimizer/planner.h
+++ b/src/include/optimizer/planner.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: planner.h,v 1.16 2000/08/21 20:55:28 tgl Exp $
+ * $Id: planner.h,v 1.17 2000/10/05 19:11:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,4 +22,6 @@ extern Plan *planner(Query *parse);
 extern Plan *subquery_planner(Query *parse, double tuple_fraction);
 extern Plan *union_planner(Query *parse, double tuple_fraction);
 
+extern Plan *make_sortplan(List *tlist, Plan *plannode, List *sortcls);
+
 #endif	 /* PLANNER_H */
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 4ffddcf5668e6fb74caf4d14dab0cb073f0d2558..dcdf6fb62f6e4870c76d86e2aba5fe3b27a3ce60 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: prep.h,v 1.23 2000/06/20 04:22:13 tgl Exp $
+ * $Id: prep.h,v 1.24 2000/10/05 19:11:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,11 +32,11 @@ extern List *preprocess_targetlist(List *tlist, int command_type,
 /*
  * prototypes for prepunion.c
  */
+extern Plan *plan_set_operations(Query *parse);
 extern List *find_all_inheritors(Oid parentrel);
 extern bool find_inheritable_rt_entry(List *rangetable,
 									  Index *rt_index, List **inheritors);
 extern Plan *plan_inherit_queries(Query *root, List *tlist,
 								  Index rt_index, List *inheritors);
-extern Plan *plan_union_queries(Query *parse);
 
 #endif	 /* PREP_H */
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index 691ec92c1fc26173fc04933d10ccd5af4e4d6c76..afd8a34fb3e43ace2534ef9f9f2777d824d67ec3 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: analyze.h,v 1.10 2000/01/26 05:58:26 momjian Exp $
+ * $Id: analyze.h,v 1.11 2000/10/05 19:11:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +17,4 @@
 
 extern List *parse_analyze(List *pl, ParseState *parentParseState);
 
-extern void create_select_list(Node *ptr, List **select_list, bool *unionall_present);
-extern Node *A_Expr_to_Expr(Node *ptr, bool *intersect_present);
-
 #endif	 /* ANALYZE_H */
diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h
index acea75d01dfdb7d5a0a9a23c5802026b9647ae52..7dd95f5b47c75153b4d85c39c8ed622d8f1e45f1 100644
--- a/src/include/parser/parse_coerce.h
+++ b/src/include/parser/parse_coerce.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_coerce.h,v 1.23 2000/08/21 04:48:52 tgl Exp $
+ * $Id: parse_coerce.h,v 1.24 2000/10/05 19:11:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -135,4 +135,9 @@ extern Node *coerce_type(ParseState *pstate, Node *node, Oid inputTypeId,
 extern Node *coerce_type_typmod(ParseState *pstate, Node *node,
 				   Oid targetTypeId, int32 atttypmod);
 
+extern Oid select_common_type(List *typeids, const char *context);
+extern Node *coerce_to_common_type(ParseState *pstate, Node *node,
+								   Oid targetTypeId,
+								   const char *context);
+
 #endif	 /* PARSE_COERCE_H */
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index bc342f5c435639677d23b694aa28a152288a6854..72d6b77fa1852a1a0808c73a564072be4ef83001 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -1,5 +1,5 @@
 --
--- UNION
+-- UNION (also INTERSECT, EXCEPT)
 --
 -- Simple UNION constructs
 SELECT 1 AS two UNION SELECT 2;
@@ -71,10 +71,10 @@ SELECT 1 AS two UNION SELECT 2.2;
  two 
 -----
    1
-   2
+ 2.2
 (2 rows)
 
-SELECT 1 AS one UNION SELECT 1.1;
+SELECT 1 AS one UNION SELECT 1.0;
  one 
 -----
    1
@@ -87,36 +87,43 @@ SELECT 1.1 AS two UNION ALL SELECT 2;
    2
 (2 rows)
 
-SELECT 1 AS two UNION ALL SELECT 1;
+SELECT 1.0 AS two UNION ALL SELECT 1;
  two 
 -----
    1
    1
 (2 rows)
 
-SELECT 1 AS three UNION SELECT 2 UNION SELECT 3;
+SELECT 1.1 AS three UNION SELECT 2 UNION SELECT 3;
  three 
 -------
-     1
+   1.1
      2
      3
 (3 rows)
 
-SELECT 1 AS two UNION SELECT 2 UNION SELECT 2;
+SELECT 1.1 AS two UNION SELECT 2 UNION SELECT 2.0;
  two 
 -----
-   1
+ 1.1
    2
 (2 rows)
 
-SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2;
+SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2;
  three 
 -------
-     1
+   1.1
      2
      2
 (3 rows)
 
+SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2);
+ two 
+-----
+ 1.1
+   2
+(2 rows)
+
 --
 -- Try testing from tables...
 --
@@ -247,3 +254,61 @@ SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL;
  hi de ho neighbor
 (5 rows)
 
+--
+-- INTERSECT and EXCEPT
+--
+SELECT q2 FROM int8_tbl INTERSECT SELECT q1 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+(2 rows)
+
+SELECT q2 FROM int8_tbl INTERSECT ALL SELECT q1 FROM int8_tbl;
+        q2        
+------------------
+              123
+ 4567890123456789
+ 4567890123456789
+(3 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl;
+        q2         
+-------------------
+ -4567890123456789
+               456
+(2 rows)
+
+SELECT q2 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q1 FROM int8_tbl;
+        q2         
+-------------------
+ -4567890123456789
+               456
+  4567890123456789
+(3 rows)
+
+--
+-- Mixed types
+--
+SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl;
+ f1 
+----
+  0
+(1 row)
+
+SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl;
+          f1           
+-----------------------
+ -1.2345678901234e+200
+               -1004.3
+                -34.84
+ -1.2345678901234e-200
+(4 rows)
+
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index 22ea190f2ee2b92f9e315d70ea563df8ddf5d80d..d232c00a23777ae57af205abcd71a914d92fa973 100644
--- a/src/test/regress/sql/union.sql
+++ b/src/test/regress/sql/union.sql
@@ -1,5 +1,5 @@
 --
--- UNION
+-- UNION (also INTERSECT, EXCEPT)
 --
 
 -- Simple UNION constructs
@@ -26,17 +26,19 @@ SELECT 1.1 AS two UNION SELECT 2;
 
 SELECT 1 AS two UNION SELECT 2.2;
 
-SELECT 1 AS one UNION SELECT 1.1;
+SELECT 1 AS one UNION SELECT 1.0;
 
 SELECT 1.1 AS two UNION ALL SELECT 2;
 
-SELECT 1 AS two UNION ALL SELECT 1;
+SELECT 1.0 AS two UNION ALL SELECT 1;
 
-SELECT 1 AS three UNION SELECT 2 UNION SELECT 3;
+SELECT 1.1 AS three UNION SELECT 2 UNION SELECT 3;
 
-SELECT 1 AS two UNION SELECT 2 UNION SELECT 2;
+SELECT 1.1 AS two UNION SELECT 2 UNION SELECT 2.0;
 
-SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2;
+SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2;
+
+SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2);
 
 --
 -- Try testing from tables...
@@ -82,3 +84,24 @@ SELECT f1 FROM VARCHAR_TBL
 UNION
 SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL;
 
+--
+-- INTERSECT and EXCEPT
+--
+
+SELECT q2 FROM int8_tbl INTERSECT SELECT q1 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl INTERSECT ALL SELECT q1 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl;
+
+SELECT q2 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q1 FROM int8_tbl;
+
+--
+-- Mixed types
+--
+
+SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl;
+
+SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl;