diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 752ba3d767..0dfb9e2e95 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2510,8 +2510,12 @@ CopyFrom(CopyState cstate)
 	/*
 	 * If there are any triggers with transition tables on the named relation,
 	 * we need to be prepared to capture transition tuples.
+	 *
+	 * Because partition tuple routing would like to know about whether
+	 * transition capture is active, we also set it in mtstate, which is
+	 * passed to ExecFindPartition below.
 	 */
-	cstate->transition_capture =
+	cstate->transition_capture = mtstate->mt_transition_capture =
 		MakeTransitionCaptureState(cstate->rel->trigdesc,
 								   RelationGetRelid(cstate->rel),
 								   CMD_INSERT);
@@ -2521,19 +2525,8 @@ CopyFrom(CopyState cstate)
 	 * CopyFrom tuple routing.
 	 */
 	if (cstate->rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-	{
 		proute = ExecSetupPartitionTupleRouting(NULL, cstate->rel);
 
-		/*
-		 * If we are capturing transition tuples, they may need to be
-		 * converted from partition format back to partitioned table format
-		 * (this is only ever necessary if a BEFORE trigger modifies the
-		 * tuple).
-		 */
-		if (cstate->transition_capture != NULL)
-			ExecSetupChildParentMapForLeaf(proute);
-	}
-
 	/*
 	 * It's more efficient to prepare a bunch of tuples for insertion, and
 	 * insert them in one heap_multi_insert() call, than call heap_insert()
@@ -2835,8 +2828,7 @@ CopyFrom(CopyState cstate)
 					 */
 					cstate->transition_capture->tcs_original_insert_tuple = NULL;
 					cstate->transition_capture->tcs_map =
-						TupConvMapForLeaf(proute, target_resultRelInfo,
-										  leaf_part_index);
+						PartitionTupRoutingGetToParentMap(proute, leaf_part_index);
 				}
 				else
 				{
@@ -2854,16 +2846,13 @@ CopyFrom(CopyState cstate)
 			 * partition rowtype.  Don't free the already stored tuple as it
 			 * may still be required for a multi-insert batch.
 			 */
-			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,
-												  false);
-			}
+			tuple =
+				ConvertPartitionTupleSlot(PartitionTupRoutingGetToChildMap(proute,
+																		   leaf_part_index),
+										  tuple,
+										  proute->partition_tuple_slot,
+										  &slot,
+										  false);
 
 			tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 		}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index ad5fb32203..49f52b9a10 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -113,7 +113,6 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 	/* We only allocate these arrays when we need to store the first map */
 	proute->parent_child_tupconv_maps = NULL;
 	proute->child_parent_tupconv_maps = NULL;
-	proute->child_parent_map_not_required = NULL;
 
 	/*
 	 * Initialize this table's PartitionDispatch object.  Here we pass in the
@@ -427,7 +426,7 @@ ExecExpandRoutingArrays(PartitionTupleRouting *proute)
 			   sizeof(TupleConversionMap *) * (new_size - old_size));
 	}
 
-	if (proute->child_parent_map_not_required != NULL)
+	if (proute->child_parent_tupconv_maps != NULL)
 	{
 		proute->child_parent_tupconv_maps = (TupleConversionMap **)
 			repalloc(proute->child_parent_tupconv_maps,
@@ -435,15 +434,6 @@ ExecExpandRoutingArrays(PartitionTupleRouting *proute)
 		memset(&proute->child_parent_tupconv_maps[old_size], 0,
 			   sizeof(TupleConversionMap *) * (new_size - old_size));
 	}
-
-	if (proute->child_parent_map_not_required != NULL)
-	{
-		proute->child_parent_map_not_required = (bool *)
-			repalloc(proute->child_parent_map_not_required,
-					 sizeof(bool) * new_size);
-		memset(&proute->child_parent_map_not_required[old_size], 0,
-			   sizeof(bool) * (new_size - old_size));
-	}
 }
 
 /*
@@ -719,9 +709,7 @@ ExecInitPartitionInfo(ModifyTableState *mtstate,
 		{
 			TupleConversionMap *map;
 
-			map = proute->parent_child_tupconv_maps ?
-				proute->parent_child_tupconv_maps[part_result_rel_index] :
-				NULL;
+			map = PartitionTupRoutingGetToChildMap(proute, part_result_rel_index);
 
 			Assert(node->onConflictSet != NIL);
 			Assert(rootResultRelInfo->ri_onConflict != NULL);
@@ -871,6 +859,34 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
 		proute->parent_child_tupconv_maps[partidx] = map;
 	}
 
+	/*
+	 * Also, if transition capture is required, store a map to convert tuples
+	 * from partition's rowtype to parent's.
+	 */
+	if (mtstate &&
+		(mtstate->mt_transition_capture || mtstate->mt_oc_transition_capture))
+	{
+		map =
+			convert_tuples_by_name(RelationGetDescr(partRelInfo->ri_RelationDesc),
+								   RelationGetDescr(partRelInfo->ri_PartitionRoot),
+								   gettext_noop("could not convert row type"));
+
+		/* Allocate child parent map array only if we need to store a map */
+		if (map)
+		{
+			if (proute->child_parent_tupconv_maps == NULL)
+			{
+				int			size;
+
+				size = proute->partitions_allocsize;
+				proute->child_parent_tupconv_maps = (TupleConversionMap **)
+					palloc0(sizeof(TupleConversionMap *) * size);
+			}
+
+			proute->child_parent_tupconv_maps[partidx] = map;
+		}
+	}
+
 	/*
 	 * If the partition is a foreign table, let the FDW init itself for
 	 * routing tuples to the partition.
@@ -963,76 +979,6 @@ ExecInitPartitionDispatchInfo(PartitionTupleRouting *proute, Oid partoid,
 	return pd;
 }
 
-/*
- * 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)
-{
-	int			size;
-
-	Assert(proute != NULL);
-
-	size = proute->partitions_allocsize;
-
-	/*
-	 * These array elements get filled up with maps on an on-demand basis.
-	 * Initially just set all of them to NULL.
-	 */
-	proute->child_parent_tupconv_maps =
-		(TupleConversionMap **) palloc0(sizeof(TupleConversionMap *) * size);
-
-	/* Same is the case for this array. All the values are set to false */
-	proute->child_parent_map_not_required = (bool *) palloc0(sizeof(bool) *
-															 size);
-}
-
-/*
- * TupConvMapForLeaf -- Get the tuple conversion map for a given leaf partition
- * index.
- */
-TupleConversionMap *
-TupConvMapForLeaf(PartitionTupleRouting *proute,
-				  ResultRelInfo *rootRelInfo, int leaf_index)
-{
-	TupleConversionMap **map;
-	TupleDesc	tupdesc;
-
-	/* If nobody else set up the per-leaf maps array, do so ourselves. */
-	if (proute->child_parent_tupconv_maps == NULL)
-		ExecSetupChildParentMapForLeaf(proute);
-
-	/* If it's already known that we don't need a map, return NULL. */
-	else if (proute->child_parent_map_not_required[leaf_index])
-		return NULL;
-
-	/* If we've already got a map, return it. */
-	map = &proute->child_parent_tupconv_maps[leaf_index];
-	if (*map != NULL)
-		return *map;
-
-	/* No map yet; try to create one. */
-	tupdesc = RelationGetDescr(proute->partitions[leaf_index]->ri_RelationDesc);
-	*map =
-		convert_tuples_by_name(tupdesc,
-							   RelationGetDescr(rootRelInfo->ri_RelationDesc),
-							   gettext_noop("could not convert row type"));
-
-	/*
-	 * If it turns out no map is needed, remember that so we don't try making
-	 * one again next time.
-	 */
-	proute->child_parent_map_not_required[leaf_index] = (*map == NULL);
-
-	return *map;
-}
-
 /*
  * ConvertPartitionTupleSlot -- convenience function for tuple conversion.
  * The tuple, if converted, is stored in new_slot, and *p_my_slot is
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index bbffbd722e..cd89263f21 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1760,7 +1760,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 			 */
 			mtstate->mt_transition_capture->tcs_original_insert_tuple = NULL;
 			mtstate->mt_transition_capture->tcs_map =
-				TupConvMapForLeaf(proute, targetRelInfo, partidx);
+				PartitionTupRoutingGetToParentMap(proute, partidx);
 		}
 		else
 		{
@@ -1775,16 +1775,15 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate,
 	if (mtstate->mt_oc_transition_capture != NULL)
 	{
 		mtstate->mt_oc_transition_capture->tcs_map =
-			TupConvMapForLeaf(proute, targetRelInfo, partidx);
+			PartitionTupRoutingGetToParentMap(proute, partidx);
 	}
 
 	/*
 	 * Convert the tuple, if necessary.
 	 */
-	if (proute->parent_child_tupconv_maps)
-		ConvertPartitionTupleSlot(proute->parent_child_tupconv_maps[partidx],
-								  tuple, proute->partition_tuple_slot, &slot,
-								  true);
+	ConvertPartitionTupleSlot(PartitionTupRoutingGetToChildMap(proute, partidx),
+							  tuple, proute->partition_tuple_slot, &slot,
+							  true);
 
 	/* Initialize information needed to handle ON CONFLICT DO UPDATE. */
 	Assert(mtstate != NULL);
diff --git a/src/include/executor/execPartition.h b/src/include/executor/execPartition.h
index 0b03b9dd76..4bf7a4033a 100644
--- a/src/include/executor/execPartition.h
+++ b/src/include/executor/execPartition.h
@@ -112,21 +112,13 @@ typedef struct PartitionDispatchData *PartitionDispatch;
  *							routing.  Maintained separately because partitions
  *							may have different rowtype.
  *
- * Note: The following fields are used only when UPDATE ends up needing to
- * do tuple routing.
- *
  * child_parent_tupconv_maps	As 'parent_child_tupconv_maps' but stores
  *							conversion maps to translate partition tuples into
- *							partition_root's rowtype.
+ *							partition_root's rowtype, needed if transition
+ *							capture is active
  *
- * child_parent_map_not_required	True if the corresponding
- *							child_parent_tupconv_maps element has been
- *							determined to require no translation or set to
- *							NULL when child_parent_tupconv_maps is NULL.  This
- *							is required in order to distinguish tuple
- *							translations which have been seen to not be
- *							required due to the TupleDescs being compatible
- *							with transactions which have yet to be determined.
+ * Note: The following fields are used only when UPDATE ends up needing to
+ * do tuple routing.
  *
  * subplan_resultrel_hash	Hash table to store subplan ResultRelInfos by Oid.
  *							This is used to cache ResultRelInfos from subplans
@@ -152,12 +144,25 @@ typedef struct PartitionTupleRouting
 	int			partitions_allocsize;
 	TupleConversionMap **parent_child_tupconv_maps;
 	TupleConversionMap **child_parent_tupconv_maps;
-	bool	   *child_parent_map_not_required;
 	HTAB	   *subplan_resultrel_hash;
 	TupleTableSlot *root_tuple_slot;
 	TupleTableSlot *partition_tuple_slot;
 } PartitionTupleRouting;
 
+/*
+ * Accessor macros for tuple conversion maps contained in
+ * PartitionTupleRouting.  Beware of multiple evaluations of p!
+ */
+#define PartitionTupRoutingGetToParentMap(p, i) \
+			((p)->child_parent_tupconv_maps != NULL ? \
+				(p)->child_parent_tupconv_maps[(i)] : \
+							NULL)
+
+#define PartitionTupRoutingGetToChildMap(p, i) \
+			((p)->parent_child_tupconv_maps != NULL ? \
+				(p)->parent_child_tupconv_maps[(i)] : \
+							NULL)
+
 /*
  * PartitionedRelPruningData - Per-partitioned-table data for run-time pruning
  * of partitions.  For a multilevel partitioned table, we have one of these
@@ -260,9 +265,6 @@ extern void ExecInitRoutingInfo(ModifyTableState *mtstate,
 					PartitionTupleRouting *proute,
 					ResultRelInfo *partRelInfo,
 					int partidx);
-extern void ExecSetupChildParentMapForLeaf(PartitionTupleRouting *proute);
-extern TupleConversionMap *TupConvMapForLeaf(PartitionTupleRouting *proute,
-				  ResultRelInfo *rootRelInfo, int leaf_index);
 extern HeapTuple ConvertPartitionTupleSlot(TupleConversionMap *map,
 						  HeapTuple tuple,
 						  TupleTableSlot *new_slot,
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 27cf5a01b3..6b841c7850 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -904,4 +904,26 @@ select * from parted_conflict order by a;
  50 | cincuenta | 2
 (1 row)
 
+-- test with statement level triggers
+create or replace function parted_conflict_update_func() returns trigger as $$
+declare
+    r record;
+begin
+ for r in select * from inserted loop
+	raise notice 'a = %, b = %, c = %', r.a, r.b, r.c;
+ end loop;
+ return new;
+end;
+$$ language plpgsql;
+create trigger parted_conflict_update
+    after update on parted_conflict
+    referencing new table as inserted
+    for each statement
+    execute procedure parted_conflict_update_func();
+truncate parted_conflict;
+insert into parted_conflict values (0, 'cero', 1);
+insert into parted_conflict values(0, 'cero', 1)
+  on conflict (a,b) do update set c = parted_conflict.c + 1;
+NOTICE:  a = 0, b = cero, c = 2
 drop table parted_conflict;
+drop function parted_conflict_update_func();
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index c677d70fb7..fe6dcfaa06 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -576,4 +576,30 @@ insert into parted_conflict values (50, 'cincuenta', 2)
 -- should see (50, 'cincuenta', 2)
 select * from parted_conflict order by a;
 
+-- test with statement level triggers
+create or replace function parted_conflict_update_func() returns trigger as $$
+declare
+    r record;
+begin
+ for r in select * from inserted loop
+	raise notice 'a = %, b = %, c = %', r.a, r.b, r.c;
+ end loop;
+ return new;
+end;
+$$ language plpgsql;
+
+create trigger parted_conflict_update
+    after update on parted_conflict
+    referencing new table as inserted
+    for each statement
+    execute procedure parted_conflict_update_func();
+
+truncate parted_conflict;
+
+insert into parted_conflict values (0, 'cero', 1);
+
+insert into parted_conflict values(0, 'cero', 1)
+  on conflict (a,b) do update set c = parted_conflict.c + 1;
+
 drop table parted_conflict;
+drop function parted_conflict_update_func();
