diff options
Diffstat (limited to 'src/backend/executor/nodeLockRows.c')
-rw-r--r-- | src/backend/executor/nodeLockRows.c | 133 |
1 files changed, 90 insertions, 43 deletions
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 5ae106c06ad..7bcf99f4889 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -25,6 +25,7 @@ #include "access/xact.h" #include "executor/executor.h" #include "executor/nodeLockRows.h" +#include "foreign/fdwapi.h" #include "storage/bufmgr.h" #include "utils/rel.h" #include "utils/tqual.h" @@ -40,7 +41,7 @@ ExecLockRows(LockRowsState *node) TupleTableSlot *slot; EState *estate; PlanState *outerPlan; - bool epq_started; + bool epq_needed; ListCell *lc; /* @@ -58,15 +59,18 @@ lnext: if (TupIsNull(slot)) return NULL; + /* We don't need EvalPlanQual unless we get updated tuple version(s) */ + epq_needed = false; + /* * Attempt to lock the source tuple(s). (Note we only have locking * rowmarks in lr_arowMarks.) */ - epq_started = false; foreach(lc, node->lr_arowMarks) { ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc); ExecRowMark *erm = aerm->rowmark; + HeapTuple *testTuple; Datum datum; bool isNull; HeapTupleData tuple; @@ -77,8 +81,10 @@ lnext: HeapTuple copyTuple; /* clear any leftover test tuple for this rel */ - if (node->lr_epqstate.estate != NULL) - EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, NULL); + testTuple = &(node->lr_curtuples[erm->rti - 1]); + if (*testTuple != NULL) + heap_freetuple(*testTuple); + *testTuple = NULL; /* if child rel, must check whether it produced this row */ if (erm->rti != erm->prti) @@ -97,10 +103,12 @@ lnext: if (tableoid != erm->relid) { /* this child is inactive right now */ + erm->ermActive = false; ItemPointerSetInvalid(&(erm->curCtid)); continue; } } + erm->ermActive = true; /* fetch the tuple's ctid */ datum = ExecGetJunkAttribute(slot, @@ -109,9 +117,45 @@ lnext: /* shouldn't ever get a null result... */ if (isNull) elog(ERROR, "ctid is NULL"); - tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); + + /* requests for foreign tables must be passed to their FDW */ + if (erm->relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + FdwRoutine *fdwroutine; + bool updated = false; + + fdwroutine = GetFdwRoutineForRelation(erm->relation, false); + /* this should have been checked already, but let's be safe */ + if (fdwroutine->RefetchForeignRow == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot lock rows in foreign table \"%s\"", + RelationGetRelationName(erm->relation)))); + copyTuple = fdwroutine->RefetchForeignRow(estate, + erm, + datum, + &updated); + if (copyTuple == NULL) + { + /* couldn't get the lock, so skip this row */ + goto lnext; + } + + /* save locked tuple for possible EvalPlanQual testing below */ + *testTuple = copyTuple; + + /* + * if FDW says tuple was updated before getting locked, we need to + * perform EPQ testing to see if quals are still satisfied + */ + if (updated) + epq_needed = true; + + continue; + } /* okay, try to lock the tuple */ + tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); switch (erm->markType) { case ROW_MARK_EXCLUSIVE: @@ -191,40 +235,11 @@ lnext: /* remember the actually locked tuple's TID */ tuple.t_self = copyTuple->t_self; - /* - * Need to run a recheck subquery. Initialize EPQ state if we - * didn't do so already. - */ - if (!epq_started) - { - ListCell *lc2; + /* Save locked tuple for EvalPlanQual testing below */ + *testTuple = copyTuple; - EvalPlanQualBegin(&node->lr_epqstate, estate); - - /* - * Ensure that rels with already-visited rowmarks are told - * not to return tuples during the first EPQ test. We can - * exit this loop once it reaches the current rowmark; - * rels appearing later in the list will be set up - * correctly by the EvalPlanQualSetTuple call at the top - * of the loop. - */ - foreach(lc2, node->lr_arowMarks) - { - ExecAuxRowMark *aerm2 = (ExecAuxRowMark *) lfirst(lc2); - - if (lc2 == lc) - break; - EvalPlanQualSetTuple(&node->lr_epqstate, - aerm2->rowmark->rti, - NULL); - } - - epq_started = true; - } - - /* Store target tuple for relation's scan node */ - EvalPlanQualSetTuple(&node->lr_epqstate, erm->rti, copyTuple); + /* Remember we need to do EPQ testing */ + epq_needed = true; /* Continue loop until we have all target tuples */ break; @@ -237,17 +252,35 @@ lnext: test); } - /* Remember locked tuple's TID for WHERE CURRENT OF */ + /* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */ erm->curCtid = tuple.t_self; } /* * If we need to do EvalPlanQual testing, do so. */ - if (epq_started) + if (epq_needed) { + int i; + + /* Initialize EPQ machinery */ + EvalPlanQualBegin(&node->lr_epqstate, estate); + + /* + * Transfer already-fetched tuples into the EPQ state, and make sure + * its test tuples for other tables are reset to NULL. + */ + for (i = 0; i < node->lr_ntables; i++) + { + EvalPlanQualSetTuple(&node->lr_epqstate, + i + 1, + node->lr_curtuples[i]); + /* freeing this tuple is now the responsibility of EPQ */ + node->lr_curtuples[i] = NULL; + } + /* - * First, fetch a copy of any rows that were successfully locked + * Next, fetch a copy of any rows that were successfully locked * without any update having occurred. (We do this in a separate pass * so as to avoid overhead in the common case where there are no * concurrent updates.) @@ -260,7 +293,7 @@ lnext: Buffer buffer; /* ignore non-active child tables */ - if (!ItemPointerIsValid(&(erm->curCtid))) + if (!erm->ermActive) { Assert(erm->rti != erm->prti); /* check it's child table */ continue; @@ -269,6 +302,10 @@ lnext: if (EvalPlanQualGetTuple(&node->lr_epqstate, erm->rti) != NULL) continue; /* it was updated and fetched above */ + /* foreign tables should have been fetched above */ + Assert(erm->relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE); + Assert(ItemPointerIsValid(&(erm->curCtid))); + /* okay, fetch the tuple */ tuple.t_self = erm->curCtid; if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, @@ -352,6 +389,13 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags) lrstate->ps.ps_ProjInfo = NULL; /* + * Create workspace in which we can remember per-RTE locked tuples + */ + lrstate->lr_ntables = list_length(estate->es_range_table); + lrstate->lr_curtuples = (HeapTuple *) + palloc0(lrstate->lr_ntables * sizeof(HeapTuple)); + + /* * Locate the ExecRowMark(s) that this node is responsible for, and * construct ExecAuxRowMarks for them. (InitPlan should already have * built the global list of ExecRowMarks.) @@ -370,8 +414,11 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags) if (rc->isParent) continue; + /* safety check on size of lr_curtuples array */ + Assert(rc->rti > 0 && rc->rti <= lrstate->lr_ntables); + /* find ExecRowMark and build ExecAuxRowMark */ - erm = ExecFindRowMark(estate, rc->rti); + erm = ExecFindRowMark(estate, rc->rti, false); aerm = ExecBuildAuxRowMark(erm, outerPlan->targetlist); /* |