EvalPlanQual was thoroughly broken for concurrent update/delete on inheritance

trees (mostly my fault).  Repair.  Also fix long-standing bug in ExecReplace:
after recomputing a concurrently updated tuple, we must recheck constraints.
Make EvalPlanQual leak memory with somewhat less enthusiasm than before,
although plugging leaks fully will require more changes than I care to risk
in a dot-release.
This commit is contained in:
Tom Lane 2001-05-15 00:33:36 +00:00
parent e611348894
commit a4155d3bbd
3 changed files with 243 additions and 161 deletions

View File

@ -27,7 +27,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.139 2001/03/22 03:59:26 momjian Exp $ * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.140 2001/05/15 00:33:36 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -603,11 +603,17 @@ InitPlan(CmdType operation, Query *parseTree, Plan *plan, EState *estate)
*/ */
{ {
int nSlots = ExecCountSlotsNode(plan); int nSlots = ExecCountSlotsNode(plan);
TupleTable tupleTable = ExecCreateTupleTable(nSlots + 10); /* why add ten? - jolly */
estate->es_tupleTable = tupleTable; estate->es_tupleTable = ExecCreateTupleTable(nSlots + 10); /* why add ten? - jolly */
} }
/* mark EvalPlanQual not active */
estate->es_origPlan = plan;
estate->es_evalPlanQual = NULL;
estate->es_evTuple = NULL;
estate->es_evTupleNull = NULL;
estate->es_useEvalPlan = false;
/* /*
* initialize the private state information for all the nodes in the * initialize the private state information for all the nodes in the
* query tree. This opens files, allocates storage and leaves us * query tree. This opens files, allocates storage and leaves us
@ -774,11 +780,6 @@ InitPlan(CmdType operation, Query *parseTree, Plan *plan, EState *estate)
estate->es_into_relation_descriptor = intoRelationDesc; estate->es_into_relation_descriptor = intoRelationDesc;
estate->es_origPlan = plan;
estate->es_evalPlanQual = NULL;
estate->es_evTuple = NULL;
estate->es_useEvalPlan = false;
return tupType; return tupType;
} }
@ -1038,13 +1039,10 @@ lnext: ;
elog(ERROR, "ExecutePlan: NO (junk) `%s' was found!", elog(ERROR, "ExecutePlan: NO (junk) `%s' was found!",
erm->resname); erm->resname);
/* /* shouldn't ever get a null result... */
* Unlike the UPDATE/DELETE case, a null result is
* possible here, when the referenced table is on the
* nullable side of an outer join. Ignore nulls.
*/
if (isNull) if (isNull)
continue; elog(ERROR, "ExecutePlan: (junk) `%s' is NULL!",
erm->resname);
tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); tuple.t_self = *((ItemPointer) DatumGetPointer(datum));
test = heap_mark4update(erm->relation, &tuple, &buffer); test = heap_mark4update(erm->relation, &tuple, &buffer);
@ -1072,7 +1070,7 @@ lnext: ;
/* /*
* if tuple was deleted or PlanQual failed for * if tuple was deleted or PlanQual failed for
* updated tuple - we have not return this * updated tuple - we must not return this
* tuple! * tuple!
*/ */
goto lnext; goto lnext;
@ -1340,6 +1338,7 @@ ldelete:;
goto ldelete; goto ldelete;
} }
} }
/* tuple already deleted; nothing to do */
return; return;
default: default:
@ -1434,14 +1433,20 @@ ExecReplace(TupleTableSlot *slot,
/* /*
* Check the constraints of the tuple * Check the constraints of the tuple
*
* If we generate a new candidate tuple after EvalPlanQual testing,
* we must loop back here and recheck constraints. (We don't need to
* redo triggers, however. If there are any BEFORE triggers then
* trigger.c will have done mark4update to lock the correct tuple,
* so there's no need to do them again.)
*/ */
lreplace:;
if (resultRelationDesc->rd_att->constr) if (resultRelationDesc->rd_att->constr)
ExecConstraints("ExecReplace", resultRelInfo, slot, estate); ExecConstraints("ExecReplace", resultRelInfo, slot, estate);
/* /*
* replace the heap tuple * replace the heap tuple
*/ */
lreplace:;
result = heap_update(resultRelationDesc, tupleid, tuple, &ctid); result = heap_update(resultRelationDesc, tupleid, tuple, &ctid);
switch (result) switch (result)
{ {
@ -1467,6 +1472,7 @@ lreplace:;
goto lreplace; goto lreplace;
} }
} }
/* tuple already deleted; nothing to do */
return; return;
default: default:
@ -1595,21 +1601,122 @@ ExecConstraints(char *caller, ResultRelInfo *resultRelInfo,
} }
} }
/*
* Check a modified tuple to see if we want to process its updated version
* under READ COMMITTED rules.
*
* See backend/executor/README for some info about how this works.
*/
TupleTableSlot * TupleTableSlot *
EvalPlanQual(EState *estate, Index rti, ItemPointer tid) EvalPlanQual(EState *estate, Index rti, ItemPointer tid)
{ {
evalPlanQual *epq = (evalPlanQual *) estate->es_evalPlanQual; evalPlanQual *epq;
evalPlanQual *oldepq; EState *epqstate;
EState *epqstate = NULL;
Relation relation; Relation relation;
Buffer buffer;
HeapTupleData tuple; HeapTupleData tuple;
bool endNode = true; HeapTuple copyTuple = NULL;
int rtsize;
bool endNode;
Assert(rti != 0); Assert(rti != 0);
/*
* find relation containing target tuple
*/
if (estate->es_result_relation_info != NULL &&
estate->es_result_relation_info->ri_RangeTableIndex == rti)
{
relation = estate->es_result_relation_info->ri_RelationDesc;
}
else
{
List *l;
relation = NULL;
foreach(l, estate->es_rowMark)
{
if (((execRowMark *) lfirst(l))->rti == rti)
{
relation = ((execRowMark *) lfirst(l))->relation;
break;
}
}
if (relation == NULL)
elog(ERROR, "EvalPlanQual: can't find RTE %d", (int) rti);
}
/*
* fetch tid tuple
*
* Loop here to deal with updated or busy tuples
*/
tuple.t_self = *tid;
for (;;)
{
Buffer buffer;
heap_fetch(relation, SnapshotDirty, &tuple, &buffer);
if (tuple.t_data != NULL)
{
TransactionId xwait = SnapshotDirty->xmax;
if (TransactionIdIsValid(SnapshotDirty->xmin))
elog(ERROR, "EvalPlanQual: t_xmin is uncommitted ?!");
/*
* If tuple is being updated by other transaction then we have
* to wait for its commit/abort.
*/
if (TransactionIdIsValid(xwait))
{
ReleaseBuffer(buffer);
XactLockTableWait(xwait);
continue;
}
/*
* We got tuple - now copy it for use by recheck query.
*/
copyTuple = heap_copytuple(&tuple);
ReleaseBuffer(buffer);
break;
}
/*
* Oops! Invalid tuple. Have to check is it updated or deleted.
* Note that it's possible to get invalid SnapshotDirty->tid if
* tuple updated by this transaction. Have we to check this ?
*/
if (ItemPointerIsValid(&(SnapshotDirty->tid)) &&
!(ItemPointerEquals(&(tuple.t_self), &(SnapshotDirty->tid))))
{
/* updated, so look at the updated copy */
tuple.t_self = SnapshotDirty->tid;
continue;
}
/*
* Deleted or updated by this transaction; forget it.
*/
return NULL;
}
/*
* For UPDATE/DELETE we have to return tid of actual row we're
* executing PQ for.
*/
*tid = tuple.t_self;
/*
* Need to run a recheck subquery. Find or create a PQ stack entry.
*/
epq = (evalPlanQual *) estate->es_evalPlanQual;
rtsize = length(estate->es_range_table);
endNode = true;
if (epq != NULL && epq->rti == 0) if (epq != NULL && epq->rti == 0)
{ {
/* Top PQ stack entry is idle, so re-use it */
Assert(!(estate->es_useEvalPlan) && Assert(!(estate->es_useEvalPlan) &&
epq->estate.es_evalPlanQual == NULL); epq->estate.es_evalPlanQual == NULL);
epq->rti = rti; epq->rti = rti;
@ -1627,20 +1734,23 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid)
{ {
do do
{ {
evalPlanQual *oldepq;
/* pop previous PlanQual from the stack */ /* pop previous PlanQual from the stack */
epqstate = &(epq->estate); epqstate = &(epq->estate);
oldepq = (evalPlanQual *) epqstate->es_evalPlanQual; oldepq = (evalPlanQual *) epqstate->es_evalPlanQual;
Assert(oldepq->rti != 0); Assert(oldepq->rti != 0);
/* stop execution */ /* stop execution */
ExecEndNode(epq->plan, epq->plan); ExecEndNode(epq->plan, epq->plan);
epqstate->es_tupleTable->next = 0; ExecDropTupleTable(epqstate->es_tupleTable, true);
epqstate->es_tupleTable = NULL;
heap_freetuple(epqstate->es_evTuple[epq->rti - 1]); heap_freetuple(epqstate->es_evTuple[epq->rti - 1]);
epqstate->es_evTuple[epq->rti - 1] = NULL; epqstate->es_evTuple[epq->rti - 1] = NULL;
/* push current PQ to freePQ stack */ /* push current PQ to freePQ stack */
oldepq->free = epq; oldepq->free = epq;
epq = oldepq; epq = oldepq;
estate->es_evalPlanQual = (Pointer) epq;
} while (epq->rti != rti); } while (epq->rti != rti);
estate->es_evalPlanQual = (Pointer) epq;
} }
/* /*
@ -1655,37 +1765,55 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid)
if (newepq == NULL) /* first call or freePQ stack is empty */ if (newepq == NULL) /* first call or freePQ stack is empty */
{ {
newepq = (evalPlanQual *) palloc(sizeof(evalPlanQual)); newepq = (evalPlanQual *) palloc(sizeof(evalPlanQual));
/* Init EState */ newepq->free = NULL;
/*
* Each stack level has its own copy of the plan tree. This
* is wasteful, but necessary as long as plan nodes point to
* exec state nodes rather than vice versa. Note that copyfuncs.c
* doesn't attempt to copy the exec state nodes, which is a good
* thing in this situation.
*/
newepq->plan = copyObject(estate->es_origPlan);
/*
* Init stack level's EState. We share top level's copy of
* es_result_relations array and other non-changing status.
* We need our own tupletable, es_param_exec_vals, and other
* changeable state.
*/
epqstate = &(newepq->estate); epqstate = &(newepq->estate);
memset(epqstate, 0, sizeof(EState)); memcpy(epqstate, estate, sizeof(EState));
epqstate->type = T_EState;
epqstate->es_direction = ForwardScanDirection; epqstate->es_direction = ForwardScanDirection;
epqstate->es_snapshot = estate->es_snapshot;
epqstate->es_range_table = estate->es_range_table;
epqstate->es_param_list_info = estate->es_param_list_info;
if (estate->es_origPlan->nParamExec > 0) if (estate->es_origPlan->nParamExec > 0)
epqstate->es_param_exec_vals = (ParamExecData *) epqstate->es_param_exec_vals = (ParamExecData *)
palloc(estate->es_origPlan->nParamExec * palloc(estate->es_origPlan->nParamExec *
sizeof(ParamExecData)); sizeof(ParamExecData));
epqstate->es_tupleTable = epqstate->es_tupleTable = NULL;
ExecCreateTupleTable(estate->es_tupleTable->size); epqstate->es_per_tuple_exprcontext = NULL;
/* ... rest */ /*
newepq->plan = copyObject(estate->es_origPlan); * Each epqstate must have its own es_evTupleNull state,
newepq->free = NULL; * but all the stack entries share es_evTuple state. This
epqstate->es_evTupleNull = (bool *) * allows sub-rechecks to inherit the value being examined by
palloc(length(estate->es_range_table) * sizeof(bool)); * an outer recheck.
if (epq == NULL) /* first call */ */
epqstate->es_evTupleNull = (bool *) palloc(rtsize * sizeof(bool));
if (epq == NULL)
{ {
/* first PQ stack entry */
epqstate->es_evTuple = (HeapTuple *) epqstate->es_evTuple = (HeapTuple *)
palloc(length(estate->es_range_table) * sizeof(HeapTuple)); palloc(rtsize * sizeof(HeapTuple));
memset(epqstate->es_evTuple, 0, memset(epqstate->es_evTuple, 0, rtsize * sizeof(HeapTuple));
length(estate->es_range_table) * sizeof(HeapTuple));
} }
else else
{
/* later stack entries share the same storage */
epqstate->es_evTuple = epq->estate.es_evTuple; epqstate->es_evTuple = epq->estate.es_evTuple;
}
} }
else else
{
/* recycle previously used EState */
epqstate = &(newepq->estate); epqstate = &(newepq->estate);
}
/* push current PQ to the stack */ /* push current PQ to the stack */
epqstate->es_evalPlanQual = (Pointer) epq; epqstate->es_evalPlanQual = (Pointer) epq;
epq = newepq; epq = newepq;
@ -1694,123 +1822,49 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid)
endNode = false; endNode = false;
} }
Assert(epq->rti == rti);
epqstate = &(epq->estate); epqstate = &(epq->estate);
/* /*
* Ok - we're requested for the same RTE (-:)). I'm not sure about * Ok - we're requested for the same RTE. Unfortunately we still
* ability to use ExecReScan instead of ExecInitNode, so... * have to end and restart execution of the plan, because ExecReScan
* wouldn't ensure that upper plan nodes would reset themselves. We
* could make that work if insertion of the target tuple were integrated
* with the Param mechanism somehow, so that the upper plan nodes know
* that their children's outputs have changed.
*/ */
if (endNode) if (endNode)
{ {
/* stop execution */
ExecEndNode(epq->plan, epq->plan); ExecEndNode(epq->plan, epq->plan);
epqstate->es_tupleTable->next = 0; ExecDropTupleTable(epqstate->es_tupleTable, true);
epqstate->es_tupleTable = NULL;
} }
/* free old RTE' tuple */ /*
if (epqstate->es_evTuple[epq->rti - 1] != NULL) * free old RTE' tuple, if any, and store target tuple where relation's
{ * scan node will see it
heap_freetuple(epqstate->es_evTuple[epq->rti - 1]); */
epqstate->es_evTuple[epq->rti - 1] = NULL; if (epqstate->es_evTuple[rti - 1] != NULL)
} heap_freetuple(epqstate->es_evTuple[rti - 1]);
epqstate->es_evTuple[rti - 1] = copyTuple;
/* ** fetch tid tuple ** */
if (estate->es_result_relation_info != NULL &&
estate->es_result_relation_info->ri_RangeTableIndex == rti)
relation = estate->es_result_relation_info->ri_RelationDesc;
else
{
List *l;
foreach(l, estate->es_rowMark)
{
if (((execRowMark *) lfirst(l))->rti == rti)
break;
}
relation = ((execRowMark *) lfirst(l))->relation;
}
tuple.t_self = *tid;
for (;;)
{
heap_fetch(relation, SnapshotDirty, &tuple, &buffer);
if (tuple.t_data != NULL)
{
TransactionId xwait = SnapshotDirty->xmax;
if (TransactionIdIsValid(SnapshotDirty->xmin))
{
elog(NOTICE, "EvalPlanQual: t_xmin is uncommitted ?!");
Assert(!TransactionIdIsValid(SnapshotDirty->xmin));
elog(ERROR, "Aborting this transaction");
}
/*
* If tuple is being updated by other transaction then we have
* to wait for its commit/abort.
*/
if (TransactionIdIsValid(xwait))
{
ReleaseBuffer(buffer);
XactLockTableWait(xwait);
continue;
}
/*
* Nice! We got tuple - now copy it.
*/
if (epqstate->es_evTuple[epq->rti - 1] != NULL)
heap_freetuple(epqstate->es_evTuple[epq->rti - 1]);
epqstate->es_evTuple[epq->rti - 1] = heap_copytuple(&tuple);
ReleaseBuffer(buffer);
break;
}
/*
* Ops! Invalid tuple. Have to check is it updated or deleted.
* Note that it's possible to get invalid SnapshotDirty->tid if
* tuple updated by this transaction. Have we to check this ?
*/
if (ItemPointerIsValid(&(SnapshotDirty->tid)) &&
!(ItemPointerEquals(&(tuple.t_self), &(SnapshotDirty->tid))))
{
tuple.t_self = SnapshotDirty->tid; /* updated ... */
continue;
}
/*
* Deleted or updated by this transaction. Do not (re-)start
* execution of this PQ. Continue previous PQ.
*/
oldepq = (evalPlanQual *) epqstate->es_evalPlanQual;
if (oldepq != NULL)
{
Assert(oldepq->rti != 0);
/* push current PQ to freePQ stack */
oldepq->free = epq;
epq = oldepq;
epqstate = &(epq->estate);
estate->es_evalPlanQual = (Pointer) epq;
}
else
{
epq->rti = 0; /* this is the first (oldest) */
estate->es_useEvalPlan = false; /* PQ - mark as free and */
return (NULL); /* continue Query execution */
}
}
/*
* Initialize for new recheck query; be careful to copy down state
* that might have changed in top EState.
*/
epqstate->es_result_relation_info = estate->es_result_relation_info;
epqstate->es_junkFilter = estate->es_junkFilter;
if (estate->es_origPlan->nParamExec > 0) if (estate->es_origPlan->nParamExec > 0)
memset(epqstate->es_param_exec_vals, 0, memset(epqstate->es_param_exec_vals, 0,
estate->es_origPlan->nParamExec * sizeof(ParamExecData)); estate->es_origPlan->nParamExec * sizeof(ParamExecData));
memset(epqstate->es_evTupleNull, false, memset(epqstate->es_evTupleNull, false, rtsize * sizeof(bool));
length(estate->es_range_table) * sizeof(bool)); epqstate->es_useEvalPlan = false;
Assert(epqstate->es_tupleTable->next == 0); Assert(epqstate->es_tupleTable == NULL);
ExecInitNode(epq->plan, epqstate, NULL); epqstate->es_tupleTable =
ExecCreateTupleTable(estate->es_tupleTable->size);
/* ExecInitNode(epq->plan, epqstate, NULL);
* For UPDATE/DELETE we have to return tid of actual row we're
* executing PQ for.
*/
*tid = tuple.t_self;
return EvalPlanQualNext(estate); return EvalPlanQualNext(estate);
} }
@ -1833,8 +1887,10 @@ lpqnext:;
*/ */
if (TupIsNull(slot)) if (TupIsNull(slot))
{ {
/* stop execution */
ExecEndNode(epq->plan, epq->plan); ExecEndNode(epq->plan, epq->plan);
epqstate->es_tupleTable->next = 0; ExecDropTupleTable(epqstate->es_tupleTable, true);
epqstate->es_tupleTable = NULL;
heap_freetuple(epqstate->es_evTuple[epq->rti - 1]); heap_freetuple(epqstate->es_evTuple[epq->rti - 1]);
epqstate->es_evTuple[epq->rti - 1] = NULL; epqstate->es_evTuple[epq->rti - 1] = NULL;
/* pop old PQ from the stack */ /* pop old PQ from the stack */
@ -1872,8 +1928,10 @@ EndEvalPlanQual(EState *estate)
for (;;) for (;;)
{ {
/* stop execution */
ExecEndNode(epq->plan, epq->plan); ExecEndNode(epq->plan, epq->plan);
epqstate->es_tupleTable->next = 0; ExecDropTupleTable(epqstate->es_tupleTable, true);
epqstate->es_tupleTable = NULL;
if (epqstate->es_evTuple[epq->rti - 1] != NULL) if (epqstate->es_evTuple[epq->rti - 1] != NULL)
{ {
heap_freetuple(epqstate->es_evTuple[epq->rti - 1]); heap_freetuple(epqstate->es_evTuple[epq->rti - 1]);

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/nodeAppend.c,v 1.41 2001/05/08 19:47:02 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/executor/nodeAppend.c,v 1.42 2001/05/15 00:33:36 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -64,6 +64,7 @@
static bool exec_append_initialize_next(Append *node); static bool exec_append_initialize_next(Append *node);
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
* exec_append_initialize_next * exec_append_initialize_next
* *
@ -79,7 +80,6 @@ exec_append_initialize_next(Append *node)
EState *estate; EState *estate;
AppendState *appendstate; AppendState *appendstate;
int whichplan; int whichplan;
int nplans;
/* /*
* get information from the append node * get information from the append node
@ -87,9 +87,8 @@ exec_append_initialize_next(Append *node)
estate = node->plan.state; estate = node->plan.state;
appendstate = node->appendstate; appendstate = node->appendstate;
whichplan = appendstate->as_whichplan; whichplan = appendstate->as_whichplan;
nplans = appendstate->as_nplans;
if (whichplan < 0) if (whichplan < appendstate->as_firstplan)
{ {
/* /*
@ -98,17 +97,17 @@ exec_append_initialize_next(Append *node)
* ExecProcAppend that we are at the end of the line by returning * ExecProcAppend that we are at the end of the line by returning
* FALSE * FALSE
*/ */
appendstate->as_whichplan = 0; appendstate->as_whichplan = appendstate->as_firstplan;
return FALSE; return FALSE;
} }
else if (whichplan >= nplans) else if (whichplan > appendstate->as_lastplan)
{ {
/* /*
* as above, end the scan if we go beyond the last scan in our * as above, end the scan if we go beyond the last scan in our
* list.. * list..
*/ */
appendstate->as_whichplan = nplans - 1; appendstate->as_whichplan = appendstate->as_lastplan;
return FALSE; return FALSE;
} }
else else
@ -145,7 +144,9 @@ exec_append_initialize_next(Append *node)
* structures get allocated in the executor's top level memory * structures get allocated in the executor's top level memory
* block instead of that of the call to ExecProcAppend.) * block instead of that of the call to ExecProcAppend.)
* *
* Returns the scan result of the first scan. * Special case: during an EvalPlanQual recheck query of an inherited
* target relation, we only want to initialize and scan the single
* subplan that corresponds to the target relation being checked.
* ---------------------------------------------------------------- * ----------------------------------------------------------------
*/ */
bool bool
@ -175,12 +176,32 @@ ExecInitAppend(Append *node, EState *estate, Plan *parent)
* create new AppendState for our append node * create new AppendState for our append node
*/ */
appendstate = makeNode(AppendState); appendstate = makeNode(AppendState);
appendstate->as_whichplan = 0;
appendstate->as_nplans = nplans; appendstate->as_nplans = nplans;
appendstate->as_initialized = initialized; appendstate->as_initialized = initialized;
node->appendstate = appendstate; node->appendstate = appendstate;
/*
* Do we want to scan just one subplan? (Special case for EvalPlanQual)
* XXX pretty dirty way of determining that this case applies ...
*/
if (node->isTarget && estate->es_evTuple != NULL)
{
int tplan;
tplan = estate->es_result_relation_info - estate->es_result_relations;
Assert(tplan >= 0 && tplan < nplans);
appendstate->as_firstplan = tplan;
appendstate->as_lastplan = tplan;
}
else
{
/* normal case, scan all subplans */
appendstate->as_firstplan = 0;
appendstate->as_lastplan = nplans - 1;
}
/* /*
* Miscellaneous initialization * Miscellaneous initialization
* *
@ -197,10 +218,10 @@ ExecInitAppend(Append *node, EState *estate, Plan *parent)
ExecInitResultTupleSlot(estate, &appendstate->cstate); ExecInitResultTupleSlot(estate, &appendstate->cstate);
/* /*
* call ExecInitNode on each of the plans in our list and save the * call ExecInitNode on each of the plans to be executed and save the
* results into the array "initialized" * results into the array "initialized"
*/ */
for (i = 0; i < nplans; i++) for (i = appendstate->as_firstplan; i <= appendstate->as_lastplan; i++)
{ {
appendstate->as_whichplan = i; appendstate->as_whichplan = i;
exec_append_initialize_next(node); exec_append_initialize_next(node);
@ -218,7 +239,7 @@ ExecInitAppend(Append *node, EState *estate, Plan *parent)
/* /*
* return the result from the first subplan's initialization * return the result from the first subplan's initialization
*/ */
appendstate->as_whichplan = 0; appendstate->as_whichplan = appendstate->as_firstplan;
exec_append_initialize_next(node); exec_append_initialize_next(node);
return TRUE; return TRUE;
@ -357,10 +378,9 @@ void
ExecReScanAppend(Append *node, ExprContext *exprCtxt, Plan *parent) ExecReScanAppend(Append *node, ExprContext *exprCtxt, Plan *parent)
{ {
AppendState *appendstate = node->appendstate; AppendState *appendstate = node->appendstate;
int nplans = length(node->appendplans);
int i; int i;
for (i = 0; i < nplans; i++) for (i = appendstate->as_firstplan; i <= appendstate->as_lastplan; i++)
{ {
Plan *subnode; Plan *subnode;
@ -383,6 +403,6 @@ ExecReScanAppend(Append *node, ExprContext *exprCtxt, Plan *parent)
ExecReScan(subnode, exprCtxt, (Plan *) node); ExecReScan(subnode, exprCtxt, (Plan *) node);
} }
} }
appendstate->as_whichplan = 0; appendstate->as_whichplan = appendstate->as_firstplan;
exec_append_initialize_next(node); exec_append_initialize_next(node);
} }

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $Id: execnodes.h,v 1.58 2001/05/07 00:43:25 tgl Exp $ * $Id: execnodes.h,v 1.59 2001/05/15 00:33:36 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -353,6 +353,8 @@ typedef struct ResultState
* AppendState information * AppendState information
* *
* whichplan which plan is being executed (0 .. n-1) * whichplan which plan is being executed (0 .. n-1)
* firstplan first plan to execute (usually 0)
* lastplan last plan to execute (usually n-1)
* nplans how many plans are in the list * nplans how many plans are in the list
* initialized array of ExecInitNode() results * initialized array of ExecInitNode() results
* ---------------- * ----------------
@ -361,6 +363,8 @@ typedef struct AppendState
{ {
CommonState cstate; /* its first field is NodeTag */ CommonState cstate; /* its first field is NodeTag */
int as_whichplan; int as_whichplan;
int as_firstplan;
int as_lastplan;
int as_nplans; int as_nplans;
bool *as_initialized; bool *as_initialized;
} AppendState; } AppendState;