aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/nodeModifyTable.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/executor/nodeModifyTable.c')
-rw-r--r--src/backend/executor/nodeModifyTable.c353
1 files changed, 200 insertions, 153 deletions
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 325d380b0a9..c570965da09 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -568,15 +568,6 @@ ExecInitInsertProjection(ModifyTableState *mtstate,
table_slot_create(resultRelInfo->ri_RelationDesc,
&estate->es_tupleTable);
- /*
- * In the ON CONFLICT UPDATE case, we will also need a slot for the old
- * tuple to calculate the updated tuple on its base.
- */
- if (node->onConflictAction == ONCONFLICT_UPDATE)
- resultRelInfo->ri_oldTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &estate->es_tupleTable);
-
/* Build ProjectionInfo if needed (it probably isn't). */
if (need_projection)
{
@@ -1165,7 +1156,7 @@ ExecInsert(ModifyTableContext *context,
ExecARUpdateTriggers(estate, resultRelInfo,
NULL, NULL,
NULL,
- resultRelInfo->ri_oldTupleSlot,
+ NULL,
slot,
NULL,
mtstate->mt_transition_capture,
@@ -1345,8 +1336,7 @@ ExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
*/
static TM_Result
ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
- ItemPointer tupleid, bool changingPart, int options,
- TupleTableSlot *oldSlot)
+ ItemPointer tupleid, bool changingPart)
{
EState *estate = context->estate;
@@ -1354,10 +1344,9 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
estate->es_output_cid,
estate->es_snapshot,
estate->es_crosscheck_snapshot,
- options,
+ true /* wait for commit */ ,
&context->tmfd,
- changingPart,
- oldSlot);
+ changingPart);
}
/*
@@ -1366,15 +1355,10 @@ ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
* Closing steps of tuple deletion; this invokes AFTER FOR EACH ROW triggers,
* including the UPDATE triggers if the deletion is being done as part of a
* cross-partition tuple move.
- *
- * The old tuple is already fetched into ‘slot’ for regular tables. For FDW,
- * the old tuple is given as 'oldtuple' and is to be stored in 'slot' when
- * needed.
*/
static void
ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
- ItemPointer tupleid, HeapTuple oldtuple,
- TupleTableSlot *slot, bool changingPart)
+ ItemPointer tupleid, HeapTuple oldtuple, bool changingPart)
{
ModifyTableState *mtstate = context->mtstate;
EState *estate = context->estate;
@@ -1392,8 +1376,8 @@ ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
{
ExecARUpdateTriggers(estate, resultRelInfo,
NULL, NULL,
- oldtuple,
- slot, NULL, NULL, mtstate->mt_transition_capture,
+ tupleid, oldtuple,
+ NULL, NULL, mtstate->mt_transition_capture,
false);
/*
@@ -1404,30 +1388,10 @@ ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
}
/* AFTER ROW DELETE Triggers */
- ExecARDeleteTriggers(estate, resultRelInfo, oldtuple, slot,
+ ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
ar_delete_trig_tcs, changingPart);
}
-/*
- * Initializes the tuple slot in a ResultRelInfo for DELETE action.
- *
- * We mark 'projectNewInfoValid' even though the projections themselves
- * are not initialized here.
- */
-static void
-ExecInitDeleteTupleSlot(ModifyTableState *mtstate,
- ResultRelInfo *resultRelInfo)
-{
- EState *estate = mtstate->ps.state;
-
- Assert(!resultRelInfo->ri_projectNewInfoValid);
-
- resultRelInfo->ri_oldTupleSlot =
- table_slot_create(resultRelInfo->ri_RelationDesc,
- &estate->es_tupleTable);
- resultRelInfo->ri_projectNewInfoValid = true;
-}
-
/* ----------------------------------------------------------------
* ExecDelete
*
@@ -1447,8 +1411,7 @@ ExecInitDeleteTupleSlot(ModifyTableState *mtstate,
* part of an UPDATE of partition-key, then the slot returned by
* EvalPlanQual() is passed back using output parameter epqreturnslot.
*
- * Returns RETURNING result if any, otherwise NULL. The deleted tuple
- * to be stored into oldslot independently that.
+ * Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
*/
static TupleTableSlot *
@@ -1456,7 +1419,6 @@ ExecDelete(ModifyTableContext *context,
ResultRelInfo *resultRelInfo,
ItemPointer tupleid,
HeapTuple oldtuple,
- TupleTableSlot *oldslot,
bool processReturning,
bool changingPart,
bool canSetTag,
@@ -1520,15 +1482,6 @@ ExecDelete(ModifyTableContext *context,
}
else
{
- int options = TABLE_MODIFY_WAIT | TABLE_MODIFY_FETCH_OLD_TUPLE;
-
- /*
- * Specify that we need to lock and fetch the last tuple version for
- * EPQ on appropriate transaction isolation levels.
- */
- if (!IsolationUsesXactSnapshot())
- options |= TABLE_MODIFY_LOCK_UPDATED;
-
/*
* delete the tuple
*
@@ -1539,8 +1492,7 @@ ExecDelete(ModifyTableContext *context,
* transaction-snapshot mode transactions.
*/
ldelete:
- result = ExecDeleteAct(context, resultRelInfo, tupleid, changingPart,
- options, oldslot);
+ result = ExecDeleteAct(context, resultRelInfo, tupleid, changingPart);
if (tmresult)
*tmresult = result;
@@ -1587,6 +1539,7 @@ ldelete:
case TM_Updated:
{
+ TupleTableSlot *inputslot;
TupleTableSlot *epqslot;
if (IsolationUsesXactSnapshot())
@@ -1595,29 +1548,87 @@ ldelete:
errmsg("could not serialize access due to concurrent update")));
/*
- * We need to do EPQ. The latest tuple is already found
- * and locked as a result of TABLE_MODIFY_LOCK_UPDATED.
+ * Already know that we're going to need to do EPQ, so
+ * fetch tuple directly into the right slot.
*/
- Assert(context->tmfd.traversed);
- epqslot = EvalPlanQual(context->epqstate,
- resultRelationDesc,
- resultRelInfo->ri_RangeTableIndex,
- oldslot);
- if (TupIsNull(epqslot))
- /* Tuple not passing quals anymore, exiting... */
- return NULL;
+ EvalPlanQualBegin(context->epqstate);
+ inputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,
+ resultRelInfo->ri_RangeTableIndex);
- /*
- * If requested, skip delete and pass back the updated
- * row.
- */
- if (epqreturnslot)
+ result = table_tuple_lock(resultRelationDesc, tupleid,
+ estate->es_snapshot,
+ inputslot, estate->es_output_cid,
+ LockTupleExclusive, LockWaitBlock,
+ TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+ &context->tmfd);
+
+ switch (result)
{
- *epqreturnslot = epqslot;
- return NULL;
+ case TM_Ok:
+ Assert(context->tmfd.traversed);
+ epqslot = EvalPlanQual(context->epqstate,
+ resultRelationDesc,
+ resultRelInfo->ri_RangeTableIndex,
+ inputslot);
+ if (TupIsNull(epqslot))
+ /* Tuple not passing quals anymore, exiting... */
+ return NULL;
+
+ /*
+ * If requested, skip delete and pass back the
+ * updated row.
+ */
+ if (epqreturnslot)
+ {
+ *epqreturnslot = epqslot;
+ return NULL;
+ }
+ else
+ goto ldelete;
+
+ case TM_SelfModified:
+
+ /*
+ * This can be reached when following an update
+ * chain from a tuple updated by another session,
+ * reaching a tuple that was already updated in
+ * this transaction. If previously updated by this
+ * command, ignore the delete, otherwise error
+ * out.
+ *
+ * See also TM_SelfModified response to
+ * table_tuple_delete() above.
+ */
+ if (context->tmfd.cmax != estate->es_output_cid)
+ ereport(ERROR,
+ (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
+ errmsg("tuple to be deleted was already modified by an operation triggered by the current command"),
+ errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
+ return NULL;
+
+ case TM_Deleted:
+ /* tuple already deleted; nothing to do */
+ return NULL;
+
+ default:
+
+ /*
+ * TM_Invisible should be impossible because we're
+ * waiting for updated row versions, and would
+ * already have errored out if the first version
+ * is invisible.
+ *
+ * TM_Updated should be impossible, because we're
+ * locking the latest version via
+ * TUPLE_LOCK_FLAG_FIND_LAST_VERSION.
+ */
+ elog(ERROR, "unexpected table_tuple_lock status: %u",
+ result);
+ return NULL;
}
- else
- goto ldelete;
+
+ Assert(false);
+ break;
}
case TM_Deleted:
@@ -1651,8 +1662,7 @@ ldelete:
if (tupleDeleted)
*tupleDeleted = true;
- ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple,
- oldslot, changingPart);
+ ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple, changingPart);
/* Process RETURNING if present and if requested */
if (processReturning && resultRelInfo->ri_projectReturning)
@@ -1670,13 +1680,17 @@ ldelete:
}
else
{
- /* Copy old tuple to the returning slot */
slot = ExecGetReturningSlot(estate, resultRelInfo);
if (oldtuple != NULL)
+ {
ExecForceStoreHeapTuple(oldtuple, slot, false);
+ }
else
- ExecCopySlot(slot, oldslot);
- Assert(!TupIsNull(slot));
+ {
+ if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid,
+ SnapshotAny, slot))
+ elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+ }
}
rslot = ExecProcessReturning(resultRelInfo, slot, context->planSlot);
@@ -1777,18 +1791,11 @@ ExecCrossPartitionUpdate(ModifyTableContext *context,
}
/*
- * Make sure ri_oldTupleSlot is initialized. The old tuple is to be saved
- * there by ExecDelete() to save effort on further re-fetching.
- */
- if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
- ExecInitUpdateProjection(mtstate, resultRelInfo);
-
- /*
* Row movement, part 1. Delete the tuple, but skip RETURNING processing.
* We want to return rows from INSERT.
*/
ExecDelete(context, resultRelInfo,
- tupleid, oldtuple, resultRelInfo->ri_oldTupleSlot,
+ tupleid, oldtuple,
false, /* processReturning */
true, /* changingPart */
false, /* canSetTag */
@@ -1829,13 +1836,21 @@ ExecCrossPartitionUpdate(ModifyTableContext *context,
return true;
else
{
- /*
- * ExecDelete already fetches the most recent version of old tuple
- * to resultRelInfo->ri_oldTupleSlot. So, just project the new
- * tuple to retry the UPDATE with.
- */
+ /* Fetch the most recent version of old tuple. */
+ TupleTableSlot *oldSlot;
+
+ /* ... but first, make sure ri_oldTupleSlot is initialized. */
+ if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
+ ExecInitUpdateProjection(mtstate, resultRelInfo);
+ oldSlot = resultRelInfo->ri_oldTupleSlot;
+ if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc,
+ tupleid,
+ SnapshotAny,
+ oldSlot))
+ elog(ERROR, "failed to fetch tuple being updated");
+ /* and project the new tuple to retry the UPDATE with */
*retry_slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
- resultRelInfo->ri_oldTupleSlot);
+ oldSlot);
return false;
}
}
@@ -1954,8 +1969,7 @@ ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
static TM_Result
ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
- bool canSetTag, int options, TupleTableSlot *oldSlot,
- UpdateContext *updateCxt)
+ bool canSetTag, UpdateContext *updateCxt)
{
EState *estate = context->estate;
Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -2047,8 +2061,7 @@ lreplace:
ExecCrossPartitionUpdateForeignKey(context,
resultRelInfo,
insert_destrel,
- tupleid,
- resultRelInfo->ri_oldTupleSlot,
+ tupleid, slot,
inserted_tuple);
return TM_Ok;
@@ -2091,10 +2104,9 @@ lreplace:
estate->es_output_cid,
estate->es_snapshot,
estate->es_crosscheck_snapshot,
- options /* wait for commit */ ,
+ true /* wait for commit */ ,
&context->tmfd, &updateCxt->lockmode,
- &updateCxt->updateIndexes,
- oldSlot);
+ &updateCxt->updateIndexes);
return result;
}
@@ -2108,8 +2120,7 @@ lreplace:
static void
ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
ResultRelInfo *resultRelInfo, ItemPointer tupleid,
- HeapTuple oldtuple, TupleTableSlot *slot,
- TupleTableSlot *oldslot)
+ HeapTuple oldtuple, TupleTableSlot *slot)
{
ModifyTableState *mtstate = context->mtstate;
List *recheckIndexes = NIL;
@@ -2125,7 +2136,7 @@ ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
/* AFTER ROW UPDATE Triggers */
ExecARUpdateTriggers(context->estate, resultRelInfo,
NULL, NULL,
- oldtuple, oldslot, slot,
+ tupleid, oldtuple, slot,
recheckIndexes,
mtstate->operation == CMD_INSERT ?
mtstate->mt_oc_transition_capture :
@@ -2214,7 +2225,7 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,
/* Perform the root table's triggers. */
ExecARUpdateTriggers(context->estate,
rootRelInfo, sourcePartInfo, destPartInfo,
- NULL, oldslot, newslot, NIL, NULL, true);
+ tupleid, NULL, newslot, NIL, NULL, true);
}
/* ----------------------------------------------------------------
@@ -2237,7 +2248,6 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,
* no relevant triggers.
*
* slot contains the new tuple value to be stored.
- * oldslot is the slot to store the old tuple.
* planSlot is the output of the ModifyTable's subplan; we use it
* to access values from other input tables (for RETURNING),
* row-ID junk columns, etc.
@@ -2248,7 +2258,7 @@ ExecCrossPartitionUpdateForeignKey(ModifyTableContext *context,
static TupleTableSlot *
ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
- TupleTableSlot *oldslot, bool canSetTag, bool locked)
+ bool canSetTag)
{
EState *estate = context->estate;
Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
@@ -2301,16 +2311,6 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
}
else
{
- int options = TABLE_MODIFY_WAIT | TABLE_MODIFY_FETCH_OLD_TUPLE;
-
- /*
- * Specify that we need to lock and fetch the last tuple version for
- * EPQ on appropriate transaction isolation levels if the tuple isn't
- * locked already.
- */
- if (!locked && !IsolationUsesXactSnapshot())
- options |= TABLE_MODIFY_LOCK_UPDATED;
-
/*
* If we generate a new candidate tuple after EvalPlanQual testing, we
* must loop back here to try again. (We don't need to redo triggers,
@@ -2320,7 +2320,7 @@ ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
*/
redo_act:
result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
- canSetTag, options, oldslot, &updateCxt);
+ canSetTag, &updateCxt);
/*
* If ExecUpdateAct reports that a cross-partition update was done,
@@ -2371,32 +2371,88 @@ redo_act:
case TM_Updated:
{
+ TupleTableSlot *inputslot;
TupleTableSlot *epqslot;
+ TupleTableSlot *oldSlot;
if (IsolationUsesXactSnapshot())
ereport(ERROR,
(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
errmsg("could not serialize access due to concurrent update")));
- /* Shouldn't get there if the tuple was previously locked */
- Assert(!locked);
-
/*
- * We need to do EPQ. The latest tuple is already found
- * and locked as a result of TABLE_MODIFY_LOCK_UPDATED.
+ * Already know that we're going to need to do EPQ, so
+ * fetch tuple directly into the right slot.
*/
- Assert(context->tmfd.traversed);
- epqslot = EvalPlanQual(context->epqstate,
- resultRelationDesc,
- resultRelInfo->ri_RangeTableIndex,
- oldslot);
- if (TupIsNull(epqslot))
- /* Tuple not passing quals anymore, exiting... */
- return NULL;
- slot = ExecGetUpdateNewTuple(resultRelInfo,
- epqslot,
- oldslot);
- goto redo_act;
+ inputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,
+ resultRelInfo->ri_RangeTableIndex);
+
+ result = table_tuple_lock(resultRelationDesc, tupleid,
+ estate->es_snapshot,
+ inputslot, estate->es_output_cid,
+ updateCxt.lockmode, LockWaitBlock,
+ TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
+ &context->tmfd);
+
+ switch (result)
+ {
+ case TM_Ok:
+ Assert(context->tmfd.traversed);
+
+ epqslot = EvalPlanQual(context->epqstate,
+ resultRelationDesc,
+ resultRelInfo->ri_RangeTableIndex,
+ inputslot);
+ if (TupIsNull(epqslot))
+ /* Tuple not passing quals anymore, exiting... */
+ return NULL;
+
+ /* Make sure ri_oldTupleSlot is initialized. */
+ if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
+ ExecInitUpdateProjection(context->mtstate,
+ resultRelInfo);
+
+ /* Fetch the most recent version of old tuple. */
+ oldSlot = resultRelInfo->ri_oldTupleSlot;
+ if (!table_tuple_fetch_row_version(resultRelationDesc,
+ tupleid,
+ SnapshotAny,
+ oldSlot))
+ elog(ERROR, "failed to fetch tuple being updated");
+ slot = ExecGetUpdateNewTuple(resultRelInfo,
+ epqslot, oldSlot);
+ goto redo_act;
+
+ case TM_Deleted:
+ /* tuple already deleted; nothing to do */
+ return NULL;
+
+ case TM_SelfModified:
+
+ /*
+ * This can be reached when following an update
+ * chain from a tuple updated by another session,
+ * reaching a tuple that was already updated in
+ * this transaction. If previously modified by
+ * this command, ignore the redundant update,
+ * otherwise error out.
+ *
+ * See also TM_SelfModified response to
+ * table_tuple_update() above.
+ */
+ if (context->tmfd.cmax != estate->es_output_cid)
+ ereport(ERROR,
+ (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION),
+ errmsg("tuple to be updated was already modified by an operation triggered by the current command"),
+ errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows.")));
+ return NULL;
+
+ default:
+ /* see table_tuple_lock call in ExecDelete() */
+ elog(ERROR, "unexpected table_tuple_lock status: %u",
+ result);
+ return NULL;
+ }
}
break;
@@ -2420,7 +2476,7 @@ redo_act:
(estate->es_processed)++;
ExecUpdateEpilogue(context, &updateCxt, resultRelInfo, tupleid, oldtuple,
- slot, oldslot);
+ slot);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
@@ -2638,8 +2694,7 @@ ExecOnConflictUpdate(ModifyTableContext *context,
*returning = ExecUpdate(context, resultRelInfo,
conflictTid, NULL,
resultRelInfo->ri_onConflict->oc_ProjSlot,
- existing,
- canSetTag, true);
+ canSetTag);
/*
* Clear out existing tuple, as there might not be another conflict among
@@ -2918,7 +2973,6 @@ lmerge_matched:
{
result = ExecUpdateAct(context, resultRelInfo, tupleid,
NULL, newslot, canSetTag,
- TABLE_MODIFY_WAIT, NULL,
&updateCxt);
/*
@@ -2940,8 +2994,7 @@ lmerge_matched:
if (result == TM_Ok)
{
ExecUpdateEpilogue(context, &updateCxt, resultRelInfo,
- tupleid, NULL, newslot,
- resultRelInfo->ri_oldTupleSlot);
+ tupleid, NULL, newslot);
mtstate->mt_merge_updated += 1;
}
break;
@@ -2967,12 +3020,12 @@ lmerge_matched:
}
else
result = ExecDeleteAct(context, resultRelInfo, tupleid,
- false, TABLE_MODIFY_WAIT, NULL);
+ false);
if (result == TM_Ok)
{
ExecDeleteEpilogue(context, resultRelInfo, tupleid, NULL,
- resultRelInfo->ri_oldTupleSlot, false);
+ false);
mtstate->mt_merge_deleted += 1;
}
break;
@@ -4037,18 +4090,12 @@ ExecModifyTable(PlanState *pstate)
/* Now apply the update. */
slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
- slot, resultRelInfo->ri_oldTupleSlot,
- node->canSetTag, false);
+ slot, node->canSetTag);
break;
case CMD_DELETE:
- /* Initialize slot for DELETE to fetch the old tuple */
- if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
- ExecInitDeleteTupleSlot(node, resultRelInfo);
-
slot = ExecDelete(&context, resultRelInfo, tupleid, oldtuple,
- resultRelInfo->ri_oldTupleSlot, true, false,
- node->canSetTag, NULL, NULL, NULL);
+ true, false, node->canSetTag, NULL, NULL, NULL);
break;
case CMD_MERGE: