Split ExecUpdate and ExecDelete into reusable pieces
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 17 Mar 2022 10:47:04 +0000 (11:47 +0100)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Thu, 17 Mar 2022 10:47:04 +0000 (11:47 +0100)
Create subroutines ExecUpdatePrologue / ExecUpdateAct /
ExecUpdateEpilogue, and similar for ExecDelete.

Introduce a new struct to be used internally in nodeModifyTable.c,
dubbed ModifyTableContext, which contains all context information needed
to perform these operations, as well as ExecInsert and others.

This allows using a different schedule and a different way of evaluating
the results of these operations, which can be exploited by a later
commit introducing support for MERGE.  It also makes ExecUpdate and
ExecDelete proper shorter and (hopefully) simpler.

Author: Álvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Amit Langote <amitlangote09@gmail.com>
Reviewed-by: Japin Li <japinli@hotmail.com>
Reviewed-by: Zhihong Yu <zyu@yugabyte.com>
Discussion: https://postgr.es/m/202202271724.4z7xv3cf46kv@alvherre.pgsql

src/backend/executor/nodeModifyTable.c
src/tools/pgindent/typedefs.list

index 5ec699a9bd1c2e6af2b2dec77a7725ca0a5d9238..6239abae90451deae1ce215af475d5f0b04fcbe2 100644 (file)
@@ -20,8 +20,8 @@
  *
  *      NOTES
  *             The ModifyTable node receives input from its outerPlan, which is
- *             the data to insert for INSERT cases, or the changed columns' new
- *             values plus row-locating info for UPDATE cases, or just the
+ *             the data to insert for INSERT cases, the changed columns' new
+ *             values plus row-locating info for UPDATE and MERGE cases, or just the
  *             row-locating info for DELETE cases.
  *
  *             If the query specifies RETURNING, then the ModifyTable returns a
@@ -60,6 +60,61 @@ typedef struct MTTargetRelLookup
        int                     relationIndex;  /* rel's index in resultRelInfo[] array */
 } MTTargetRelLookup;
 
+/*
+ * Context struct for a ModifyTable operation, containing basic execution
+ * state and some output variables populated by ExecUpdateAct() and
+ * ExecDeleteAct() to report the result of their actions to callers.
+ */
+typedef struct ModifyTableContext
+{
+       /* Operation state */
+       ModifyTableState *mtstate;
+       EPQState   *epqstate;
+       EState     *estate;
+
+       /*
+        * Slot containing tuple obtained from ModifyTable's subplan.  Used to
+        * access "junk" columns that are not going to be stored.
+        */
+       TupleTableSlot *planSlot;
+
+
+       /*
+        * Information about the changes that were made concurrently to a tuple
+        * being updated or deleted
+        */
+       TM_FailureData tmfd;
+
+       /*
+        * The tuple produced by EvalPlanQual to retry from, if a cross-partition
+        * UPDATE requires it
+        */
+       TupleTableSlot *cpUpdateRetrySlot;
+
+       /*
+        * The tuple projected by the INSERT's RETURNING clause, when doing a
+        * cross-partition UPDATE
+        */
+       TupleTableSlot *cpUpdateReturningSlot;
+
+       /*
+        * Lock mode to acquire on the latest tuple version before performing
+        * EvalPlanQual on it
+        */
+       LockTupleMode lockmode;
+} ModifyTableContext;
+
+/*
+ * Context struct containing output data specific to UPDATE operations.
+ */
+typedef struct UpdateContext
+{
+       bool            updated;                /* did UPDATE actually occur? */
+       bool            updateIndexes;  /* index update required? */
+       bool            crossPartUpdate;        /* was it a cross-partition update? */
+} UpdateContext;
+
+
 static void ExecBatchInsert(ModifyTableState *mtstate,
                                                        ResultRelInfo *resultRelInfo,
                                                        TupleTableSlot **slots,
@@ -67,12 +122,10 @@ static void ExecBatchInsert(ModifyTableState *mtstate,
                                                        int numSlots,
                                                        EState *estate,
                                                        bool canSetTag);
-static bool ExecOnConflictUpdate(ModifyTableState *mtstate,
+static bool ExecOnConflictUpdate(ModifyTableContext *context,
                                                                 ResultRelInfo *resultRelInfo,
                                                                 ItemPointer conflictTid,
-                                                                TupleTableSlot *planSlot,
                                                                 TupleTableSlot *excludedSlot,
-                                                                EState *estate,
                                                                 bool canSetTag,
                                                                 TupleTableSlot **returning);
 static TupleTableSlot *ExecPrepareTupleRouting(ModifyTableState *mtstate,
@@ -580,8 +633,6 @@ ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
  *             relations.
  *
  *             slot contains the new tuple value to be stored.
- *             planSlot is the output of the ModifyTable's subplan; we use it
- *             to access "junk" columns that are not going to be stored.
  *
  *             Returns RETURNING result if any, otherwise NULL.
  *
@@ -591,15 +642,16 @@ ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecInsert(ModifyTableState *mtstate,
+ExecInsert(ModifyTableContext *context,
                   ResultRelInfo *resultRelInfo,
                   TupleTableSlot *slot,
-                  TupleTableSlot *planSlot,
-                  EState *estate,
                   bool canSetTag)
 {
+       ModifyTableState *mtstate = context->mtstate;
+       EState     *estate = context->estate;
        Relation        resultRelationDesc;
        List       *recheckIndexes = NIL;
+       TupleTableSlot *planSlot = context->planSlot;
        TupleTableSlot *result = NULL;
        TransitionCaptureState *ar_insert_trig_tcs;
        ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
@@ -850,9 +902,9 @@ ExecInsert(ModifyTableState *mtstate,
                                         */
                                        TupleTableSlot *returning = NULL;
 
-                                       if (ExecOnConflictUpdate(mtstate, resultRelInfo,
-                                                                                        &conflictTid, planSlot, slot,
-                                                                                        estate, canSetTag, &returning))
+                                       if (ExecOnConflictUpdate(context, resultRelInfo,
+                                                                                        &conflictTid, slot, canSetTag,
+                                                                                        &returning))
                                        {
                                                InstrCountTuples2(&mtstate->ps, 1);
                                                return returning;
@@ -1055,6 +1107,90 @@ ExecBatchInsert(ModifyTableState *mtstate,
                estate->es_processed += numInserted;
 }
 
+/*
+ * ExecDeletePrologue -- subroutine for ExecDelete
+ *
+ * Prepare executor state for DELETE.  Actually, the only thing we have to do
+ * here is execute BEFORE ROW triggers.  We return false if one of them makes
+ * the delete a no-op; otherwise, return true.
+ */
+static bool
+ExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
+                                  ItemPointer tupleid, HeapTuple oldtuple,
+                                  TupleTableSlot **epqreturnslot)
+{
+       /* BEFORE ROW DELETE triggers */
+       if (resultRelInfo->ri_TrigDesc &&
+               resultRelInfo->ri_TrigDesc->trig_delete_before_row)
+               return ExecBRDeleteTriggers(context->estate, context->epqstate,
+                                                                       resultRelInfo, tupleid, oldtuple,
+                                                                       epqreturnslot);
+
+       return true;
+}
+
+/*
+ * ExecDeleteAct -- subroutine for ExecDelete
+ *
+ * Actually delete the tuple from a plain table.
+ *
+ * Caller is in charge of doing EvalPlanQual as necessary
+ */
+static TM_Result
+ExecDeleteAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
+                         ItemPointer tupleid, bool changingPart)
+{
+       EState     *estate = context->estate;
+
+       return table_tuple_delete(resultRelInfo->ri_RelationDesc, tupleid,
+                                                         estate->es_output_cid,
+                                                         estate->es_snapshot,
+                                                         estate->es_crosscheck_snapshot,
+                                                         true /* wait for commit */ ,
+                                                         &context->tmfd,
+                                                         changingPart);
+}
+
+/*
+ * ExecDeleteEpilogue -- subroutine for ExecDelete
+ *
+ * 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.
+ */
+static void
+ExecDeleteEpilogue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
+                                  ItemPointer tupleid, HeapTuple oldtuple)
+{
+       ModifyTableState *mtstate = context->mtstate;
+       EState     *estate = context->estate;
+       TransitionCaptureState *ar_delete_trig_tcs;
+
+       /*
+        * If this delete is the result of a partition key update that moved the
+        * tuple to a new partition, put this row into the transition OLD TABLE,
+        * if there is one. We need to do this separately for DELETE and INSERT
+        * because they happen on different tables.
+        */
+       ar_delete_trig_tcs = mtstate->mt_transition_capture;
+       if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture &&
+               mtstate->mt_transition_capture->tcs_update_old_table)
+       {
+               ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple,
+                                                        NULL, NULL, mtstate->mt_transition_capture);
+
+               /*
+                * We've already captured the NEW TABLE row, so make sure any AR
+                * DELETE trigger fired below doesn't capture it again.
+                */
+               ar_delete_trig_tcs = NULL;
+       }
+
+       /* AFTER ROW DELETE Triggers */
+       ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
+                                                ar_delete_trig_tcs);
+}
+
 /* ----------------------------------------------------------------
  *             ExecDelete
  *
@@ -1072,46 +1208,37 @@ ExecBatchInsert(ModifyTableState *mtstate,
  *             whether the tuple is actually deleted, callers can use it to
  *             decide whether to continue the operation.  When this DELETE is a
  *             part of an UPDATE of partition-key, then the slot returned by
- *             EvalPlanQual() is passed back using output parameter epqslot.
+ *             EvalPlanQual() is passed back using output parameter epqreturnslot.
  *
  *             Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ModifyTableState *mtstate,
+ExecDelete(ModifyTableContext *context,
                   ResultRelInfo *resultRelInfo,
                   ItemPointer tupleid,
                   HeapTuple oldtuple,
-                  TupleTableSlot *planSlot,
-                  EPQState *epqstate,
-                  EState *estate,
                   bool processReturning,
-                  bool canSetTag,
                   bool changingPart,
+                  bool canSetTag,
                   bool *tupleDeleted,
                   TupleTableSlot **epqreturnslot)
 {
+       EState     *estate = context->estate;
        Relation        resultRelationDesc = resultRelInfo->ri_RelationDesc;
-       TM_Result       result;
-       TM_FailureData tmfd;
        TupleTableSlot *slot = NULL;
-       TransitionCaptureState *ar_delete_trig_tcs;
+       TM_Result       result;
 
        if (tupleDeleted)
                *tupleDeleted = false;
 
-       /* BEFORE ROW DELETE Triggers */
-       if (resultRelInfo->ri_TrigDesc &&
-               resultRelInfo->ri_TrigDesc->trig_delete_before_row)
-       {
-               bool            dodelete;
-
-               dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-                                                                               tupleid, oldtuple, epqreturnslot);
-
-               if (!dodelete)                  /* "do nothing" */
-                       return NULL;
-       }
+       /*
+        * Prepare for the delete.  This includes BEFORE ROW triggers, so we're
+        * done if it says we are.
+        */
+       if (!ExecDeletePrologue(context, resultRelInfo, tupleid, oldtuple,
+                                                       epqreturnslot))
+               return NULL;
 
        /* INSTEAD OF ROW DELETE Triggers */
        if (resultRelInfo->ri_TrigDesc &&
@@ -1137,7 +1264,7 @@ ExecDelete(ModifyTableState *mtstate,
                slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
                                                                                                                           resultRelInfo,
                                                                                                                           slot,
-                                                                                                                          planSlot);
+                                                                                                                          context->planSlot);
 
                if (slot == NULL)               /* "do nothing" */
                        return NULL;
@@ -1156,20 +1283,14 @@ ExecDelete(ModifyTableState *mtstate,
                /*
                 * delete the tuple
                 *
-                * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check
-                * that the row to be deleted is visible to that snapshot, and throw a
-                * can't-serialize error if not. This is a special-case behavior
-                * needed for referential integrity updates in transaction-snapshot
-                * mode transactions.
+                * Note: if context->estate->es_crosscheck_snapshot isn't
+                * InvalidSnapshot, we check that the row to be deleted is visible to
+                * that snapshot, and throw a can't-serialize error if not. This is a
+                * special-case behavior needed for referential integrity updates in
+                * transaction-snapshot mode transactions.
                 */
 ldelete:;
-               result = table_tuple_delete(resultRelationDesc, tupleid,
-                                                                       estate->es_output_cid,
-                                                                       estate->es_snapshot,
-                                                                       estate->es_crosscheck_snapshot,
-                                                                       true /* wait for commit */ ,
-                                                                       &tmfd,
-                                                                       changingPart);
+               result = ExecDeleteAct(context, resultRelInfo, tupleid, changingPart);
 
                switch (result)
                {
@@ -1199,7 +1320,7 @@ ldelete:;
                                 * can re-execute the DELETE and then return NULL to cancel
                                 * the outer delete.
                                 */
-                               if (tmfd.cmax != estate->es_output_cid)
+                               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"),
@@ -1225,8 +1346,8 @@ ldelete:;
                                         * Already know that we're going to need to do EPQ, so
                                         * fetch tuple directly into the right slot.
                                         */
-                                       EvalPlanQualBegin(epqstate);
-                                       inputslot = EvalPlanQualSlot(epqstate, resultRelationDesc,
+                                       EvalPlanQualBegin(context->epqstate);
+                                       inputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,
                                                                                                 resultRelInfo->ri_RangeTableIndex);
 
                                        result = table_tuple_lock(resultRelationDesc, tupleid,
@@ -1234,13 +1355,13 @@ ldelete:;
                                                                                          inputslot, estate->es_output_cid,
                                                                                          LockTupleExclusive, LockWaitBlock,
                                                                                          TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
-                                                                                         &tmfd);
+                                                                                         &context->tmfd);
 
                                        switch (result)
                                        {
                                                case TM_Ok:
-                                                       Assert(tmfd.traversed);
-                                                       epqslot = EvalPlanQual(epqstate,
+                                                       Assert(context->tmfd.traversed);
+                                                       epqslot = EvalPlanQual(context->epqstate,
                                                                                                   resultRelationDesc,
                                                                                                   resultRelInfo->ri_RangeTableIndex,
                                                                                                   inputslot);
@@ -1273,7 +1394,7 @@ ldelete:;
                                                         * See also TM_SelfModified response to
                                                         * table_tuple_delete() above.
                                                         */
-                                                       if (tmfd.cmax != estate->es_output_cid)
+                                                       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"),
@@ -1336,33 +1457,7 @@ ldelete:;
        if (tupleDeleted)
                *tupleDeleted = true;
 
-       /*
-        * If this delete is the result of a partition key update that moved the
-        * tuple to a new partition, put this row into the transition OLD TABLE,
-        * if there is one. We need to do this separately for DELETE and INSERT
-        * because they happen on different tables.
-        */
-       ar_delete_trig_tcs = mtstate->mt_transition_capture;
-       if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
-               && mtstate->mt_transition_capture->tcs_update_old_table)
-       {
-               ExecARUpdateTriggers(estate, resultRelInfo,
-                                                        tupleid,
-                                                        oldtuple,
-                                                        NULL,
-                                                        NULL,
-                                                        mtstate->mt_transition_capture);
-
-               /*
-                * We've already captured the NEW TABLE row, so make sure any AR
-                * DELETE trigger fired below doesn't capture it again.
-                */
-               ar_delete_trig_tcs = NULL;
-       }
-
-       /* AFTER ROW DELETE Triggers */
-       ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
-                                                ar_delete_trig_tcs);
+       ExecDeleteEpilogue(context, resultRelInfo, tupleid, oldtuple);
 
        /* Process RETURNING if present and if requested */
        if (processReturning && resultRelInfo->ri_projectReturning)
@@ -1393,7 +1488,7 @@ ldelete:;
                        }
                }
 
-               rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
+               rslot = ExecProcessReturning(resultRelInfo, slot, context->planSlot);
 
                /*
                 * Before releasing the target tuple again, make sure rslot has a
@@ -1427,21 +1522,20 @@ ldelete:;
  * and call this function again or perform a regular update accordingly.
  */
 static bool
-ExecCrossPartitionUpdate(ModifyTableState *mtstate,
+ExecCrossPartitionUpdate(ModifyTableContext *context,
                                                 ResultRelInfo *resultRelInfo,
                                                 ItemPointer tupleid, HeapTuple oldtuple,
-                                                TupleTableSlot *slot, TupleTableSlot *planSlot,
-                                                EPQState *epqstate, bool canSetTag,
-                                                TupleTableSlot **retry_slot,
-                                                TupleTableSlot **inserted_tuple)
+                                                TupleTableSlot *slot,
+                                                bool canSetTag, UpdateContext *updateCxt)
 {
+       ModifyTableState *mtstate = context->mtstate;
        EState     *estate = mtstate->ps.state;
        TupleConversionMap *tupconv_map;
        bool            tuple_deleted;
        TupleTableSlot *epqslot = NULL;
 
-       *inserted_tuple = NULL;
-       *retry_slot = NULL;
+       context->cpUpdateReturningSlot = NULL;
+       context->cpUpdateRetrySlot = NULL;
 
        /*
         * Disallow an INSERT ON CONFLICT DO UPDATE that causes the original row
@@ -1488,11 +1582,11 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
         * Row movement, part 1.  Delete the tuple, but skip RETURNING processing.
         * We want to return rows from INSERT.
         */
-       ExecDelete(mtstate, resultRelInfo, tupleid, oldtuple, planSlot,
-                          epqstate, estate,
+       ExecDelete(context, resultRelInfo,
+                          tupleid, oldtuple,
                           false,                       /* processReturning */
-                          false,                       /* canSetTag */
                           true,                        /* changingPart */
+                          false,                       /* canSetTag */
                           &tuple_deleted, &epqslot);
 
        /*
@@ -1538,8 +1632,9 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
                                                                                           SnapshotAny,
                                                                                           oldSlot))
                                elog(ERROR, "failed to fetch tuple being updated");
-                       *retry_slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
-                                                                                               oldSlot);
+                       /* and project the new tuple to retry the UPDATE with */
+                       context->cpUpdateRetrySlot =
+                               ExecGetUpdateNewTuple(resultRelInfo, epqslot, oldSlot);
                        return false;
                }
        }
@@ -1556,8 +1651,8 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
                                                                         mtstate->mt_root_tuple_slot);
 
        /* Tuple routing starts from the root table. */
-       *inserted_tuple = ExecInsert(mtstate, mtstate->rootResultRelInfo, slot,
-                                                                planSlot, estate, canSetTag);
+       context->cpUpdateReturningSlot =
+               ExecInsert(context, mtstate->rootResultRelInfo, slot, canSetTag);
 
        /*
         * Reset the transition state that may possibly have been written by
@@ -1570,6 +1665,233 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
        return true;
 }
 
+/*
+ * ExecUpdatePrologue -- subroutine for ExecUpdate
+ *
+ * Prepare executor state for UPDATE.  This includes running BEFORE ROW
+ * triggers.  We return false if one of them makes the update a no-op;
+ * otherwise, return true.
+ */
+static bool
+ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
+                                  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot)
+{
+       Relation        resultRelationDesc = resultRelInfo->ri_RelationDesc;
+
+       ExecMaterializeSlot(slot);
+
+       /*
+        * Open the table's indexes, if we have not done so already, so that we
+        * can add new index entries for the updated tuple.
+        */
+       if (resultRelationDesc->rd_rel->relhasindex &&
+               resultRelInfo->ri_IndexRelationDescs == NULL)
+               ExecOpenIndices(resultRelInfo, false);
+
+       /* BEFORE ROW UPDATE triggers */
+       if (resultRelInfo->ri_TrigDesc &&
+               resultRelInfo->ri_TrigDesc->trig_update_before_row)
+               return ExecBRUpdateTriggers(context->estate, context->epqstate,
+                                                                       resultRelInfo, tupleid, oldtuple, slot);
+
+       return true;
+}
+
+/*
+ * ExecUpdatePrepareSlot -- subroutine for ExecUpdate
+ *
+ * Apply the final modifications to the tuple slot before the update.
+ */
+static void
+ExecUpdatePrepareSlot(ResultRelInfo *resultRelInfo,
+                                         TupleTableSlot *slot,
+                                         EState *estate)
+{
+       Relation        resultRelationDesc = resultRelInfo->ri_RelationDesc;
+
+       /*
+        * Constraints and GENERATED expressions might reference the tableoid
+        * column, so (re-)initialize tts_tableOid before evaluating them.
+        */
+       slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
+
+       /*
+        * Compute stored generated columns
+        */
+       if (resultRelationDesc->rd_att->constr &&
+               resultRelationDesc->rd_att->constr->has_generated_stored)
+               ExecComputeStoredGenerated(resultRelInfo, estate, slot,
+                                                                  CMD_UPDATE);
+}
+
+/*
+ * ExecUpdateAct -- subroutine for ExecUpdate
+ *
+ * Actually update the tuple, when operating on a plain table.  If the
+ * table is a partition, and the command was called referencing an ancestor
+ * partitioned table, this routine migrates the resulting tuple to another
+ * partition.
+ *
+ * The caller is in charge of keeping indexes current as necessary.  The
+ * caller is also in charge of doing EvalPlanQual if the tuple is found to
+ * be concurrently updated.  However, in case of a cross-partition update,
+ * this routine does it.
+ *
+ * Caller is in charge of doing EvalPlanQual as necessary, and of keeping
+ * indexes current for the update.
+ */
+static TM_Result
+ExecUpdateAct(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
+                         ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
+                         bool canSetTag, UpdateContext *updateCxt)
+{
+       EState     *estate = context->estate;
+       Relation        resultRelationDesc = resultRelInfo->ri_RelationDesc;
+       bool            partition_constraint_failed;
+       TM_Result       result;
+
+       updateCxt->crossPartUpdate = false;
+
+       /*
+        * If we generate a new candidate tuple after EvalPlanQual testing, we
+        * must loop back here and recheck any RLS policies and constraints. (We
+        * don't need to redo triggers, however.  If there are any BEFORE triggers
+        * then trigger.c will have done table_tuple_lock to lock the correct
+        * tuple, so there's no need to do them again.)
+        */
+lreplace:;
+
+       /* ensure slot is independent, consider e.g. EPQ */
+       ExecMaterializeSlot(slot);
+
+       /*
+        * If partition constraint fails, this row might get moved to another
+        * partition, in which case we should check the RLS CHECK policy just
+        * before inserting into the new partition, rather than doing it here.
+        * This is because a trigger on that partition might again change the row.
+        * So skip the WCO checks if the partition constraint fails.
+        */
+       partition_constraint_failed =
+               resultRelationDesc->rd_rel->relispartition &&
+               !ExecPartitionCheck(resultRelInfo, slot, estate, false);
+
+       /* Check any RLS UPDATE WITH CHECK policies */
+       if (!partition_constraint_failed &&
+               resultRelInfo->ri_WithCheckOptions != NIL)
+       {
+               /*
+                * ExecWithCheckOptions() will skip any WCOs which are not of the kind
+                * we are looking for at this point.
+                */
+               ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
+                                                        resultRelInfo, slot, estate);
+       }
+
+       /*
+        * If a partition check failed, try to move the row into the right
+        * partition.
+        */
+       if (partition_constraint_failed)
+       {
+               /*
+                * ExecCrossPartitionUpdate will first DELETE the row from the
+                * partition it's currently in and then insert it back into the root
+                * table, which will re-route it to the correct partition.  However,
+                * if the tuple has been concurrently updated, a retry is needed.
+                */
+               if (ExecCrossPartitionUpdate(context, resultRelInfo,
+                                                                        tupleid, oldtuple, slot,
+                                                                        canSetTag, updateCxt))
+               {
+                       /* success! */
+                       updateCxt->updated = true;
+                       updateCxt->crossPartUpdate = true;
+                       return TM_Ok;
+               }
+
+               /*
+                * ExecCrossPartitionUpdate installed an updated version of the new
+                * tuple in the retry slot; start over.
+                */
+               slot = context->cpUpdateRetrySlot;
+               goto lreplace;
+       }
+
+       /*
+        * Check the constraints of the tuple.  We've already checked the
+        * partition constraint above; however, we must still ensure the tuple
+        * passes all other constraints, so we will call ExecConstraints() and
+        * have it validate all remaining checks.
+        */
+       if (resultRelationDesc->rd_att->constr)
+               ExecConstraints(resultRelInfo, slot, estate);
+
+       /*
+        * replace the heap tuple
+        *
+        * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check that
+        * the row to be updated is visible to that snapshot, and throw a
+        * can't-serialize error if not. This is a special-case behavior needed
+        * for referential integrity updates in transaction-snapshot mode
+        * transactions.
+        */
+       result = table_tuple_update(resultRelationDesc, tupleid, slot,
+                                                               estate->es_output_cid,
+                                                               estate->es_snapshot,
+                                                               estate->es_crosscheck_snapshot,
+                                                               true /* wait for commit */ ,
+                                                               &context->tmfd, &context->lockmode,
+                                                               &updateCxt->updateIndexes);
+       if (result == TM_Ok)
+               updateCxt->updated = true;
+
+       return result;
+}
+
+/*
+ * ExecUpdateEpilogue -- subroutine for ExecUpdate
+ *
+ * Closing steps of updating a tuple.  Must be called if ExecUpdateAct
+ * returns indicating that the tuple was updated.
+ */
+static void
+ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
+                                  ResultRelInfo *resultRelInfo, ItemPointer tupleid,
+                                  HeapTuple oldtuple, TupleTableSlot *slot,
+                                  List *recheckIndexes)
+{
+       ModifyTableState *mtstate = context->mtstate;
+
+       /* insert index entries for tuple if necessary */
+       if (resultRelInfo->ri_NumIndices > 0 && updateCxt->updateIndexes)
+               recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+                                                                                          slot, context->estate,
+                                                                                          true, false,
+                                                                                          NULL, NIL);
+
+       /* AFTER ROW UPDATE Triggers */
+       ExecARUpdateTriggers(context->estate, resultRelInfo,
+                                                tupleid, oldtuple, slot,
+                                                recheckIndexes,
+                                                mtstate->operation == CMD_INSERT ?
+                                                mtstate->mt_oc_transition_capture :
+                                                mtstate->mt_transition_capture);
+
+       /*
+        * Check any WITH CHECK OPTION constraints from parent views.  We are
+        * required to do this after testing all constraints and uniqueness
+        * violations per the SQL spec, so we do it after actually updating the
+        * record in the heap and all indexes.
+        *
+        * ExecWithCheckOptions() will skip any WCOs which are not of the kind we
+        * are looking for at this point.
+        */
+       if (resultRelInfo->ri_WithCheckOptions != NIL)
+               ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo,
+                                                        slot, context->estate);
+}
+
+
 /* ----------------------------------------------------------------
  *             ExecUpdate
  *
@@ -1598,20 +1920,15 @@ ExecCrossPartitionUpdate(ModifyTableState *mtstate,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ModifyTableState *mtstate,
-                  ResultRelInfo *resultRelInfo,
-                  ItemPointer tupleid,
-                  HeapTuple oldtuple,
-                  TupleTableSlot *slot,
-                  TupleTableSlot *planSlot,
-                  EPQState *epqstate,
-                  EState *estate,
+ExecUpdate(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
+                  ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
                   bool canSetTag)
 {
+       EState     *estate = context->estate;
        Relation        resultRelationDesc = resultRelInfo->ri_RelationDesc;
-       TM_Result       result;
-       TM_FailureData tmfd;
+       UpdateContext updateCxt = {0};
        List       *recheckIndexes = NIL;
+       TM_Result       result;
 
        /*
         * abort the operation if not running transactions
@@ -1619,24 +1936,12 @@ ExecUpdate(ModifyTableState *mtstate,
        if (IsBootstrapProcessingMode())
                elog(ERROR, "cannot UPDATE during bootstrap");
 
-       ExecMaterializeSlot(slot);
-
        /*
-        * Open the table's indexes, if we have not done so already, so that we
-        * can add new index entries for the updated tuple.
+        * Prepare for the update.  This includes BEFORE ROW triggers, so we're
+        * done if it says we are.
         */
-       if (resultRelationDesc->rd_rel->relhasindex &&
-               resultRelInfo->ri_IndexRelationDescs == NULL)
-               ExecOpenIndices(resultRelInfo, false);
-
-       /* BEFORE ROW UPDATE Triggers */
-       if (resultRelInfo->ri_TrigDesc &&
-               resultRelInfo->ri_TrigDesc->trig_update_before_row)
-       {
-               if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-                                                                 tupleid, oldtuple, slot))
-                       return NULL;            /* "do nothing" */
-       }
+       if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, oldtuple, slot))
+               return NULL;
 
        /* INSTEAD OF ROW UPDATE Triggers */
        if (resultRelInfo->ri_TrigDesc &&
@@ -1648,19 +1953,7 @@ ExecUpdate(ModifyTableState *mtstate,
        }
        else if (resultRelInfo->ri_FdwRoutine)
        {
-               /*
-                * GENERATED expressions might reference the tableoid column, so
-                * (re-)initialize tts_tableOid before evaluating them.
-                */
-               slot->tts_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
-
-               /*
-                * Compute stored generated columns
-                */
-               if (resultRelationDesc->rd_att->constr &&
-                       resultRelationDesc->rd_att->constr->has_generated_stored)
-                       ExecComputeStoredGenerated(resultRelInfo, estate, slot,
-                                                                          CMD_UPDATE);
+               ExecUpdatePrepareSlot(resultRelInfo, slot, estate);
 
                /*
                 * update in foreign table: let the FDW do it
@@ -1668,7 +1961,7 @@ ExecUpdate(ModifyTableState *mtstate,
                slot = resultRelInfo->ri_FdwRoutine->ExecForeignUpdate(estate,
                                                                                                                           resultRelInfo,
                                                                                                                           slot,
-                                                                                                                          planSlot);
+                                                                                                                          context->planSlot);
 
                if (slot == NULL)               /* "do nothing" */
                        return NULL;
@@ -1682,114 +1975,20 @@ ExecUpdate(ModifyTableState *mtstate,
        }
        else
        {
-               LockTupleMode lockmode;
-               bool            partition_constraint_failed;
-               bool            update_indexes;
-
-               /*
-                * Constraints and GENERATED expressions might reference the tableoid
-                * column, so (re-)initialize tts_tableOid before evaluating them.
-                */
-               slot->tts_tableOid = RelationGetRelid(resultRelationDesc);
-
-               /*
-                * Compute stored generated columns
-                */
-               if (resultRelationDesc->rd_att->constr &&
-                       resultRelationDesc->rd_att->constr->has_generated_stored)
-                       ExecComputeStoredGenerated(resultRelInfo, estate, slot,
-                                                                          CMD_UPDATE);
-
-               /*
-                * Check any RLS UPDATE WITH CHECK policies
-                *
-                * If we generate a new candidate tuple after EvalPlanQual testing, we
-                * must loop back here and recheck any RLS policies and constraints.
-                * (We don't need to redo triggers, however.  If there are any BEFORE
-                * triggers then trigger.c will have done table_tuple_lock to lock the
-                * correct tuple, so there's no need to do them again.)
-                */
-lreplace:;
-
-               /* ensure slot is independent, consider e.g. EPQ */
-               ExecMaterializeSlot(slot);
-
-               /*
-                * If partition constraint fails, this row might get moved to another
-                * partition, in which case we should check the RLS CHECK policy just
-                * before inserting into the new partition, rather than doing it here.
-                * This is because a trigger on that partition might again change the
-                * row.  So skip the WCO checks if the partition constraint fails.
-                */
-               partition_constraint_failed =
-                       resultRelationDesc->rd_rel->relispartition &&
-                       !ExecPartitionCheck(resultRelInfo, slot, estate, false);
-
-               if (!partition_constraint_failed &&
-                       resultRelInfo->ri_WithCheckOptions != NIL)
-               {
-                       /*
-                        * ExecWithCheckOptions() will skip any WCOs which are not of the
-                        * kind we are looking for at this point.
-                        */
-                       ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
-                                                                resultRelInfo, slot, estate);
-               }
-
-               /*
-                * If a partition check failed, try to move the row into the right
-                * partition.
-                */
-               if (partition_constraint_failed)
-               {
-                       TupleTableSlot *inserted_tuple,
-                                          *retry_slot;
-                       bool            retry;
+               /* Fill in the slot appropriately */
+               ExecUpdatePrepareSlot(resultRelInfo, slot, estate);
 
-                       /*
-                        * ExecCrossPartitionUpdate will first DELETE the row from the
-                        * partition it's currently in and then insert it back into the
-                        * root table, which will re-route it to the correct partition.
-                        * The first part may have to be repeated if it is detected that
-                        * the tuple we're trying to move has been concurrently updated.
-                        */
-                       retry = !ExecCrossPartitionUpdate(mtstate, resultRelInfo, tupleid,
-                                                                                         oldtuple, slot, planSlot,
-                                                                                         epqstate, canSetTag,
-                                                                                         &retry_slot, &inserted_tuple);
-                       if (retry)
-                       {
-                               slot = retry_slot;
-                               goto lreplace;
-                       }
-
-                       return inserted_tuple;
-               }
+redo_act:
+               result = ExecUpdateAct(context, resultRelInfo, tupleid, oldtuple, slot,
+                                                          canSetTag, &updateCxt);
 
                /*
-                * Check the constraints of the tuple.  We've already checked the
-                * partition constraint above; however, we must still ensure the tuple
-                * passes all other constraints, so we will call ExecConstraints() and
-                * have it validate all remaining checks.
+                * If ExecUpdateAct reports that a cross-partition update was done,
+                * then the returning tuple has been projected and there's nothing
+                * else for us to do.
                 */
-               if (resultRelationDesc->rd_att->constr)
-                       ExecConstraints(resultRelInfo, slot, estate);
-
-               /*
-                * replace the heap tuple
-                *
-                * Note: if es_crosscheck_snapshot isn't InvalidSnapshot, we check
-                * that the row to be updated is visible to that snapshot, and throw a
-                * can't-serialize error if not. This is a special-case behavior
-                * needed for referential integrity updates in transaction-snapshot
-                * mode transactions.
-                */
-               result = table_tuple_update(resultRelationDesc, tupleid, slot,
-                                                                       estate->es_output_cid,
-                                                                       estate->es_snapshot,
-                                                                       estate->es_crosscheck_snapshot,
-                                                                       true /* wait for commit */ ,
-                                                                       &tmfd, &lockmode, &update_indexes);
+               if (updateCxt.crossPartUpdate)
+                       return context->cpUpdateReturningSlot;
 
                switch (result)
                {
@@ -1818,7 +2017,7 @@ lreplace:;
                                 * can re-execute the UPDATE (assuming it can figure out how)
                                 * and then return NULL to cancel the outer update.
                                 */
-                               if (tmfd.cmax != estate->es_output_cid)
+                               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"),
@@ -1845,22 +2044,22 @@ lreplace:;
                                         * Already know that we're going to need to do EPQ, so
                                         * fetch tuple directly into the right slot.
                                         */
-                                       inputslot = EvalPlanQualSlot(epqstate, resultRelationDesc,
+                                       inputslot = EvalPlanQualSlot(context->epqstate, resultRelationDesc,
                                                                                                 resultRelInfo->ri_RangeTableIndex);
 
                                        result = table_tuple_lock(resultRelationDesc, tupleid,
                                                                                          estate->es_snapshot,
                                                                                          inputslot, estate->es_output_cid,
-                                                                                         lockmode, LockWaitBlock,
+                                                                                         context->lockmode, LockWaitBlock,
                                                                                          TUPLE_LOCK_FLAG_FIND_LAST_VERSION,
-                                                                                         &tmfd);
+                                                                                         &context->tmfd);
 
                                        switch (result)
                                        {
                                                case TM_Ok:
-                                                       Assert(tmfd.traversed);
+                                                       Assert(context->tmfd.traversed);
 
-                                                       epqslot = EvalPlanQual(epqstate,
+                                                       epqslot = EvalPlanQual(context->epqstate,
                                                                                                   resultRelationDesc,
                                                                                                   resultRelInfo->ri_RangeTableIndex,
                                                                                                   inputslot);
@@ -1870,7 +2069,8 @@ lreplace:;
 
                                                        /* Make sure ri_oldTupleSlot is initialized. */
                                                        if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
-                                                               ExecInitUpdateProjection(mtstate, resultRelInfo);
+                                                               ExecInitUpdateProjection(context->mtstate,
+                                                                                                                resultRelInfo);
 
                                                        /* Fetch the most recent version of old tuple. */
                                                        oldSlot = resultRelInfo->ri_oldTupleSlot;
@@ -1881,7 +2081,7 @@ lreplace:;
                                                                elog(ERROR, "failed to fetch tuple being updated");
                                                        slot = ExecGetUpdateNewTuple(resultRelInfo,
                                                                                                                 epqslot, oldSlot);
-                                                       goto lreplace;
+                                                       goto redo_act;
 
                                                case TM_Deleted:
                                                        /* tuple already deleted; nothing to do */
@@ -1900,7 +2100,7 @@ lreplace:;
                                                         * See also TM_SelfModified response to
                                                         * table_tuple_update() above.
                                                         */
-                                                       if (tmfd.cmax != estate->es_output_cid)
+                                                       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"),
@@ -1930,41 +2130,19 @@ lreplace:;
                                         result);
                                return NULL;
                }
-
-               /* insert index entries for tuple if necessary */
-               if (resultRelInfo->ri_NumIndices > 0 && update_indexes)
-                       recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
-                                                                                                  slot, estate, true, false,
-                                                                                                  NULL, NIL);
        }
 
        if (canSetTag)
                (estate->es_processed)++;
 
-       /* AFTER ROW UPDATE Triggers */
-       ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, slot,
-                                                recheckIndexes,
-                                                mtstate->operation == CMD_INSERT ?
-                                                mtstate->mt_oc_transition_capture :
-                                                mtstate->mt_transition_capture);
+       ExecUpdateEpilogue(context, &updateCxt, resultRelInfo, tupleid, oldtuple,
+                                          slot, recheckIndexes);
 
        list_free(recheckIndexes);
 
-       /*
-        * Check any WITH CHECK OPTION constraints from parent views.  We are
-        * required to do this after testing all constraints and uniqueness
-        * violations per the SQL spec, so we do it after actually updating the
-        * record in the heap and all indexes.
-        *
-        * ExecWithCheckOptions() will skip any WCOs which are not of the kind we
-        * are looking for at this point.
-        */
-       if (resultRelInfo->ri_WithCheckOptions != NIL)
-               ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
-
        /* Process RETURNING if present */
        if (resultRelInfo->ri_projectReturning)
-               return ExecProcessReturning(resultRelInfo, slot, planSlot);
+               return ExecProcessReturning(resultRelInfo, slot, context->planSlot);
 
        return NULL;
 }
@@ -1981,15 +2159,14 @@ lreplace:;
  * the caller must retry the INSERT from scratch.
  */
 static bool
-ExecOnConflictUpdate(ModifyTableState *mtstate,
+ExecOnConflictUpdate(ModifyTableContext *context,
                                         ResultRelInfo *resultRelInfo,
                                         ItemPointer conflictTid,
-                                        TupleTableSlot *planSlot,
                                         TupleTableSlot *excludedSlot,
-                                        EState *estate,
                                         bool canSetTag,
                                         TupleTableSlot **returning)
 {
+       ModifyTableState *mtstate = context->mtstate;
        ExprContext *econtext = mtstate->ps.ps_ExprContext;
        Relation        relation = resultRelInfo->ri_RelationDesc;
        ExprState  *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause;
@@ -2002,7 +2179,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
        bool            isnull;
 
        /* Determine lock mode to use */
-       lockmode = ExecUpdateLockMode(estate, resultRelInfo);
+       lockmode = ExecUpdateLockMode(context->estate, resultRelInfo);
 
        /*
         * Lock tuple for update.  Don't follow updates when tuple cannot be
@@ -2011,8 +2188,8 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
         * true anymore.
         */
        test = table_tuple_lock(relation, conflictTid,
-                                                       estate->es_snapshot,
-                                                       existing, estate->es_output_cid,
+                                                       context->estate->es_snapshot,
+                                                       existing, context->estate->es_output_cid,
                                                        lockmode, LockWaitBlock, 0,
                                                        &tmfd);
        switch (test)
@@ -2119,7 +2296,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
         * snapshot.  This is in line with the way UPDATE deals with newer tuple
         * versions.
         */
-       ExecCheckTupleVisible(estate, relation, existing);
+       ExecCheckTupleVisible(context->estate, relation, existing);
 
        /*
         * Make tuple and any needed join variables available to ExecQual and
@@ -2174,10 +2351,9 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
         */
 
        /* Execute UPDATE with projection */
-       *returning = ExecUpdate(mtstate, resultRelInfo, conflictTid, NULL,
+       *returning = ExecUpdate(context, resultRelInfo,
+                                                       conflictTid, NULL,
                                                        resultRelInfo->ri_onConflict->oc_ProjSlot,
-                                                       planSlot,
-                                                       &mtstate->mt_epqstate, mtstate->ps.state,
                                                        canSetTag);
 
        /*
@@ -2349,6 +2525,7 @@ static TupleTableSlot *
 ExecModifyTable(PlanState *pstate)
 {
        ModifyTableState *node = castNode(ModifyTableState, pstate);
+       ModifyTableContext context;
        EState     *estate = node->ps.state;
        CmdType         operation = node->operation;
        ResultRelInfo *resultRelInfo;
@@ -2356,10 +2533,10 @@ ExecModifyTable(PlanState *pstate)
        TupleTableSlot *slot;
        TupleTableSlot *planSlot;
        TupleTableSlot *oldSlot;
-       ItemPointer tupleid;
        ItemPointerData tuple_ctid;
        HeapTupleData oldtupdata;
        HeapTuple       oldtuple;
+       ItemPointer tupleid;
        PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
        List       *relinfos = NIL;
        ListCell   *lc;
@@ -2400,6 +2577,11 @@ ExecModifyTable(PlanState *pstate)
        resultRelInfo = node->resultRelInfo + node->mt_lastResultIndex;
        subplanstate = outerPlanState(node);
 
+       /* Set global context */
+       context.mtstate = node;
+       context.epqstate = &node->mt_epqstate;
+       context.estate = estate;
+
        /*
         * Fetch rows from subplan, and execute the required table modification
         * for each row.
@@ -2551,6 +2733,10 @@ ExecModifyTable(PlanState *pstate)
                        }
                }
 
+               /* complete context setup */
+               context.planSlot = planSlot;
+               context.lockmode = 0;
+
                switch (operation)
                {
                        case CMD_INSERT:
@@ -2558,9 +2744,10 @@ ExecModifyTable(PlanState *pstate)
                                if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
                                        ExecInitInsertProjection(node, resultRelInfo);
                                slot = ExecGetInsertNewTuple(resultRelInfo, planSlot);
-                               slot = ExecInsert(node, resultRelInfo, slot, planSlot,
-                                                                 estate, node->canSetTag);
+                               slot = ExecInsert(&context, resultRelInfo, slot,
+                                                                 node->canSetTag);
                                break;
+
                        case CMD_UPDATE:
                                /* Initialize projection info if first time for this table */
                                if (unlikely(!resultRelInfo->ri_projectNewInfoValid))
@@ -2581,7 +2768,6 @@ ExecModifyTable(PlanState *pstate)
                                        /* Fetch the most recent version of old tuple. */
                                        Relation        relation = resultRelInfo->ri_RelationDesc;
 
-                                       Assert(tupleid != NULL);
                                        if (!table_tuple_fetch_row_version(relation, tupleid,
                                                                                                           SnapshotAny,
                                                                                                           oldSlot))
@@ -2591,18 +2777,15 @@ ExecModifyTable(PlanState *pstate)
                                                                                         oldSlot);
 
                                /* Now apply the update. */
-                               slot = ExecUpdate(node, resultRelInfo, tupleid, oldtuple, slot,
-                                                                 planSlot, &node->mt_epqstate, estate,
-                                                                 node->canSetTag);
+                               slot = ExecUpdate(&context, resultRelInfo, tupleid, oldtuple,
+                                                                 slot, node->canSetTag);
                                break;
+
                        case CMD_DELETE:
-                               slot = ExecDelete(node, resultRelInfo, tupleid, oldtuple,
-                                                                 planSlot, &node->mt_epqstate, estate,
-                                                                 true, /* processReturning */
-                                                                 node->canSetTag,
-                                                                 false,        /* changingPart */
-                                                                 NULL, NULL);
+                               slot = ExecDelete(&context, resultRelInfo, tupleid, oldtuple,
+                                                                 true, false, node->canSetTag, NULL, NULL);
                                break;
+
                        default:
                                elog(ERROR, "unknown operation");
                                break;
@@ -2800,8 +2983,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
                }
 
                /* Initialize the usesFdwDirectModify flag */
-               resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
-                                                                                                                         node->fdwDirectModifyPlans);
+               resultRelInfo->ri_usesFdwDirectModify =
+                       bms_is_member(i, node->fdwDirectModifyPlans);
 
                /*
                 * Verify result relation is a valid target for the current operation
index eaf3e7a8d44ee08fe01f2fb8d90f9b04c8799ce9..6ffd4474bcf9e03adf775b05dfb973a64116a9b1 100644 (file)
@@ -1460,6 +1460,7 @@ MinimalTupleTableSlot
 MinmaxMultiOpaque
 MinmaxOpaque
 ModifyTable
+ModifyTableContext
 ModifyTablePath
 ModifyTableState
 MorphOpaque
@@ -2793,6 +2794,7 @@ UnlistenStmt
 UnpackTarState
 UnresolvedTup
 UnresolvedTupData
+UpdateContext
 UpdateStmt
 UpperRelationKind
 UpperUniquePath