diff options
Diffstat (limited to 'src/backend/executor/execMain.c')
-rw-r--r-- | src/backend/executor/execMain.c | 348 |
1 files changed, 203 insertions, 145 deletions
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index fc1dccd0467..eda6ce518d2 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.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); - 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 * 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_origPlan = plan; - estate->es_evalPlanQual = NULL; - estate->es_evTuple = NULL; - estate->es_useEvalPlan = false; - return tupType; } @@ -1038,13 +1039,10 @@ lnext: ; elog(ERROR, "ExecutePlan: NO (junk) `%s' was found!", erm->resname); - /* - * 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. - */ + /* shouldn't ever get a null result... */ if (isNull) - continue; + elog(ERROR, "ExecutePlan: (junk) `%s' is NULL!", + erm->resname); tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); test = heap_mark4update(erm->relation, &tuple, &buffer); @@ -1072,7 +1070,7 @@ lnext: ; /* * if tuple was deleted or PlanQual failed for - * updated tuple - we have not return this + * updated tuple - we must not return this * tuple! */ goto lnext; @@ -1340,6 +1338,7 @@ ldelete:; goto ldelete; } } + /* tuple already deleted; nothing to do */ return; default: @@ -1434,14 +1433,20 @@ ExecReplace(TupleTableSlot *slot, /* * 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) ExecConstraints("ExecReplace", resultRelInfo, slot, estate); /* * replace the heap tuple */ -lreplace:; result = heap_update(resultRelationDesc, tupleid, tuple, &ctid); switch (result) { @@ -1467,6 +1472,7 @@ lreplace:; goto lreplace; } } + /* tuple already deleted; nothing to do */ return; 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 * EvalPlanQual(EState *estate, Index rti, ItemPointer tid) { - evalPlanQual *epq = (evalPlanQual *) estate->es_evalPlanQual; - evalPlanQual *oldepq; - EState *epqstate = NULL; + evalPlanQual *epq; + EState *epqstate; Relation relation; - Buffer buffer; HeapTupleData tuple; - bool endNode = true; + HeapTuple copyTuple = NULL; + int rtsize; + bool endNode; 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) { + /* Top PQ stack entry is idle, so re-use it */ Assert(!(estate->es_useEvalPlan) && epq->estate.es_evalPlanQual == NULL); epq->rti = rti; @@ -1627,20 +1734,23 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid) { do { + evalPlanQual *oldepq; + /* pop previous PlanQual from the stack */ epqstate = &(epq->estate); oldepq = (evalPlanQual *) epqstate->es_evalPlanQual; Assert(oldepq->rti != 0); /* stop execution */ 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]); epqstate->es_evTuple[epq->rti - 1] = NULL; /* push current PQ to freePQ stack */ oldepq->free = epq; epq = oldepq; + estate->es_evalPlanQual = (Pointer) epq; } 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 */ { 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); - memset(epqstate, 0, sizeof(EState)); - epqstate->type = T_EState; + memcpy(epqstate, estate, sizeof(EState)); 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) epqstate->es_param_exec_vals = (ParamExecData *) palloc(estate->es_origPlan->nParamExec * sizeof(ParamExecData)); - epqstate->es_tupleTable = - ExecCreateTupleTable(estate->es_tupleTable->size); - /* ... rest */ - newepq->plan = copyObject(estate->es_origPlan); - newepq->free = NULL; - epqstate->es_evTupleNull = (bool *) - palloc(length(estate->es_range_table) * sizeof(bool)); - if (epq == NULL) /* first call */ + epqstate->es_tupleTable = NULL; + epqstate->es_per_tuple_exprcontext = NULL; + /* + * Each epqstate must have its own es_evTupleNull state, + * but all the stack entries share es_evTuple state. This + * allows sub-rechecks to inherit the value being examined by + * an outer recheck. + */ + epqstate->es_evTupleNull = (bool *) palloc(rtsize * sizeof(bool)); + if (epq == NULL) { + /* first PQ stack entry */ epqstate->es_evTuple = (HeapTuple *) - palloc(length(estate->es_range_table) * sizeof(HeapTuple)); - memset(epqstate->es_evTuple, 0, - length(estate->es_range_table) * sizeof(HeapTuple)); + palloc(rtsize * sizeof(HeapTuple)); + memset(epqstate->es_evTuple, 0, rtsize * sizeof(HeapTuple)); } else + { + /* later stack entries share the same storage */ epqstate->es_evTuple = epq->estate.es_evTuple; + } } else + { + /* recycle previously used EState */ epqstate = &(newepq->estate); + } /* push current PQ to the stack */ epqstate->es_evalPlanQual = (Pointer) epq; epq = newepq; @@ -1694,123 +1822,49 @@ EvalPlanQual(EState *estate, Index rti, ItemPointer tid) endNode = false; } + Assert(epq->rti == rti); epqstate = &(epq->estate); /* - * Ok - we're requested for the same RTE (-:)). I'm not sure about - * ability to use ExecReScan instead of ExecInitNode, so... + * Ok - we're requested for the same RTE. Unfortunately we still + * 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) { + /* stop execution */ 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) - { - heap_freetuple(epqstate->es_evTuple[epq->rti - 1]); - epqstate->es_evTuple[epq->rti - 1] = NULL; - } - - /* ** 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 */ - } - } + /* + * free old RTE' tuple, if any, and store target tuple where relation's + * scan node will see it + */ + if (epqstate->es_evTuple[rti - 1] != NULL) + heap_freetuple(epqstate->es_evTuple[rti - 1]); + epqstate->es_evTuple[rti - 1] = copyTuple; + /* + * 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) memset(epqstate->es_param_exec_vals, 0, estate->es_origPlan->nParamExec * sizeof(ParamExecData)); - memset(epqstate->es_evTupleNull, false, - length(estate->es_range_table) * sizeof(bool)); - Assert(epqstate->es_tupleTable->next == 0); - ExecInitNode(epq->plan, epqstate, NULL); + memset(epqstate->es_evTupleNull, false, rtsize * sizeof(bool)); + epqstate->es_useEvalPlan = false; + Assert(epqstate->es_tupleTable == NULL); + epqstate->es_tupleTable = + ExecCreateTupleTable(estate->es_tupleTable->size); - /* - * For UPDATE/DELETE we have to return tid of actual row we're - * executing PQ for. - */ - *tid = tuple.t_self; + ExecInitNode(epq->plan, epqstate, NULL); return EvalPlanQualNext(estate); } @@ -1833,8 +1887,10 @@ lpqnext:; */ if (TupIsNull(slot)) { + /* stop execution */ 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]); epqstate->es_evTuple[epq->rti - 1] = NULL; /* pop old PQ from the stack */ @@ -1872,8 +1928,10 @@ EndEvalPlanQual(EState *estate) for (;;) { + /* stop execution */ 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) { heap_freetuple(epqstate->es_evTuple[epq->rti - 1]); |