diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 770c75fe2c..277e74b173 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2721,30 +2721,35 @@ 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.
+				 * We must validate the partition constraint when copying
+				 * 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.
 				 */
-				bool		check_partition_constr =
-				(resultRelInfo->ri_PartitionCheck != NIL);
-
-				if (saved_resultRelInfo != NULL &&
-					!(resultRelInfo->ri_TrigDesc &&
-					  resultRelInfo->ri_TrigDesc->trig_insert_before_row))
+				if (resultRelInfo->ri_PartitionCheck != NIL &&
+					saved_resultRelInfo == NULL)
+					check_partition_constr = true;
+				else if (saved_resultRelInfo && resultRelInfo->ri_TrigDesc &&
+						 resultRelInfo->ri_TrigDesc->trig_insert_before_row)
+					check_partition_constr = true;
+				else
 					check_partition_constr = false;
 
 				/*
-				 * 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/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c4c841cdd7..555116c07f 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,30 @@ 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.
+		 * 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_PartitionRoot != NULL &&
-			!(resultRelInfo->ri_TrigDesc &&
-			  resultRelInfo->ri_TrigDesc->trig_insert_before_row))
+		if (resultRelInfo->ri_PartitionCheck != NIL &&
+			!resultRelInfo->ri_PartitionRoot)
+			check_partition_constr = true;
+		else if (resultRelInfo->ri_PartitionRoot &&
+				 resultRelInfo->ri_TrigDesc &&
+				 resultRelInfo->ri_TrigDesc->trig_insert_before_row)
+			check_partition_constr = true;
+		else
 			check_partition_constr = false;
 
-		/* 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)
 		{
