diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 44cf3bba12..6fc1e2b41c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2684,12 +2684,15 @@ CopyFrom(CopyState cstate)
 			 * We might need to convert from the parent rowtype to the
 			 * partition rowtype.
 			 */
-			tuple = ConvertPartitionTupleSlot(proute->parent_child_tupconv_maps ?
-												proute->parent_child_tupconv_maps[leaf_part_index] :
-												NULL,
-											  tuple,
-											  proute->partition_tuple_slot,
-											  &slot);
+			if (proute->parent_child_tupconv_maps)
+			{
+				TupleConversionMap *map =
+				proute->parent_child_tupconv_maps[leaf_part_index];
+
+				tuple = ConvertPartitionTupleSlot(map, tuple,
+												  proute->partition_tuple_slot,
+												  &slot);
+			}
 
 			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 		}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 24a9d6b426..2a18a30b3e 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -33,8 +33,7 @@
 
 #define PARTITION_ROUTING_INITSIZE	8
 
-static void
-ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
+static void ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 							   PartitionTupleRouting *proute);
 static void ExecExpandRoutingArrays(PartitionTupleRouting *proute);
 static int ExecInitPartitionInfo(ModifyTableState *mtstate,
@@ -43,7 +42,7 @@ static int ExecInitPartitionInfo(ModifyTableState *mtstate,
 					  EState *estate,
 					  PartitionDispatch parent, int partidx);
 static PartitionDispatch ExecInitPartitionDispatchInfo(PartitionTupleRouting *proute,
-						Oid partoid, PartitionDispatch parent_pd, int partidx);
+							  Oid partoid, PartitionDispatch parent_pd, int partidx);
 static void FormPartitionKeyDatum(PartitionDispatch pd,
 					  TupleTableSlot *slot,
 					  EState *estate,
@@ -91,24 +90,23 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 	 * single tuple into a single partition.
 	 *
 	 * We initially allocate enough memory to hold PARTITION_ROUTING_INITSIZE
-	 * PartitionDispatch and ResultRelInfo pointers in their respective arrays.
-	 * More space can be allocated later, if required via
+	 * PartitionDispatch and ResultRelInfo pointers in their respective
+	 * arrays. More space can be allocated later, if required via
 	 * ExecExpandRoutingArrays.
 	 *
-	 * We're certain to only need just 1 PartitionDispatch; the one for the
-	 * partitioned table which is the target of the command.  We'll only setup
-	 * PartitionDispatchs for any subpartitions if tuples actually get routed
-	 * to (through) them.
+	 * The PartitionDispatch for the target partitioned table of the command
+	 * must be setup, but any sub-partitioned tables can be setup lazily as
+	 * and when the tuples get routed to (through) them.
 	 */
 	proute = (PartitionTupleRouting *) palloc(sizeof(PartitionTupleRouting));
 	proute->partition_root = rel;
 	proute->partition_dispatch_info = (PartitionDispatchData **)
-			palloc(sizeof(PartitionDispatchData) * PARTITION_ROUTING_INITSIZE);
+		palloc(sizeof(PartitionDispatchData) * PARTITION_ROUTING_INITSIZE);
 	proute->num_dispatch = 0;
 	proute->dispatch_allocsize = PARTITION_ROUTING_INITSIZE;
 
 	proute->partitions = (ResultRelInfo **)
-			palloc(sizeof(ResultRelInfo *) * PARTITION_ROUTING_INITSIZE);
+		palloc(sizeof(ResultRelInfo *) * PARTITION_ROUTING_INITSIZE);
 	proute->num_partitions = 0;
 	proute->partitions_allocsize = PARTITION_ROUTING_INITSIZE;
 
@@ -118,20 +116,23 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 	proute->child_parent_map_not_required = NULL;
 
 	/*
-	 * Initialize this table's PartitionDispatch object.  Here we pass in
-	 * the parent is NULL as we don't need to care about any parent of the
-	 * target partitioned table.
+	 * Initialize this table's PartitionDispatch object.  Here we pass in the
+	 * parent is NULL as we don't need to care about any parent of the target
+	 * partitioned table.
 	 */
 	(void) ExecInitPartitionDispatchInfo(proute, RelationGetRelid(rel), NULL,
 										 0);
 
 	/*
-	 * If UPDATE needs to do tuple routing, we'll need a slot that will
-	 * transiently store the tuple being routed using the root parent's
-	 * rowtype.  We must set up at least this slot, because it's needed even
-	 * before tuple routing begins.  Other necessary information is
-	 * initialized when  tuple routing code calls
-	 * ExecUseUpdateResultRelForRouting.
+	 * If UPDATE needs to do tuple routing, we can reuse partition sub-plan
+	 * result rels after tuple routing, so build a hash table to map the OIDs
+	 * of partitions present in mtstate->resultRelInfo to their
+	 * ResultRelInfos.  Every time a tuple is routed to one of the partitions
+	 * present in mtstate->resultRelInfo, looking its OID up in the hash table
+	 * will give us its ResultRelInfo.
+	 *
+	 * Also, we'll need a slot that will transiently store the tuple being
+	 * routed using the root parent's rowtype.
 	 */
 	if (node && node->operation == CMD_UPDATE)
 	{
@@ -140,7 +141,7 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 	}
 	else
 	{
-		proute->subplan_partition_table = NULL;
+		proute->subplan_resultrel_hash = NULL;
 		proute->root_tuple_slot = NULL;
 	}
 
@@ -175,7 +176,7 @@ ExecFindPartition(ModifyTableState *mtstate,
 	bool		isnull[PARTITION_MAX_KEYS];
 	Relation	rel;
 	PartitionDispatch parent;
-	PartitionDesc	partdesc;
+	PartitionDesc partdesc;
 	ExprContext *ecxt = GetPerTupleExprContext(estate);
 	TupleTableSlot *ecxt_scantuple_old = ecxt->ecxt_scantuple;
 
@@ -244,9 +245,9 @@ ExecFindPartition(ModifyTableState *mtstate,
 		if (partdesc->is_leaf[partidx])
 		{
 			/*
-			 * Get the index for PartitionTupleRouting->partitions array index
-			 * for this leaf partition.  This may require building a new
-			 * ResultRelInfo.
+			 * Get this leaf partition's index in the
+			 * PartitionTupleRouting->partitions array.  We may require
+			 * building a new ResultRelInfo.
 			 */
 			if (likely(parent->indexes[partidx] >= 0))
 			{
@@ -256,12 +257,17 @@ ExecFindPartition(ModifyTableState *mtstate,
 			}
 			else
 			{
-				if (proute->subplan_partition_table)
+				/*
+				 * A ResultRelInfo has not been setup for this partition yet,
+				 * so either use one of the sub-plan result rels or create a
+				 * fresh one.
+				 */
+				if (proute->subplan_resultrel_hash)
 				{
 					ResultRelInfo *rri;
 					Oid			partoid = partdesc->oids[partidx];
 
-					rri = hash_search(proute->subplan_partition_table,
+					rri = hash_search(proute->subplan_resultrel_hash,
 									  &partoid, HASH_FIND, NULL);
 
 					if (rri)
@@ -308,8 +314,8 @@ ExecFindPartition(ModifyTableState *mtstate,
 				PartitionDispatch subparent;
 
 				subparent = ExecInitPartitionDispatchInfo(proute,
-													partdesc->oids[partidx],
-													parent, partidx);
+														  partdesc->oids[partidx],
+														  parent, partidx);
 				Assert(parent->indexes[partidx] >= 0 &&
 					   parent->indexes[partidx] < proute->num_dispatch);
 				parent = subparent;
@@ -328,12 +334,12 @@ static void
 ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 							   PartitionTupleRouting *proute)
 {
-	ModifyTable	   *node = (ModifyTable *) mtstate->ps.plan;
-	ResultRelInfo  *subplan_result_rels;
-	HASHCTL			ctl;
-	HTAB		   *htab;
-	int				nsubplans;
-	int				i;
+	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+	ResultRelInfo *subplan_result_rels;
+	HASHCTL		ctl;
+	HTAB	   *htab;
+	int			nsubplans;
+	int			i;
 
 	subplan_result_rels = mtstate->resultRelInfo;
 	nsubplans = list_length(node->plans);
@@ -345,9 +351,9 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 
 	htab = hash_create("PartitionTupleRouting table", nsubplans, &ctl,
 					   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
-	proute->subplan_partition_table = htab;
+	proute->subplan_resultrel_hash = htab;
 
-	/* Hash all subplan by Oid */
+	/* Hash all subplans by their Oid */
 	for (i = 0; i < nsubplans; i++)
 	{
 		ResultRelInfo *rri = &subplan_result_rels[i];
@@ -356,16 +362,15 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 		ResultRelInfo **subplanrri;
 
 		subplanrri = (ResultRelInfo **) hash_search(htab, &partoid, HASH_ENTER,
-												   &found);
+													&found);
 
 		if (!found)
 			*subplanrri = rri;
 
 		/*
-		 * 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.
+		 * 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;
 	}
@@ -378,8 +383,8 @@ ExecHashSubPlanResultRelsByOid(ModifyTableState *mtstate,
 static void
 ExecExpandRoutingArrays(PartitionTupleRouting *proute)
 {
-	int		new_size = proute->partitions_allocsize * 2;
-	int		old_size = proute->partitions_allocsize;
+	int			new_size = proute->partitions_allocsize * 2;
+	int			old_size = proute->partitions_allocsize;
 
 	proute->partitions_allocsize = new_size;
 
@@ -389,8 +394,8 @@ ExecExpandRoutingArrays(PartitionTupleRouting *proute)
 	if (proute->parent_child_tupconv_maps != NULL)
 	{
 		proute->parent_child_tupconv_maps = (TupleConversionMap **)
-			repalloc( proute->parent_child_tupconv_maps,
-						sizeof(TupleConversionMap *) * new_size);
+			repalloc(proute->parent_child_tupconv_maps,
+					 sizeof(TupleConversionMap *) * new_size);
 		memset(&proute->parent_child_tupconv_maps[old_size], 0,
 			   sizeof(TupleConversionMap *) * (new_size - old_size));
 	}
@@ -827,7 +832,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
 
 	if (map)
 	{
-		int		new_size;
+		int			new_size;
 
 		/* Allocate parent child map array only if we need to store a map */
 		if (proute->parent_child_tupconv_maps == NULL)
@@ -858,8 +863,8 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
  *		Initialize PartitionDispatch for a partitioned table
  *
  * This also stores it in the proute->partition_dispatch_info array at the
- * specified index ('dispatchidx'), possibly expanding the array if there
- * isn't space left in it.
+ * specified index ('partidx'), possibly expanding the array if there isn't
+ * space left in it.
  */
 static PartitionDispatch
 ExecInitPartitionDispatchInfo(PartitionTupleRouting *proute, Oid partoid,
@@ -880,7 +885,8 @@ ExecInitPartitionDispatchInfo(PartitionTupleRouting *proute, Oid partoid,
 	partdesc = RelationGetPartitionDesc(rel);
 	partkey = RelationGetPartitionKey(rel);
 
-	pd = (PartitionDispatch) palloc(sizeof(PartitionDispatchData));
+	pd = (PartitionDispatch) palloc(offsetof(PartitionDispatchData, indexes)
+									+ (partdesc->nparts * sizeof(int)));
 	pd->reldesc = rel;
 	pd->key = partkey;
 	pd->keystate = NIL;
@@ -897,9 +903,9 @@ ExecInitPartitionDispatchInfo(PartitionTupleRouting *proute, Oid partoid,
 		 */
 		pd->tupslot = MakeSingleTupleTableSlot(tupdesc);
 		pd->tupmap =
-				convert_tuples_by_name(RelationGetDescr(parent_pd->reldesc),
-									   tupdesc,
-									   gettext_noop("could not convert row type"));
+			convert_tuples_by_name(RelationGetDescr(parent_pd->reldesc),
+								   tupdesc,
+								   gettext_noop("could not convert row type"));
 	}
 	else
 	{
@@ -908,8 +914,6 @@ ExecInitPartitionDispatchInfo(PartitionTupleRouting *proute, Oid partoid,
 		pd->tupmap = NULL;
 	}
 
-	pd->indexes = (int *) palloc(sizeof(int) * partdesc->nparts);
-
 	/*
 	 * Initialize with -1 to signify that the corresponding partition's
 	 * ResultRelInfo or PartitionDispatch has not been created yet.
@@ -1046,6 +1050,7 @@ void
 ExecCleanupTupleRouting(ModifyTableState *mtstate,
 						PartitionTupleRouting *proute)
 {
+	HTAB	   *resultrel_hash = proute->subplan_resultrel_hash;
 	int			i;
 
 	/*
@@ -1078,15 +1083,14 @@ ExecCleanupTupleRouting(ModifyTableState *mtstate,
 		 * Check if this result rel is one belonging to the node's subplans,
 		 * if so, let ExecEndPlan() clean it up.
 		 */
-		if (proute->subplan_partition_table)
+		if (resultrel_hash)
 		{
 			Oid			partoid;
 			bool		found;
 
 			partoid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 
-			(void) hash_search(proute->subplan_partition_table, &partoid,
-							   HASH_FIND, &found);
+			(void) hash_search(resultrel_hash, &partoid, HASH_FIND, &found);
 			if (found)
 				continue;
 		}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6e0c7862dc..4f7cea7668 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1779,12 +1779,9 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 	/*
 	 * Convert the tuple, if necessary.
 	 */
-	ConvertPartitionTupleSlot(proute->parent_child_tupconv_maps ?
-								proute->parent_child_tupconv_maps[partidx] :
-								NULL,
-							  tuple,
-							  proute->partition_tuple_slot,
-							  &slot);
+	if (proute->parent_child_tupconv_maps)
+		ConvertPartitionTupleSlot(proute->parent_child_tupconv_maps[partidx],
+								  tuple, proute->partition_tuple_slot, &slot);
 
 	/* Initialize information needed to handle ON CONFLICT DO UPDATE. */
 	Assert(mtstate != NULL);
diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h
index 4cc7508067..4b3b5ae770 100644
--- a/src/include/catalog/partition.h
+++ b/src/include/catalog/partition.h
@@ -27,8 +27,7 @@ typedef struct PartitionDescData
 {
 	int			nparts;			/* Number of partitions */
 	Oid		   *oids;			/* Array of length 'nparts' containing
-								 * partition OIDs in order of the their
-								 * bounds */
+								 * partition OIDs in order of the their bounds */
 	bool	   *is_leaf;		/* Array of 'nparts' elements storing whether
 								 * a partition is a leaf partition or not */
 	PartitionBoundInfo boundinfo;	/* collection of partition bounds */
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index 1b421f2ec5..d921ab6ca0 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -48,7 +48,7 @@ typedef struct PartitionDispatchData
 	PartitionDesc partdesc;
 	TupleTableSlot *tupslot;
 	TupleConversionMap *tupmap;
-	int		   *indexes;
+	int		   indexes[FLEXIBLE_ARRAY_MEMBER];
 } PartitionDispatchData;
 
 typedef struct PartitionDispatchData *PartitionDispatch;
@@ -58,23 +58,23 @@ typedef struct PartitionDispatchData *PartitionDispatch;
  * route a tuple inserted into a partitioned table to one of its leaf
  * partitions
  *
- *	partition_root			Root table, that is, the table mentioned in the
+ * partition_root			Root table, that is, the table mentioned in the
  *							command.
  *
- *	partition_dispatch_info	Contains PartitionDispatch objects for every
+ * partition_dispatch_info	Contains PartitionDispatch objects for every
  *							partitioned table touched by tuple routing.  The
  *							entry for the root partitioned table is *always*
  *							present as the first entry of this array.
  *
- *	num_dispatch			The number of existing entries and also serves as
+ * num_dispatch				The number of existing entries and also serves as
  *							the index of the next entry to be allocated and
  *							placed in 'partition_dispatch_info'.
  *
- *	dispatch_allocsize		(>= 'num_dispatch') is the number of entries that
+ * dispatch_allocsize		(>= 'num_dispatch') is the number of entries that
  *							can be stored in 'partition_dispatch_info' before
  *							needing to reallocate more space.
  *
- *	partitions				Contains pointers to a ResultRelInfos of all leaf
+ * partitions				Contains pointers to a ResultRelInfos of all leaf
  *							partitions touched by tuple routing.  Some of
  *							these are pointers to "reused" ResultRelInfos,
  *							that is, those that are created and destroyed
@@ -83,18 +83,18 @@ typedef struct PartitionDispatchData *PartitionDispatch;
  *							the partition key.  Rest of them are pointers to
  *							ResultRelInfos managed by execPartition.c itself
  *
- *	num_partitions			The number of existing entries and also serves as
+ * num_partitions			The number of existing entries and also serves as
  *							the index of the next entry to be allocated and
  *							placed in 'partitions'
  *
- *	partitions_allocsize	(>= 'num_partitions') is the number of entries
+ * partitions_allocsize		(>= 'num_partitions') is the number of entries
  *							that can be stored in 'partitions',
  *							'parent_child_tupconv_maps',
  *							'child_parent_tupconv_maps' and
  *							'child_parent_map_not_required' arrays before
  *							needing to reallocate more space
  *
- *	parent_child_tupconv_maps	Contains information to convert tuples of the
+ * parent_child_tupconv_maps	Contains information to convert tuples of the
  *							root parent's rowtype to those of the leaf
  *							partitions' rowtype, but only for those partitions
  *							whose TupleDescs are physically different from the
@@ -105,7 +105,7 @@ typedef struct PartitionDispatchData *PartitionDispatch;
  *							need not be more of these maps than there are
  *							partitions that were touched.
  *
- *	partition_tuple_slot	This is a tuple slot used to store a tuple using
+ * partition_tuple_slot		This is a tuple slot used to store a tuple using
  *							rowtype of the partition chosen by tuple
  *							routing.  Maintained separately because partitions
  *							may have different rowtype.
@@ -113,7 +113,7 @@ typedef struct PartitionDispatchData *PartitionDispatch;
  * Note: The following fields are used only when UPDATE ends up needing to
  * do tuple routing.
  *
- *	child_parent_tupconv_maps	Information to convert tuples of the leaf
+ * child_parent_tupconv_maps	Information to convert tuples of the leaf
  *							partitions' rowtype to the root parent's rowtype.
  *							These are needed by transition table machinery
  *							when storing tuples of partition's rowtype into
@@ -124,14 +124,14 @@ typedef struct PartitionDispatchData *PartitionDispatch;
  *							needed a conversion map.  Also, if non-NULL, is of
  *							the same size as 'partitions'.
  *
- *	child_parent_map_not_required	Stores if we don't need a conversion
+ * child_parent_map_not_required	Stores if we don't need a conversion
  *							map for a partition so that TupConvMapForLeaf
  *							can return without having to re-check if it needs
  *							to build a map.
  *
- *	subplan_partition_table	Hash table to store subplan index by Oid.
+ * subplan_resultrel_hash	Hash table to store subplan index by Oid.
  *
- *	root_tuple_slot			During UPDATE tuple routing, this tuple slot is
+ * root_tuple_slot			During UPDATE tuple routing, this tuple slot is
  *							used to transiently store a tuple using the root
  *							table's rowtype after converting it from the
  *							tuple's source leaf partition's rowtype.  That is,
@@ -151,7 +151,7 @@ typedef struct PartitionTupleRouting
 	TupleConversionMap **parent_child_tupconv_maps;
 	TupleConversionMap **child_parent_tupconv_maps;
 	bool	   *child_parent_map_not_required;
-	HTAB	   *subplan_partition_table;
+	HTAB	   *subplan_resultrel_hash;
 	TupleTableSlot *root_tuple_slot;
 	TupleTableSlot *partition_tuple_slot;
 } PartitionTupleRouting;
