diff options
Diffstat (limited to 'src')
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; |