diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index cf555fe..be57d3e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -434,7 +434,7 @@ ExecInsert(ModifyTableState *mtstate,
 		 * Check the constraints of the tuple
 		 */
 		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
-			ExecConstraints(resultRelInfo, slot, estate);
+			ExecConstraints(resultRelInfo, slot, estate, true);
 
 		if (onconflict != ONCONFLICT_NONE && resultRelInfo->ri_NumIndices > 0)
 		{
@@ -624,6 +624,8 @@ ExecDelete(ItemPointer tupleid,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
 		   EState *estate,
+		   bool   *concurrently_deleted,
+		   bool process_returning,
 		   bool canSetTag)
 {
 	ResultRelInfo *resultRelInfo;
@@ -632,6 +634,9 @@ ExecDelete(ItemPointer tupleid,
 	HeapUpdateFailureData hufd;
 	TupleTableSlot *slot = NULL;
 
+	if (concurrently_deleted)
+		*concurrently_deleted = false;
+
 	/*
 	 * get information on the (current) result relation
 	 */
@@ -775,6 +780,8 @@ ldelete:;
 					}
 				}
 				/* tuple already deleted; nothing to do */
+				if (concurrently_deleted)
+					*concurrently_deleted = true;
 				return NULL;
 
 			default:
@@ -798,8 +805,8 @@ ldelete:;
 	/* AFTER ROW DELETE Triggers */
 	ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple);
 
-	/* Process RETURNING if present */
-	if (resultRelInfo->ri_projectReturning)
+	/* Process RETURNING if present and if requested */
+	if (process_returning && resultRelInfo->ri_projectReturning)
 	{
 		/*
 		 * We have to put the target tuple into a slot, which means first we
@@ -877,7 +884,8 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(ModifyTableState *mtstate,
+		   ItemPointer tupleid,
 		   HeapTuple oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -891,6 +899,8 @@ ExecUpdate(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	List	   *recheckIndexes = NIL;
+	bool		partition_check_passed = true;
+	bool		has_br_trigger;
 
 	/*
 	 * abort the operation if not running transactions
@@ -911,16 +921,56 @@ ExecUpdate(ItemPointer tupleid,
 	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
 	/* BEFORE ROW UPDATE Triggers */
-	if (resultRelInfo->ri_TrigDesc &&
-		resultRelInfo->ri_TrigDesc->trig_update_before_row)
+	has_br_trigger = (resultRelInfo->ri_TrigDesc &&
+					  resultRelInfo->ri_TrigDesc->trig_update_before_row);
+
+	if (has_br_trigger)
 	{
-		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, oldtuple, slot);
+		TupleTableSlot *trig_slot;
 
-		if (slot == NULL)		/* "do nothing" */
+		trig_slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
+										 tupleid, oldtuple, slot);
+
+		if (trig_slot == NULL)		/* "do nothing" */
 			return NULL;
 
+		if (resultRelInfo->ri_PartitionCheck)
+		{
+			bool		partition_check_passed_with_trig_tuple;
+
+			partition_check_passed =
+				(resultRelInfo->ri_PartitionCheck &&
+				 ExecPartitionCheck(resultRelInfo, slot, estate));
+
+			partition_check_passed_with_trig_tuple =
+				(resultRelInfo->ri_PartitionCheck &&
+				 ExecPartitionCheck(resultRelInfo, trig_slot, estate));
+
+			if (partition_check_passed)
+			{
+				/*
+				 * If it's the trigger that is causing partition constraint
+				 * violation, abort. We don't want a trigger to cause tuple
+				 * routing.
+				 */
+				if (!partition_check_passed_with_trig_tuple)
+					ExecPartitionCheckEmitError(resultRelInfo,
+												trig_slot, estate);
+			}
+			else
+			{
+				/*
+				 * Partition constraint failed with original NEW tuple. But the
+				 * trigger might even have modifed the tuple such that it fits
+				 * back into the partition. So partition constraint check
+				 * should be based on *final* NEW tuple.
+				 */
+				partition_check_passed = partition_check_passed_with_trig_tuple;
+			}
+		}
+
 		/* trigger might have changed tuple */
+		slot = trig_slot;
 		tuple = ExecMaterializeSlot(slot);
 	}
 
@@ -987,12 +1037,48 @@ lreplace:;
 								 resultRelInfo, slot, estate);
 
 		/*
+		 * If a partition check fails, try to move the row into the right
+		 * partition. With a BR trigger, the tuple has already gone through EPQ
+		 * and has been locked; so it won't change again. So, avoid an extra
+		 * partition check if we already did it above in the presence of BR
+		 * triggers.
+		 */
+		if (!has_br_trigger)
+		{
+			partition_check_passed =
+				(!resultRelInfo->ri_PartitionCheck ||
+				ExecPartitionCheck(resultRelInfo, slot, estate));
+		}
+
+		if (!partition_check_passed)
+		{
+			bool	concurrently_deleted;
+
+			Assert(mtstate->mt_partition_dispatch_info != NULL);
+
+			/*
+			 * Skip RETURNING processing for DELETE. We want to return rows
+			 * from INSERT.
+			 */
+			ExecDelete(tupleid, oldtuple, planSlot, epqstate, estate,
+					   &concurrently_deleted, false, false);
+
+			if (concurrently_deleted)
+				return NULL;
+
+			return ExecInsert(mtstate, slot, planSlot, NULL,
+								  ONCONFLICT_NONE, estate, canSetTag);
+		}
+
+		/*
 		 * Check the constraints of the tuple.  Note that we pass the same
 		 * slot for the orig_slot argument, because unlike ExecInsert(), no
 		 * tuple-routing is performed here, hence the slot remains unchanged.
+		 * We have already checked partition constraints above, so skip them
+		 * below.
 		 */
-		if (resultRelationDesc->rd_att->constr || resultRelInfo->ri_PartitionCheck)
-			ExecConstraints(resultRelInfo, slot, estate);
+		if (resultRelationDesc->rd_att->constr)
+			ExecConstraints(resultRelInfo, slot, estate, false);
 
 		/*
 		 * replace the heap tuple
@@ -1312,7 +1398,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
 	 */
 
 	/* Execute UPDATE with projection */
-	*returning = ExecUpdate(&tuple.t_self, NULL,
+	*returning = ExecUpdate(mtstate, &tuple.t_self, NULL,
 							mtstate->mt_conflproj, planSlot,
 							&mtstate->mt_epqstate, mtstate->ps.state,
 							canSetTag);
@@ -1602,12 +1688,13 @@ ExecModifyTable(ModifyTableState *node)
 								  estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(node, tupleid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
 				slot = ExecDelete(tupleid, oldtuple, planSlot,
-								&node->mt_epqstate, estate, node->canSetTag);
+								&node->mt_epqstate, estate,
+								NULL, true, node->canSetTag);
 				break;
 			default:
 				elog(ERROR, "unknown operation");
