diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index c7bc666c2f978ed09d90ea888e7af4d05442fe17..9e0cef44ab18e7c8be32950a7c569b6aa34c12a9 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeLimit.c,v 1.1 2000/10/26 21:35:15 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/nodeLimit.c,v 1.2 2000/11/05 00:15:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -188,17 +188,11 @@ recompute_limits(Limit *node)
 														econtext,
 														&isNull,
 														NULL));
-		/* Interpret NULL count as no count */
+		/* Interpret NULL count as no count (LIMIT ALL) */
 		if (isNull)
 			limitstate->noCount = true;
-		else
-		{
-			/* Currently, LIMIT 0 is specified as meaning no limit.
-			 * I think this is pretty bogus, but ...
-			 */
-			if (limitstate->count <= 0)
-				limitstate->noCount = true;
-		}
+		else if (limitstate->count < 0)
+			limitstate->count = 0;
 	}
 	else
 	{
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9435a396f0795205aa45aef3c9213910b0cf627f..406e85ce623aefa7193805a547d682028a6e4474 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.128 2000/10/31 10:22:10 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.129 2000/11/05 00:15:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1826,6 +1826,7 @@ _copySelectStmt(SelectStmt *from)
 	Node_Copy(from, newnode, distinctClause);
 	if (from->into)
 		newnode->into = pstrdup(from->into);
+	newnode->istemp = from->istemp;
 	Node_Copy(from, newnode, targetList);
 	Node_Copy(from, newnode, fromClause);
 	Node_Copy(from, newnode, whereClause);
@@ -1835,10 +1836,13 @@ _copySelectStmt(SelectStmt *from)
 	if (from->portalname)
 		newnode->portalname = pstrdup(from->portalname);
 	newnode->binary = from->binary;
-	newnode->istemp = from->istemp;
 	Node_Copy(from, newnode, limitOffset);
 	Node_Copy(from, newnode, limitCount);
 	Node_Copy(from, newnode, forUpdate);
+	newnode->op = from->op;
+	newnode->all = from->all;
+	Node_Copy(from, newnode, larg);
+	Node_Copy(from, newnode, rarg);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8519fd6115e0b696d6c6cca23c81e13d2d37e381..08087136a10d96675b88700d86091599cdc73c43 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.78 2000/10/31 10:22:10 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.79 2000/11/05 00:15:52 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -686,6 +686,8 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
 		return false;
 	if (!equalstr(a->into, b->into))
 		return false;
+	if (a->istemp != b->istemp)
+		return false;
 	if (!equal(a->targetList, b->targetList))
 		return false;
 	if (!equal(a->fromClause, b->fromClause))
@@ -702,14 +704,20 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
 		return false;
 	if (a->binary != b->binary)
 		return false;
-	if (a->istemp != b->istemp)
-		return false;
 	if (!equal(a->limitOffset, b->limitOffset))
 		return false;
 	if (!equal(a->limitCount, b->limitCount))
 		return false;
 	if (!equal(a->forUpdate, b->forUpdate))
 		return false;
+	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;
 
 	return true;
 }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f9c70f7137d4ea3527036d4a8567c24109fc3b19..5951e8f0c9b03a41206f3913e1d6aee6e45ac8f9 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.93 2000/10/26 21:36:09 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.94 2000/11/05 00:15:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -809,25 +809,26 @@ union_planner(Query *parse,
 				if (IsA(parse->limitCount, Const))
 				{
 					Const	   *limitc = (Const *) parse->limitCount;
-					int			count = (int) (limitc->constvalue);
+					int32		count = DatumGetInt32(limitc->constvalue);
 
 					/*
-					 * The constant can legally be either 0 ("ALL") or a
-					 * positive integer.  If it is not ALL, we also need
-					 * to consider the OFFSET part of LIMIT.
+					 * A NULL-constant LIMIT represents "LIMIT ALL",
+					 * which we treat the same as no limit (ie,
+					 * expect to retrieve all the tuples).
 					 */
-					if (count > 0)
+					if (!limitc->constisnull && count > 0)
 					{
 						tuple_fraction = (double) count;
+						/* We must also consider the OFFSET, if present */
 						if (parse->limitOffset != NULL)
 						{
 							if (IsA(parse->limitOffset, Const))
 							{
-								int			offset;
+								int32		offset;
 
 								limitc = (Const *) parse->limitOffset;
-								offset = (int) (limitc->constvalue);
-								if (offset > 0)
+								offset = DatumGetInt32(limitc->constvalue);
+								if (!limitc->constisnull && offset > 0)
 									tuple_fraction += (double) offset;
 							}
 							else
@@ -850,14 +851,14 @@ union_planner(Query *parse,
 			}
 
 			/*
-			 * Check for a retrieve-into-portal, ie DECLARE CURSOR.
+			 * If no LIMIT, check for retrieve-into-portal, ie DECLARE CURSOR.
 			 *
 			 * We have no real idea how many tuples the user will ultimately
 			 * FETCH from a cursor, but it seems a good bet that he
 			 * doesn't want 'em all.  Optimize for 10% retrieval (you
 			 * gotta better number?)
 			 */
-			if (parse->isPortal)
+			else if (parse->isPortal)
 				tuple_fraction = 0.10;
 		}
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index b86dab4bea0d3671d8f479f81baef340ab2135be..45e81aec60811d77b999c17da9d59dc9c961905e 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.162 2000/11/04 18:29:09 momjian Exp $
+ *	$Id: analyze.c,v 1.163 2000/11/05 00:15:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,8 +46,8 @@ 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 *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
+static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
 static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
 static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt);
 static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt);
@@ -257,11 +257,12 @@ transformStmt(ParseState *pstate, Node *parseTree)
 			break;
 
 		case T_SelectStmt:
-			result = transformSelectStmt(pstate, (SelectStmt *) parseTree);
-			break;
-
-		case T_SetOperationStmt:
-			result = transformSetOperationStmt(pstate, (SetOperationStmt *) parseTree);
+			if (((SelectStmt *) parseTree)->op == SETOP_NONE)
+				result = transformSelectStmt(pstate,
+											 (SelectStmt *) parseTree);
+			else
+				result = transformSetOperationStmt(pstate,
+												   (SelectStmt *) parseTree);
 			break;
 
 		default:
@@ -1173,7 +1174,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt)
 					found=1;
 				}
 				if (!found)
-					elog(ERROR, "columns in foreign key table of constraint not found.");
+					elog(ERROR, "columns referenced in foreign key constraint not found.");
 			}
 
 			/*
@@ -1772,29 +1773,30 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
 /*
  * transformSetOperationsStmt -
- *	  transforms a SetOperations Statement
+ *	  transforms a set-operations tree
  *
- * SetOperations is actually just a SELECT, but with UNION/INTERSECT/EXCEPT
+ * A set-operation tree is 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.
+ * The tree of set operations is converted into the setOperations field of
+ * the top-level Query.
  */
 static Query *
-transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt)
+transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
-	Node	   *node;
 	SelectStmt *leftmostSelect;
 	Query	   *leftmostQuery;
+	SetOperationStmt *sostmt;
 	char	   *into;
+	bool		istemp;
 	char	   *portalname;
 	bool		binary;
-	bool		istemp;
 	List	   *sortClause;
 	Node	   *limitOffset;
 	Node	   *limitCount;
 	List	   *forUpdate;
+	Node	   *node;
 	List	   *lefttl,
 			   *dtlist;
 	int			tllen;
@@ -1802,50 +1804,55 @@ transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt)
 	qry->commandType = CMD_SELECT;
 
 	/*
-	 * Find leftmost leaf SelectStmt and extract the one-time-only items
-	 * from it.
+	 * Find leftmost leaf SelectStmt; extract the one-time-only items
+	 * from it and from the top-level node.
 	 */
-	node = stmt->larg;
-	while (node && IsA(node, SetOperationStmt))
-		node = ((SetOperationStmt *) node)->larg;
-	Assert(node && IsA(node, SelectStmt));
-	leftmostSelect = (SelectStmt *) node;
-
+	leftmostSelect = stmt->larg;
+	while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
+		leftmostSelect = leftmostSelect->larg;
+	Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
+		   leftmostSelect->larg == NULL);
 	into = leftmostSelect->into;
-	portalname = leftmostSelect->portalname;
-	binary = leftmostSelect->binary;
 	istemp = leftmostSelect->istemp;
-	sortClause = leftmostSelect->sortClause;
-	limitOffset = leftmostSelect->limitOffset;
-	limitCount = leftmostSelect->limitCount;
-	forUpdate = leftmostSelect->forUpdate;
+	portalname = stmt->portalname;
+	binary = stmt->binary;
 
 	/* 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;
+	stmt->portalname = NULL;
+	stmt->binary = false;
+
+	/*
+	 * These are not one-time, exactly, but we want to process them here
+	 * and not let transformSetOperationTree() see them --- else it'll just
+	 * recurse right back here!
+	 */
+	sortClause = stmt->sortClause;
+	limitOffset = stmt->limitOffset;
+	limitCount = stmt->limitCount;
+	forUpdate = stmt->forUpdate;
+
+	stmt->sortClause = NIL;
+	stmt->limitOffset = NULL;
+	stmt->limitCount = NULL;
+	stmt->forUpdate = NIL;
 
-	/* We don't actually support forUpdate with set ops at the moment. */
+	/* We don't support forUpdate with set ops at the moment. */
 	if (forUpdate)
 		elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT");
 
 	/*
 	 * Recursively transform the components of the tree.
 	 */
-	stmt = (SetOperationStmt *)
-		transformSetOperationTree(pstate, (Node *) stmt);
-	Assert(stmt && IsA(stmt, SetOperationStmt));
-	qry->setOperations = (Node *) stmt;
+	sostmt = (SetOperationStmt *) transformSetOperationTree(pstate, stmt);
+	Assert(sostmt && IsA(sostmt, SetOperationStmt));
+	qry->setOperations = (Node *) sostmt;
 
 	/*
 	 * Re-find leftmost SELECT (now it's a sub-query in rangetable)
 	 */
-	node = stmt->larg;
+	node = sostmt->larg;
 	while (node && IsA(node, SetOperationStmt))
 		node = ((SetOperationStmt *) node)->larg;
 	Assert(node && IsA(node, RangeTblRef));
@@ -1858,7 +1865,7 @@ transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt)
 	 */
 	qry->targetList = NIL;
 	lefttl = leftmostQuery->targetList;
-	foreach(dtlist, stmt->colTypes)
+	foreach(dtlist, sostmt->colTypes)
 	{
 		Oid		colType = (Oid) lfirsti(dtlist);
 		char   *colName = ((TargetEntry *) lfirst(lefttl))->resdom->resname;
@@ -1953,11 +1960,47 @@ transformSetOperationStmt(ParseState *pstate, SetOperationStmt *stmt)
  *		Recursively transform leaves and internal nodes of a set-op tree
  */
 static Node *
-transformSetOperationTree(ParseState *pstate, Node *node)
+transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
 {
-	if (IsA(node, SelectStmt))
+	bool	isLeaf;
+
+	Assert(stmt && IsA(stmt, SelectStmt));
+
+	/*
+	 * Validity-check both leaf and internal SELECTs for disallowed ops.
+	 */
+	if (stmt->into)
+		elog(ERROR, "INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT");
+	if (stmt->portalname)		/* should not happen */
+		elog(ERROR, "Portal may not appear in UNION/INTERSECT/EXCEPT");
+	/* We don't support forUpdate with set ops at the moment. */
+	if (stmt->forUpdate)
+		elog(ERROR, "SELECT FOR UPDATE is not allowed with UNION/INTERSECT/EXCEPT");
+
+	/*
+	 * If an internal node of a set-op tree has ORDER BY, UPDATE, or LIMIT
+	 * clauses attached, we need to treat it like a leaf node to generate
+	 * an independent sub-Query tree.  Otherwise, it can be represented by
+	 * a SetOperationStmt node underneath the parent Query.
+	 */
+	if (stmt->op == SETOP_NONE)
 	{
-		SelectStmt *stmt = (SelectStmt *) node;
+		Assert(stmt->larg == NULL && stmt->rarg == NULL);
+		isLeaf = true;
+	}
+	else
+	{
+		Assert(stmt->larg != NULL && stmt->rarg != NULL);
+		if (stmt->sortClause || stmt->limitOffset || stmt->limitCount ||
+			stmt->forUpdate)
+			isLeaf = true;
+		else
+			isLeaf = false;
+	}
+
+	if (isLeaf)
+	{
+		/* Process leaf SELECT */
 		List   *save_rtable;
 		List   *selectList;
 		Query  *selectQuery;
@@ -1965,20 +2008,6 @@ transformSetOperationTree(ParseState *pstate, Node *node)
 		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
@@ -2011,21 +2040,26 @@ transformSetOperationTree(ParseState *pstate, Node *node)
 		Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
 		return (Node *) rtr;
 	}
-	else if (IsA(node, SetOperationStmt))
+	else
 	{
-		SetOperationStmt *op = (SetOperationStmt *) node;
+		/* Process an internal node (set operation node) */
+		SetOperationStmt *op = makeNode(SetOperationStmt);
 		List   *lcoltypes;
 		List   *rcoltypes;
 		const char *context;
 
-		context = (op->op == SETOP_UNION ? "UNION" :
-				   (op->op == SETOP_INTERSECT ? "INTERSECT" :
+		context = (stmt->op == SETOP_UNION ? "UNION" :
+				   (stmt->op == SETOP_INTERSECT ? "INTERSECT" :
 					"EXCEPT"));
+
+		op->op = stmt->op;
+		op->all = stmt->all;
+
 		/*
 		 * Recursively transform the child nodes.
 		 */
-		op->larg = transformSetOperationTree(pstate, op->larg);
-		op->rarg = transformSetOperationTree(pstate, op->rarg);
+		op->larg = transformSetOperationTree(pstate, stmt->larg);
+		op->rarg = transformSetOperationTree(pstate, stmt->rarg);
 		/*
 		 * Verify that the two children have the same number of non-junk
 		 * columns, and determine the types of the merged output columns.
@@ -2048,14 +2082,9 @@ transformSetOperationTree(ParseState *pstate, Node *node)
 			lcoltypes = lnext(lcoltypes);
 			rcoltypes = lnext(rcoltypes);
 		}
+
 		return (Node *) op;
 	}
-	else
-	{
-		elog(ERROR, "transformSetOperationTree: unexpected node %d",
-			 (int) nodeTag(node));
-		return NULL;			/* keep compiler quiet */
-	}
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f6859429f2ac5e9ec56b85c51a384aacc9ba411f..2fbfa853dd300663a4c211fcf8c638d0f796a90a 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.203 2000/11/04 21:04:55 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.204 2000/11/05 00:15:54 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -80,7 +80,11 @@ 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 SelectStmt *findLeftmostSelect(SelectStmt *node);
+static void insertSelectOptions(SelectStmt *stmt,
+								List *sortClause, List *forUpdate,
+								Node *limitOffset, Node *limitCount);
+static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static bool exprIsNullConstant(Node *arg);
 static Node *doNegate(Node *n);
 static void doNegateFloat(Value *v);
@@ -134,7 +138,7 @@ static void doNegateFloat(Value *v);
 		UnlistenStmt, UpdateStmt, VacuumStmt, VariableResetStmt,
 		VariableSetStmt, VariableShowStmt, ViewStmt
 
-%type <node>	select_clause, select_subclause
+%type <node>	select_no_parens, select_clause, simple_select
 
 %type <list>	SessionList
 %type <node>	SessionClause
@@ -174,8 +178,8 @@ static void doNegateFloat(Value *v);
 %type <chr>		operation, TriggerOneEvent
 
 %type <list>	stmtblock, stmtmulti,
-		result, OptTempTableName, relation_name_list, OptTableElementList,
-		OptUnder, OptInherit, definition, opt_distinct,
+		into_clause, OptTempTableName, relation_name_list,
+		OptTableElementList, OptUnder, OptInherit, definition, opt_distinct,
 		opt_with, func_args, func_args_list, func_as,
 		oper_argtypes, RuleActionList, RuleActionMulti,
 		opt_column_list, columnList, opt_va_list, va_list,
@@ -183,13 +187,13 @@ static void doNegateFloat(Value *v);
 		from_clause, from_list, opt_array_bounds,
 		expr_list, attrs, target_list, update_target_list,
 		def_list, opt_indirection, group_clause, TriggerFuncArgs,
-		opt_select_limit
+		select_limit, opt_select_limit
 
 %type <typnam>	func_arg, func_return, aggr_argtype
 
 %type <boolean>	opt_arg, TriggerForOpt, TriggerForType, OptTemp
 
-%type <list>	for_update_clause, update_list
+%type <list>	for_update_clause, opt_for_update_clause, update_list
 %type <boolean>	opt_all
 %type <boolean>	opt_table
 %type <boolean>	opt_chain, opt_trans
@@ -385,6 +389,7 @@ static void doNegateFloat(Value *v);
 %right		UMINUS
 %left		'.'
 %left		'[' ']'
+%left		'(' ')'
 %left		TYPECAST
 %%
 
@@ -444,6 +449,7 @@ stmt :	AlterSchemaStmt
 		| ListenStmt
 		| UnlistenStmt
 		| LockStmt
+		| NotifyStmt
 		| ProcedureStmt
 		| ReindexStmt
 		| RemoveAggrStmt
@@ -1527,7 +1533,14 @@ OptInherit:  INHERITS '(' relation_name_list ')'		{ $$ = $3; }
 
 CreateAsStmt:  CREATE OptTemp TABLE relation_name OptUnder OptCreateAs AS SelectStmt
 				{
-					SelectStmt *n = findLeftmostSelect($8);
+					/*
+					 * When the SelectStmt is a set-operation tree, we must
+					 * stuff the INTO information into the leftmost component
+					 * Select, because that's where analyze.c will expect
+					 * to find it.  Similarly, the output column names must
+					 * be attached to that Select's target list.
+					 */
+					SelectStmt *n = findLeftmostSelect((SelectStmt *) $8);
 					if (n->into != NULL)
 						elog(ERROR,"CREATE TABLE/AS SELECT may not specify INTO");
 					n->istemp = $2;
@@ -1541,7 +1554,7 @@ CreateAsStmt:  CREATE OptTemp TABLE relation_name OptUnder OptCreateAs AS Select
 		;
 
 OptCreateAs:  '(' CreateAsList ')'				{ $$ = $2; }
-			| /*EMPTY*/							{ $$ = NULL; }
+			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
 CreateAsList:  CreateAsList ',' CreateAsElement	{ $$ = lappend($1, $3); }
@@ -2682,7 +2695,6 @@ RuleStmt:  CREATE RULE name AS
 		;
 
 RuleActionList:  NOTHING				{ $$ = NIL; }
-		| SelectStmt					{ $$ = makeList1($1); }
 		| RuleActionStmt				{ $$ = makeList1($1); }
 		| '[' RuleActionMulti ']'		{ $$ = $2; }
 		| '(' RuleActionMulti ')'		{ $$ = $2; } 
@@ -2703,7 +2715,17 @@ RuleActionMulti:  RuleActionMulti ';' RuleActionStmtOrEmpty
 				}
 		;
 
-RuleActionStmt:	InsertStmt
+/*
+ * Allowing RuleActionStmt to be a SelectStmt creates an ambiguity:
+ * is the RuleActionList "((SELECT foo))" a standalone RuleActionStmt,
+ * or a one-entry RuleActionMulti list?  We don't really care, but yacc
+ * wants to know.  We use operator precedence to resolve the ambiguity:
+ * giving this rule a higher precedence than ')' will force a reduce
+ * rather than shift decision, causing the one-entry-list interpretation
+ * to be chosen.
+ */
+RuleActionStmt:	SelectStmt				%prec TYPECAST
+		| InsertStmt
 		| UpdateStmt
 		| DeleteStmt
 		| NotifyStmt
@@ -3070,7 +3092,6 @@ OptimizableStmt:  SelectStmt
 		| CursorStmt
 		| UpdateStmt
 		| InsertStmt
-		| NotifyStmt
 		| DeleteStmt					/* by default all are $$=$1 */
 		;
 
@@ -3225,7 +3246,7 @@ UpdateStmt:  UPDATE opt_only relation_name
  *****************************************************************************/
 CursorStmt:  DECLARE name opt_cursor CURSOR FOR SelectStmt
   				{
- 					SelectStmt *n = findLeftmostSelect($6);
+ 					SelectStmt *n = (SelectStmt *)$6;
 					n->portalname = $2;
 					n->binary = $3;
 					$$ = $6;
@@ -3246,55 +3267,99 @@ 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 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.
+/* A complete SELECT statement looks like this.
+ *
+ * The rule returns either a single SelectStmt node or a tree of them,
+ * representing a set-operation tree.
+ *
+ * To avoid ambiguity problems with nested parentheses, we have to define
+ * a "select_no_parens" nonterminal in which there are no parentheses
+ * at the outermost level.  This is used in the production
+ *		c_expr: '(' select_no_parens ')'
+ * This gives a unique parsing of constructs where a subselect is nested
+ * in an expression with extra parentheses: the parentheses are not part
+ * of the subselect but of the outer expression.  yacc is not quite bright
+ * enough to handle the situation completely, however.  To prevent a shift/
+ * reduce conflict, we also have to attach a precedence to the
+ *		SelectStmt: select_no_parens
+ * rule that is higher than the precedence of ')'.  This means that when
+ * "((SELECT foo" has been parsed in an expression context, and the
+ * next token is ')', the parser will follow the '(' SelectStmt ')' reduction
+ * path rather than '(' select_no_parens ')'.  The upshot is that excess
+ * parens don't work in this context: SELECT ((SELECT foo)) will give a
+ * parse error, whereas SELECT ((SELECT foo) UNION (SELECT bar)) is OK.
+ * This is ugly, but it beats not allowing excess parens anywhere...
+ *
+ * In all other contexts, we can use SelectStmt which allows outer parens.
  */
 
-SelectStmt:	  select_clause sort_clause for_update_clause opt_select_limit
+SelectStmt: select_no_parens			%prec TYPECAST
 			{
-				SelectStmt *n = findLeftmostSelect($1);
+				$$ = $1;
+			}
+		| '(' SelectStmt ')'
+			{
+				$$ = $2;
+			}
+		;
 
-				n->sortClause = $2;
-				n->forUpdate = $3;
-				n->limitOffset = nth(0, $4);
-				n->limitCount = nth(1, $4);
+select_no_parens: simple_select
+			{
+				$$ = $1;
+			}
+		| select_clause sort_clause opt_for_update_clause opt_select_limit
+			{
+				insertSelectOptions((SelectStmt *) $1, $2, $3,
+									nth(0, $4), nth(1, $4));
+				$$ = $1;
+			}
+		| select_clause for_update_clause opt_select_limit
+			{
+				insertSelectOptions((SelectStmt *) $1, NIL, $2,
+									nth(0, $3), nth(1, $3));
+				$$ = $1;
+			}
+		| select_clause select_limit
+			{
+				insertSelectOptions((SelectStmt *) $1, NIL, NIL,
+									nth(0, $2), nth(1, $2));
 				$$ = $1;
 			}
 		;
 
-/* This rule parses Select statements that can appear within set operations,
- * including UNION, INTERSECT and EXCEPT.  '(' and ')' can be used to specify
- * the ordering of the set operations.  Without '(' and ')' we want the
- * operations to be 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.
- */
-select_clause: '(' select_subclause ')'
+select_clause: simple_select
 			{
-				$$ = $2; 
+				$$ = $1;
 			}
-		| select_subclause
+		| '(' SelectStmt ')'
 			{
-				$$ = $1; 
+				$$ = $2;
 			}
 		;
 
-select_subclause: SELECT opt_distinct target_list
-			 result from_clause where_clause
+/*
+ * This rule parses SELECT statements that can appear within set operations,
+ * including UNION, INTERSECT and EXCEPT.  '(' and ')' can be used to specify
+ * the ordering of the set operations.  Without '(' and ')' we want the
+ * operations to be ordered per the precedence specs at the head of this file.
+ *
+ * As with select_no_parens, simple_select cannot have outer parentheses,
+ * but can have parenthesized subclauses.
+ *
+ * Note that sort clauses cannot be included at this level --- SQL92 requires
+ *		SELECT foo UNION SELECT bar ORDER BY baz
+ * to be parsed as
+ *		(SELECT foo UNION SELECT bar) ORDER BY baz
+ * not
+ *		SELECT foo UNION (SELECT bar ORDER BY baz)
+ * Likewise FOR UPDATE and LIMIT.  This does not limit functionality,
+ * because you can reintroduce sort and limit clauses inside parentheses.
+ *
+ * NOTE: only the leftmost component SelectStmt should have INTO.
+ * However, this is not checked by the grammar; parse analysis must check it.
+ */
+simple_select: SELECT opt_distinct target_list
+			 into_clause from_clause where_clause
 			 group_clause having_clause
 				{
 					SelectStmt *n = makeNode(SelectStmt);
@@ -3310,35 +3375,20 @@ select_subclause: SELECT opt_distinct target_list
 				}
 		| 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;
+				$$ = makeSetOp(SETOP_UNION, $3, $1, $4);
 			}
 		| 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;
+				$$ = makeSetOp(SETOP_INTERSECT, $3, $1, $4);
 			}
 		| 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;
+				$$ = makeSetOp(SETOP_EXCEPT, $3, $1, $4);
 			}
 		; 
 
 		/* easy way to return two values. Can someone improve this?  bjm */
-result:  INTO OptTempTableName			{ $$ = $2; }
+into_clause:  INTO OptTempTableName		{ $$ = $2; }
 		| /*EMPTY*/						{ $$ = lcons(makeInteger(FALSE), NIL); }
 		;
 
@@ -3391,7 +3441,6 @@ opt_distinct:  DISTINCT							{ $$ = makeList1(NIL); }
 		;
 
 sort_clause:  ORDER BY sortby_list				{ $$ = $3; }
-		| /*EMPTY*/								{ $$ = NIL; }
 		;
 
 sortby_list:  sortby							{ $$ = makeList1($1); }
@@ -3413,7 +3462,7 @@ OptUseOp:  USING all_Op							{ $$ = $2; }
 		;
 
 
-opt_select_limit:	LIMIT select_limit_value ',' select_offset_value
+select_limit:	LIMIT select_limit_value ',' select_offset_value
 			{ $$ = makeList2($4, $2); }
 		| LIMIT select_limit_value OFFSET select_offset_value
 			{ $$ = makeList2($4, $2); }
@@ -3423,20 +3472,22 @@ opt_select_limit:	LIMIT select_limit_value ',' select_offset_value
 			{ $$ = makeList2($2, $4); }
 		| OFFSET select_offset_value
 			{ $$ = makeList2($2, NULL); }
-		| /* EMPTY */
-			{ $$ = makeList2(NULL, NULL); }
+		;
+
+opt_select_limit:	select_limit				{ $$ = $1; }
+		| /* EMPTY */							{ $$ = makeList2(NULL,NULL); }
 		;
 
 select_limit_value:  Iconst
 			{
 				Const	*n = makeNode(Const);
 
-				if ($1 < 1)
-					elog(ERROR, "Selection limit must be ALL or a positive integer value");
+				if ($1 < 0)
+					elog(ERROR, "LIMIT must not be negative");
 
 				n->consttype	= INT4OID;
 				n->constlen		= sizeof(int4);
-				n->constvalue	= (Datum)$1;
+				n->constvalue	= Int32GetDatum($1);
 				n->constisnull	= FALSE;
 				n->constbyval	= TRUE;
 				n->constisset	= FALSE;
@@ -3445,12 +3496,13 @@ select_limit_value:  Iconst
 			}
 		| ALL
 			{
+				/* LIMIT ALL is represented as a NULL constant */
 				Const	*n = makeNode(Const);
 
 				n->consttype	= INT4OID;
 				n->constlen		= sizeof(int4);
-				n->constvalue	= (Datum)0;
-				n->constisnull	= FALSE;
+				n->constvalue	= (Datum) 0;
+				n->constisnull	= TRUE;
 				n->constbyval	= TRUE;
 				n->constisset	= FALSE;
 				n->constiscast	= FALSE;
@@ -3471,9 +3523,12 @@ select_offset_value:	Iconst
 			{
 				Const	*n = makeNode(Const);
 
+				if ($1 < 0)
+					elog(ERROR, "OFFSET must not be negative");
+
 				n->consttype	= INT4OID;
 				n->constlen		= sizeof(int4);
-				n->constvalue	= (Datum)$1;
+				n->constvalue	= Int32GetDatum($1);
 				n->constisnull	= FALSE;
 				n->constbyval	= TRUE;
 				n->constisset	= FALSE;
@@ -3490,6 +3545,7 @@ select_offset_value:	Iconst
 				$$ = (Node *)n;
 			}
 		;
+
 /*
  *	jimmy bell-style recursive queries aren't supported in the
  *	current system.
@@ -3522,6 +3578,9 @@ having_clause:  HAVING a_expr
 
 for_update_clause:  FOR UPDATE update_list		{ $$ = $3; }
 		| FOR READ ONLY							{ $$ = NULL; }
+		;
+
+opt_for_update_clause:	for_update_clause		{ $$ = $1; }
 		| /* EMPTY */							{ $$ = NULL; }
 		;
 
@@ -3565,7 +3624,7 @@ table_ref:  relation_expr
 					$1->name = $2;
 					$$ = (Node *) $1;
 				}
-		| '(' select_subclause ')' alias_clause
+		| '(' SelectStmt ')' alias_clause
 				{
 					RangeSubselect *n = makeNode(RangeSubselect);
 					n->subquery = $2;
@@ -4101,7 +4160,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 '(' select_subclause ')'
+row_expr: '(' row_descriptor ')' IN '(' SelectStmt ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = $2;
@@ -4111,7 +4170,7 @@ row_expr: '(' row_descriptor ')' IN '(' select_subclause ')'
 					n->subselect = $6;
 					$$ = (Node *)n;
 				}
-		| '(' row_descriptor ')' NOT IN '(' select_subclause ')'
+		| '(' row_descriptor ')' NOT IN '(' SelectStmt ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = $2;
@@ -4121,7 +4180,7 @@ row_expr: '(' row_descriptor ')' IN '(' select_subclause ')'
 					n->subselect = $7;
 					$$ = (Node *)n;
 				}
-		| '(' row_descriptor ')' all_Op sub_type '(' select_subclause ')'
+		| '(' row_descriptor ')' all_Op sub_type '(' SelectStmt ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = $2;
@@ -4134,7 +4193,7 @@ row_expr: '(' row_descriptor ')' IN '(' select_subclause ')'
 					n->subselect = $7;
 					$$ = (Node *)n;
 				}
-		| '(' row_descriptor ')' all_Op '(' select_subclause ')'
+		| '(' row_descriptor ')' all_Op '(' SelectStmt ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = $2;
@@ -4458,7 +4517,7 @@ a_expr:  c_expr
 						$$ = n;
 					}
 				}
-		| a_expr all_Op sub_type '(' select_subclause ')'
+		| a_expr all_Op sub_type '(' SelectStmt ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = makeList1($1);
@@ -4851,7 +4910,7 @@ c_expr:  attr
 					n->agg_distinct = FALSE;
 					$$ = (Node *)n;
 				}
-		| '(' select_subclause ')'
+		| '(' select_no_parens ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = NIL;
@@ -4861,7 +4920,7 @@ c_expr:  attr
 					n->subselect = $2;
 					$$ = (Node *)n;
 				}
-		| EXISTS '(' select_subclause ')'
+		| EXISTS '(' SelectStmt ')'
 				{
 					SubLink *n = makeNode(SubLink);
 					n->lefthand = NIL;
@@ -4960,7 +5019,7 @@ trim_list:  a_expr FROM expr_list
 				{ $$ = $1; }
 		;
 
-in_expr:  select_subclause
+in_expr:  SelectStmt
 				{
 					SubLink *n = makeNode(SubLink);
 					n->subselect = $1;
@@ -5688,20 +5747,71 @@ mapTargetColumns(List *src, List *dst)
 		src = lnext(src);
 		dst = lnext(dst);
 	}
-	return;
 } /* mapTargetColumns() */
 
 
 /* findLeftmostSelect()
- *		Find the leftmost SelectStmt in a SetOperationStmt parsetree.
+ *		Find the leftmost component SelectStmt in a set-operation parsetree.
  */
 static SelectStmt *
-findLeftmostSelect(Node *node)
+findLeftmostSelect(SelectStmt *node)
 {
-	while (node && IsA(node, SetOperationStmt))
-		node = ((SetOperationStmt *) node)->larg;
-	Assert(node && IsA(node, SelectStmt));
-	return (SelectStmt *) node;
+	while (node && node->op != SETOP_NONE)
+		node = node->larg;
+	Assert(node && IsA(node, SelectStmt) && node->larg == NULL);
+	return node;
+}
+
+/* insertSelectOptions()
+ *		Insert ORDER BY, etc into an already-constructed SelectStmt.
+ *
+ * This routine is just to avoid duplicating code in SelectStmt productions.
+ */
+static void
+insertSelectOptions(SelectStmt *stmt,
+					List *sortClause, List *forUpdate,
+					Node *limitOffset, Node *limitCount)
+{
+	/*
+	 * Tests here are to reject constructs like
+	 *	(SELECT foo ORDER BY bar) ORDER BY baz
+	 */
+	if (sortClause)
+	{
+		if (stmt->sortClause)
+			elog(ERROR, "Multiple ORDER BY clauses not allowed");
+		stmt->sortClause = sortClause;
+	}
+	if (forUpdate)
+	{
+		if (stmt->forUpdate)
+			elog(ERROR, "Multiple FOR UPDATE clauses not allowed");
+		stmt->forUpdate = forUpdate;
+	}
+	if (limitOffset)
+	{
+		if (stmt->limitOffset)
+			elog(ERROR, "Multiple OFFSET clauses not allowed");
+		stmt->limitOffset = limitOffset;
+	}
+	if (limitCount)
+	{
+		if (stmt->limitCount)
+			elog(ERROR, "Multiple LIMIT clauses not allowed");
+		stmt->limitCount = limitCount;
+	}
+}
+
+static Node *
+makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg)
+{
+	SelectStmt *n = makeNode(SelectStmt);
+
+	n->op = op;
+	n->all = all;
+	n->larg = (SelectStmt *) larg;
+	n->rarg = (SelectStmt *) rarg;
+	return (Node *) n;
 }
 
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 70dfe9706bc34c6046a88ab39e7e35a69fb4ccde..cc25a5a026a84b9cc57956df9ed963b28da4f960 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.67 2000/10/26 21:37:45 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.68 2000/11/05 00:15:53 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -941,7 +941,11 @@ get_select_query_def(Query *query, deparse_context *context)
 	if (query->limitCount != NULL)
 	{
 		appendStringInfo(buf, " LIMIT ");
-		get_rule_expr(query->limitCount, context);
+		if (IsA(query->limitCount, Const) &&
+			((Const *) query->limitCount)->constisnull)
+			appendStringInfo(buf, "ALL");
+		else
+			get_rule_expr(query->limitCount, context);
 	}
 }
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3913d88ce91a4bdc54d1e89758c9027d5af0ab08..6ac6d0be4dad013ca1428ef4b527f6c553c9a347 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.117 2000/10/18 16:16:10 momjian Exp $
+ * $Id: parsenodes.h,v 1.118 2000/11/05 00:15:53 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -782,7 +782,7 @@ typedef struct InsertStmt
 	/*
 	 * 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.
+	 * If SELECT, a complete SelectStmt (or set-operation tree) is supplied.
 	 */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	Node	   *selectStmt;		/* the source SELECT */
@@ -816,51 +816,71 @@ typedef struct UpdateStmt
 
 /* ----------------------
  *		Select Statement
+ *
+ * A "simple" SELECT is represented in the output of gram.y by a single
+ * SelectStmt node.  A SELECT construct containing set operators (UNION,
+ * INTERSECT, EXCEPT) is represented by a tree of SelectStmt nodes, in
+ * which the leaf nodes are component SELECTs and the internal nodes
+ * represent UNION, INTERSECT, or EXCEPT operators.  Using the same node
+ * type for both leaf and internal nodes allows gram.y to stick ORDER BY,
+ * LIMIT, etc, clause values into a SELECT statement without worrying
+ * whether it is a simple or compound SELECT.
  * ----------------------
  */
+typedef enum SetOperation
+{
+	SETOP_NONE = 0,
+	SETOP_UNION,
+	SETOP_INTERSECT,
+	SETOP_EXCEPT
+} SetOperation;
+
 typedef struct SelectStmt
 {
 	NodeTag		type;
+	/*
+	 * These fields are used only in "leaf" SelectStmts.
+	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT
 								 * DISTINCT) */
 	char	   *into;			/* name of table (for select into table) */
+	bool		istemp;			/* into is a temp table? */
 	List	   *targetList;		/* the target list (of ResTarget) */
-	List	   *fromClause;		/* the from clause */
-	Node	   *whereClause;	/* qualifications */
+	List	   *fromClause;		/* the FROM clause */
+	Node	   *whereClause;	/* WHERE qualification */
 	List	   *groupClause;	/* GROUP BY clauses */
-	Node	   *havingClause;	/* having conditional-expression */
+	Node	   *havingClause;	/* HAVING conditional-expression */
+	/*
+	 * These fields are used in both "leaf" SelectStmts and upper-level
+	 * SelectStmts.  portalname/binary may only be set at the top level.
+	 */
 	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 */
 	Node	   *limitOffset;	/* # of result tuples to skip */
 	Node	   *limitCount;		/* # of result tuples to return */
 	List	   *forUpdate;		/* FOR UPDATE clause */
+	/*
+	 * These fields are used only in upper-level SelectStmts.
+	 */
+	SetOperation op;			/* type of set op */
+	bool		all;			/* ALL specified? */
+	struct SelectStmt *larg;	/* left child */
+	struct SelectStmt *rarg;	/* right child */
+	/* Eventually add fields for CORRESPONDING spec here */
 } 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.
+ *		Set Operation node for post-analysis query trees
  *
- * 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.
+ * After parse analysis, a SELECT with set operations is represented by a
+ * top-level Query node containing the leaf SELECTs as subqueries in its
+ * range table.  Its setOperations field shows the tree of set operations,
+ * with leaf SelectStmt nodes replaced by RangeTblRef nodes, and internal
+ * nodes replaced by SetOperationStmt nodes.
  * ----------------------
  */
-typedef enum SetOperation
-{
-	SETOP_UNION,
-	SETOP_INTERSECT,
-	SETOP_EXCEPT
-} SetOperation;
-
 typedef struct SetOperationStmt
 {
 	NodeTag		type;
@@ -870,7 +890,7 @@ typedef struct SetOperationStmt
 	Node	   *rarg;			/* right child */
 	/* Eventually add fields for CORRESPONDING spec here */
 
-	/* This field is filled in during parse analysis: */
+	/* Fields derived during parse analysis: */
 	List	   *colTypes;		/* integer list of OIDs of output column types */
 } SetOperationStmt;