
locParam lists can be converted to bitmapsets to speed updating. Also, replace 'locParam' with 'allParam', which contains all the paramIDs relevant to the node (i.e., the union of extParam and locParam); this saves a step during SetChangedParamList() without costing anything elsewhere.
783 lines
18 KiB
C
783 lines
18 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* execProcnode.c
|
|
* contains dispatch functions which call the appropriate "initialize",
|
|
* "get a tuple", and "cleanup" routines for the given node type.
|
|
* If the node has children, then it will presumably call ExecInitNode,
|
|
* ExecProcNode, or ExecEndNode on its subnodes and do the appropriate
|
|
* processing.
|
|
*
|
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.35 2003/02/09 00:30:39 tgl Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
/*
|
|
* INTERFACE ROUTINES
|
|
* ExecCountSlotsNode - count tuple slots needed by plan tree
|
|
* ExecInitNode - initialize a plan node and its subplans
|
|
* ExecProcNode - get a tuple by executing the plan node
|
|
* ExecEndNode - shut down a plan node and its subplans
|
|
* ExecGetTupType - get result tuple type of a plan node
|
|
*
|
|
* NOTES
|
|
* This used to be three files. It is now all combined into
|
|
* one file so that it is easier to keep ExecInitNode, ExecProcNode,
|
|
* and ExecEndNode in sync when new nodes are added.
|
|
*
|
|
* EXAMPLE
|
|
* suppose we want the age of the manager of the shoe department and
|
|
* the number of employees in that department. so we have the query:
|
|
*
|
|
* retrieve (DEPT.no_emps, EMP.age)
|
|
* where EMP.name = DEPT.mgr and
|
|
* DEPT.name = "shoe"
|
|
*
|
|
* Suppose the planner gives us the following plan:
|
|
*
|
|
* Nest Loop (DEPT.mgr = EMP.name)
|
|
* / \
|
|
* / \
|
|
* Seq Scan Seq Scan
|
|
* DEPT EMP
|
|
* (name = "shoe")
|
|
*
|
|
* ExecStart() is called first.
|
|
* It calls InitPlan() which calls ExecInitNode() on
|
|
* the root of the plan -- the nest loop node.
|
|
*
|
|
* * ExecInitNode() notices that it is looking at a nest loop and
|
|
* as the code below demonstrates, it calls ExecInitNestLoop().
|
|
* Eventually this calls ExecInitNode() on the right and left subplans
|
|
* and so forth until the entire plan is initialized. The result
|
|
* of ExecInitNode() is a plan state tree built with the same structure
|
|
* as the underlying plan tree.
|
|
*
|
|
* * Then when ExecRun() is called, it calls ExecutePlan() which calls
|
|
* ExecProcNode() repeatedly on the top node of the plan state tree.
|
|
* Each time this happens, ExecProcNode() will end up calling
|
|
* ExecNestLoop(), which calls ExecProcNode() on its subplans.
|
|
* Each of these subplans is a sequential scan so ExecSeqScan() is
|
|
* called. The slots returned by ExecSeqScan() may contain
|
|
* tuples which contain the attributes ExecNestLoop() uses to
|
|
* form the tuples it returns.
|
|
*
|
|
* * Eventually ExecSeqScan() stops returning tuples and the nest
|
|
* loop join ends. Lastly, ExecEnd() calls ExecEndNode() which
|
|
* calls ExecEndNestLoop() which in turn calls ExecEndNode() on
|
|
* its subplans which result in ExecEndSeqScan().
|
|
*
|
|
* This should show how the executor works by having
|
|
* ExecInitNode(), ExecProcNode() and ExecEndNode() dispatch
|
|
* their work to the appopriate node support routines which may
|
|
* in turn call these routines themselves on their subplans.
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "executor/executor.h"
|
|
#include "executor/instrument.h"
|
|
#include "executor/nodeAgg.h"
|
|
#include "executor/nodeAppend.h"
|
|
#include "executor/nodeFunctionscan.h"
|
|
#include "executor/nodeGroup.h"
|
|
#include "executor/nodeHash.h"
|
|
#include "executor/nodeHashjoin.h"
|
|
#include "executor/nodeIndexscan.h"
|
|
#include "executor/nodeLimit.h"
|
|
#include "executor/nodeMaterial.h"
|
|
#include "executor/nodeMergejoin.h"
|
|
#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"
|
|
#include "executor/nodeTidscan.h"
|
|
#include "executor/nodeUnique.h"
|
|
#include "miscadmin.h"
|
|
#include "tcop/tcopprot.h"
|
|
|
|
/* ------------------------------------------------------------------------
|
|
* ExecInitNode
|
|
*
|
|
* Recursively initializes all the nodes in the plan rooted
|
|
* at 'node'.
|
|
*
|
|
* Initial States:
|
|
* 'node' is the plan produced by the query planner
|
|
* 'estate' is the shared execution state for the query tree
|
|
*
|
|
* Returns a PlanState node corresponding to the given Plan node.
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
PlanState *
|
|
ExecInitNode(Plan *node, EState *estate)
|
|
{
|
|
PlanState *result;
|
|
List *subps;
|
|
List *subp;
|
|
|
|
/*
|
|
* do nothing when we get to the end of a leaf on tree.
|
|
*/
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
switch (nodeTag(node))
|
|
{
|
|
/*
|
|
* control nodes
|
|
*/
|
|
case T_Result:
|
|
result = (PlanState *) ExecInitResult((Result *) node, estate);
|
|
break;
|
|
|
|
case T_Append:
|
|
result = (PlanState *) ExecInitAppend((Append *) node, estate);
|
|
break;
|
|
|
|
/*
|
|
* scan nodes
|
|
*/
|
|
case T_SeqScan:
|
|
result = (PlanState *) ExecInitSeqScan((SeqScan *) node, estate);
|
|
break;
|
|
|
|
case T_IndexScan:
|
|
result = (PlanState *) ExecInitIndexScan((IndexScan *) node, estate);
|
|
break;
|
|
|
|
case T_TidScan:
|
|
result = (PlanState *) ExecInitTidScan((TidScan *) node, estate);
|
|
break;
|
|
|
|
case T_SubqueryScan:
|
|
result = (PlanState *) ExecInitSubqueryScan((SubqueryScan *) node, estate);
|
|
break;
|
|
|
|
case T_FunctionScan:
|
|
result = (PlanState *) ExecInitFunctionScan((FunctionScan *) node, estate);
|
|
break;
|
|
|
|
/*
|
|
* join nodes
|
|
*/
|
|
case T_NestLoop:
|
|
result = (PlanState *) ExecInitNestLoop((NestLoop *) node, estate);
|
|
break;
|
|
|
|
case T_MergeJoin:
|
|
result = (PlanState *) ExecInitMergeJoin((MergeJoin *) node, estate);
|
|
break;
|
|
|
|
case T_HashJoin:
|
|
result = (PlanState *) ExecInitHashJoin((HashJoin *) node, estate);
|
|
break;
|
|
|
|
/*
|
|
* materialization nodes
|
|
*/
|
|
case T_Material:
|
|
result = (PlanState *) ExecInitMaterial((Material *) node, estate);
|
|
break;
|
|
|
|
case T_Sort:
|
|
result = (PlanState *) ExecInitSort((Sort *) node, estate);
|
|
break;
|
|
|
|
case T_Group:
|
|
result = (PlanState *) ExecInitGroup((Group *) node, estate);
|
|
break;
|
|
|
|
case T_Agg:
|
|
result = (PlanState *) ExecInitAgg((Agg *) node, estate);
|
|
break;
|
|
|
|
case T_Unique:
|
|
result = (PlanState *) ExecInitUnique((Unique *) node, estate);
|
|
break;
|
|
|
|
case T_Hash:
|
|
result = (PlanState *) ExecInitHash((Hash *) node, estate);
|
|
break;
|
|
|
|
case T_SetOp:
|
|
result = (PlanState *) ExecInitSetOp((SetOp *) node, estate);
|
|
break;
|
|
|
|
case T_Limit:
|
|
result = (PlanState *) ExecInitLimit((Limit *) node, estate);
|
|
break;
|
|
|
|
default:
|
|
elog(ERROR, "ExecInitNode: node type %d unsupported",
|
|
(int) nodeTag(node));
|
|
result = NULL; /* keep compiler quiet */
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Initialize any initPlans present in this node. The planner put
|
|
* them in a separate list for us.
|
|
*/
|
|
subps = NIL;
|
|
foreach(subp, node->initPlan)
|
|
{
|
|
SubPlan *subplan = (SubPlan *) lfirst(subp);
|
|
SubPlanState *sstate;
|
|
|
|
Assert(IsA(subplan, SubPlan));
|
|
sstate = ExecInitExprInitPlan(subplan, result);
|
|
ExecInitSubPlan(sstate, estate);
|
|
subps = lappend(subps, sstate);
|
|
}
|
|
result->initPlan = subps;
|
|
|
|
/*
|
|
* Initialize any subPlans present in this node. These were found
|
|
* by ExecInitExpr during initialization of the PlanState. Note we
|
|
* must do this after initializing initPlans, in case their arguments
|
|
* contain subPlans (is that actually possible? perhaps not).
|
|
*/
|
|
subps = NIL;
|
|
foreach(subp, result->subPlan)
|
|
{
|
|
SubPlanState *sstate = (SubPlanState *) lfirst(subp);
|
|
|
|
Assert(IsA(sstate, SubPlanState));
|
|
ExecInitSubPlan(sstate, estate);
|
|
subps = lappend(subps, sstate);
|
|
}
|
|
result->subPlan = subps;
|
|
|
|
/* Set up instrumentation for this node if requested */
|
|
if (estate->es_instrument)
|
|
result->instrument = InstrAlloc();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecProcNode
|
|
*
|
|
* Execute the given node to return a(nother) tuple.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
TupleTableSlot *
|
|
ExecProcNode(PlanState *node)
|
|
{
|
|
TupleTableSlot *result;
|
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
/*
|
|
* deal with NULL nodes..
|
|
*/
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
if (node->chgParam != NULL) /* something changed */
|
|
ExecReScan(node, NULL); /* let ReScan handle this */
|
|
|
|
if (node->instrument)
|
|
InstrStartNode(node->instrument);
|
|
|
|
switch (nodeTag(node))
|
|
{
|
|
/*
|
|
* control nodes
|
|
*/
|
|
case T_ResultState:
|
|
result = ExecResult((ResultState *) node);
|
|
break;
|
|
|
|
case T_AppendState:
|
|
result = ExecProcAppend((AppendState *) node);
|
|
break;
|
|
|
|
/*
|
|
* scan nodes
|
|
*/
|
|
case T_SeqScanState:
|
|
result = ExecSeqScan((SeqScanState *) node);
|
|
break;
|
|
|
|
case T_IndexScanState:
|
|
result = ExecIndexScan((IndexScanState *) node);
|
|
break;
|
|
|
|
case T_TidScanState:
|
|
result = ExecTidScan((TidScanState *) node);
|
|
break;
|
|
|
|
case T_SubqueryScanState:
|
|
result = ExecSubqueryScan((SubqueryScanState *) node);
|
|
break;
|
|
|
|
case T_FunctionScanState:
|
|
result = ExecFunctionScan((FunctionScanState *) node);
|
|
break;
|
|
|
|
/*
|
|
* join nodes
|
|
*/
|
|
case T_NestLoopState:
|
|
result = ExecNestLoop((NestLoopState *) node);
|
|
break;
|
|
|
|
case T_MergeJoinState:
|
|
result = ExecMergeJoin((MergeJoinState *) node);
|
|
break;
|
|
|
|
case T_HashJoinState:
|
|
result = ExecHashJoin((HashJoinState *) node);
|
|
break;
|
|
|
|
/*
|
|
* materialization nodes
|
|
*/
|
|
case T_MaterialState:
|
|
result = ExecMaterial((MaterialState *) node);
|
|
break;
|
|
|
|
case T_SortState:
|
|
result = ExecSort((SortState *) node);
|
|
break;
|
|
|
|
case T_GroupState:
|
|
result = ExecGroup((GroupState *) node);
|
|
break;
|
|
|
|
case T_AggState:
|
|
result = ExecAgg((AggState *) node);
|
|
break;
|
|
|
|
case T_UniqueState:
|
|
result = ExecUnique((UniqueState *) node);
|
|
break;
|
|
|
|
case T_HashState:
|
|
result = ExecHash((HashState *) node);
|
|
break;
|
|
|
|
case T_SetOpState:
|
|
result = ExecSetOp((SetOpState *) node);
|
|
break;
|
|
|
|
case T_LimitState:
|
|
result = ExecLimit((LimitState *) node);
|
|
break;
|
|
|
|
default:
|
|
elog(ERROR, "ExecProcNode: node type %d unsupported",
|
|
(int) nodeTag(node));
|
|
result = NULL;
|
|
break;
|
|
}
|
|
|
|
if (node->instrument)
|
|
InstrStopNode(node->instrument, !TupIsNull(result));
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* ExecCountSlotsNode - count up the number of tuple table slots needed
|
|
*
|
|
* Note that this scans a Plan tree, not a PlanState tree, because we
|
|
* haven't built the PlanState tree yet ...
|
|
*/
|
|
int
|
|
ExecCountSlotsNode(Plan *node)
|
|
{
|
|
if (node == NULL)
|
|
return 0;
|
|
|
|
switch (nodeTag(node))
|
|
{
|
|
/*
|
|
* control nodes
|
|
*/
|
|
case T_Result:
|
|
return ExecCountSlotsResult((Result *) node);
|
|
|
|
case T_Append:
|
|
return ExecCountSlotsAppend((Append *) node);
|
|
|
|
/*
|
|
* scan nodes
|
|
*/
|
|
case T_SeqScan:
|
|
return ExecCountSlotsSeqScan((SeqScan *) node);
|
|
|
|
case T_IndexScan:
|
|
return ExecCountSlotsIndexScan((IndexScan *) node);
|
|
|
|
case T_TidScan:
|
|
return ExecCountSlotsTidScan((TidScan *) node);
|
|
|
|
case T_SubqueryScan:
|
|
return ExecCountSlotsSubqueryScan((SubqueryScan *) node);
|
|
|
|
case T_FunctionScan:
|
|
return ExecCountSlotsFunctionScan((FunctionScan *) node);
|
|
|
|
/*
|
|
* join nodes
|
|
*/
|
|
case T_NestLoop:
|
|
return ExecCountSlotsNestLoop((NestLoop *) node);
|
|
|
|
case T_MergeJoin:
|
|
return ExecCountSlotsMergeJoin((MergeJoin *) node);
|
|
|
|
case T_HashJoin:
|
|
return ExecCountSlotsHashJoin((HashJoin *) node);
|
|
|
|
/*
|
|
* materialization nodes
|
|
*/
|
|
case T_Material:
|
|
return ExecCountSlotsMaterial((Material *) node);
|
|
|
|
case T_Sort:
|
|
return ExecCountSlotsSort((Sort *) node);
|
|
|
|
case T_Group:
|
|
return ExecCountSlotsGroup((Group *) node);
|
|
|
|
case T_Agg:
|
|
return ExecCountSlotsAgg((Agg *) node);
|
|
|
|
case T_Unique:
|
|
return ExecCountSlotsUnique((Unique *) node);
|
|
|
|
case T_Hash:
|
|
return ExecCountSlotsHash((Hash *) node);
|
|
|
|
case T_SetOp:
|
|
return ExecCountSlotsSetOp((SetOp *) node);
|
|
|
|
case T_Limit:
|
|
return ExecCountSlotsLimit((Limit *) node);
|
|
|
|
default:
|
|
elog(ERROR, "ExecCountSlotsNode: node type %d unsupported",
|
|
(int) nodeTag(node));
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecEndNode
|
|
*
|
|
* Recursively cleans up all the nodes in the plan rooted
|
|
* at 'node'.
|
|
*
|
|
* After this operation, the query plan will not be able to
|
|
* processed any further. This should be called only after
|
|
* the query plan has been fully executed.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
void
|
|
ExecEndNode(PlanState *node)
|
|
{
|
|
List *subp;
|
|
|
|
/*
|
|
* do nothing when we get to the end of a leaf on tree.
|
|
*/
|
|
if (node == NULL)
|
|
return;
|
|
|
|
/* Clean up initPlans and subPlans */
|
|
foreach(subp, node->initPlan)
|
|
ExecEndSubPlan((SubPlanState *) lfirst(subp));
|
|
foreach(subp, node->subPlan)
|
|
ExecEndSubPlan((SubPlanState *) lfirst(subp));
|
|
|
|
if (node->chgParam != NULL)
|
|
{
|
|
bms_free(node->chgParam);
|
|
node->chgParam = NULL;
|
|
}
|
|
|
|
switch (nodeTag(node))
|
|
{
|
|
/*
|
|
* control nodes
|
|
*/
|
|
case T_ResultState:
|
|
ExecEndResult((ResultState *) node);
|
|
break;
|
|
|
|
case T_AppendState:
|
|
ExecEndAppend((AppendState *) node);
|
|
break;
|
|
|
|
/*
|
|
* scan nodes
|
|
*/
|
|
case T_SeqScanState:
|
|
ExecEndSeqScan((SeqScanState *) node);
|
|
break;
|
|
|
|
case T_IndexScanState:
|
|
ExecEndIndexScan((IndexScanState *) node);
|
|
break;
|
|
|
|
case T_TidScanState:
|
|
ExecEndTidScan((TidScanState *) node);
|
|
break;
|
|
|
|
case T_SubqueryScanState:
|
|
ExecEndSubqueryScan((SubqueryScanState *) node);
|
|
break;
|
|
|
|
case T_FunctionScanState:
|
|
ExecEndFunctionScan((FunctionScanState *) node);
|
|
break;
|
|
|
|
/*
|
|
* join nodes
|
|
*/
|
|
case T_NestLoopState:
|
|
ExecEndNestLoop((NestLoopState *) node);
|
|
break;
|
|
|
|
case T_MergeJoinState:
|
|
ExecEndMergeJoin((MergeJoinState *) node);
|
|
break;
|
|
|
|
case T_HashJoinState:
|
|
ExecEndHashJoin((HashJoinState *) node);
|
|
break;
|
|
|
|
/*
|
|
* materialization nodes
|
|
*/
|
|
case T_MaterialState:
|
|
ExecEndMaterial((MaterialState *) node);
|
|
break;
|
|
|
|
case T_SortState:
|
|
ExecEndSort((SortState *) node);
|
|
break;
|
|
|
|
case T_GroupState:
|
|
ExecEndGroup((GroupState *) node);
|
|
break;
|
|
|
|
case T_AggState:
|
|
ExecEndAgg((AggState *) node);
|
|
break;
|
|
|
|
case T_UniqueState:
|
|
ExecEndUnique((UniqueState *) node);
|
|
break;
|
|
|
|
case T_HashState:
|
|
ExecEndHash((HashState *) node);
|
|
break;
|
|
|
|
case T_SetOpState:
|
|
ExecEndSetOp((SetOpState *) node);
|
|
break;
|
|
|
|
case T_LimitState:
|
|
ExecEndLimit((LimitState *) node);
|
|
break;
|
|
|
|
default:
|
|
elog(ERROR, "ExecEndNode: node type %d unsupported",
|
|
(int) nodeTag(node));
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecGetTupType
|
|
*
|
|
* this gives you the tuple descriptor for tuples returned
|
|
* by this node. I really wish I could ditch this routine,
|
|
* but since not all nodes store their type info in the same
|
|
* place, we have to do something special for each node type.
|
|
*
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
TupleDesc
|
|
ExecGetTupType(PlanState *node)
|
|
{
|
|
TupleTableSlot *slot;
|
|
|
|
if (node == NULL)
|
|
return NULL;
|
|
|
|
switch (nodeTag(node))
|
|
{
|
|
case T_ResultState:
|
|
{
|
|
ResultState *resstate = (ResultState *) node;
|
|
|
|
slot = resstate->ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_AppendState:
|
|
{
|
|
AppendState *appendstate = (AppendState *) node;
|
|
|
|
slot = appendstate->ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_SeqScanState:
|
|
{
|
|
SeqScanState *scanstate = (SeqScanState *) node;
|
|
|
|
slot = scanstate->ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_IndexScanState:
|
|
{
|
|
IndexScanState *scanstate = (IndexScanState *) node;
|
|
|
|
slot = scanstate->ss.ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_TidScanState:
|
|
{
|
|
TidScanState *scanstate = (TidScanState *) node;
|
|
|
|
slot = scanstate->ss.ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_SubqueryScanState:
|
|
{
|
|
SubqueryScanState *scanstate = (SubqueryScanState *) node;
|
|
|
|
slot = scanstate->ss.ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_FunctionScanState:
|
|
{
|
|
FunctionScanState *scanstate = (FunctionScanState *) node;
|
|
|
|
slot = scanstate->ss.ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_NestLoopState:
|
|
{
|
|
NestLoopState *nlstate = (NestLoopState *) node;
|
|
|
|
slot = nlstate->js.ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_MergeJoinState:
|
|
{
|
|
MergeJoinState *mergestate = (MergeJoinState *) node;
|
|
|
|
slot = mergestate->js.ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_HashJoinState:
|
|
{
|
|
HashJoinState *hashjoinstate = (HashJoinState *) node;
|
|
|
|
slot = hashjoinstate->js.ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_MaterialState:
|
|
{
|
|
MaterialState *matstate = (MaterialState *) node;
|
|
|
|
slot = matstate->ss.ss_ScanTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_SortState:
|
|
{
|
|
SortState *sortstate = (SortState *) node;
|
|
|
|
slot = sortstate->ss.ss_ScanTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_GroupState:
|
|
{
|
|
GroupState *grpstate = (GroupState *) node;
|
|
|
|
slot = grpstate->ss.ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_AggState:
|
|
{
|
|
AggState *aggstate = (AggState *) node;
|
|
|
|
slot = aggstate->ss.ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_UniqueState:
|
|
{
|
|
UniqueState *uniquestate = (UniqueState *) node;
|
|
|
|
slot = uniquestate->ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_HashState:
|
|
{
|
|
HashState *hashstate = (HashState *) node;
|
|
|
|
slot = hashstate->ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_SetOpState:
|
|
{
|
|
SetOpState *setopstate = (SetOpState *) node;
|
|
|
|
slot = setopstate->ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
case T_LimitState:
|
|
{
|
|
LimitState *limitstate = (LimitState *) node;
|
|
|
|
slot = limitstate->ps.ps_ResultTupleSlot;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
/*
|
|
* should never get here
|
|
*/
|
|
elog(ERROR, "ExecGetTupType: node type %d unsupported",
|
|
(int) nodeTag(node));
|
|
return NULL;
|
|
}
|
|
|
|
return slot->ttc_tupleDescriptor;
|
|
}
|