diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 0a1706c0d1..a2a51ce9c4 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2725,30 +2725,25 @@ CopyFrom(CopyState cstate)
 			}
 			else
 			{
+				bool		check_partition_constr;
+
 				/*
-				 * We always check the partition constraint, including when
-				 * the tuple got here via tuple-routing.  However we don't
-				 * need to in the latter case if no BR trigger is defined on
-				 * the partition.  Note that a BR trigger might modify the
-				 * tuple such that the partition constraint is no longer
-				 * satisfied, so we need to check in that case.
+				 * Determine if we need to check any partition constaint for
+				 * this rel.
 				 */
-				bool		check_partition_constr =
-				(resultRelInfo->ri_PartitionCheck != NIL);
-
-				if (saved_resultRelInfo != NULL &&
-					!(resultRelInfo->ri_TrigDesc &&
-					  resultRelInfo->ri_TrigDesc->trig_insert_before_row))
-					check_partition_constr = false;
+				check_partition_constr =
+						ExecConstraintsPartConstrNeedsRecheck(resultRelInfo,
+												saved_resultRelInfo != NULL);
 
 				/*
-				 * If the target is a plain table, check the constraints of
-				 * the tuple.
+				 * Check the tuple is valid against all constraints, and the
+				 * partition constraint, if required.
 				 */
 				if (resultRelInfo->ri_FdwRoutine == NULL &&
 					(resultRelInfo->ri_RelationDesc->rd_att->constr ||
 					 check_partition_constr))
-					ExecConstraints(resultRelInfo, slot, estate, true);
+					ExecConstraints(resultRelInfo, slot, estate,
+									check_partition_constr);
 
 				if (useHeapMultiInsert)
 				{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 3d12f9c76f..1fc2135151 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2082,6 +2082,35 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		ExecPartitionCheckEmitError(resultRelInfo, slot, estate);
 }
 
+/*
+ * ExecConstraintsPartConstrNeedsRecheck
+ *		Returns true if 'resultRelInfo' requires a partition bound re-check
+ *		for an inserted tuple.
+ *
+ * Callers must pass 'tupleRouting' as true if resultRelInfo is a partition
+ * and the target of the command was a partitioned table.
+ */
+bool
+ExecConstraintsPartConstrNeedsRecheck(ResultRelInfo *resultRelInfo,
+									  bool tupleRouting)
+{
+	/*
+	 * We must validate the partition constraint when inserting directly into
+	 * a partition, or when the tuple has been routed here and a BR trigger
+	 * exists.  Such a trigger could have changed the tuple so that it
+	 * violates the partition constraint.  If no such trigger exists then the
+	 * correct partition has already been selected, so we can skip the check.
+	 */
+	if (resultRelInfo->ri_PartitionCheck != NIL && !tupleRouting)
+		return true;
+
+	if (tupleRouting &&
+		resultRelInfo->ri_TrigDesc &&
+		resultRelInfo->ri_TrigDesc->trig_insert_before_row)
+		return true;
+
+	return false;
+}
 
 /*
  * ExecWithCheckOptions -- check that tuple satisfies any WITH CHECK OPTIONs
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c4c841cdd7..743f4f5d72 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -367,15 +367,6 @@ ExecInsert(ModifyTableState *mtstate,
 		WCOKind		wco_kind;
 		bool		check_partition_constr;
 
-		/*
-		 * We always check the partition constraint, including when the tuple
-		 * got here via tuple-routing.  However we don't need to in the latter
-		 * case if no BR trigger is defined on the partition.  Note that a BR
-		 * trigger might modify the tuple such that the partition constraint
-		 * is no longer satisfied, so we need to check in that case.
-		 */
-		check_partition_constr = (resultRelInfo->ri_PartitionCheck != NIL);
-
 		/*
 		 * Constraints might reference the tableoid column, so initialize
 		 * t_tableOid before evaluating them.
@@ -402,17 +393,19 @@ ExecInsert(ModifyTableState *mtstate,
 			ExecWithCheckOptions(wco_kind, resultRelInfo, slot, estate);
 
 		/*
-		 * No need though if the tuple has been routed, and a BR trigger
-		 * doesn't exist.
+		 * Determine if we need to check any partition constaint for this rel.
 		 */
-		if (resultRelInfo->ri_PartitionRoot != NULL &&
-			!(resultRelInfo->ri_TrigDesc &&
-			  resultRelInfo->ri_TrigDesc->trig_insert_before_row))
-			check_partition_constr = false;
+		check_partition_constr =
+				ExecConstraintsPartConstrNeedsRecheck(resultRelInfo,
+									resultRelInfo->ri_PartitionRoot != NULL);
 
-		/* Check the constraints of the tuple */
+		/*
+		 * Check the tuple is valid against all constraints, and the partition
+		 * constraint, if required.
+		 */
 		if (resultRelationDesc->rd_att->constr || check_partition_constr)
-			ExecConstraints(resultRelInfo, slot, estate, true);
+			ExecConstraints(resultRelInfo, slot, estate,
+							check_partition_constr);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
 		{
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index a7ea3c7d10..796bfc2b4d 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -182,6 +182,8 @@ extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate,
 				bool check_partition_constraint);
+extern bool ExecConstraintsPartConstrNeedsRecheck(ResultRelInfo *resultRelInfo,
+									  bool tupleRouting);
 extern bool ExecPartitionCheck(ResultRelInfo *resultRelInfo,
 				   TupleTableSlot *slot, EState *estate);
 extern void ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
