mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-04 08:17:19 +02:00
342 lines
9.2 KiB
C
342 lines
9.2 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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-2001, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/executor/nodeSetOp.c,v 1.2 2001/01/24 19:42:55 momjian 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);
|
|
}
|