aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/nodeLockRows.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor/nodeLockRows.c')
-rw-r--r--src/backend/executor/nodeLockRows.c133
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);
/*