diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
new file mode 100644
index d9d9201..7326ece
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -84,8 +84,9 @@ static bool GetTupleForTrigger(EState *e
 							   ItemPointer tid,
 							   LockTupleMode lockmode,
 							   TupleTableSlot *oldslot,
-							   TupleTableSlot **newSlot,
-							   TM_FailureData *tmfpd);
+							   TupleTableSlot **epqslot,
+							   TM_Result *tmresultp,
+							   TM_FailureData *tmfdp);
 static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 						   Trigger *trigger, TriggerEvent event,
 						   Bitmapset *modifiedCols,
@@ -2753,11 +2754,13 @@ ExecASDeleteTriggers(EState *estate, Res
  * back the concurrently updated tuple if any.
  */
 bool
-ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
-					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
-					 TupleTableSlot **epqslot)
+ExecBRDeleteTriggersNew(EState *estate, EPQState *epqstate,
+						ResultRelInfo *relinfo,
+						ItemPointer tupleid,
+						HeapTuple fdw_trigtuple,
+						TupleTableSlot **epqslot,
+						TM_Result *tmresult,
+						TM_FailureData *tmfd)
 {
 	TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo);
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
@@ -2774,7 +2777,7 @@ ExecBRDeleteTriggers(EState *estate, EPQ
 
 		if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
 								LockTupleExclusive, slot, &epqslot_candidate,
-								NULL))
+								tmresult, tmfd))
 			return false;
 
 		/*
@@ -2838,6 +2841,21 @@ ExecBRDeleteTriggers(EState *estate, EPQ
 }
 
 /*
+ * ABI-compatible wrapper to emulate old version of the above function.
+ * Do not call this version in new code.
+ */
+bool
+ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
+					 ResultRelInfo *relinfo,
+					 ItemPointer tupleid,
+					 HeapTuple fdw_trigtuple,
+					 TupleTableSlot **epqslot)
+{
+	return ExecBRDeleteTriggersNew(estate, epqstate, relinfo, tupleid,
+								   fdw_trigtuple, epqslot, NULL, NULL);
+}
+
+/*
  * Note: is_crosspart_update must be true if the DELETE is being performed
  * as part of a cross-partition update.
  */
@@ -2865,6 +2883,7 @@ ExecARDeleteTriggers(EState *estate,
 							   LockTupleExclusive,
 							   slot,
 							   NULL,
+							   NULL,
 							   NULL);
 		else
 			ExecForceStoreHeapTuple(fdw_trigtuple, slot, false);
@@ -3001,12 +3020,13 @@ ExecASUpdateTriggers(EState *estate, Res
 }
 
 bool
-ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
-					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid,
-					 HeapTuple fdw_trigtuple,
-					 TupleTableSlot *newslot,
-					 TM_FailureData *tmfd)
+ExecBRUpdateTriggersNew(EState *estate, EPQState *epqstate,
+						ResultRelInfo *relinfo,
+						ItemPointer tupleid,
+						HeapTuple fdw_trigtuple,
+						TupleTableSlot *newslot,
+						TM_Result *tmresult,
+						TM_FailureData *tmfd)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo);
@@ -3030,7 +3050,7 @@ ExecBRUpdateTriggers(EState *estate, EPQ
 		/* get a copy of the on-disk tuple we are planning to update */
 		if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
 								lockmode, oldslot, &epqslot_candidate,
-								tmfd))
+								tmresult, tmfd))
 			return false;		/* cancel the update action */
 
 		/*
@@ -3135,6 +3155,22 @@ ExecBRUpdateTriggers(EState *estate, EPQ
 }
 
 /*
+ * ABI-compatible wrapper to emulate old version of the above function.
+ * Do not call this version in new code.
+ */
+bool
+ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
+					 ResultRelInfo *relinfo,
+					 ItemPointer tupleid,
+					 HeapTuple fdw_trigtuple,
+					 TupleTableSlot *newslot,
+					 TM_FailureData *tmfd)
+{
+	return ExecBRUpdateTriggersNew(estate, epqstate, relinfo, tupleid,
+								   fdw_trigtuple, newslot, NULL, tmfd);
+}
+
+/*
  * Note: 'src_partinfo' and 'dst_partinfo', when non-NULL, refer to the source
  * and destination partitions, respectively, of a cross-partition update of
  * the root partitioned table mentioned in the query, given by 'relinfo'.
@@ -3185,6 +3221,7 @@ ExecARUpdateTriggers(EState *estate, Res
 							   LockTupleExclusive,
 							   oldslot,
 							   NULL,
+							   NULL,
 							   NULL);
 		else if (fdw_trigtuple != NULL)
 			ExecForceStoreHeapTuple(fdw_trigtuple, oldslot, false);
@@ -3340,6 +3377,7 @@ GetTupleForTrigger(EState *estate,
 				   LockTupleMode lockmode,
 				   TupleTableSlot *oldslot,
 				   TupleTableSlot **epqslot,
+				   TM_Result *tmresultp,
 				   TM_FailureData *tmfdp)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
@@ -3367,6 +3405,8 @@ GetTupleForTrigger(EState *estate,
 								&tmfd);
 
 		/* Let the caller know about the status of this operation */
+		if (tmresultp)
+			*tmresultp = test;
 		if (tmfdp)
 			*tmfdp = tmfd;
 
@@ -3394,6 +3434,18 @@ GetTupleForTrigger(EState *estate,
 			case TM_Ok:
 				if (tmfd.traversed)
 				{
+					/*
+					 * Recheck the tuple using EPQ. For MERGE, we leave this
+					 * to the caller (it must do additional rechecking, and
+					 * might end up executing a different action entirely).
+					 */
+					if (estate->es_plannedstmt->commandType == CMD_MERGE)
+					{
+						if (tmresultp)
+							*tmresultp = TM_Updated;
+						return false;
+					}
+
 					*epqslot = EvalPlanQual(epqstate,
 											relation,
 											relinfo->ri_RangeTableIndex,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
new file mode 100644
index 802bea0..e1fc8ff
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -88,15 +88,6 @@ typedef struct ModifyTableContext
 	 */
 	TupleTableSlot *planSlot;
 
-	/*
-	 * During EvalPlanQual, project and return the new version of the new
-	 * tuple
-	 */
-	TupleTableSlot *(*GetUpdateNewTuple) (ResultRelInfo *resultRelInfo,
-										  TupleTableSlot *epqslot,
-										  TupleTableSlot *oldSlot,
-										  MergeActionState *relaction);
-
 	/* MERGE specific */
 	MergeActionState *relaction;	/* MERGE action in progress */
 
@@ -107,12 +98,6 @@ typedef struct ModifyTableContext
 	TM_FailureData tmfd;
 
 	/*
-	 * The tuple produced by EvalPlanQual to retry from, if a cross-partition
-	 * UPDATE requires it
-	 */
-	TupleTableSlot *cpUpdateRetrySlot;
-
-	/*
 	 * The tuple projected by the INSERT's RETURNING clause, when doing a
 	 * cross-partition UPDATE
 	 */
@@ -162,10 +147,6 @@ static TupleTableSlot *ExecPrepareTupleR
 											   ResultRelInfo *targetRelInfo,
 											   TupleTableSlot *slot,
 											   ResultRelInfo **partRelInfo);
-static TupleTableSlot *internalGetUpdateNewTuple(ResultRelInfo *relinfo,
-												 TupleTableSlot *planSlot,
-												 TupleTableSlot *oldSlot,
-												 MergeActionState *relaction);
 
 static TupleTableSlot *ExecMerge(ModifyTableContext *context,
 								 ResultRelInfo *resultRelInfo,
@@ -179,10 +160,6 @@ static bool ExecMergeMatched(ModifyTable
 static void ExecMergeNotMatched(ModifyTableContext *context,
 								ResultRelInfo *resultRelInfo,
 								bool canSetTag);
-static TupleTableSlot *mergeGetUpdateNewTuple(ResultRelInfo *relinfo,
-											  TupleTableSlot *planSlot,
-											  TupleTableSlot *oldSlot,
-											  MergeActionState *relaction);
 
 
 /*
@@ -719,26 +696,14 @@ ExecGetUpdateNewTuple(ResultRelInfo *rel
 					  TupleTableSlot *planSlot,
 					  TupleTableSlot *oldSlot)
 {
+	ProjectionInfo *newProj = relinfo->ri_projectNew;
+	ExprContext *econtext;
+
 	/* Use a few extra Asserts to protect against outside callers */
 	Assert(relinfo->ri_projectNewInfoValid);
 	Assert(planSlot != NULL && !TTS_EMPTY(planSlot));
 	Assert(oldSlot != NULL && !TTS_EMPTY(oldSlot));
 
-	return internalGetUpdateNewTuple(relinfo, planSlot, oldSlot, NULL);
-}
-
-/*
- * Callback for ModifyTableState->GetUpdateNewTuple for use by regular UPDATE.
- */
-static TupleTableSlot *
-internalGetUpdateNewTuple(ResultRelInfo *relinfo,
-						  TupleTableSlot *planSlot,
-						  TupleTableSlot *oldSlot,
-						  MergeActionState *relaction)
-{
-	ProjectionInfo *newProj = relinfo->ri_projectNew;
-	ExprContext *econtext;
-
 	econtext = newProj->pi_exprContext;
 	econtext->ecxt_outertuple = planSlot;
 	econtext->ecxt_scantuple = oldSlot;
@@ -1317,8 +1282,11 @@ ExecPendingInserts(EState *estate)
 static bool
 ExecDeletePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
 				   ItemPointer tupleid, HeapTuple oldtuple,
-				   TupleTableSlot **epqreturnslot)
+				   TupleTableSlot **epqreturnslot, TM_Result *result)
 {
+	if (result)
+		*result = TM_Ok;
+
 	/* BEFORE ROW DELETE triggers */
 	if (resultRelInfo->ri_TrigDesc &&
 		resultRelInfo->ri_TrigDesc->trig_delete_before_row)
@@ -1327,9 +1295,9 @@ ExecDeletePrologue(ModifyTableContext *c
 		if (context->estate->es_insert_pending_result_relations != NIL)
 			ExecPendingInserts(context->estate);
 
-		return ExecBRDeleteTriggers(context->estate, context->epqstate,
-									resultRelInfo, tupleid, oldtuple,
-									epqreturnslot);
+		return ExecBRDeleteTriggersNew(context->estate, context->epqstate,
+									   resultRelInfo, tupleid, oldtuple,
+									   epqreturnslot, result, &context->tmfd);
 	}
 
 	return true;
@@ -1446,7 +1414,7 @@ ExecDelete(ModifyTableContext *context,
 	 * done if it says we are.
 	 */
 	if (!ExecDeletePrologue(context, resultRelInfo, tupleid, oldtuple,
-							epqreturnslot))
+							epqreturnslot, NULL))
 		return NULL;
 
 	/* INSTEAD OF ROW DELETE Triggers */
@@ -1727,8 +1695,10 @@ ldelete:;
  *
  * False is returned if the tuple we're trying to move is found to have been
  * concurrently updated.  In that case, the caller must check if the updated
- * tuple (in updateCxt->cpUpdateRetrySlot) still needs to be re-routed, and
- * call this function again or perform a regular update accordingly.
+ * tuple that's returned in *retry_slot still needs to be re-routed, and call
+ * this function again or perform a regular update accordingly.  For MERGE,
+ * the updated tuple is not returned in *retry_slot; it has its own retry
+ * logic.
  */
 static bool
 ExecCrossPartitionUpdate(ModifyTableContext *context,
@@ -1737,6 +1707,7 @@ ExecCrossPartitionUpdate(ModifyTableCont
 						 TupleTableSlot *slot,
 						 bool canSetTag,
 						 UpdateContext *updateCxt,
+						 TupleTableSlot **retry_slot,
 						 TupleTableSlot **inserted_tuple,
 						 ResultRelInfo **insert_destrel)
 {
@@ -1747,7 +1718,7 @@ ExecCrossPartitionUpdate(ModifyTableCont
 	TupleTableSlot *epqslot = NULL;
 
 	context->cpUpdateReturningSlot = NULL;
-	context->cpUpdateRetrySlot = NULL;
+	*retry_slot = NULL;
 
 	/*
 	 * Disallow an INSERT ON CONFLICT DO UPDATE that causes the original row
@@ -1826,9 +1797,13 @@ ExecCrossPartitionUpdate(ModifyTableCont
 		 * another transaction has concurrently updated the same row, it
 		 * re-fetches the row, skips the delete, and epqslot is set to the
 		 * re-fetched tuple slot.  In that case, we need to do all the checks
-		 * again.
+		 * again.  For MERGE, we leave everything to the caller (it must do
+		 * additional rechecking, and might end up executing a different
+		 * action entirely).
 		 */
-		if (TupIsNull(epqslot))
+		if (context->relaction != NULL)
+			return false;
+		else if (TupIsNull(epqslot))
 			return true;
 		else
 		{
@@ -1845,9 +1820,8 @@ ExecCrossPartitionUpdate(ModifyTableCont
 											   oldSlot))
 				elog(ERROR, "failed to fetch tuple being updated");
 			/* and project the new tuple to retry the UPDATE with */
-			context->cpUpdateRetrySlot =
-				context->GetUpdateNewTuple(resultRelInfo, epqslot, oldSlot,
-										   context->relaction);
+			*retry_slot = ExecGetUpdateNewTuple(resultRelInfo, epqslot,
+												oldSlot);
 			return false;
 		}
 	}
@@ -1888,10 +1862,14 @@ ExecCrossPartitionUpdate(ModifyTableCont
  */
 static bool
 ExecUpdatePrologue(ModifyTableContext *context, ResultRelInfo *resultRelInfo,
-				   ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot)
+				   ItemPointer tupleid, HeapTuple oldtuple, TupleTableSlot *slot,
+				   TM_Result *result)
 {
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 
+	if (result)
+		*result = TM_Ok;
+
 	ExecMaterializeSlot(slot);
 
 	/*
@@ -1910,9 +1888,9 @@ ExecUpdatePrologue(ModifyTableContext *c
 		if (context->estate->es_insert_pending_result_relations != NIL)
 			ExecPendingInserts(context->estate);
 
-		return ExecBRUpdateTriggers(context->estate, context->epqstate,
-									resultRelInfo, tupleid, oldtuple, slot,
-									&context->tmfd);
+		return ExecBRUpdateTriggersNew(context->estate, context->epqstate,
+									   resultRelInfo, tupleid, oldtuple, slot,
+									   result, &context->tmfd);
 	}
 
 	return true;
@@ -2016,7 +1994,8 @@ lreplace:
 	 */
 	if (partition_constraint_failed)
 	{
-		TupleTableSlot *inserted_tuple;
+		TupleTableSlot *inserted_tuple,
+				   *retry_slot;
 		ResultRelInfo *insert_destrel = NULL;
 
 		/*
@@ -2028,6 +2007,7 @@ lreplace:
 		if (ExecCrossPartitionUpdate(context, resultRelInfo,
 									 tupleid, oldtuple, slot,
 									 canSetTag, updateCxt,
+									 &retry_slot,
 									 &inserted_tuple,
 									 &insert_destrel))
 		{
@@ -2072,7 +2052,7 @@ lreplace:
 		 * ExecCrossPartitionUpdate installed an updated version of the new
 		 * tuple in the retry slot; start over.
 		 */
-		slot = context->cpUpdateRetrySlot;
+		slot = retry_slot;
 		goto lreplace;
 	}
 
@@ -2116,10 +2096,10 @@ lreplace:
 static void
 ExecUpdateEpilogue(ModifyTableContext *context, UpdateContext *updateCxt,
 				   ResultRelInfo *resultRelInfo, ItemPointer tupleid,
-				   HeapTuple oldtuple, TupleTableSlot *slot,
-				   List *recheckIndexes)
+				   HeapTuple oldtuple, TupleTableSlot *slot)
 {
 	ModifyTableState *mtstate = context->mtstate;
+	List	   *recheckIndexes = NIL;
 
 	/* insert index entries for tuple if necessary */
 	if (resultRelInfo->ri_NumIndices > 0 && updateCxt->updateIndexes)
@@ -2138,6 +2118,8 @@ ExecUpdateEpilogue(ModifyTableContext *c
 						 mtstate->mt_transition_capture,
 						 false);
 
+	list_free(recheckIndexes);
+
 	/*
 	 * Check any WITH CHECK OPTION constraints from parent views.  We are
 	 * required to do this after testing all constraints and uniqueness
@@ -2256,7 +2238,6 @@ ExecUpdate(ModifyTableContext *context,
 	EState	   *estate = context->estate;
 	Relation	resultRelationDesc = resultRelInfo->ri_RelationDesc;
 	UpdateContext updateCxt = {0};
-	List	   *recheckIndexes = NIL;
 	TM_Result	result;
 
 	/*
@@ -2269,7 +2250,7 @@ ExecUpdate(ModifyTableContext *context,
 	 * Prepare for the update.  This includes BEFORE ROW triggers, so we're
 	 * done if it says we are.
 	 */
-	if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, oldtuple, slot))
+	if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, oldtuple, slot, NULL))
 		return NULL;
 
 	/* INSTEAD OF ROW UPDATE Triggers */
@@ -2470,9 +2451,7 @@ redo_act:
 		(estate->es_processed)++;
 
 	ExecUpdateEpilogue(context, &updateCxt, resultRelInfo, tupleid, oldtuple,
-					   slot, recheckIndexes);
-
-	list_free(recheckIndexes);
+					   slot);
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -2839,7 +2818,6 @@ lmerge_matched:;
 	{
 		MergeActionState *relaction = (MergeActionState *) lfirst(l);
 		CmdType		commandType = relaction->mas_action->commandType;
-		List	   *recheckIndexes = NIL;
 		TM_Result	result;
 		UpdateContext updateCxt = {0};
 
@@ -2886,13 +2864,10 @@ lmerge_matched:;
 				newslot = ExecProject(relaction->mas_proj);
 
 				context->relaction = relaction;
-				context->GetUpdateNewTuple = mergeGetUpdateNewTuple;
-				context->cpUpdateRetrySlot = NULL;
-
 				if (!ExecUpdatePrologue(context, resultRelInfo,
-										tupleid, NULL, newslot))
+										tupleid, NULL, newslot, &result))
 				{
-					result = TM_Ok;
+					/* Blocked by trigger, or concurrent update/delete */
 					break;
 				}
 				result = ExecUpdateAct(context, resultRelInfo, tupleid, NULL,
@@ -2900,18 +2875,17 @@ lmerge_matched:;
 				if (result == TM_Ok && updateCxt.updated)
 				{
 					ExecUpdateEpilogue(context, &updateCxt, resultRelInfo,
-									   tupleid, NULL, newslot, recheckIndexes);
+									   tupleid, NULL, newslot);
 					mtstate->mt_merge_updated += 1;
 				}
-
 				break;
 
 			case CMD_DELETE:
 				context->relaction = relaction;
 				if (!ExecDeletePrologue(context, resultRelInfo, tupleid,
-										NULL, NULL))
+										NULL, NULL, &result))
 				{
-					result = TM_Ok;
+					/* Blocked by trigger, or concurrent update/delete */
 					break;
 				}
 				result = ExecDeleteAct(context, resultRelInfo, tupleid, false);
@@ -2978,34 +2952,13 @@ lmerge_matched:;
 
 					/*
 					 * The target tuple was concurrently updated by some other
-					 * transaction.
-					 */
-
-					/*
-					 * During an UPDATE, if cpUpdateRetrySlot is set, then
-					 * ExecCrossPartitionUpdate() must have detected that the
-					 * tuple was concurrently updated, so we restart the search
-					 * for an appropriate WHEN MATCHED clause to process the
-					 * updated tuple.
-					 *
-					 * In this case, ExecDelete() would already have performed
-					 * EvalPlanQual() on the latest version of the tuple,
-					 * which in turn would already have been loaded into
-					 * ri_oldTupleSlot, so no need to do either of those
-					 * things.
-					 */
-					if (commandType == CMD_UPDATE &&
-						!TupIsNull(context->cpUpdateRetrySlot))
-						goto lmerge_matched;
-
-					/*
-					 * Otherwise, we run the EvalPlanQual() with the new
-					 * version of the tuple. If EvalPlanQual() does not return
-					 * a tuple, then we switch to the NOT MATCHED list of
-					 * actions. If it does return a tuple and the join qual is
-					 * still satisfied, then we just need to recheck the
-					 * MATCHED actions, starting from the top, and execute the
-					 * first qualifying action.
+					 * transaction. Run EvalPlanQual() with the new version of
+					 * the tuple. If it does not return a tuple, then we
+					 * switch to the NOT MATCHED list of actions. If it does
+					 * return a tuple and the join qual is still satisfied,
+					 * then we just need to recheck the MATCHED actions,
+					 * starting from the top, and execute the first qualifying
+					 * action.
 					 */
 					resultRelationDesc = resultRelInfo->ri_RelationDesc;
 					lockmode = ExecUpdateLockMode(estate, resultRelInfo);
@@ -3381,25 +3334,6 @@ ExecInitMergeTupleSlots(ModifyTableState
 }
 
 /*
- * Callback for ModifyTableContext->GetUpdateNewTuple for use by MERGE.  It
- * computes the updated tuple by projecting from the current merge action's
- * projection.
- */
-static TupleTableSlot *
-mergeGetUpdateNewTuple(ResultRelInfo *relinfo,
-					   TupleTableSlot *planSlot,
-					   TupleTableSlot *oldSlot,
-					   MergeActionState *relaction)
-{
-	ExprContext *econtext = relaction->mas_proj->pi_exprContext;
-
-	econtext->ecxt_scantuple = oldSlot;
-	econtext->ecxt_innertuple = planSlot;
-
-	return ExecProject(relaction->mas_proj);
-}
-
-/*
  * Process BEFORE EACH STATEMENT triggers
  */
 static void
@@ -3854,9 +3788,8 @@ ExecModifyTable(PlanState *pstate)
 													   oldSlot))
 						elog(ERROR, "failed to fetch tuple being updated");
 				}
-				slot = internalGetUpdateNewTuple(resultRelInfo, context.planSlot,
-												 oldSlot, NULL);
-				context.GetUpdateNewTuple = internalGetUpdateNewTuple;
+				slot = ExecGetUpdateNewTuple(resultRelInfo, context.planSlot,
+											 oldSlot);
 				context.relaction = NULL;
 
 				/* Now apply the update. */
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
new file mode 100644
index 0d7558e..fff8d16
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -211,6 +211,14 @@ extern void ExecBSDeleteTriggers(EState
 extern void ExecASDeleteTriggers(EState *estate,
 								 ResultRelInfo *relinfo,
 								 TransitionCaptureState *transition_capture);
+extern bool ExecBRDeleteTriggersNew(EState *estate,
+									EPQState *epqstate,
+									ResultRelInfo *relinfo,
+									ItemPointer tupleid,
+									HeapTuple fdw_trigtuple,
+									TupleTableSlot **epqslot,
+									TM_Result *tmresult,
+									TM_FailureData *tmfd);
 extern bool ExecBRDeleteTriggers(EState *estate,
 								 EPQState *epqstate,
 								 ResultRelInfo *relinfo,
@@ -231,6 +239,14 @@ extern void ExecBSUpdateTriggers(EState
 extern void ExecASUpdateTriggers(EState *estate,
 								 ResultRelInfo *relinfo,
 								 TransitionCaptureState *transition_capture);
+extern bool ExecBRUpdateTriggersNew(EState *estate,
+									EPQState *epqstate,
+									ResultRelInfo *relinfo,
+									ItemPointer tupleid,
+									HeapTuple fdw_trigtuple,
+									TupleTableSlot *newslot,
+									TM_Result *tmresult,
+									TM_FailureData *tmfd);
 extern bool ExecBRUpdateTriggers(EState *estate,
 								 EPQState *epqstate,
 								 ResultRelInfo *relinfo,
diff --git a/src/test/isolation/expected/merge-delete.out b/src/test/isolation/expected/merge-delete.out
new file mode 100644
index b2befa8..897b935
--- a/src/test/isolation/expected/merge-delete.out
+++ b/src/test/isolation/expected/merge-delete.out
@@ -10,20 +10,31 @@ key|val
 
 step c2: COMMIT;
 
-starting permutation: merge_delete c1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
+starting permutation: delete_pa c1 select2_pa c2
+step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
 step c1: COMMIT;
-step select2: SELECT * FROM target;
+step select2_pa: SELECT * FROM target_pa;
 key|val
 ---+---
 (0 rows)
 
 step c2: COMMIT;
 
-starting permutation: delete c1 update1 select2 c2
+starting permutation: delete_tg c1 select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step c1: COMMIT;
+step select2_tg: SELECT * FROM target_tg;
+key|val
+---+---
+(0 rows)
+
+step c2: COMMIT;
+
+starting permutation: delete c1 update2 select2 c2
 step delete: DELETE FROM target t WHERE t.key = 1;
 step c1: COMMIT;
-step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1;
+step update2: UPDATE target t SET val = t.val || ' updated by update2' WHERE t.key = 1;
 step select2: SELECT * FROM target;
 key|val
 ---+---
@@ -31,11 +42,23 @@ key|val
 
 step c2: COMMIT;
 
-starting permutation: merge_delete c1 update1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
+starting permutation: delete_pa c1 update2_pa select2_pa c2
+step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
 step c1: COMMIT;
-step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1;
-step select2: SELECT * FROM target;
+step update2_pa: UPDATE target_pa t SET val = t.val || ' updated by update2_pa' WHERE t.key = 1;
+step select2_pa: SELECT * FROM target_pa;
+key|val
+---+---
+(0 rows)
+
+step c2: COMMIT;
+
+starting permutation: delete_tg c1 update2_tg select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step c1: COMMIT;
+step update2_tg: UPDATE target_tg t SET val = t.val || ' updated by update2_tg' WHERE t.key = 1;
+step select2_tg: SELECT * FROM target_tg;
 key|val
 ---+---
 (0 rows)
@@ -45,32 +68,72 @@ step c2: COMMIT;
 starting permutation: delete c1 merge2 select2 c2
 step delete: DELETE FROM target t WHERE t.key = 1;
 step c1: COMMIT;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
+step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
 step select2: SELECT * FROM target;
-key|val    
----+-------
-  1|merge2a
+key|val   
+---+------
+  1|merge2
 (1 row)
 
 step c2: COMMIT;
 
-starting permutation: merge_delete c1 merge2 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
+starting permutation: delete_pa c1 merge2_pa select2_pa c2
+step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
 step c1: COMMIT;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
+step merge2_pa: MERGE INTO target_pa t USING (SELECT 1 as key, 'merge2_pa' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
+step select2_pa: SELECT * FROM target_pa;
+key|val      
+---+---------
+  1|merge2_pa
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: delete_tg c1 merge2_tg select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step c1: COMMIT;
+s2: NOTICE:  Insert: (1,merge2_tg)
+step merge2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val;
+step select2_tg: SELECT * FROM target_tg;
+key|val      
+---+---------
+  1|merge2_tg
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: delete c1 merge_delete2 select2 c2
+step delete: DELETE FROM target t WHERE t.key = 1;
+step c1: COMMIT;
+step merge_delete2: MERGE INTO target t USING (SELECT 1 as key, 'merge_delete2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE;
 step select2: SELECT * FROM target;
-key|val    
----+-------
-  1|merge2a
+key|val          
+---+-------------
+  1|merge_delete2
 (1 row)
 
 step c2: COMMIT;
 
-starting permutation: delete update1 c1 select2 c2
+starting permutation: delete_tg c1 merge_delete2_tg select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step c1: COMMIT;
+s2: NOTICE:  Insert: (1,merge_delete2_tg)
+step merge_delete2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge_delete2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE;
+step select2_tg: SELECT * FROM target_tg;
+key|val             
+---+----------------
+  1|merge_delete2_tg
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: delete update2 c1 select2 c2
 step delete: DELETE FROM target t WHERE t.key = 1;
-step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; <waiting ...>
+step update2: UPDATE target t SET val = t.val || ' updated by update2' WHERE t.key = 1; <waiting ...>
 step c1: COMMIT;
-step update1: <... completed>
+step update2: <... completed>
 step select2: SELECT * FROM target;
 key|val
 ---+---
@@ -78,12 +141,25 @@ key|val
 
 step c2: COMMIT;
 
-starting permutation: merge_delete update1 c1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
-step update1: UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; <waiting ...>
+starting permutation: delete_pa update2_pa c1 select2_pa c2
+step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
+step update2_pa: UPDATE target_pa t SET val = t.val || ' updated by update2_pa' WHERE t.key = 1; <waiting ...>
 step c1: COMMIT;
-step update1: <... completed>
-step select2: SELECT * FROM target;
+step update2_pa: <... completed>
+step select2_pa: SELECT * FROM target_pa;
+key|val
+---+---
+(0 rows)
+
+step c2: COMMIT;
+
+starting permutation: delete_tg update2_tg c1 select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step update2_tg: UPDATE target_tg t SET val = t.val || ' updated by update2_tg' WHERE t.key = 1; <waiting ...>
+step c1: COMMIT;
+step update2_tg: <... completed>
+step select2_tg: SELECT * FROM target_tg;
 key|val
 ---+---
 (0 rows)
@@ -92,26 +168,69 @@ step c2: COMMIT;
 
 starting permutation: delete merge2 c1 select2 c2
 step delete: DELETE FROM target t WHERE t.key = 1;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
+step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
 step c1: COMMIT;
 step merge2: <... completed>
 step select2: SELECT * FROM target;
-key|val    
----+-------
-  1|merge2a
+key|val   
+---+------
+  1|merge2
 (1 row)
 
 step c2: COMMIT;
 
-starting permutation: merge_delete merge2 c1 select2 c2
-step merge_delete: MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE;
-step merge2: MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
+starting permutation: delete_pa merge2_pa c1 select2_pa c2
+step delete_pa: DELETE FROM target_pa t WHERE t.key = 1;
+step merge2_pa: MERGE INTO target_pa t USING (SELECT 1 as key, 'merge2_pa' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
 step c1: COMMIT;
-step merge2: <... completed>
+step merge2_pa: <... completed>
+step select2_pa: SELECT * FROM target_pa;
+key|val      
+---+---------
+  1|merge2_pa
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: delete_tg merge2_tg c1 select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step merge2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; <waiting ...>
+step c1: COMMIT;
+s2: NOTICE:  Insert: (1,merge2_tg)
+step merge2_tg: <... completed>
+step select2_tg: SELECT * FROM target_tg;
+key|val      
+---+---------
+  1|merge2_tg
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: delete merge_delete2 c1 select2 c2
+step delete: DELETE FROM target t WHERE t.key = 1;
+step merge_delete2: MERGE INTO target t USING (SELECT 1 as key, 'merge_delete2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; <waiting ...>
+step c1: COMMIT;
+step merge_delete2: <... completed>
 step select2: SELECT * FROM target;
-key|val    
----+-------
-  1|merge2a
+key|val          
+---+-------------
+  1|merge_delete2
+(1 row)
+
+step c2: COMMIT;
+
+starting permutation: delete_tg merge_delete2_tg c1 select2_tg c2
+s1: NOTICE:  Delete: (1,setup1)
+step delete_tg: DELETE FROM target_tg t WHERE t.key = 1;
+step merge_delete2_tg: MERGE INTO target_tg t USING (SELECT 1 as key, 'merge_delete2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; <waiting ...>
+step c1: COMMIT;
+s2: NOTICE:  Insert: (1,merge_delete2_tg)
+step merge_delete2_tg: <... completed>
+step select2_tg: SELECT * FROM target_tg;
+key|val             
+---+----------------
+  1|merge_delete2_tg
 (1 row)
 
 step c2: COMMIT;
diff --git a/src/test/isolation/expected/merge-match-recheck.out b/src/test/isolation/expected/merge-match-recheck.out
new file mode 100644
index 8183f52..9a44a59
--- a/src/test/isolation/expected/merge-match-recheck.out
+++ b/src/test/isolation/expected/merge-match-recheck.out
@@ -23,6 +23,31 @@ key|balance|status|val
 
 step c1: COMMIT;
 
+starting permutation: update1_tg merge_status_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,170,s1,"setup updated by update1_tg")
+step update1_tg: UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1;
+step merge_status_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+	UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+	UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+	UPDATE SET status = 's4', val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,170,s1,"setup updated by update1_tg") -> (1,170,s2,"setup updated by update1_tg when1")
+step merge_status_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                              
+---+-------+------+---------------------------------
+  1|    170|s2    |setup updated by update1_tg when1
+(1 row)
+
+step c1: COMMIT;
+
 starting permutation: update2 merge_status c2 select1 c1
 step update2: UPDATE target t SET status = 's2', val = t.val || ' updated by update2' WHERE t.key = 1;
 step merge_status: 
@@ -46,6 +71,31 @@ key|balance|status|val
 
 step c1: COMMIT;
 
+starting permutation: update2_tg merge_status_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,160,s2,"setup updated by update2_tg")
+step update2_tg: UPDATE target_tg t SET status = 's2', val = t.val || ' updated by update2_tg' WHERE t.key = 1;
+step merge_status_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+	UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+	UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+	UPDATE SET status = 's4', val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,160,s2,"setup updated by update2_tg") -> (1,160,s3,"setup updated by update2_tg when2")
+step merge_status_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                              
+---+-------+------+---------------------------------
+  1|    160|s3    |setup updated by update2_tg when2
+(1 row)
+
+step c1: COMMIT;
+
 starting permutation: update3 merge_status c2 select1 c1
 step update3: UPDATE target t SET status = 's3', val = t.val || ' updated by update3' WHERE t.key = 1;
 step merge_status: 
@@ -69,6 +119,31 @@ key|balance|status|val
 
 step c1: COMMIT;
 
+starting permutation: update3_tg merge_status_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,160,s3,"setup updated by update3_tg")
+step update3_tg: UPDATE target_tg t SET status = 's3', val = t.val || ' updated by update3_tg' WHERE t.key = 1;
+step merge_status_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+	UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+	UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+	UPDATE SET status = 's4', val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,160,s3,"setup updated by update3_tg") -> (1,160,s4,"setup updated by update3_tg when3")
+step merge_status_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                              
+---+-------+------+---------------------------------
+  1|    160|s4    |setup updated by update3_tg when3
+(1 row)
+
+step c1: COMMIT;
+
 starting permutation: update5 merge_status c2 select1 c1
 step update5: UPDATE target t SET status = 's5', val = t.val || ' updated by update5' WHERE t.key = 1;
 step merge_status: 
@@ -92,6 +167,30 @@ key|balance|status|val
 
 step c1: COMMIT;
 
+starting permutation: update5_tg merge_status_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,160,s5,"setup updated by update5_tg")
+step update5_tg: UPDATE target_tg t SET status = 's5', val = t.val || ' updated by update5_tg' WHERE t.key = 1;
+step merge_status_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+	UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+	UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+	UPDATE SET status = 's4', val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+step merge_status_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                        
+---+-------+------+---------------------------
+  1|    160|s5    |setup updated by update5_tg
+(1 row)
+
+step c1: COMMIT;
+
 starting permutation: update_bal1 merge_bal c2 select1 c1
 step update_bal1: UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1;
 step merge_bal: 
@@ -114,3 +213,137 @@ key|balance|status|val
 (1 row)
 
 step c1: COMMIT;
+
+starting permutation: update_bal1_pa merge_bal_pa c2 select1_pa c1
+step update_bal1_pa: UPDATE target_pa t SET balance = 50, val = t.val || ' updated by update_bal1_pa' WHERE t.key = 1;
+step merge_bal_pa: 
+  MERGE INTO target_pa t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+	UPDATE SET balance = balance * 4, val = t.val || ' when2'
+  WHEN MATCHED AND balance < 300 THEN
+	UPDATE SET balance = balance * 8, val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+step merge_bal_pa: <... completed>
+step select1_pa: SELECT * FROM target_pa;
+key|balance|status|val                                  
+---+-------+------+-------------------------------------
+  1|    100|s1    |setup updated by update_bal1_pa when1
+(1 row)
+
+step c1: COMMIT;
+
+starting permutation: update_bal1_tg merge_bal_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,50,s1,"setup updated by update_bal1_tg")
+step update_bal1_tg: UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1;
+step merge_bal_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+	UPDATE SET balance = balance * 4, val = t.val || ' when2'
+  WHEN MATCHED AND balance < 300 THEN
+	UPDATE SET balance = balance * 8, val = t.val || ' when3';
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,50,s1,"setup updated by update_bal1_tg") -> (1,100,s1,"setup updated by update_bal1_tg when1")
+step merge_bal_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                                  
+---+-------+------+-------------------------------------
+  1|    100|s1    |setup updated by update_bal1_tg when1
+(1 row)
+
+step c1: COMMIT;
+
+starting permutation: update1 merge_delete c2 select1 c1
+step update1: UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1;
+step merge_delete: 
+  MERGE INTO target t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+ <waiting ...>
+step c2: COMMIT;
+step merge_delete: <... completed>
+step select1: SELECT * FROM target;
+key|balance|status|val
+---+-------+------+---
+(0 rows)
+
+step c1: COMMIT;
+
+starting permutation: update1_tg merge_delete_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,170,s1,"setup updated by update1_tg")
+step update1_tg: UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1;
+step merge_delete_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Delete: (1,170,s1,"setup updated by update1_tg")
+step merge_delete_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val
+---+-------+------+---
+(0 rows)
+
+step c1: COMMIT;
+
+starting permutation: update_bal1 merge_delete c2 select1 c1
+step update_bal1: UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1;
+step merge_delete: 
+  MERGE INTO target t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+ <waiting ...>
+step c2: COMMIT;
+step merge_delete: <... completed>
+step select1: SELECT * FROM target;
+key|balance|status|val                               
+---+-------+------+----------------------------------
+  1|    100|s1    |setup updated by update_bal1 when1
+(1 row)
+
+step c1: COMMIT;
+
+starting permutation: update_bal1_tg merge_delete_tg c2 select1_tg c1
+s2: NOTICE:  Update: (1,160,s1,setup) -> (1,50,s1,"setup updated by update_bal1_tg")
+step update_bal1_tg: UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1;
+step merge_delete_tg: 
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+ <waiting ...>
+step c2: COMMIT;
+s1: NOTICE:  Update: (1,50,s1,"setup updated by update_bal1_tg") -> (1,100,s1,"setup updated by update_bal1_tg when1")
+step merge_delete_tg: <... completed>
+step select1_tg: SELECT * FROM target_tg;
+key|balance|status|val                                  
+---+-------+------+-------------------------------------
+  1|    100|s1    |setup updated by update_bal1_tg when1
+(1 row)
+
+step c1: COMMIT;
diff --git a/src/test/isolation/specs/merge-delete.spec b/src/test/isolation/specs/merge-delete.spec
new file mode 100644
index 0e70532..ba5f70e
--- a/src/test/isolation/specs/merge-delete.spec
+++ b/src/test/isolation/specs/merge-delete.spec
@@ -7,11 +7,39 @@ setup
 {
   CREATE TABLE target (key int primary key, val text);
   INSERT INTO target VALUES (1, 'setup1');
+
+  CREATE TABLE target_pa (key int primary key, val text) PARTITION BY LIST (key);
+  CREATE TABLE target_pa1 PARTITION OF target_pa FOR VALUES IN (1);
+  CREATE TABLE target_pa2 PARTITION OF target_pa FOR VALUES IN (2);
+  INSERT INTO target_pa VALUES (1, 'setup1');
+
+  CREATE TABLE target_tg (key int primary key, val text);
+  CREATE FUNCTION target_tg_trig_fn() RETURNS trigger LANGUAGE plpgsql AS
+  $$
+  BEGIN
+    IF tg_op = 'INSERT' THEN
+      RAISE NOTICE 'Insert: %', NEW;
+      RETURN NEW;
+    ELSIF tg_op = 'UPDATE' THEN
+      RAISE NOTICE 'Update: % -> %', OLD, NEW;
+      RETURN NEW;
+    ELSE
+      RAISE NOTICE 'Delete: %', OLD;
+      RETURN OLD;
+    END IF;
+  END
+  $$;
+  CREATE TRIGGER target_tg_trig BEFORE INSERT OR UPDATE OR DELETE ON target_tg
+    FOR EACH ROW EXECUTE FUNCTION target_tg_trig_fn();
+  INSERT INTO target_tg VALUES (1, 'setup1');
 }
 
 teardown
 {
   DROP TABLE target;
+  DROP TABLE target_pa;
+  DROP TABLE target_tg;
+  DROP FUNCTION target_tg_trig_fn;
 }
 
 session "s1"
@@ -20,7 +48,8 @@ setup
   BEGIN ISOLATION LEVEL READ COMMITTED;
 }
 step "delete" { DELETE FROM target t WHERE t.key = 1; }
-step "merge_delete" { MERGE INTO target t USING (SELECT 1 as key) s ON s.key = t.key WHEN MATCHED THEN DELETE; }
+step "delete_pa" { DELETE FROM target_pa t WHERE t.key = 1; }
+step "delete_tg" { DELETE FROM target_tg t WHERE t.key = 1; }
 step "c1" { COMMIT; }
 
 session "s2"
@@ -28,23 +57,40 @@ setup
 {
   BEGIN ISOLATION LEVEL READ COMMITTED;
 }
-step "update1" { UPDATE target t SET val = t.val || ' updated by update1' WHERE t.key = 1; }
-step "merge2" { MERGE INTO target t USING (SELECT 1 as key, 'merge2a' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
+step "update2" { UPDATE target t SET val = t.val || ' updated by update2' WHERE t.key = 1; }
+step "update2_pa" { UPDATE target_pa t SET val = t.val || ' updated by update2_pa' WHERE t.key = 1; }
+step "update2_tg" { UPDATE target_tg t SET val = t.val || ' updated by update2_tg' WHERE t.key = 1; }
+step "merge2" { MERGE INTO target t USING (SELECT 1 as key, 'merge2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
+step "merge2_pa" { MERGE INTO target_pa t USING (SELECT 1 as key, 'merge2_pa' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
+step "merge2_tg" { MERGE INTO target_tg t USING (SELECT 1 as key, 'merge2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN UPDATE set key = t.key + 1, val = t.val || ' updated by ' || s.val; }
+step "merge_delete2" { MERGE INTO target t USING (SELECT 1 as key, 'merge_delete2' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; }
+step "merge_delete2_tg" { MERGE INTO target_tg t USING (SELECT 1 as key, 'merge_delete2_tg' as val) s ON s.key = t.key WHEN NOT MATCHED THEN INSERT VALUES (s.key, s.val) WHEN MATCHED THEN DELETE; }
 step "select2" { SELECT * FROM target; }
+step "select2_pa" { SELECT * FROM target_pa; }
+step "select2_tg" { SELECT * FROM target_tg; }
 step "c2" { COMMIT; }
 
 # Basic effects
 permutation "delete" "c1" "select2" "c2"
-permutation "merge_delete" "c1" "select2" "c2"
+permutation "delete_pa" "c1" "select2_pa" "c2"
+permutation "delete_tg" "c1" "select2_tg" "c2"
 
 # One after the other, no concurrency
-permutation "delete" "c1" "update1" "select2" "c2"
-permutation "merge_delete" "c1" "update1" "select2" "c2"
+permutation "delete" "c1" "update2" "select2" "c2"
+permutation "delete_pa" "c1" "update2_pa" "select2_pa" "c2"
+permutation "delete_tg" "c1" "update2_tg" "select2_tg" "c2"
 permutation "delete" "c1" "merge2" "select2" "c2"
-permutation "merge_delete" "c1" "merge2" "select2" "c2"
+permutation "delete_pa" "c1" "merge2_pa" "select2_pa" "c2"
+permutation "delete_tg" "c1" "merge2_tg" "select2_tg" "c2"
+permutation "delete" "c1" "merge_delete2" "select2" "c2"
+permutation "delete_tg" "c1" "merge_delete2_tg" "select2_tg" "c2"
 
 # Now with concurrency
-permutation "delete" "update1" "c1" "select2" "c2"
-permutation "merge_delete" "update1" "c1" "select2" "c2"
+permutation "delete" "update2" "c1" "select2" "c2"
+permutation "delete_pa" "update2_pa" "c1" "select2_pa" "c2"
+permutation "delete_tg" "update2_tg" "c1" "select2_tg" "c2"
 permutation "delete" "merge2" "c1" "select2" "c2"
-permutation "merge_delete" "merge2" "c1" "select2" "c2"
+permutation "delete_pa" "merge2_pa" "c1" "select2_pa" "c2"
+permutation "delete_tg" "merge2_tg" "c1" "select2_tg" "c2"
+permutation "delete" "merge_delete2" "c1" "select2" "c2"
+permutation "delete_tg" "merge_delete2_tg" "c1" "select2_tg" "c2"
diff --git a/src/test/isolation/specs/merge-match-recheck.spec b/src/test/isolation/specs/merge-match-recheck.spec
new file mode 100644
index d56400a..298b2bf
--- a/src/test/isolation/specs/merge-match-recheck.spec
+++ b/src/test/isolation/specs/merge-match-recheck.spec
@@ -8,11 +8,39 @@ setup
 {
   CREATE TABLE target (key int primary key, balance integer, status text, val text);
   INSERT INTO target VALUES (1, 160, 's1', 'setup');
+
+  CREATE TABLE target_pa (key int, balance integer, status text, val text) PARTITION BY RANGE (balance);
+  CREATE TABLE target_pa1 PARTITION OF target_pa FOR VALUES FROM (0) TO (200);
+  CREATE TABLE target_pa2 PARTITION OF target_pa FOR VALUES FROM (200) TO (1000);
+  INSERT INTO target_pa VALUES (1, 160, 's1', 'setup');
+
+  CREATE TABLE target_tg (key int primary key, balance integer, status text, val text);
+  CREATE FUNCTION target_tg_trig_fn() RETURNS trigger LANGUAGE plpgsql AS
+  $$
+  BEGIN
+    IF tg_op = 'INSERT' THEN
+      RAISE NOTICE 'Insert: %', NEW;
+      RETURN NEW;
+    ELSIF tg_op = 'UPDATE' THEN
+      RAISE NOTICE 'Update: % -> %', OLD, NEW;
+      RETURN NEW;
+    ELSE
+      RAISE NOTICE 'Delete: %', OLD;
+      RETURN OLD;
+    END IF;
+  END
+  $$;
+  CREATE TRIGGER target_tg_trig BEFORE INSERT OR UPDATE OR DELETE ON target_tg
+    FOR EACH ROW EXECUTE FUNCTION target_tg_trig_fn();
+  INSERT INTO target_tg VALUES (1, 160, 's1', 'setup');
 }
 
 teardown
 {
   DROP TABLE target;
+  DROP TABLE target_pa;
+  DROP TABLE target_tg;
+  DROP FUNCTION target_tg_trig_fn;
 }
 
 session "s1"
@@ -32,6 +60,18 @@ step "merge_status"
   WHEN MATCHED AND status = 's3' THEN
 	UPDATE SET status = 's4', val = t.val || ' when3';
 }
+step "merge_status_tg"
+{
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND status = 's1' THEN
+	UPDATE SET status = 's2', val = t.val || ' when1'
+  WHEN MATCHED AND status = 's2' THEN
+	UPDATE SET status = 's3', val = t.val || ' when2'
+  WHEN MATCHED AND status = 's3' THEN
+	UPDATE SET status = 's4', val = t.val || ' when3';
+}
 
 step "merge_bal"
 {
@@ -45,8 +85,55 @@ step "merge_bal"
   WHEN MATCHED AND balance < 300 THEN
 	UPDATE SET balance = balance * 8, val = t.val || ' when3';
 }
+step "merge_bal_pa"
+{
+  MERGE INTO target_pa t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+	UPDATE SET balance = balance * 4, val = t.val || ' when2'
+  WHEN MATCHED AND balance < 300 THEN
+	UPDATE SET balance = balance * 8, val = t.val || ' when3';
+}
+step "merge_bal_tg"
+{
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+	UPDATE SET balance = balance * 4, val = t.val || ' when2'
+  WHEN MATCHED AND balance < 300 THEN
+	UPDATE SET balance = balance * 8, val = t.val || ' when3';
+}
+
+step "merge_delete"
+{
+  MERGE INTO target t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+}
+step "merge_delete_tg"
+{
+  MERGE INTO target_tg t
+  USING (SELECT 1 as key) s
+  ON s.key = t.key
+  WHEN MATCHED AND balance < 100 THEN
+	UPDATE SET balance = balance * 2, val = t.val || ' when1'
+  WHEN MATCHED AND balance < 200 THEN
+    DELETE;
+}
 
 step "select1" { SELECT * FROM target; }
+step "select1_pa" { SELECT * FROM target_pa; }
+step "select1_tg" { SELECT * FROM target_tg; }
 step "c1" { COMMIT; }
 
 session "s2"
@@ -55,23 +142,43 @@ setup
   BEGIN ISOLATION LEVEL READ COMMITTED;
 }
 step "update1" { UPDATE target t SET balance = balance + 10, val = t.val || ' updated by update1' WHERE t.key = 1; }
+step "update1_tg" { UPDATE target_tg t SET balance = balance + 10, val = t.val || ' updated by update1_tg' WHERE t.key = 1; }
 step "update2" { UPDATE target t SET status = 's2', val = t.val || ' updated by update2' WHERE t.key = 1; }
+step "update2_tg" { UPDATE target_tg t SET status = 's2', val = t.val || ' updated by update2_tg' WHERE t.key = 1; }
 step "update3" { UPDATE target t SET status = 's3', val = t.val || ' updated by update3' WHERE t.key = 1; }
+step "update3_tg" { UPDATE target_tg t SET status = 's3', val = t.val || ' updated by update3_tg' WHERE t.key = 1; }
 step "update5" { UPDATE target t SET status = 's5', val = t.val || ' updated by update5' WHERE t.key = 1; }
+step "update5_tg" { UPDATE target_tg t SET status = 's5', val = t.val || ' updated by update5_tg' WHERE t.key = 1; }
 step "update_bal1" { UPDATE target t SET balance = 50, val = t.val || ' updated by update_bal1' WHERE t.key = 1; }
+step "update_bal1_pa" { UPDATE target_pa t SET balance = 50, val = t.val || ' updated by update_bal1_pa' WHERE t.key = 1; }
+step "update_bal1_tg" { UPDATE target_tg t SET balance = 50, val = t.val || ' updated by update_bal1_tg' WHERE t.key = 1; }
 step "c2" { COMMIT; }
 
 # merge_status sees concurrently updated row and rechecks WHEN conditions, but recheck passes and final status = 's2'
 permutation "update1" "merge_status" "c2" "select1" "c1"
+permutation "update1_tg" "merge_status_tg" "c2" "select1_tg" "c1"
 
 # merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final status = 's3' not 's2'
 permutation "update2" "merge_status" "c2" "select1" "c1"
+permutation "update2_tg" "merge_status_tg" "c2" "select1_tg" "c1"
 
 # merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final status = 's4' not 's2'
 permutation "update3" "merge_status" "c2" "select1" "c1"
+permutation "update3_tg" "merge_status_tg" "c2" "select1_tg" "c1"
 
 # merge_status sees concurrently updated row and rechecks WHEN conditions, recheck fails, but we skip update and MERGE does nothing
 permutation "update5" "merge_status" "c2" "select1" "c1"
+permutation "update5_tg" "merge_status_tg" "c2" "select1_tg" "c1"
 
 # merge_bal sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final balance = 100 not 640
 permutation "update_bal1" "merge_bal" "c2" "select1" "c1"
+permutation "update_bal1_pa" "merge_bal_pa" "c2" "select1_pa" "c1"
+permutation "update_bal1_tg" "merge_bal_tg" "c2" "select1_tg" "c1"
+
+# merge_delete sees concurrently updated row and rechecks WHEN conditions, but recheck passes and row is deleted
+permutation "update1" "merge_delete" "c2" "select1" "c1"
+permutation "update1_tg" "merge_delete_tg" "c2" "select1_tg" "c1"
+
+# merge_delete sees concurrently updated row and rechecks WHEN conditions, recheck fails, so final balance is 100
+permutation "update_bal1" "merge_delete" "c2" "select1" "c1"
+permutation "update_bal1_tg" "merge_delete_tg" "c2" "select1_tg" "c1"
