aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/explain.c24
-rw-r--r--src/backend/executor/Makefile4
-rw-r--r--src/backend/executor/execAmi.c7
-rw-r--r--src/backend/executor/execMain.c9
-rw-r--r--src/backend/executor/execProcnode.c18
-rw-r--r--src/backend/executor/execTuples.c14
-rw-r--r--src/backend/executor/nodeAppend.c29
-rw-r--r--src/backend/executor/nodeSetOp.c341
-rw-r--r--src/backend/executor/nodeSubplan.c18
-rw-r--r--src/backend/executor/nodeSubqueryscan.c55
-rw-r--r--src/backend/nodes/copyfuncs.c78
-rw-r--r--src/backend/nodes/equalfuncs.c86
-rw-r--r--src/backend/nodes/list.c26
-rw-r--r--src/backend/nodes/outfuncs.c68
-rw-r--r--src/backend/nodes/print.c5
-rw-r--r--src/backend/nodes/readfuncs.c65
-rw-r--r--src/backend/optimizer/path/allpaths.c5
-rw-r--r--src/backend/optimizer/plan/createplan.c70
-rw-r--r--src/backend/optimizer/plan/planmain.c42
-rw-r--r--src/backend/optimizer/plan/planner.c142
-rw-r--r--src/backend/optimizer/plan/setrefs.c12
-rw-r--r--src/backend/optimizer/plan/subselect.c63
-rw-r--r--src/backend/optimizer/prep/prepkeyset.c4
-rw-r--r--src/backend/optimizer/prep/preptlist.c13
-rw-r--r--src/backend/optimizer/prep/prepunion.c543
-rw-r--r--src/backend/optimizer/util/clauses.c111
-rw-r--r--src/backend/optimizer/util/var.c8
-rw-r--r--src/backend/parser/analyze.c697
-rw-r--r--src/backend/parser/gram.y349
-rw-r--r--src/backend/parser/parse_clause.c136
-rw-r--r--src/backend/parser/parse_coerce.c96
-rw-r--r--src/backend/parser/parse_expr.c113
-rw-r--r--src/backend/rewrite/rewriteDefine.c5
-rw-r--r--src/backend/rewrite/rewriteHandler.c588
-rw-r--r--src/backend/rewrite/rewriteManip.c98
-rw-r--r--src/backend/utils/adt/ruleutils.c212
-rw-r--r--src/include/catalog/catversion.h4
-rw-r--r--src/include/executor/nodeSetOp.h25
-rw-r--r--src/include/nodes/execnodes.h26
-rw-r--r--src/include/nodes/nodes.h9
-rw-r--r--src/include/nodes/parsenodes.h79
-rw-r--r--src/include/nodes/pg_list.h3
-rw-r--r--src/include/nodes/plannodes.h37
-rw-r--r--src/include/optimizer/clauses.h6
-rw-r--r--src/include/optimizer/planmain.h7
-rw-r--r--src/include/optimizer/planner.h4
-rw-r--r--src/include/optimizer/prep.h4
-rw-r--r--src/include/parser/analyze.h5
-rw-r--r--src/include/parser/parse_coerce.h7
-rw-r--r--src/test/regress/expected/union.out85
-rw-r--r--src/test/regress/sql/union.sql35
51 files changed, 2588 insertions, 1902 deletions
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 25b7517e1ec..f98ca70514f 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 f26c35e9e10..7c79df5904d 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 31ad291236b..9d008494b30 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 a202da98d2e..3393559d630 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 d6a15371311..6269a7caa10 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 008105719f3..d1cdfabab3b 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 2b1eceb15d9..6c547854b55 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 00000000000..8c285525051
--- /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 aee6911e5e4..933dcc83425 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 45f07a08b10..5593f71d0c6 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 808ffbd075a..1a0f4623978 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 bcb8e396ede..ab8779cb375 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))
@@ -745,6 +715,23 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b)
}
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)
{
if (a->subtype != b->subtype)
@@ -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 358e6a7eb6f..66674b5c364 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
@@ -237,10 +237,32 @@ 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 39bc497343b..cf8c90ecad6 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 a0417f8108b..c507cea3c45 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 da0ba22684f..57174bfb60e 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;
}
@@ -209,6 +202,39 @@ _readGroupClause()
}
/* ----------------
+ * _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 8ab2aeec918..7e017a746f1 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 cc308b4fc96..eb005121cd5 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 29cfccfef7b..a9747b32799 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;
@@ -228,24 +226,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.
*/
final_rel = make_one_rel(root);
@@ -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 937628121b3..d73ca9a34ac 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 ad1b47aaeb6..14c9dad3ef3 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 182d1384aa1..03e38371df5 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 60166289a59..764697636c7 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 e3b9803d0ab..822c0c79f05 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 d3df8863270..0c91631563d 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 06c67daebfc..f52ec6d2d87 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 ec9f9dafd0b..a18db71ec55 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 169075c47e2..ffedca05ed9 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,34 +1698,357 @@ 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 31aea152fa6..f13b942abd8 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 cc849ebf07b..20233ed1950 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 bd098fb6c68..ef13d67cf1c 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 591fdab8782..7b647124d1f 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 e06896d58bc..c08ddbc6782 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 5d1e3a40705..d0fe6a5ee10 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 0b07b5e9c46..cffe624deb4 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 64d0c3a820a..4d0fa04bdf3 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);
@@ -876,6 +880,63 @@ 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;
List *l;
@@ -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 c64b5a9a9de..8310bc3c01a 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 00000000000..0414cc46ef0
--- /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 65d35a2977e..06de4be54cb 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 c72516477dc..fe798492c78 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 ab805406eca..41309426e8b 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 953f6edebee..07c58348e07 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 ca5727f0152..d8e3df4829a 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;
@@ -349,6 +353,29 @@ typedef struct Unique
} 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 881940f9a18..612750dfaf3 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 e46d0bdbd84..015590a5ee2 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 da099940e6e..3fc0f435dc3 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 4ffddcf5668..dcdf6fb62f6 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 691ec92c1fc..afd8a34fb3e 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 acea75d01df..7dd95f5b47c 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 bc342f5c435..72d6b77fa18 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 22ea190f2ee..d232c00a237 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;