#include "utils/ruleutils.h"
 
 
+/*-----------------------
+ * PartitionTupleRouting - Encapsulates all information required to
+ * route a tuple inserted into a partitioned table to one of its leaf
+ * partitions.
+ *
+ * partition_root
+ *             The partitioned table that's the target of the command.
+ *
+ * partition_dispatch_info
+ *             Array of 'max_dispatch' elements containing a pointer to a
+ *             PartitionDispatch object for every partitioned table touched by tuple
+ *             routing.  The entry for the target partitioned table is *always*
+ *             present in the 0th element of this array.  See comment for
+ *             PartitionDispatchData->indexes for details on how this array is
+ *             indexed.
+ *
+ * num_dispatch
+ *             The current number of items stored in the 'partition_dispatch_info'
+ *             array.  Also serves as the index of the next free array element for
+ *             new PartitionDispatch objects that need to be stored.
+ *
+ * max_dispatch
+ *             The current allocated size of the 'partition_dispatch_info' array.
+ *
+ * partitions
+ *             Array of 'max_partitions' elements containing a pointer to a
+ *             ResultRelInfo for every leaf partitions touched by tuple routing.
+ *             Some of these are pointers to ResultRelInfos which are borrowed out of
+ *             'subplan_resultrel_htab'.  The remainder have been built especially
+ *             for tuple routing.  See comment for PartitionDispatchData->indexes for
+ *             details on how this array is indexed.
+ *
+ * num_partitions
+ *             The current number of items stored in the 'partitions' array.  Also
+ *             serves as the index of the next free array element for new
+ *             ResultRelInfo objects that need to be stored.
+ *
+ * max_partitions
+ *             The current allocated size of the 'partitions' array.
+ *
+ * subplan_resultrel_htab
+ *             Hash table to store subplan ResultRelInfos by Oid.  This is used to
+ *             cache ResultRelInfos from subplans of an UPDATE ModifyTable node;
+ *             NULL in other cases.  Some of these may be useful for tuple routing
+ *             to save having to build duplicates.
+ *
+ * memcxt
+ *             Memory context used to allocate subsidiary structs.
+ *-----------------------
+ */
+typedef struct PartitionTupleRouting
+{
+       Relation        partition_root;
+       PartitionDispatch *partition_dispatch_info;
+       int                     num_dispatch;
+       int                     max_dispatch;
+       ResultRelInfo **partitions;
+       int                     num_partitions;
+       int                     max_partitions;
+       HTAB       *subplan_resultrel_htab;
+       MemoryContext memcxt;
+} PartitionTupleRouting;
+
 /*-----------------------
  * PartitionDispatch - information about one partitioned table in a partition
- * hierarchy required to route a tuple to one of its partitions
+ * hierarchy required to route a tuple to any of its partitions.  A
+ * PartitionDispatch is always encapsulated inside a PartitionTupleRouting
+ * struct and stored inside its 'partition_dispatch_info' array.
  *
- *     reldesc         Relation descriptor of the table
- *     key                     Partition key information of the table
- *     keystate        Execution state required for expressions in the partition key
- *     partdesc        Partition descriptor of the table
- *     tupslot         A standalone TupleTableSlot initialized with this table's tuple
- *                             descriptor
- *     tupmap          TupleConversionMap to convert from the parent's rowtype to
- *                             this table's rowtype (when extracting the partition key of a
- *                             tuple just before routing it through this table)
- *     indexes         Array with partdesc->nparts members (for details on what
- *                             individual members represent, see how they are set in
- *                             get_partition_dispatch_recurse())
+ * reldesc
+ *             Relation descriptor of the table
+ * key
+ *             Partition key information of the table
+ * keystate
+ *             Execution state required for expressions in the partition key
+ * partdesc
+ *             Partition descriptor of the table
+ * tupslot
+ *             A standalone TupleTableSlot initialized with this table's tuple
+ *             descriptor, or NULL if no tuple conversion between the parent is
+ *             required.
+ * tupmap
+ *             TupleConversionMap to convert from the parent's rowtype to this table's
+ *             rowtype  (when extracting the partition key of a tuple just before
+ *             routing it through this table). A NULL value is stored if no tuple
+ *             conversion is required.
+ * indexes
+ *             Array of partdesc->nparts elements.  For leaf partitions the index
+ *             corresponds to the partition's ResultRelInfo in the encapsulating
+ *             PartitionTupleRouting's partitions array.  For partitioned partitions,
+ *             the index corresponds to the PartitionDispatch for it in its
+ *             partition_dispatch_info array.  -1 indicates we've not yet allocated
+ *             anything in PartitionTupleRouting for the partition.
  *-----------------------
  */
 typedef struct PartitionDispatchData
        PartitionDesc partdesc;
        TupleTableSlot *tupslot;
        AttrNumber *tupmap;
-       int                *indexes;
+       int                     indexes[FLEXIBLE_ARRAY_MEMBER];
 } PartitionDispatchData;
 
-
-static PartitionDispatch *RelationGetPartitionDispatchInfo(Relation rel,
-                                                                int *num_parted, List **leaf_part_oids);
-static void get_partition_dispatch_recurse(Relation rel, Relation parent,
-                                                          List **pds, List **leaf_part_oids);
+/* struct to hold result relations coming from UPDATE subplans */
+typedef struct SubplanResultRelHashElem
+{
+       Oid             relid;          /* hash key -- must be first */
+       ResultRelInfo *rri;
+} SubplanResultRelHashElem;
+
+
+static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
+                                                          PartitionTupleRouting *proute);
+static ResultRelInfo *ExecInitPartitionInfo(ModifyTableState *mtstate,
+                                         EState *estate, PartitionTupleRouting *proute,
+                                         PartitionDispatch dispatch,
+                                         ResultRelInfo *rootResultRelInfo,
+                                         int partidx);
+static void ExecInitRoutingInfo(ModifyTableState *mtstate,
+                                       EState *estate,
+                                       PartitionTupleRouting *proute,
+                                       PartitionDispatch dispatch,
+                                       ResultRelInfo *partRelInfo,
+                                       int partidx);
+static PartitionDispatch ExecInitPartitionDispatchInfo(PartitionTupleRouting *proute,
+                                                         Oid partoid, PartitionDispatch parent_pd, int partidx);
 static void FormPartitionKeyDatum(PartitionDispatch pd,
                                          TupleTableSlot *slot,
                                          EState *estate,
  * Note that all the relations in the partition tree are locked using the
  * RowExclusiveLock mode upon return from this function.
  *
- * While we allocate the arrays of pointers of ResultRelInfo and
- * TupleConversionMap for all partitions here, actual objects themselves are
- * lazily allocated for a given partition if a tuple is actually routed to it;
- * see ExecInitPartitionInfo.  However, if the function is invoked for update
- * tuple routing, caller would already have initialized ResultRelInfo's for
- * some of the partitions, which are reused and assigned to their respective
- * slot in the aforementioned array.  For such partitions, we delay setting
- * up objects such as TupleConversionMap until those are actually chosen as
- * the partitions to route tuples to.  See ExecPrepareTupleRouting.
+ * Callers must use the returned PartitionTupleRouting during calls to
+ * ExecFindPartition().  The actual ResultRelInfo for a partition is only
+ * allocated when the partition is found for the first time.
+ *
+ * The current memory context is used to allocate this struct and all
+ * subsidiary structs that will be allocated from it later on.  Typically
+ * it should be estate->es_query_cxt.
  */
 PartitionTupleRouting *
 ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 {
-       List       *leaf_parts;
-       ListCell   *cell;
-       int                     i;
-       ResultRelInfo *update_rri = NULL;
-       int                     num_update_rri = 0,
-                               update_rri_index = 0;
        PartitionTupleRouting *proute;
-       int                     nparts;
        ModifyTable *node = mtstate ? (ModifyTable *) mtstate->ps.plan : NULL;
 
+       /* Lock all the partitions. */
+       (void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL);
+
        /*
-        * Get the information about the partition tree after locking all the
-        * partitions.
+        * Here we attempt to expend as little effort as possible in setting up
+        * the PartitionTupleRouting.  Each partition's ResultRelInfo is built on
+        * demand, only when we actually need to route a tuple to that partition.
+        * The reason for this is that a common case is for INSERT to insert a
+        * single tuple into a partitioned table and this must be fast.
         */
-       (void) find_all_inheritors(RelationGetRelid(rel), RowExclusiveLock, NULL);
        proute = (PartitionTupleRouting *) palloc0(sizeof(PartitionTupleRouting));
-       proute->partition_dispatch_info =
-               RelationGetPartitionDispatchInfo(rel, &proute->num_dispatch,
-                                                                                &leaf_parts);
-       proute->num_partitions = nparts = list_length(leaf_parts);
-       proute->partitions =
-               (ResultRelInfo **) palloc(nparts * sizeof(ResultRelInfo *));
-       proute->parent_child_tupconv_maps =
-               (TupleConversionMap **) palloc0(nparts * sizeof(TupleConversionMap *));
-       proute->partition_oids = (Oid *) palloc(nparts * sizeof(Oid));
-
-       /* Set up details specific to the type of tuple routing we are doing. */
-       if (node && node->operation == CMD_UPDATE)
-       {
-               update_rri = mtstate->resultRelInfo;
-               num_update_rri = list_length(node->plans);
-               proute->subplan_partition_offsets =
-                       palloc(num_update_rri * sizeof(int));
-               proute->num_subplan_partition_offsets = num_update_rri;
-
-               /*
-                * We need an additional tuple slot for storing transient tuples that
-                * are converted to the root table descriptor.
-                */
-               proute->root_tuple_slot = MakeTupleTableSlot(RelationGetDescr(rel),
-                                                                                                        &TTSOpsHeapTuple);
-       }
-
-       i = 0;
-       foreach(cell, leaf_parts)
-       {
-               ResultRelInfo *leaf_part_rri = NULL;
-               Oid                     leaf_oid = lfirst_oid(cell);
-
-               proute->partition_oids[i] = leaf_oid;
-
-               /*
-                * If the leaf partition is already present in the per-subplan result
-                * rels, we re-use that rather than initialize a new result rel. The
-                * per-subplan resultrels and the resultrels of the leaf partitions
-                * are both in the same canonical order. So while going through the
-                * leaf partition oids, we need to keep track of the next per-subplan
-                * result rel to be looked for in the leaf partition resultrels.
-                */
-               if (update_rri_index < num_update_rri &&
-                       RelationGetRelid(update_rri[update_rri_index].ri_RelationDesc) == leaf_oid)
-               {
-                       leaf_part_rri = &update_rri[update_rri_index];
-
-                       /*
-                        * This is required in order to convert the partition's tuple to
-                        * be compatible with the root partitioned table's tuple
-                        * descriptor.  When generating the per-subplan result rels, this
-                        * was not set.
-                        */
-                       leaf_part_rri->ri_PartitionRoot = rel;
-
-                       /* Remember the subplan offset for this ResultRelInfo */
-                       proute->subplan_partition_offsets[update_rri_index] = i;
+       proute->partition_root = rel;
+       proute->memcxt = CurrentMemoryContext;
+       /* Rest of members initialized by zeroing */
 
-                       update_rri_index++;
-               }
-
-               proute->partitions[i] = leaf_part_rri;
-               i++;
-       }
+       /*
+        * Initialize this table's PartitionDispatch object.  Here we pass in the
+        * parent as NULL as we don't need to care about any parent of the target
+        * partitioned table.
+        */
+       ExecInitPartitionDispatchInfo(proute, RelationGetRelid(rel), NULL, 0);
 
        /*
-        * For UPDATE, we should have found all the per-subplan resultrels in the
-        * leaf partitions.  (If this is an INSERT, both values will be zero.)
+        * If performing an UPDATE with tuple routing, we can reuse partition
+        * sub-plan result rels.  We build a hash table to map the OIDs of
+        * partitions present in mtstate->resultRelInfo to their ResultRelInfos.
+        * Every time a tuple is routed to a partition that we've yet to set the
+        * ResultRelInfo for, before we go to the trouble of making one, we check
+        * for a pre-made one in the hash table.
         */
-       Assert(update_rri_index == num_update_rri);
+       if (node && node->operation == CMD_UPDATE)
+               ExecHashSubPlanResultRelsByOid(mtstate, proute);
 
        return proute;
 }
 
 /*
- * ExecFindPartition -- Find a leaf partition in the partition tree rooted
- * at parent, for the heap tuple contained in *slot
+ * ExecFindPartition -- Return the ResultRelInfo for the leaf partition that
+ * the tuple contained in *slot should belong to.
+ *
+ * If the partition's ResultRelInfo does not yet exist in 'proute' then we set
+ * one up or reuse one from mtstate's resultRelInfo array.  When reusing a
+ * ResultRelInfo from the mtstate we verify that the relation is a valid
+ * target for INSERTs and then set up a PartitionRoutingInfo for it.
+ *
+ * rootResultRelInfo is the relation named in the query.
  *
  * estate must be non-NULL; we'll need it to compute any expressions in the
- * partition key(s)
+ * partition keys.  Also, its per-tuple contexts are used as evaluation
+ * scratch space.
  *
  * If no leaf partition is found, this routine errors out with the appropriate
- * error message, else it returns the leaf partition sequence number
- * as an index into the array of (ResultRelInfos of) all leaf partitions in
- * the partition tree.
+ * error message.  An error may also raised if the found target partition is
+ * not a valid target for an INSERT.
  */
-int
-ExecFindPartition(ResultRelInfo *resultRelInfo, PartitionDispatch *pd,
+ResultRelInfo *
+ExecFindPartition(ModifyTableState *mtstate,
+                                 ResultRelInfo *rootResultRelInfo,
+                                 PartitionTupleRouting *proute,
                                  TupleTableSlot *slot, EState *estate)
 {
-       int                     result;
+       PartitionDispatch *pd = proute->partition_dispatch_info;
        Datum           values[PARTITION_MAX_KEYS];
        bool            isnull[PARTITION_MAX_KEYS];
        Relation        rel;
        PartitionDispatch dispatch;
+       PartitionDesc partdesc;
        ExprContext *ecxt = GetPerTupleExprContext(estate);
        TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
        TupleTableSlot *myslot = NULL;
         * First check the root table's partition constraint, if any.  No point in
         * routing the tuple if it doesn't belong in the root table itself.
         */
-       if (resultRelInfo->ri_PartitionCheck)
-               ExecPartitionCheck(resultRelInfo, slot, estate, true);
+       if (rootResultRelInfo->ri_PartitionCheck)
+               ExecPartitionCheck(rootResultRelInfo, slot, estate, true);
 
        /* start with the root partitioned table */
        dispatch = pd[0];
        while (true)
        {
                AttrNumber *map = dispatch->tupmap;
-               int                     cur_index = -1;
+               int                     partidx = -1;
+
+               CHECK_FOR_INTERRUPTS();
 
                rel = dispatch->reldesc;
+               partdesc = dispatch->partdesc;
 
                /*
                 * Convert the tuple to this parent's layout, if different from the
                 * current relation.
                 */
                myslot = dispatch->tupslot;
-               if (myslot != NULL && map != NULL)
+               if (myslot != NULL)
+               {
+                       Assert(map != NULL);
                        slot = execute_attr_map_slot(map, slot, myslot);
+               }
 
                /*
                 * Extract partition key from tuple. Expression evaluation machinery
                FormPartitionKeyDatum(dispatch, slot, estate, values, isnull);
 
                /*
-                * Nothing for get_partition_for_tuple() to do if there are no
-                * partitions to begin with.
+                * If this partitioned table has no partitions or no partition for
+                * these values, error out.
                 */
-               if (dispatch->partdesc->nparts == 0)
+               if (partdesc->nparts == 0 ||
+                       (partidx = get_partition_for_tuple(dispatch, values, isnull)) < 0)
                {
-                       result = -1;
-                       break;
+                       char       *val_desc;
+
+                       val_desc = ExecBuildSlotPartitionKeyDescription(rel,
+                                                                                                                       values, isnull, 64);
+                       Assert(OidIsValid(RelationGetRelid(rel)));
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_CHECK_VIOLATION),
+                                        errmsg("no partition of relation \"%s\" found for row",
+                                                       RelationGetRelationName(rel)),
+                                        val_desc ?
+                                        errdetail("Partition key of the failing row contains %s.",
+                                                          val_desc) : 0));
                }
 
-               cur_index = get_partition_for_tuple(dispatch, values, isnull);
-
-               /*
-                * cur_index < 0 means we failed to find a partition of this parent.
-                * cur_index >= 0 means we either found the leaf partition, or the
-                * next parent to find a partition of.
-                */
-               if (cur_index < 0)
+               if (partdesc->is_leaf[partidx])
                {
-                       result = -1;
-                       break;
-               }
-               else if (dispatch->indexes[cur_index] >= 0)
-               {
-                       result = dispatch->indexes[cur_index];
-                       /* success! */
-                       break;
+                       ResultRelInfo *rri;
+
+                       /*
+                        * Look to see if we've already got a ResultRelInfo for this
+                        * partition.
+                        */
+                       if (likely(dispatch->indexes[partidx] >= 0))
+                       {
+                               /* ResultRelInfo already built */
+                               Assert(dispatch->indexes[partidx] < proute->num_partitions);
+                               rri = proute->partitions[dispatch->indexes[partidx]];
+                       }
+                       else
+                       {
+                               bool            found = false;
+
+                               /*
+                                * We have not yet set up a ResultRelInfo for this partition,
+                                * but if we have a subplan hash table, we might have one
+                                * there.  If not, we'll have to create one.
+                                */
+                               if (proute->subplan_resultrel_htab)
+                               {
+                                       Oid                     partoid = partdesc->oids[partidx];
+                                       SubplanResultRelHashElem   *elem;
+
+                                       elem = hash_search(proute->subplan_resultrel_htab,
+                                                                          &partoid, HASH_FIND, NULL);
+                                       if (elem)
+                                       {
+                                               found = true;
+                                               rri = elem->rri;
+
+                                               /* Verify this ResultRelInfo allows INSERTs */
+                                               CheckValidResultRel(rri, CMD_INSERT);
+
+                                               /* Set up the PartitionRoutingInfo for it */
+                                               ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
+                                                                                       rri, partidx);
+                                       }
+                               }
+
+                               /* We need to create a new one. */
+                               if (!found)
+                                       rri = ExecInitPartitionInfo(mtstate, estate, proute,
+                                                                                               dispatch,
+                                                                                               rootResultRelInfo, partidx);
+                       }
+
+                       /* Release the tuple in the lowest parent's dedicated slot. */
+                       if (slot == myslot)
+                               ExecClearTuple(myslot);
+
+                       MemoryContextSwitchTo(oldcxt);
+                       ecxt->ecxt_scantuple = ecxt_scantuple_old;
+                       return rri;
                }
                else
                {
-                       /* move down one level */
-                       dispatch = pd[-dispatch->indexes[cur_index]];
+                       /*
+                        * Partition is a sub-partitioned table; get the PartitionDispatch
+                        */
+                       if (likely(dispatch->indexes[partidx] >= 0))
+                       {
+                               /* Already built. */
+                               Assert(dispatch->indexes[partidx] < proute->num_dispatch);
+
+                               /*
+                                * Move down to the next partition level and search again
+                                * until we find a leaf partition that matches this tuple
+                                */
+                               dispatch = pd[dispatch->indexes[partidx]];
+                       }
+                       else
+                       {
+                               /* Not yet built. Do that now. */
+                               PartitionDispatch subdispatch;
+
+                               /*
+                                * Create the new PartitionDispatch.  We pass the current one
+                                * in as the parent PartitionDispatch
+                                */
+                               subdispatch = ExecInitPartitionDispatchInfo(proute,
+                                                                                                                       partdesc->oids[partidx],
+                                                                                                                       dispatch, partidx);
+                               Assert(dispatch->indexes[partidx] >= 0 &&
+                                          dispatch->indexes[partidx] < proute->num_dispatch);
+                               dispatch = subdispatch;
+                       }
                }
        }
+}
+
+/*
+ * ExecHashSubPlanResultRelsByOid
+ *             Build a hash table to allow fast lookups of subplan ResultRelInfos by
+ *             partition Oid.  We also populate the subplan ResultRelInfo with an
+ *             ri_PartitionRoot.
+ */
+static void
+ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
+                                                          PartitionTupleRouting *proute)
+{
+       HASHCTL         ctl;
+       HTAB       *htab;
+       int                     i;
+
+       memset(&ctl, 0, sizeof(ctl));
+       ctl.keysize = sizeof(Oid);
+       ctl.entrysize = sizeof(SubplanResultRelHashElem);
+       ctl.hcxt = CurrentMemoryContext;
 
-       /* Release the tuple in the lowest parent's dedicated slot. */
-       if (slot == myslot)
-               ExecClearTuple(myslot);
+       htab = hash_create("PartitionTupleRouting table", mtstate->mt_nplans,
+                                          &ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+       proute->subplan_resultrel_htab = htab;
 
-       /* A partition was not found. */
-       if (result < 0)
+       /* Hash all subplans by their Oid */
+       for (i = 0; i < mtstate->mt_nplans; i++)
        {
-               char       *val_desc;
-
-               val_desc = ExecBuildSlotPartitionKeyDescription(rel,
-                                                                                                               values, isnull, 64);
-               Assert(OidIsValid(RelationGetRelid(rel)));
-               ereport(ERROR,
-                               (errcode(ERRCODE_CHECK_VIOLATION),
-                                errmsg("no partition of relation \"%s\" found for row",
-                                               RelationGetRelationName(rel)),
-                                val_desc ? errdetail("Partition key of the failing row contains %s.", val_desc) : 0));
-       }
+               ResultRelInfo *rri = &mtstate->resultRelInfo[i];
+               bool            found;
+               Oid                     partoid = RelationGetRelid(rri->ri_RelationDesc);
+               SubplanResultRelHashElem   *elem;
 
-       MemoryContextSwitchTo(oldcxt);
-       ecxt->ecxt_scantuple = ecxt_scantuple_old;
+               elem = (SubplanResultRelHashElem *)
+                       hash_search(htab, &partoid, HASH_ENTER, &found);
+               Assert(!found);
+               elem->rri = rri;
 
-       return result;
+               /*
+                * This is required in order to convert the partition's tuple to be
+                * compatible with the root partitioned table's tuple descriptor. When
+                * generating the per-subplan result rels, this was not set.
+                */
+               rri->ri_PartitionRoot = proute->partition_root;
+       }
 }
 
 /*
  * ExecInitPartitionInfo
  *             Initialize ResultRelInfo and other information for a partition
+ *             and store it in the next empty slot in the proute->partitions array.
  *
  * Returns the ResultRelInfo
  */
-ResultRelInfo *
-ExecInitPartitionInfo(ModifyTableState *mtstate,
-                                         ResultRelInfo *resultRelInfo,
+static ResultRelInfo *
+ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
                                          PartitionTupleRouting *proute,
-                                         EState *estate, int partidx)
+                                         PartitionDispatch dispatch,
+                                         ResultRelInfo *rootResultRelInfo,
+                                         int partidx)
 {
        ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
-       Relation        rootrel = resultRelInfo->ri_RelationDesc,
+       Relation        rootrel = rootResultRelInfo->ri_RelationDesc,
                                partrel;
        Relation        firstResultRel = mtstate->resultRelInfo[0].ri_RelationDesc;
        ResultRelInfo *leaf_part_rri;
-       MemoryContext oldContext;
+       MemoryContext oldcxt;
        AttrNumber *part_attnos = NULL;
        bool            found_whole_row;
 
+       oldcxt = MemoryContextSwitchTo(proute->memcxt);
+
        /*
         * We locked all the partitions in ExecSetupPartitionTupleRouting
         * including the leaf partitions.
         */
-       partrel = heap_open(proute->partition_oids[partidx], NoLock);
-
-       /*
-        * Keep ResultRelInfo and other information for this partition in the
-        * per-query memory context so they'll survive throughout the query.
-        */
-       oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+       partrel = heap_open(dispatch->partdesc->oids[partidx], NoLock);
 
        leaf_part_rri = makeNode(ResultRelInfo);
        InitResultRelInfo(leaf_part_rri,
         */
        CheckValidResultRel(leaf_part_rri, CMD_INSERT);
 
-       /*
-        * Since we've just initialized this ResultRelInfo, it's not in any list
-        * attached to the estate as yet.  Add it, so that it can be found later.
-        *
-        * Note that the entries in this list appear in no predetermined order,
-        * because partition result rels are initialized as and when they're
-        * needed.
-        */
-       estate->es_tuple_routing_result_relations =
-               lappend(estate->es_tuple_routing_result_relations,
-                               leaf_part_rri);
-
        /*
         * Open partition indices.  The user may have asked to check for conflicts
         * within this leaf partition and do "nothing" instead of throwing an
        }
 
        /* Set up information needed for routing tuples to the partition. */
-       ExecInitRoutingInfo(mtstate, estate, proute, leaf_part_rri, partidx);
+       ExecInitRoutingInfo(mtstate, estate, proute, dispatch,
+                                               leaf_part_rri, partidx);
 
        /*
         * If there is an ON CONFLICT clause, initialize state for it.
         */
        if (node && node->onConflictAction != ONCONFLICT_NONE)
        {
-               TupleConversionMap *map = proute->parent_child_tupconv_maps[partidx];
                int                     firstVarno = mtstate->resultRelInfo[0].ri_RangeTableIndex;
                TupleDesc       partrelDesc = RelationGetDescr(partrel);
                ExprContext *econtext = mtstate->ps.ps_ExprContext;
                 * list and searching for ancestry relationships to each index in the
                 * ancestor table.
                 */
-               if (list_length(resultRelInfo->ri_onConflictArbiterIndexes) > 0)
+               if (list_length(rootResultRelInfo->ri_onConflictArbiterIndexes) > 0)
                {
                        List       *childIdxs;
 
                                ListCell   *lc2;
 
                                ancestors = get_partition_ancestors(childIdx);
-                               foreach(lc2, resultRelInfo->ri_onConflictArbiterIndexes)
+                               foreach(lc2, rootResultRelInfo->ri_onConflictArbiterIndexes)
                                {
                                        if (list_member_oid(ancestors, lfirst_oid(lc2)))
                                                arbiterIndexes = lappend_oid(arbiterIndexes, childIdx);
                 * (This shouldn't happen, since arbiter index selection should not
                 * pick up an invalid index.)
                 */
-               if (list_length(resultRelInfo->ri_onConflictArbiterIndexes) !=
+               if (list_length(rootResultRelInfo->ri_onConflictArbiterIndexes) !=
                        list_length(arbiterIndexes))
                        elog(ERROR, "invalid arbiter index list");
                leaf_part_rri->ri_onConflictArbiterIndexes = arbiterIndexes;
                 */
                if (node->onConflictAction == ONCONFLICT_UPDATE)
                {
+                       TupleConversionMap *map;
+
+                       map = leaf_part_rri->ri_PartitionInfo->pi_RootToPartitionMap;
+
                        Assert(node->onConflictSet != NIL);
-                       Assert(resultRelInfo->ri_onConflict != NULL);
+                       Assert(rootResultRelInfo->ri_onConflict != NULL);
 
                        /*
                         * If the partition's tuple descriptor matches exactly the root
                         * need to create state specific to this partition.
                         */
                        if (map == NULL)
-                               leaf_part_rri->ri_onConflict = resultRelInfo->ri_onConflict;
+                               leaf_part_rri->ri_onConflict = rootResultRelInfo->ri_onConflict;
                        else
                        {
                                List       *onconflset;
                }
        }
 
-       Assert(proute->partitions[partidx] == NULL);
-       proute->partitions[partidx] = leaf_part_rri;
+       /*
+        * Since we've just initialized this ResultRelInfo, it's not in any list
+        * attached to the estate as yet.  Add it, so that it can be found later.
+        *
+        * Note that the entries in this list appear in no predetermined order,
+        * because partition result rels are initialized as and when they're
+        * needed.
+        */
+       MemoryContextSwitchTo(estate->es_query_cxt);
+       estate->es_tuple_routing_result_relations =
+               lappend(estate->es_tuple_routing_result_relations,
+                               leaf_part_rri);
 
-       MemoryContextSwitchTo(oldContext);
+       MemoryContextSwitchTo(oldcxt);
 
        return leaf_part_rri;
 }
 
 /*
  * ExecInitRoutingInfo
- *             Set up information needed for routing tuples to a leaf partition
+ *             Set up information needed for translating tuples between root
+ *             partitioned table format and partition format, and keep track of it
+ *             in PartitionTupleRouting.
  */
-void
+static void
 ExecInitRoutingInfo(ModifyTableState *mtstate,
                                        EState *estate,
                                        PartitionTupleRouting *proute,
+                                       PartitionDispatch dispatch,
                                        ResultRelInfo *partRelInfo,
                                        int partidx)
 {
-       MemoryContext oldContext;
+       MemoryContext oldcxt;
+       PartitionRoutingInfo *partrouteinfo;
+       int             rri_index;
 
-       /*
-        * Switch into per-query memory context.
-        */
-       oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
+       oldcxt = MemoryContextSwitchTo(proute->memcxt);
+
+       partrouteinfo = palloc(sizeof(PartitionRoutingInfo));
 
        /*
         * Set up a tuple conversion map to convert a tuple routed to the
         * partition from the parent's type to the partition's.
         */
-       proute->parent_child_tupconv_maps[partidx] =
+       partrouteinfo->pi_RootToPartitionMap =
                convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_PartitionRoot),
                                                           RelationGetDescr(partRelInfo->ri_RelationDesc),
                                                           gettext_noop("could not convert row type"));
         * for various operations that are applied to tuples after routing, such
         * as checking constraints.
         */
-       if (proute->parent_child_tupconv_maps[partidx] != NULL)
+       if (partrouteinfo->pi_RootToPartitionMap != NULL)
        {
                Relation        partrel = partRelInfo->ri_RelationDesc;
 
-               /*
-                * Initialize the array in proute where these slots are stored, if not
-                * already done.
-                */
-               if (proute->partition_tuple_slots == NULL)
-                       proute->partition_tuple_slots = (TupleTableSlot **)
-                               palloc0(proute->num_partitions *
-                                               sizeof(TupleTableSlot *));
-
                /*
                 * Initialize the slot itself setting its descriptor to this
                 * partition's TupleDesc; TupleDesc reference will be released at the
                 * end of the command.
                 */
-               proute->partition_tuple_slots[partidx] =
-                       ExecInitExtraTupleSlot(estate,
-                                                                  RelationGetDescr(partrel),
+               partrouteinfo->pi_PartitionTupleSlot =
+                       ExecInitExtraTupleSlot(estate, RelationGetDescr(partrel),
                                                                   &TTSOpsHeapTuple);
        }
+       else
+               partrouteinfo->pi_PartitionTupleSlot = NULL;
+
+       /*
+        * Also, if transition capture is required, store a map to convert tuples
+        * from partition's rowtype to the root partition table's.
+        */
+       if (mtstate &&
+               (mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture))
+       {
+               partrouteinfo->pi_PartitionToRootMap =
+                       convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
+                                                                  RelationGetDescr(partRelInfo->ri_PartitionRoot),
+                                                                  gettext_noop("could not convert row type"));
+       }
+       else
+               partrouteinfo->pi_PartitionToRootMap = NULL;
 
        /*
         * If the partition is a foreign table, let the FDW init itself for
                partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
                partRelInfo->ri_FdwRoutine->BeginForeignInsert(mtstate, partRelInfo);
 
-       MemoryContextSwitchTo(oldContext);
-
-       partRelInfo->ri_PartitionReadyForRouting = true;
-}
-
-/*
- * ExecSetupChildParentMapForLeaf -- Initialize the per-leaf-partition
- * child-to-root tuple conversion map array.
- *
- * This map is required for capturing transition tuples when the target table
- * is a partitioned table. For a tuple that is routed by an INSERT or UPDATE,
- * we need to convert it from the leaf partition to the target table
- * descriptor.
- */
-void
-ExecSetupChildParentMapForLeaf(PartitionTupleRouting *proute)
-{
-       Assert(proute != NULL);
+       partRelInfo->ri_PartitionInfo = partrouteinfo;
 
        /*
-        * These array elements get filled up with maps on an on-demand basis.
-        * Initially just set all of them to NULL.
+        * Keep track of it in the PartitionTupleRouting->partitions array.
         */
-       proute->child_parent_tupconv_maps =
-               (TupleConversionMap **) palloc0(sizeof(TupleConversionMap *) *
-                                                                               proute->num_partitions);
+       Assert(dispatch->indexes[partidx] == -1);
+
+       rri_index = proute->num_partitions++;
+
+       /* Allocate or enlarge the array, as needed */
+       if (proute->num_partitions >= proute->max_partitions)
+       {
+               if (proute->max_partitions == 0)
+               {
+                       proute->max_partitions = 8;
+                       proute->partitions = (ResultRelInfo **)
+                               palloc(sizeof(ResultRelInfo *) * proute->max_partitions);
+               }
+               else
+               {
+                       proute->max_partitions *= 2;
+                       proute->partitions = (ResultRelInfo **)
+                               repalloc(proute->partitions, sizeof(ResultRelInfo *) *
+                                                proute->max_partitions);
+               }
+       }
 
-       /* Same is the case for this array. All the values are set to false */
-       proute->child_parent_map_not_required =
-               (bool *) palloc0(sizeof(bool) * proute->num_partitions);
+       proute->partitions[rri_index] = partRelInfo;
+       dispatch->indexes[partidx] = rri_index;
+
+       MemoryContextSwitchTo(oldcxt);
 }
 
 /*
- * TupConvMapForLeaf -- Get the tuple conversion map for a given leaf partition
- * index.
+ * ExecInitPartitionDispatchInfo
+ *             Initialize PartitionDispatch for a partitioned table and store it in
+ *             the next available slot in the proute->partition_dispatch_info array.
+ *             Also, record the index into this array in the parent_pd->indexes[]
+ *             array in the partidx element so that we can properly retrieve the
+ *             newly created PartitionDispatch later.
  */
-TupleConversionMap *
-TupConvMapForLeaf(PartitionTupleRouting *proute,
-                                 ResultRelInfo *rootRelInfo, int leaf_index)
+static PartitionDispatch
+ExecInitPartitionDispatchInfo(PartitionTupleRouting *proute, Oid partoid,
+                                                         PartitionDispatch parent_pd, int partidx)
 {
-       ResultRelInfo **resultRelInfos = proute->partitions;
-       TupleConversionMap **map;
-       TupleDesc       tupdesc;
+       Relation        rel;
+       PartitionDesc partdesc;
+       PartitionDispatch pd;
+       int                     dispatchidx;
+       MemoryContext oldcxt;
 
-       /* Don't call this if we're not supposed to be using this type of map. */
-       Assert(proute->child_parent_tupconv_maps != NULL);
+       oldcxt = MemoryContextSwitchTo(proute->memcxt);
 
-       /* If it's already known that we don't need a map, return NULL. */
-       if (proute->child_parent_map_not_required[leaf_index])
-               return NULL;
+       if (partoid != RelationGetRelid(proute->partition_root))
+               rel = heap_open(partoid, NoLock);
+       else
+               rel = proute->partition_root;
+       partdesc = RelationGetPartitionDesc(rel);
 
-       /* If we've already got a map, return it. */
-       map = &proute->child_parent_tupconv_maps[leaf_index];
-       if (*map != NULL)
-               return *map;
+       pd = (PartitionDispatch) palloc(offsetof(PartitionDispatchData, indexes) +
+                                                                       partdesc->nparts * sizeof(int));
+       pd->reldesc = rel;
+       pd->key = RelationGetPartitionKey(rel);
+       pd->keystate = NIL;
+       pd->partdesc = partdesc;
+       if (parent_pd != NULL)
+       {
+               TupleDesc       tupdesc = RelationGetDescr(rel);
 
-       /* No map yet; try to create one. */
-       tupdesc = RelationGetDescr(resultRelInfos[leaf_index]->ri_RelationDesc);
-       *map =
-               convert_tuples_by_name(tupdesc,
-                                                          RelationGetDescr(rootRelInfo->ri_RelationDesc),
-                                                          gettext_noop("could not convert row type"));
+               /*
+                * For sub-partitioned tables where the column order differs from its
+                * direct parent partitioned table, we must store a tuple table slot
+                * initialized with its tuple descriptor and a tuple conversion map to
+                * convert a tuple from its parent's rowtype to its own.  This is to
+                * make sure that we are looking at the correct row using the correct
+                * tuple descriptor when computing its partition key for tuple
+                * routing.
+                */
+               pd->tupmap = convert_tuples_by_name_map_if_req(RelationGetDescr(parent_pd->reldesc),
+                                                                                                          tupdesc,
+                                                                                                          gettext_noop("could not convert row type"));
+               pd->tupslot = pd->tupmap ?
+                       MakeSingleTupleTableSlot(tupdesc, &TTSOpsHeapTuple) : NULL;
+       }
+       else
+       {
+               /* Not required for the root partitioned table */
+               pd->tupmap = NULL;
+               pd->tupslot = NULL;
+       }
 
-       /* If it turns out no map is needed, remember for next time. */
-       proute->child_parent_map_not_required[leaf_index] = (*map == NULL);
+       /*
+        * Initialize with -1 to signify that the corresponding partition's
+        * ResultRelInfo or PartitionDispatch has not been created yet.
+        */
+       memset(pd->indexes, -1, sizeof(int) * partdesc->nparts);
+
+       /* Track in PartitionTupleRouting for later use */
+       dispatchidx = proute->num_dispatch++;
+
+       /* Allocate or enlarge the array, as needed */
+       if (proute->num_dispatch >= proute->max_dispatch)
+       {
+               if (proute->max_dispatch == 0)
+               {
+                       proute->max_dispatch = 4;
+                       proute->partition_dispatch_info = (PartitionDispatch *)
+                               palloc(sizeof(PartitionDispatch) * proute->max_dispatch);
+               }
+               else
+               {
+                       proute->max_dispatch *= 2;
+                       proute->partition_dispatch_info = (PartitionDispatch *)
+                               repalloc(proute->partition_dispatch_info,
+                                                sizeof(PartitionDispatch) * proute->max_dispatch);
+               }
+       }
+       proute->partition_dispatch_info[dispatchidx] = pd;
 
-       return *map;
+       /*
+        * Finally, if setting up a PartitionDispatch for a sub-partitioned table,
+        * install a downlink in the parent to allow quick descent.
+        */
+       if (parent_pd)
+       {
+               Assert(parent_pd->indexes[partidx] == -1);
+               parent_pd->indexes[partidx] = dispatchidx;
+       }
+
+       MemoryContextSwitchTo(oldcxt);
+
+       return pd;
 }
 
 /*
 ExecCleanupTupleRouting(ModifyTableState *mtstate,
                                                PartitionTupleRouting *proute)
 {
+       HTAB       *htab = proute->subplan_resultrel_htab;
        int                     i;
-       int                     subplan_index = 0;
 
        /*
         * Remember, proute->partition_dispatch_info[0] corresponds to the root
                PartitionDispatch pd = proute->partition_dispatch_info[i];
 
                heap_close(pd->reldesc, NoLock);
-               ExecDropSingleTupleTableSlot(pd->tupslot);
+
+               if (pd->tupslot)
+                       ExecDropSingleTupleTableSlot(pd->tupslot);
        }
 
        for (i = 0; i < proute->num_partitions; i++)
        {
                ResultRelInfo *resultRelInfo = proute->partitions[i];
 
-               /* skip further processing for uninitialized partitions */
-               if (resultRelInfo == NULL)
-                       continue;
+               /*
+                * Check if this result rel is one belonging to the node's subplans,
+                * if so, let ExecEndPlan() clean it up.
+                */
+               if (htab)
+               {
+                       Oid                     partoid;
+                       bool            found;
+
+                       partoid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+
+                       (void) hash_search(htab, &partoid, HASH_FIND, &found);
+                       if (found)
+                               continue;
+               }
 
                /* Allow any FDWs to shut down if they've been exercised */
-               if (resultRelInfo->ri_PartitionReadyForRouting &&
-                       resultRelInfo->ri_FdwRoutine != NULL &&
+               if (resultRelInfo->ri_FdwRoutine != NULL &&
                        resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
                        resultRelInfo->ri_FdwRoutine->EndForeignInsert(mtstate->ps.state,
                                                                                                                   resultRelInfo);
 
-               /*
-                * If this result rel is one of the UPDATE subplan result rels, let
-                * ExecEndPlan() close it. For INSERT or COPY,
-                * proute->subplan_partition_offsets will always be NULL. Note that
-                * the subplan_partition_offsets array and the partitions array have
-                * the partitions in the same order. So, while we iterate over
-                * partitions array, we also iterate over the
-                * subplan_partition_offsets array in order to figure out which of the
-                * result rels are present in the UPDATE subplans.
-                */
-               if (proute->subplan_partition_offsets &&
-                       subplan_index < proute->num_subplan_partition_offsets &&
-                       proute->subplan_partition_offsets[subplan_index] == i)
-               {
-                       subplan_index++;
-                       continue;
-               }
-
                ExecCloseIndices(resultRelInfo);
                heap_close(resultRelInfo->ri_RelationDesc, NoLock);
        }
-
-       /* Release the standalone partition tuple descriptors, if any */
-       if (proute->root_tuple_slot)
-               ExecDropSingleTupleTableSlot(proute->root_tuple_slot);
-}
-
-/*
- * RelationGetPartitionDispatchInfo
- *             Returns information necessary to route tuples down a partition tree
- *
- * The number of elements in the returned array (that is, the number of
- * PartitionDispatch objects for the partitioned tables in the partition tree)
- * is returned in *num_parted and a list of the OIDs of all the leaf
- * partitions of rel is returned in *leaf_part_oids.
- *
- * All the relations in the partition tree (including 'rel') must have been
- * locked (using at least the AccessShareLock) by the caller.
- */
-static PartitionDispatch *
-RelationGetPartitionDispatchInfo(Relation rel,
-                                                                int *num_parted, List **leaf_part_oids)
-{
-       List       *pdlist = NIL;
-       PartitionDispatchData **pd;
-       ListCell   *lc;
-       int                     i;
-
-       Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
-
-       *num_parted = 0;
-       *leaf_part_oids = NIL;
-
-       get_partition_dispatch_recurse(rel, NULL, &pdlist, leaf_part_oids);
-       *num_parted = list_length(pdlist);
-       pd = (PartitionDispatchData **) palloc(*num_parted *
-                                                                                  sizeof(PartitionDispatchData *));
-       i = 0;
-       foreach(lc, pdlist)
-       {
-               pd[i++] = lfirst(lc);
-       }
-
-       return pd;
-}
-
-/*
- * get_partition_dispatch_recurse
- *             Recursively expand partition tree rooted at rel
- *
- * As the partition tree is expanded in a depth-first manner, we maintain two
- * global lists: of PartitionDispatch objects corresponding to partitioned
- * tables in *pds and of the leaf partition OIDs in *leaf_part_oids.
- *
- * Note that the order of OIDs of leaf partitions in leaf_part_oids matches
- * the order in which the planner's expand_partitioned_rtentry() processes
- * them.  It's not necessarily the case that the offsets match up exactly,
- * because constraint exclusion might prune away some partitions on the
- * planner side, whereas we'll always have the complete list; but unpruned
- * partitions will appear in the same order in the plan as they are returned
- * here.
- */
-static void
-get_partition_dispatch_recurse(Relation rel, Relation parent,
-                                                          List **pds, List **leaf_part_oids)
-{
-       TupleDesc       tupdesc = RelationGetDescr(rel);
-       PartitionDesc partdesc = RelationGetPartitionDesc(rel);
-       PartitionKey partkey = RelationGetPartitionKey(rel);
-       PartitionDispatch pd;
-       int                     i;
-
-       check_stack_depth();
-
-       /* Build a PartitionDispatch for this table and add it to *pds. */
-       pd = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
-       *pds = lappend(*pds, pd);
-       pd->reldesc = rel;
-       pd->key = partkey;
-       pd->keystate = NIL;
-       pd->partdesc = partdesc;
-       if (parent != NULL)
-       {
-               /*
-                * For every partitioned table other than the root, we must store a
-                * tuple table slot initialized with its tuple descriptor and a tuple
-                * conversion map to convert a tuple from its parent's rowtype to its
-                * own. That is to make sure that we are looking at the correct row
-                * using the correct tuple descriptor when computing its partition key
-                * for tuple routing.
-                */
-               pd->tupslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsHeapTuple);
-               pd->tupmap = convert_tuples_by_name_map_if_req(RelationGetDescr(parent),
-                                                                                                          tupdesc,
-                                                                                                          gettext_noop("could not convert row type"));
-       }
-       else
-       {
-               /* Not required for the root partitioned table */
-               pd->tupslot = NULL;
-               pd->tupmap = NULL;
-       }
-
-       /*
-        * Go look at each partition of this table.  If it's a leaf partition,
-        * simply add its OID to *leaf_part_oids.  If it's a partitioned table,
-        * recursively call get_partition_dispatch_recurse(), so that its
-        * partitions are processed as well and a corresponding PartitionDispatch
-        * object gets added to *pds.
-        *
-        * The 'indexes' array is used when searching for a partition matching a
-        * given tuple.  The actual value we store here depends on whether the
-        * array element belongs to a leaf partition or a subpartitioned table.
-        * For leaf partitions we store the index into *leaf_part_oids, and for
-        * sub-partitioned tables we store a negative version of the index into
-        * the *pds list.  Both indexes are 0-based, but the first element of the
-        * *pds list is the root partition, so 0 always means the first leaf. When
-        * searching, if we see a negative value, the search must continue in the
-        * corresponding sub-partition; otherwise, we've identified the correct
-        * partition.
-        */
-       pd->indexes = (int *) palloc(partdesc->nparts * sizeof(int));
-       for (i = 0; i < partdesc->nparts; i++)
-       {
-               Oid                     partrelid = partdesc->oids[i];
-
-               if (get_rel_relkind(partrelid) != RELKIND_PARTITIONED_TABLE)
-               {
-                       *leaf_part_oids = lappend_oid(*leaf_part_oids, partrelid);
-                       pd->indexes[i] = list_length(*leaf_part_oids) - 1;
-               }
-               else
-               {
-                       /*
-                        * We assume all tables in the partition tree were already locked
-                        * by the caller.
-                        */
-                       Relation        partrel = heap_open(partrelid, NoLock);
-
-                       pd->indexes[i] = -list_length(*pds);
-                       get_partition_dispatch_recurse(partrel, rel, pds, leaf_part_oids);
-               }
-       }
 }
 
 /* ----------------
 
                                                ResultRelInfo *targetRelInfo,
                                                TupleTableSlot *slot);
 static ResultRelInfo *getTargetResultRelInfo(ModifyTableState *node);
-static void ExecSetupChildParentMapForTcs(ModifyTableState *mtstate);
 static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
 static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
                                                int whichplan);
                        tupconv_map = tupconv_map_for_subplan(mtstate, map_index);
                        if (tupconv_map != NULL)
                                slot = execute_attr_map_slot(tupconv_map->attrMap,
-                                                                                        slot, proute->root_tuple_slot);
+                                                                                        slot,
+                                                                                        mtstate->mt_root_tuple_slot);
 
                        /*
                         * Prepare for tuple routing, making it look like we're inserting
        if (mtstate->mt_transition_capture != NULL ||
                mtstate->mt_oc_transition_capture != NULL)
        {
-               ExecSetupChildParentMapForTcs(mtstate);
+               ExecSetupChildParentMapForSubplan(mtstate);
 
                /*
                 * Install the conversion map for the first plan for UPDATE and DELETE
                                                TupleTableSlot *slot)
 {
        ModifyTable *node;
-       int                     partidx;
        ResultRelInfo *partrel;
+       PartitionRoutingInfo *partrouteinfo;
        HeapTuple       tuple;
        TupleConversionMap *map;
 
        /*
-        * Determine the target partition.  If ExecFindPartition does not find a
-        * partition after all, it doesn't return here; otherwise, the returned
-        * value is to be used as an index into the arrays for the ResultRelInfo
-        * and TupleConversionMap for the partition.
-        */
-       partidx = ExecFindPartition(targetRelInfo,
-                                                               proute->partition_dispatch_info,
-                                                               slot,
-                                                               estate);
-       Assert(partidx >= 0 && partidx < proute->num_partitions);
-
-       /*
-        * Get the ResultRelInfo corresponding to the selected partition; if not
-        * yet there, initialize it.
+        * Lookup the target partition's ResultRelInfo.  If ExecFindPartition does
+        * not find a valid partition for the tuple in 'slot' then an error is
+        * raised.  An error may also be raised if the found partition is not a
+        * valid target for INSERTs.  This is required since a partitioned table
+        * UPDATE to another partition becomes a DELETE+INSERT.
         */
-       partrel = proute->partitions[partidx];
-       if (partrel == NULL)
-               partrel = ExecInitPartitionInfo(mtstate, targetRelInfo,
-                                                                               proute, estate,
-                                                                               partidx);
-
-       /*
-        * Check whether the partition is routable if we didn't yet
-        *
-        * Note: an UPDATE of a partition key invokes an INSERT that moves the
-        * tuple to a new partition.  This check would be applied to a subplan
-        * partition of such an UPDATE that is chosen as the partition to route
-        * the tuple to.  The reason we do this check here rather than in
-        * ExecSetupPartitionTupleRouting is to avoid aborting such an UPDATE
-        * unnecessarily due to non-routable subplan partitions that may not be
-        * chosen for update tuple movement after all.
-        */
-       if (!partrel->ri_PartitionReadyForRouting)
-       {
-               /* Verify the partition is a valid target for INSERT. */
-               CheckValidResultRel(partrel, CMD_INSERT);
-
-               /* Set up information needed for routing tuples to the partition. */
-               ExecInitRoutingInfo(mtstate, estate, proute, partrel, partidx);
-       }
+       partrel = ExecFindPartition(mtstate, targetRelInfo, proute, slot, estate);
+       partrouteinfo = partrel->ri_PartitionInfo;
+       Assert(partrouteinfo != NULL);
 
        /*
         * Make it look like we are inserting into the partition.
 
        /*
         * If we're capturing transition tuples, we might need to convert from the
-        * partition rowtype to parent rowtype.
+        * partition rowtype to root partitioned table's rowtype.
         */
        if (mtstate->mt_transition_capture != NULL)
        {
                         */
                        mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
                        mtstate->mt_transition_capture->tcs_map =
-                               TupConvMapForLeaf(proute, targetRelInfo, partidx);
+                               partrouteinfo->pi_PartitionToRootMap;
                }
                else
                {
        if (mtstate->mt_oc_transition_capture != NULL)
        {
                mtstate->mt_oc_transition_capture->tcs_map =
-                       TupConvMapForLeaf(proute, targetRelInfo, partidx);
+                       partrouteinfo->pi_PartitionToRootMap;
        }
 
        /*
         * Convert the tuple, if necessary.
         */
-       map = proute->parent_child_tupconv_maps[partidx];
+       map = partrouteinfo->pi_RootToPartitionMap;
        if (map != NULL)
        {
-               TupleTableSlot *new_slot;
+               TupleTableSlot *new_slot = partrouteinfo->pi_PartitionTupleSlot;
 
-               Assert(proute->partition_tuple_slots != NULL &&
-                          proute->partition_tuple_slots[partidx] != NULL);
-               new_slot = proute->partition_tuple_slots[partidx];
                slot = execute_attr_map_slot(map->attrMap, slot, new_slot);
        }
 
        int                     numResultRelInfos = mtstate->mt_nplans;
        int                     i;
 
-       /*
-        * First check if there is already a per-subplan array allocated. Even if
-        * there is already a per-leaf map array, we won't require a per-subplan
-        * one, since we will use the subplan offset array to convert the subplan
-        * index to per-leaf index.
-        */
-       if (mtstate->mt_per_subplan_tupconv_maps ||
-               (mtstate->mt_partition_tuple_routing &&
-                mtstate->mt_partition_tuple_routing->child_parent_tupconv_maps))
-               return;
-
        /*
         * Build array of conversion maps from each child's TupleDesc to the one
         * used in the target relation.  The map pointers may be NULL when no
        }
 }
 
-/*
- * Initialize the child-to-root tuple conversion map array required for
- * capturing transition tuples.
- *
- * The map array can be indexed either by subplan index or by leaf-partition
- * index.  For transition tables, we need a subplan-indexed access to the map,
- * and where tuple-routing is present, we also require a leaf-indexed access.
- */
-static void
-ExecSetupChildParentMapForTcs(ModifyTableState *mtstate)
-{
-       PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
-
-       /*
-        * If partition tuple routing is set up, we will require partition-indexed
-        * access. In that case, create the map array indexed by partition; we
-        * will still be able to access the maps using a subplan index by
-        * converting the subplan index to a partition index using
-        * subplan_partition_offsets. If tuple routing is not set up, it means we
-        * don't require partition-indexed access. In that case, create just a
-        * subplan-indexed map.
-        */
-       if (proute)
-       {
-               /*
-                * If a partition-indexed map array is to be created, the subplan map
-                * array has to be NULL.  If the subplan map array is already created,
-                * we won't be able to access the map using a partition index.
-                */
-               Assert(mtstate->mt_per_subplan_tupconv_maps == NULL);
-
-               ExecSetupChildParentMapForLeaf(proute);
-       }
-       else
-               ExecSetupChildParentMapForSubplan(mtstate);
-}
-
 /*
  * For a given subplan index, get the tuple conversion map.
  */
 static TupleConversionMap *
 tupconv_map_for_subplan(ModifyTableState *mtstate, int whichplan)
 {
-       /*
-        * If a partition-index tuple conversion map array is allocated, we need
-        * to first get the index into the partition array. Exactly *one* of the
-        * two arrays is allocated. This is because if there is a partition array
-        * required, we don't require subplan-indexed array since we can translate
-        * subplan index into partition index. And, we create a subplan-indexed
-        * array *only* if partition-indexed array is not required.
-        */
+       /* If nobody else set the per-subplan array of maps, do so ourselves. */
        if (mtstate->mt_per_subplan_tupconv_maps == NULL)
-       {
-               int                     leaf_index;
-               PartitionTupleRouting *proute = mtstate->mt_partition_tuple_routing;
-
-               /*
-                * If subplan-indexed array is NULL, things should have been arranged
-                * to convert the subplan index to partition index.
-                */
-               Assert(proute && proute->subplan_partition_offsets != NULL &&
-                          whichplan < proute->num_subplan_partition_offsets);
-
-               leaf_index = proute->subplan_partition_offsets[whichplan];
+               ExecSetupChildParentMapForSubplan(mtstate);
 
-               return TupConvMapForLeaf(proute, getTargetResultRelInfo(mtstate),
-                                                                leaf_index);
-       }
-       else
-       {
-               Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans);
-               return mtstate->mt_per_subplan_tupconv_maps[whichplan];
-       }
+       Assert(whichplan >= 0 && whichplan < mtstate->mt_nplans);
+       return mtstate->mt_per_subplan_tupconv_maps[whichplan];
 }
 
 /* ----------------------------------------------------------------
         * descriptor of a source partition does not match the root partitioned
         * table descriptor.  In such a case we need to convert tuples to the root
         * tuple descriptor, because the search for destination partition starts
-        * from the root.  Skip this setup if it's not a partition key update.
+        * from the root.  We'll also need a slot to store these converted tuples.
+        * We can skip this setup if it's not a partition key update.
         */
        if (update_tuple_routing_needed)
+       {
                ExecSetupChildParentMapForSubplan(mtstate);
+               mtstate->mt_root_tuple_slot = MakeTupleTableSlot(RelationGetDescr(rel),
+                                                                                                                &TTSOpsHeapTuple);
+       }
 
        /*
         * Initialize any WITH CHECK OPTION constraints if needed.
                                                                                                                   resultRelInfo);
        }
 
-       /* Close all the partitioned tables, leaf partitions, and their indices */
+       /*
+        * Close all the partitioned tables, leaf partitions, and their indices
+        * and release the slot used for tuple routing, if set.
+        */
        if (node->mt_partition_tuple_routing)
+       {
                ExecCleanupTupleRouting(node, node->mt_partition_tuple_routing);
 
+               if (node->mt_root_tuple_slot)
+                       ExecDropSingleTupleTableSlot(node->mt_root_tuple_slot);
+       }
+
        /*
         * Free the exprcontext
         */