EState *estate,
                                               PartitionTupleRouting *proute,
                                               ResultRelInfo *targetRelInfo,
-                                              TupleTableSlot *slot);
+                                              TupleTableSlot *slot,
+                                              ResultRelInfo **partRelInfo);
 static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
 static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
 static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
  * Compute stored generated columns for a tuple
  */
 void
-ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype)
+ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
+                          EState *estate, TupleTableSlot *slot,
+                          CmdType cmdtype)
 {
-   ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
    Relation    rel = resultRelInfo->ri_RelationDesc;
    TupleDesc   tupdesc = RelationGetDescr(rel);
    int         natts = tupdesc->natts;
  *     ExecInsert
  *
  *     For INSERT, we have to insert the tuple into the target relation
- *     and insert appropriate tuples into the index relations.
+ *     (or partition thereof) and insert appropriate tuples into the index
+ *     relations.
  *
  *     Returns RETURNING result if any, otherwise NULL.
+ *
+ *     This may change the currently active tuple conversion map in
+ *     mtstate->mt_transition_capture, so the callers must take care to
+ *     save the previous value to avoid losing track of it.
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
 ExecInsert(ModifyTableState *mtstate,
+          ResultRelInfo *resultRelInfo,
           TupleTableSlot *slot,
           TupleTableSlot *planSlot,
           EState *estate,
           bool canSetTag)
 {
-   ResultRelInfo *resultRelInfo;
    Relation    resultRelationDesc;
    List       *recheckIndexes = NIL;
    TupleTableSlot *result = NULL;
    TransitionCaptureState *ar_insert_trig_tcs;
    ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
    OnConflictAction onconflict = node->onConflictAction;
-
-   ExecMaterializeSlot(slot);
+   PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
 
    /*
-    * get information on the (current) result relation
+    * If the input result relation is a partitioned table, find the leaf
+    * partition to insert the tuple into.
     */
-   resultRelInfo = estate->es_result_relation_info;
+   if (proute)
+   {
+       ResultRelInfo *partRelInfo;
+
+       slot = ExecPrepareTupleRouting(mtstate, estate, proute,
+                                      resultRelInfo, slot,
+                                      &partRelInfo);
+       resultRelInfo = partRelInfo;
+   }
+
+   ExecMaterializeSlot(slot);
+
    resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
    /*
         */
        if (resultRelationDesc->rd_att->constr &&
            resultRelationDesc->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
+           ExecComputeStoredGenerated(resultRelInfo, estate, slot,
+                                      CMD_INSERT);
 
        /*
         * insert into foreign table: let the FDW do it
         */
        if (resultRelationDesc->rd_att->constr &&
            resultRelationDesc->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
+           ExecComputeStoredGenerated(resultRelInfo, estate, slot,
+                                      CMD_INSERT);
 
        /*
         * Check any RLS WITH CHECK policies.
             */
    vlock:
            specConflict = false;
-           if (!ExecCheckIndexConstraints(slot, estate, &conflictTid,
-                                          arbiterIndexes))
+           if (!ExecCheckIndexConstraints(resultRelInfo, slot, estate,
+                                          &conflictTid, arbiterIndexes))
            {
                /* committed conflict tuple found */
                if (onconflict == ONCONFLICT_UPDATE)
                                           specToken);
 
            /* insert index entries for tuple */
-           recheckIndexes = ExecInsertIndexTuples(slot, estate, true,
+           recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+                                                  slot, estate, true,
                                                   &specConflict,
                                                   arbiterIndexes);
 
 
            /* insert index entries for tuple */
            if (resultRelInfo->ri_NumIndices > 0)
-               recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL,
-                                                      NIL);
+               recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+                                                      slot, estate, false,
+                                                      NULL, NIL);
        }
    }
 
  */
 static TupleTableSlot *
 ExecDelete(ModifyTableState *mtstate,
+          ResultRelInfo *resultRelInfo,
           ItemPointer tupleid,
           HeapTuple oldtuple,
           TupleTableSlot *planSlot,
           bool *tupleDeleted,
           TupleTableSlot **epqreturnslot)
 {
-   ResultRelInfo *resultRelInfo;
-   Relation    resultRelationDesc;
+   Relation    resultRelationDesc = resultRelInfo->ri_RelationDesc;
    TM_Result   result;
    TM_FailureData tmfd;
    TupleTableSlot *slot = NULL;
    if (tupleDeleted)
        *tupleDeleted = false;
 
-   /*
-    * get information on the (current) result relation
-    */
-   resultRelInfo = estate->es_result_relation_info;
-   resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
    /* BEFORE ROW DELETE Triggers */
    if (resultRelInfo->ri_TrigDesc &&
        resultRelInfo->ri_TrigDesc->trig_delete_before_row)
  */
 static TupleTableSlot *
 ExecUpdate(ModifyTableState *mtstate,
+          ResultRelInfo *resultRelInfo,
           ItemPointer tupleid,
           HeapTuple oldtuple,
           TupleTableSlot *slot,
           EState *estate,
           bool canSetTag)
 {
-   ResultRelInfo *resultRelInfo;
-   Relation    resultRelationDesc;
+   Relation    resultRelationDesc = resultRelInfo->ri_RelationDesc;
    TM_Result   result;
    TM_FailureData tmfd;
    List       *recheckIndexes = NIL;
-   TupleConversionMap *saved_tcs_map = NULL;
 
    /*
     * abort the operation if not running transactions
 
    ExecMaterializeSlot(slot);
 
-   /*
-    * get information on the (current) result relation
-    */
-   resultRelInfo = estate->es_result_relation_info;
-   resultRelationDesc = resultRelInfo->ri_RelationDesc;
-
    /* BEFORE ROW UPDATE Triggers */
    if (resultRelInfo->ri_TrigDesc &&
        resultRelInfo->ri_TrigDesc->trig_update_before_row)
         */
        if (resultRelationDesc->rd_att->constr &&
            resultRelationDesc->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
+           ExecComputeStoredGenerated(resultRelInfo, estate, slot,
+                                      CMD_UPDATE);
 
        /*
         * update in foreign table: let the FDW do it
         */
        if (resultRelationDesc->rd_att->constr &&
            resultRelationDesc->rd_att->constr->has_generated_stored)
-           ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
+           ExecComputeStoredGenerated(resultRelInfo, estate, slot,
+                                      CMD_UPDATE);
 
        /*
         * Check any RLS UPDATE WITH CHECK policies
            PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
            int         map_index;
            TupleConversionMap *tupconv_map;
+           TupleConversionMap *saved_tcs_map = NULL;
 
            /*
             * Disallow an INSERT ON CONFLICT DO UPDATE that causes the
             * Row movement, part 1.  Delete the tuple, but skip RETURNING
             * processing. We want to return rows from INSERT.
             */
-           ExecDelete(mtstate, tupleid, oldtuple, planSlot, epqstate,
-                      estate, false, false /* canSetTag */ ,
-                      true /* changingPart */ , &tuple_deleted, &epqslot);
+           ExecDelete(mtstate, resultRelInfo, tupleid, oldtuple, planSlot,
+                      epqstate, estate,
+                      false,   /* processReturning */
+                      false,   /* canSetTag */
+                      true,    /* changingPart */
+                      &tuple_deleted, &epqslot);
 
            /*
             * For some reason if DELETE didn't happen (e.g. trigger prevented
                }
            }
 
-           /*
-            * Updates set the transition capture map only when a new subplan
-            * is chosen.  But for inserts, it is set for each row. So after
-            * INSERT, we need to revert back to the map created for UPDATE;
-            * otherwise the next UPDATE will incorrectly use the one created
-            * for INSERT.  So first save the one created for UPDATE.
-            */
-           if (mtstate->mt_transition_capture)
-               saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
-
            /*
             * resultRelInfo is one of the per-subplan resultRelInfos.  So we
             * should convert the tuple into root's tuple descriptor, since
                                             mtstate->mt_root_tuple_slot);
 
            /*
-            * Prepare for tuple routing, making it look like we're inserting
-            * into the root.
+            * ExecInsert() may scribble on mtstate->mt_transition_capture, so
+            * save the currently active map.
             */
-           Assert(mtstate->rootResultRelInfo != NULL);
-           slot = ExecPrepareTupleRouting(mtstate, estate, proute,
-                                          mtstate->rootResultRelInfo, slot);
+           if (mtstate->mt_transition_capture)
+               saved_tcs_map = mtstate->mt_transition_capture->tcs_map;
 
-           ret_slot = ExecInsert(mtstate, slot, planSlot,
-                                 estate, canSetTag);
+           /* Tuple routing starts from the root table. */
+           Assert(mtstate->rootResultRelInfo != NULL);
+           ret_slot = ExecInsert(mtstate, mtstate->rootResultRelInfo, slot,
+                                 planSlot, estate, canSetTag);
 
-           /* Revert ExecPrepareTupleRouting's node change. */
-           estate->es_result_relation_info = resultRelInfo;
+           /* Clear the INSERT's tuple and restore the saved map. */
            if (mtstate->mt_transition_capture)
            {
                mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
 
        /* insert index entries for tuple if necessary */
        if (resultRelInfo->ri_NumIndices > 0 && update_indexes)
-           recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL);
+           recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+                                                  slot, estate, false,
+                                                  NULL, NIL);
    }
 
    if (canSetTag)
     */
 
    /* Execute UPDATE with projection */
-   *returning = ExecUpdate(mtstate, conflictTid, NULL,
+   *returning = ExecUpdate(mtstate, resultRelInfo, conflictTid, NULL,
                            resultRelInfo->ri_onConflict->oc_ProjSlot,
                            planSlot,
                            &mtstate->mt_epqstate, mtstate->ps.state,
  * ExecPrepareTupleRouting --- prepare for routing one tuple
  *
  * Determine the partition in which the tuple in slot is to be inserted,
- * and modify mtstate and estate to prepare for it.
- *
- * Caller must revert the estate changes after executing the insertion!
- * In mtstate, transition capture changes may also need to be reverted.
+ * and return its ResultRelInfo in *partRelInfo.  The return value is
+ * a slot holding the tuple of the partition rowtype.
  *
- * Returns a slot holding the tuple of the partition rowtype.
+ * This also sets the transition table information in mtstate based on the
+ * selected partition.
  */
 static TupleTableSlot *
 ExecPrepareTupleRouting(ModifyTableState *mtstate,
                        EState *estate,
                        PartitionTupleRouting *proute,
                        ResultRelInfo *targetRelInfo,
-                       TupleTableSlot *slot)
+                       TupleTableSlot *slot,
+                       ResultRelInfo **partRelInfo)
 {
    ResultRelInfo *partrel;
    PartitionRoutingInfo *partrouteinfo;
    partrouteinfo = partrel->ri_PartitionInfo;
    Assert(partrouteinfo != NULL);
 
-   /*
-    * Make it look like we are inserting into the partition.
-    */
-   estate->es_result_relation_info = partrel;
-
    /*
     * If we're capturing transition tuples, we might need to convert from the
     * partition rowtype to root partitioned table's rowtype.
        slot = execute_attr_map_slot(map->attrMap, slot, new_slot);
    }
 
+   *partRelInfo = partrel;
    return slot;
 }
 
 ExecModifyTable(PlanState *pstate)
 {
    ModifyTableState *node = castNode(ModifyTableState, pstate);
-   PartitionTupleRouting *proute = node->mt_partition_tuple_routing;
    EState     *estate = node->ps.state;
    CmdType     operation = node->operation;
-   ResultRelInfo *saved_resultRelInfo;
    ResultRelInfo *resultRelInfo;
    PlanState  *subplanstate;
    JunkFilter *junkfilter;
    subplanstate = node->mt_plans[node->mt_whichplan];
    junkfilter = resultRelInfo->ri_junkFilter;
 
-   /*
-    * es_result_relation_info must point to the currently active result
-    * relation while we are within this ModifyTable node.  Even though
-    * ModifyTable nodes can't be nested statically, they can be nested
-    * dynamically (since our subplan could include a reference to a modifying
-    * CTE).  So we have to save and restore the caller's value.
-    */
-   saved_resultRelInfo = estate->es_result_relation_info;
-
-   estate->es_result_relation_info = resultRelInfo;
-
    /*
     * Fetch rows from subplan(s), and execute the required table modification
     * for each row.
                resultRelInfo++;
                subplanstate = node->mt_plans[node->mt_whichplan];
                junkfilter = resultRelInfo->ri_junkFilter;
-               estate->es_result_relation_info = resultRelInfo;
                EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
                                    node->mt_arowmarks[node->mt_whichplan]);
                /* Prepare to convert transition tuples from this child. */
             */
            slot = ExecProcessReturning(resultRelInfo, NULL, planSlot);
 
-           estate->es_result_relation_info = saved_resultRelInfo;
            return slot;
        }
 
        switch (operation)
        {
            case CMD_INSERT:
-               /* Prepare for tuple routing if needed. */
-               if (proute)
-                   slot = ExecPrepareTupleRouting(node, estate, proute,
-                                                  resultRelInfo, slot);
-               slot = ExecInsert(node, slot, planSlot,
+               slot = ExecInsert(node, resultRelInfo, slot, planSlot,
                                  estate, node->canSetTag);
-               /* Revert ExecPrepareTupleRouting's state change. */
-               if (proute)
-                   estate->es_result_relation_info = resultRelInfo;
                break;
            case CMD_UPDATE:
-               slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
-                                 &node->mt_epqstate, estate, node->canSetTag);
+               slot = ExecUpdate(node, resultRelInfo, tupleid, oldtuple, slot,
+                                 planSlot, &node->mt_epqstate, estate,
+                                 node->canSetTag);
                break;
            case CMD_DELETE:
-               slot = ExecDelete(node, tupleid, oldtuple, planSlot,
-                                 &node->mt_epqstate, estate,
-                                 true, node->canSetTag,
-                                 false /* changingPart */ , NULL, NULL);
+               slot = ExecDelete(node, resultRelInfo, tupleid, oldtuple,
+                                 planSlot, &node->mt_epqstate, estate,
+                                 true, /* processReturning */
+                                 node->canSetTag,
+                                 false,    /* changingPart */
+                                 NULL, NULL);
                break;
            default:
                elog(ERROR, "unknown operation");
         * the work on next call.
         */
        if (slot)
-       {
-           estate->es_result_relation_info = saved_resultRelInfo;
            return slot;
-       }
    }
 
-   /* Restore es_result_relation_info before exiting */
-   estate->es_result_relation_info = saved_resultRelInfo;
-
    /*
     * We're done, but fire AFTER STATEMENT triggers before exiting.
     */
    ModifyTableState *mtstate;
    CmdType     operation = node->operation;
    int         nplans = list_length(node->plans);
-   ResultRelInfo *saved_resultRelInfo;
    ResultRelInfo *resultRelInfo;
    Plan       *subplan;
    ListCell   *l,
     * call ExecInitNode on each of the plans to be executed and save the
     * results into the array "mt_plans".  This is also a convenient place to
     * verify that the proposed target relations are valid and open their
-    * indexes for insertion of new index entries.  Note we *must* set
-    * estate->es_result_relation_info correctly while we initialize each
-    * sub-plan; external modules such as FDWs may depend on that (see
-    * contrib/postgres_fdw/postgres_fdw.c: postgresBeginDirectModify() as one
-    * example).
+    * indexes for insertion of new index entries.
     */
-   saved_resultRelInfo = estate->es_result_relation_info;
-
    resultRelInfo = mtstate->resultRelInfo;
    i = 0;
    forboth(l, node->resultRelations, l1, node->plans)
            update_tuple_routing_needed = true;
 
        /* Now init the plan for this result rel */
-       estate->es_result_relation_info = resultRelInfo;
        mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
        mtstate->mt_scans[i] =
            ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]),
        i++;
    }
 
-   estate->es_result_relation_info = saved_resultRelInfo;
-
    /* Get the target relation */
    rel = (getTargetResultRelInfo(mtstate))->ri_RelationDesc;