diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index e6aa36a9d8..c2a643e5a8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -94,15 +94,14 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
 									 FmgrInfo *finfo,
 									 Instrumentation *instr,
 									 MemoryContext per_tuple_context);
-static void AfterTriggerSaveEvent(EState *estate,
-								  ModifyTableState *mtstate,
-								  ResultRelInfo *relinfo,
+static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 								  ResultRelInfo *src_partinfo,
 								  ResultRelInfo *dst_partinfo,
 								  int event, bool row_trigger,
 								  TupleTableSlot *oldtup, TupleTableSlot *newtup,
 								  List *recheckIndexes, Bitmapset *modifiedCols,
-								  TransitionCaptureState *transition_capture);
+								  TransitionCaptureState *transition_capture,
+								  bool is_crosspart_update);
 static void AfterTriggerEnlargeQueryState(void);
 static bool before_stmt_triggers_fired(Oid relid, CmdType cmdType);
 
@@ -2462,10 +2461,10 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if (trigdesc && trigdesc->trig_insert_after_statement)
-		AfterTriggerSaveEvent(estate, NULL, relinfo,
-							  NULL, NULL,
+		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_INSERT,
-							  false, NULL, NULL, NIL, NULL, transition_capture);
+							  false, NULL, NULL, NIL, NULL, transition_capture,
+							  false);
 }
 
 bool
@@ -2553,12 +2552,12 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 
 	if ((trigdesc && trigdesc->trig_insert_after_row) ||
 		(transition_capture && transition_capture->tcs_insert_new_table))
-		AfterTriggerSaveEvent(estate, NULL, relinfo,
-							  NULL, NULL,
+		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_INSERT,
 							  true, NULL, slot,
 							  recheckIndexes, NULL,
-							  transition_capture);
+							  transition_capture,
+							  false);
 }
 
 bool
@@ -2680,10 +2679,10 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if (trigdesc && trigdesc->trig_delete_after_statement)
-		AfterTriggerSaveEvent(estate, NULL, relinfo,
-							  NULL, NULL,
+		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_DELETE,
-							  false, NULL, NULL, NIL, NULL, transition_capture);
+							  false, NULL, NULL, NIL, NULL, transition_capture,
+							  false);
 }
 
 /*
@@ -2778,12 +2777,17 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 	return result;
 }
 
+/*
+ * Note: is_crosspart_update must be true if the DELETE is being performed
+ * as part of a cross-partition update.
+ */
 void
-ExecARDeleteTriggers(EState *estate, ModifyTableState *mtstate,
+ExecARDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
-					 TransitionCaptureState *transition_capture)
+					 TransitionCaptureState *transition_capture,
+					 bool is_crosspart_update)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
@@ -2804,11 +2808,11 @@ ExecARDeleteTriggers(EState *estate, ModifyTableState *mtstate,
 		else
 			ExecForceStoreHeapTuple(fdw_trigtuple, slot, false);
 
-		AfterTriggerSaveEvent(estate, mtstate, relinfo,
-							  NULL, NULL,
+		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_DELETE,
 							  true, slot, NULL, NIL, NULL,
-							  transition_capture);
+							  transition_capture,
+							  is_crosspart_update);
 	}
 }
 
@@ -2927,12 +2931,12 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 	Assert(relinfo->ri_RootResultRelInfo == NULL);
 
 	if (trigdesc && trigdesc->trig_update_after_statement)
-		AfterTriggerSaveEvent(estate, NULL, relinfo,
-							  NULL, NULL,
+		AfterTriggerSaveEvent(estate, relinfo, NULL, NULL,
 							  TRIGGER_EVENT_UPDATE,
 							  false, NULL, NULL, NIL,
 							  ExecGetAllUpdatedCols(relinfo, estate),
-							  transition_capture);
+							  transition_capture,
+							  false);
 }
 
 bool
@@ -3068,24 +3072,25 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 }
 
 /*
- * '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'.
+ * 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'.
  * 'tupleid' in that case refers to the ctid of the "old" tuple in the source
  * partition, and 'newslot' contains the "new" tuple in the destination
  * partition.  This interface allows to support the requirements of
- * ExecCrossPartitionUpdateForeignKey().
+ * ExecCrossPartitionUpdateForeignKey(); is_crosspart_update must be true in
+ * that case.
  */
 void
-ExecARUpdateTriggers(EState *estate, ModifyTableState *mtstate,
-					 ResultRelInfo *relinfo,
+ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 					 ResultRelInfo *src_partinfo,
 					 ResultRelInfo *dst_partinfo,
 					 ItemPointer tupleid,
 					 HeapTuple fdw_trigtuple,
 					 TupleTableSlot *newslot,
 					 List *recheckIndexes,
-					 TransitionCaptureState *transition_capture)
+					 TransitionCaptureState *transition_capture,
+					 bool is_crosspart_update)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
@@ -3103,6 +3108,9 @@ ExecARUpdateTriggers(EState *estate, ModifyTableState *mtstate,
 		TupleTableSlot *oldslot;
 		ResultRelInfo *tupsrc;
 
+		Assert((src_partinfo != NULL && dst_partinfo != NULL) ||
+			   !is_crosspart_update);
+
 		tupsrc = src_partinfo ? src_partinfo : relinfo;
 		oldslot = ExecGetTriggerOldSlot(estate, tupsrc);
 
@@ -3119,12 +3127,14 @@ ExecARUpdateTriggers(EState *estate, ModifyTableState *mtstate,
 		else
 			ExecClearTuple(oldslot);
 
-		AfterTriggerSaveEvent(estate, mtstate, relinfo,
+		AfterTriggerSaveEvent(estate, relinfo,
 							  src_partinfo, dst_partinfo,
 							  TRIGGER_EVENT_UPDATE,
-							  true, oldslot, newslot, recheckIndexes,
+							  true,
+							  oldslot, newslot, recheckIndexes,
 							  ExecGetAllUpdatedCols(relinfo, estate),
-							  transition_capture);
+							  transition_capture,
+							  is_crosspart_update);
 	}
 }
 
@@ -3247,10 +3257,11 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if (trigdesc && trigdesc->trig_truncate_after_statement)
-		AfterTriggerSaveEvent(estate, NULL, relinfo,
+		AfterTriggerSaveEvent(estate, relinfo,
 							  NULL, NULL,
 							  TRIGGER_EVENT_TRUNCATE,
-							  false, NULL, NULL, NIL, NULL, NULL);
+							  false, NULL, NULL, NIL, NULL, NULL,
+							  false);
 }
 
 
@@ -5829,24 +5840,26 @@ AfterTriggerPendingOnRel(Oid relid)
  *	UPDATE; in this case, this function is called with relinfo as the
  *	partitioned table, and src_partinfo and dst_partinfo referring to the
  *	source and target leaf partitions, respectively.
+ *
+ *	is_crosspart_update is true either when a DELETE event is fired on the
+ *	source partition (which is to be ignored) or an UPDATE event is fired on
+ *	the root partitioned table.
  * ----------
  */
 static void
-AfterTriggerSaveEvent(EState *estate, ModifyTableState *mtstate,
-					  ResultRelInfo *relinfo,
+AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 					  ResultRelInfo *src_partinfo,
 					  ResultRelInfo *dst_partinfo,
 					  int event, bool row_trigger,
 					  TupleTableSlot *oldslot, TupleTableSlot *newslot,
 					  List *recheckIndexes, Bitmapset *modifiedCols,
-					  TransitionCaptureState *transition_capture)
+					  TransitionCaptureState *transition_capture,
+					  bool is_crosspart_update)
 {
 	Relation	rel = relinfo->ri_RelationDesc;
-	Relation	rootRel;
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	AfterTriggerEventData new_event;
 	AfterTriggerSharedData new_shared;
-	bool		maybe_crosspart_update;
 	char		relkind = rel->rd_rel->relkind;
 	int			tgtype_event;
 	int			tgtype_level;
@@ -5957,16 +5970,9 @@ AfterTriggerSaveEvent(EState *estate, ModifyTableState *mtstate,
 	 * queue an update event on the root target partitioned table, also
 	 * passing the source and destination partitions and their tuples.
 	 */
-	rootRel = relinfo->ri_RootResultRelInfo ?
-		relinfo->ri_RootResultRelInfo->ri_RelationDesc : NULL;
-	maybe_crosspart_update =
-		(row_trigger && mtstate &&
-		 mtstate->operation == CMD_UPDATE &&
-		 (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ||
-		  (rootRel && rootRel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)));
 	Assert(!row_trigger ||
 		   rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE ||
-		   (maybe_crosspart_update &&
+		   (is_crosspart_update &&
 			TRIGGER_FIRED_BY_UPDATE(event) &&
 			src_partinfo != NULL && dst_partinfo != NULL));
 
@@ -6161,7 +6167,7 @@ AfterTriggerSaveEvent(EState *estate, ModifyTableState *mtstate,
 					 * (partitioned) target table will be used to perform the
 					 * necessary foreign key enforcement action.
 					 */
-					if (maybe_crosspart_update &&
+					if (is_crosspart_update &&
 						TRIGGER_FIRED_BY_DELETE(event) &&
 						trigger->tgisclone)
 						continue;
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index e2a338ba33..f978e28ba9 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -516,10 +516,10 @@ ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
 												   NULL, NIL);
 
 		/* AFTER ROW UPDATE Triggers */
-		ExecARUpdateTriggers(estate, NULL, resultRelInfo,
+		ExecARUpdateTriggers(estate, resultRelInfo,
 							 NULL, NULL,
 							 tid, NULL, slot,
-							 recheckIndexes, NULL);
+							 recheckIndexes, NULL, false);
 
 		list_free(recheckIndexes);
 	}
@@ -557,8 +557,8 @@ ExecSimpleRelationDelete(ResultRelInfo *resultRelInfo,
 		simple_table_tuple_delete(rel, tid, estate->es_snapshot);
 
 		/* AFTER ROW DELETE Triggers */
-		ExecARDeleteTriggers(estate, NULL, resultRelInfo,
-							 tid, NULL, NULL);
+		ExecARDeleteTriggers(estate, resultRelInfo,
+							 tid, NULL, NULL, false);
 	}
 }
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 6a16d0e673..204126a29f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -962,13 +962,14 @@ ExecInsert(ModifyTableState *mtstate,
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_new_table)
 	{
-		ExecARUpdateTriggers(estate, mtstate, resultRelInfo,
+		ExecARUpdateTriggers(estate, resultRelInfo,
 							 NULL, NULL,
 							 NULL,
 							 NULL,
 							 slot,
 							 NULL,
-							 mtstate->mt_transition_capture);
+							 mtstate->mt_transition_capture,
+							 false);
 
 		/*
 		 * We've already captured the NEW TABLE row, so make sure any AR
@@ -1359,13 +1360,14 @@ ldelete:;
 	if (mtstate->operation == CMD_UPDATE && mtstate->mt_transition_capture
 		&& mtstate->mt_transition_capture->tcs_update_old_table)
 	{
-		ExecARUpdateTriggers(estate, mtstate, resultRelInfo,
+		ExecARUpdateTriggers(estate, resultRelInfo,
 							 NULL, NULL,
 							 tupleid,
 							 oldtuple,
 							 NULL,
 							 NULL,
-							 mtstate->mt_transition_capture);
+							 mtstate->mt_transition_capture,
+							 false);
 
 		/*
 		 * We've already captured the NEW TABLE row, so make sure any AR
@@ -1375,8 +1377,8 @@ ldelete:;
 	}
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, mtstate, resultRelInfo, tupleid, oldtuple,
-						 ar_delete_trig_tcs);
+	ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
+						 ar_delete_trig_tcs, changingPart);
 
 	/* Process RETURNING if present and if requested */
 	if (processReturning && resultRelInfo->ri_projectReturning)
@@ -1642,13 +1644,13 @@ GetAncestorResultRels(ResultRelInfo *resultRelInfo)
  * keys pointing into it.
  */
 static void
-ExecCrossPartitionUpdateForeignKey(ResultRelInfo *sourcePartInfo,
+ExecCrossPartitionUpdateForeignKey(ModifyTableState *mtstate,
+								   EState *estate,
+								   ResultRelInfo *sourcePartInfo,
 								   ResultRelInfo *destPartInfo,
 								   ItemPointer tupleid,
 								   TupleTableSlot *oldslot,
-								   TupleTableSlot *newslot,
-								   ModifyTableState *mtstate,
-								   EState *estate)
+								   TupleTableSlot *newslot)
 {
 	ListCell   *lc;
 	ResultRelInfo *rootRelInfo = sourcePartInfo->ri_RootResultRelInfo;
@@ -1699,10 +1701,9 @@ ExecCrossPartitionUpdateForeignKey(ResultRelInfo *sourcePartInfo,
 	}
 
 	/* Perform the root table's triggers. */
-	ExecARUpdateTriggers(estate, mtstate, rootRelInfo,
-						 sourcePartInfo, destPartInfo,
-						 tupleid, NULL,
-						 newslot, NIL, NULL);
+	ExecARUpdateTriggers(estate,
+						 rootRelInfo, sourcePartInfo, destPartInfo,
+						 tupleid, NULL, newslot, NIL, NULL, true);
 }
 
 /* ----------------------------------------------------------------
@@ -1920,11 +1921,11 @@ lreplace:;
 			if (insert_destrel &&
 				resultRelInfo->ri_TrigDesc &&
 				resultRelInfo->ri_TrigDesc->trig_update_after_row)
-				ExecCrossPartitionUpdateForeignKey(resultRelInfo,
+				ExecCrossPartitionUpdateForeignKey(mtstate, estate,
+												   resultRelInfo,
 												   insert_destrel,
 												   tupleid, oldslot,
-												   inserted_tuple,
-												   mtstate, estate);
+												   inserted_tuple);
 
 			return returning_slot;
 		}
@@ -2105,14 +2106,15 @@ lreplace:;
 		(estate->es_processed)++;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, mtstate, resultRelInfo,
+	ExecARUpdateTriggers(estate, resultRelInfo,
 						 NULL, NULL,
 						 tupleid, oldtuple,
 						 slot,
 						 recheckIndexes,
 						 mtstate->operation == CMD_INSERT ?
 						 mtstate->mt_oc_transition_capture :
-						 mtstate->mt_transition_capture);
+						 mtstate->mt_transition_capture,
+						 false);
 
 	list_free(recheckIndexes);
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 1ba3a54499..66bf6c16e3 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -211,11 +211,11 @@ extern bool ExecBRDeleteTriggers(EState *estate,
 								 HeapTuple fdw_trigtuple,
 								 TupleTableSlot **epqslot);
 extern void ExecARDeleteTriggers(EState *estate,
-								 ModifyTableState *mtstate,
 								 ResultRelInfo *relinfo,
 								 ItemPointer tupleid,
 								 HeapTuple fdw_trigtuple,
-								 TransitionCaptureState *transition_capture);
+								 TransitionCaptureState *transition_capture,
+								 bool is_crosspart_update);
 extern bool ExecIRDeleteTriggers(EState *estate,
 								 ResultRelInfo *relinfo,
 								 HeapTuple trigtuple);
@@ -231,7 +231,6 @@ extern bool ExecBRUpdateTriggers(EState *estate,
 								 HeapTuple fdw_trigtuple,
 								 TupleTableSlot *slot);
 extern void ExecARUpdateTriggers(EState *estate,
-								 ModifyTableState *mtstate,
 								 ResultRelInfo *relinfo,
 								 ResultRelInfo *src_partinfo,
 								 ResultRelInfo *dst_partinfo,
@@ -239,7 +238,8 @@ extern void ExecARUpdateTriggers(EState *estate,
 								 HeapTuple fdw_trigtuple,
 								 TupleTableSlot *slot,
 								 List *recheckIndexes,
-								 TransitionCaptureState *transition_capture);
+								 TransitionCaptureState *transition_capture,
+								 bool is_crosspart_update);
 extern bool ExecIRUpdateTriggers(EState *estate,
 								 ResultRelInfo *relinfo,
 								 HeapTuple trigtuple,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6c7eef1e54..37ad2b7663 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -530,7 +530,10 @@ typedef struct ResultRelInfo
 	/* for use by copyfrom.c when performing multi-inserts */
 	struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer;
 
-	/* Used during cross-partition updates on partitioned tables. */
+	/*
+	 * Used when a leaf partition is involved in a cross-partition update of
+	 * one of its ancestors; see ExecCrossPartitionUpdateForeignKey().
+	 */
 	List	   *ri_ancestorResultRels;
 } ResultRelInfo;
 
