diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml
index f579340..b77d9f8 100644
--- a/doc/src/sgml/trigger.sgml
+++ b/doc/src/sgml/trigger.sgml
@@ -627,6 +627,34 @@ typedef struct Trigger
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><structfield>tg_tot_num_events</></term>
+      <listitem>
+       <para>
+        The total number of after trigger events being fired in one set. The
+        after trigger may use this awareness of multiple consecutive events to
+        optimise the execution of the trigger. Where triggers fire recursively
+        there may be multiple sets of events fired.
+       </para>
+       <para>
+        Value will be set to zero except for after row trigger events.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><structfield>tg_event_num</></term>
+      <listitem>
+       <para>
+        The current event number within the current set of after trigger events being
+        fired.
+       </para>
+       <para>
+        Value will be set to zero except for after row trigger events.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index ed65bab..c7fcfc5 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3099,7 +3099,8 @@ static void AfterTriggerExecute(AfterTriggerEvent event,
 					Relation rel, TriggerDesc *trigdesc,
 					FmgrInfo *finfo,
 					Instrumentation *instr,
-					MemoryContext per_tuple_context);
+					MemoryContext per_tuple_context,
+					int total_num_events, int event_num);
 static SetConstraintState SetConstraintStateCreate(int numalloc);
 static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
 static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
@@ -3352,7 +3353,8 @@ static void
 AfterTriggerExecute(AfterTriggerEvent event,
 					Relation rel, TriggerDesc *trigdesc,
 					FmgrInfo *finfo, Instrumentation *instr,
-					MemoryContext per_tuple_context)
+					MemoryContext per_tuple_context,
+					int total_num_events, int event_num)
 {
 	AfterTriggerShared evtshared = GetTriggerSharedData(event);
 	Oid			tgoid = evtshared->ats_tgoid;
@@ -3364,6 +3366,9 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	Buffer		buffer2 = InvalidBuffer;
 	int			tgindx;
 
+	LocTriggerData.tg_event_num = event_num;
+	LocTriggerData.tg_tot_num_events = total_num_events;
+
 	/*
 	 * Locate trigger in trigdesc.
 	 */
@@ -3475,11 +3480,11 @@ AfterTriggerExecute(AfterTriggerEvent event,
 static bool
 afterTriggerMarkEvents(AfterTriggerEventList *events,
 					   AfterTriggerEventList *move_list,
-					   bool immediate_only)
+					   bool immediate_only, int *num_events)
 {
-	bool		found = false;
 	AfterTriggerEvent event;
 	AfterTriggerEventChunk *chunk;
+	int		evcount = 0;
 
 	for_each_event_chunk(event, chunk, *events)
 	{
@@ -3504,7 +3509,7 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
 				 */
 				evtshared->ats_firing_id = afterTriggers->firing_counter;
 				event->ate_flags |= AFTER_TRIGGER_IN_PROGRESS;
-				found = true;
+				evcount++;
 			}
 		}
 
@@ -3520,7 +3525,8 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
 		}
 	}
 
-	return found;
+	*num_events = evcount;
+	return evcount > 0;
 }
 
 /*
@@ -3548,7 +3554,7 @@ static bool
 afterTriggerInvokeEvents(AfterTriggerEventList *events,
 						 CommandId firing_id,
 						 EState *estate,
-						 bool delete_ok)
+						 bool delete_ok, int num_events)
 {
 	bool		all_fired = true;
 	AfterTriggerEventChunk *chunk;
@@ -3558,6 +3564,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 	TriggerDesc *trigdesc = NULL;
 	FmgrInfo   *finfo = NULL;
 	Instrumentation *instr = NULL;
+	int			event_num = 0;
 
 	/* Make a local EState if need be */
 	if (estate == NULL)
@@ -3613,7 +3620,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 				 * won't try to re-fire it.
 				 */
 				AfterTriggerExecute(event, rel, trigdesc, finfo, instr,
-									per_tuple_context);
+									per_tuple_context, num_events, ++event_num);
 
 				/*
 				 * Mark the event as done.
@@ -3645,6 +3652,10 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 		}
 	}
 
+	if (event_num != num_events)
+		elog(ERROR, "expected to execute %u events, yet executed %u after triggers",
+					 num_events, event_num);
+
 	/* Release working resources */
 	MemoryContextDelete(per_tuple_context);
 
@@ -3796,13 +3807,15 @@ AfterTriggerEndQuery(EState *estate)
 	 */
 	for (;;)
 	{
+		int		num_events = 0;
+
 		events = &afterTriggers->query_stack[afterTriggers->query_depth];
-		if (afterTriggerMarkEvents(events, &afterTriggers->events, true))
+		if (afterTriggerMarkEvents(events, &afterTriggers->events, true, &num_events))
 		{
 			CommandId	firing_id = afterTriggers->firing_counter++;
 
 			/* OK to delete the immediate events after processing them */
-			if (afterTriggerInvokeEvents(events, firing_id, estate, true))
+			if (afterTriggerInvokeEvents(events, firing_id, estate, true, num_events))
 				break;			/* all fired */
 		}
 		else
@@ -3832,6 +3845,7 @@ AfterTriggerFireDeferred(void)
 {
 	AfterTriggerEventList *events;
 	bool		snap_pushed = false;
+	int			num_events = 0;
 
 	/* Must be inside a transaction */
 	Assert(afterTriggers != NULL);
@@ -3855,11 +3869,11 @@ AfterTriggerFireDeferred(void)
 	 * Run all the remaining triggers.	Loop until they are all gone, in case
 	 * some trigger queues more for us to do.
 	 */
-	while (afterTriggerMarkEvents(events, NULL, false))
+	while (afterTriggerMarkEvents(events, NULL, false, &num_events))
 	{
 		CommandId	firing_id = afterTriggers->firing_counter++;
 
-		if (afterTriggerInvokeEvents(events, firing_id, NULL, true))
+		if (afterTriggerInvokeEvents(events, firing_id, NULL, true, num_events))
 			break;				/* all fired */
 	}
 
@@ -4405,8 +4419,9 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
 	{
 		AfterTriggerEventList *events = &afterTriggers->events;
 		bool		snapshot_set = false;
+		int			num_events = 0;
 
-		while (afterTriggerMarkEvents(events, NULL, true))
+		while (afterTriggerMarkEvents(events, NULL, true, &num_events))
 		{
 			CommandId	firing_id = afterTriggers->firing_counter++;
 
@@ -4431,7 +4446,7 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
 			 * subtransaction could later get rolled back.
 			 */
 			if (afterTriggerInvokeEvents(events, firing_id, NULL,
-										 !IsSubTransaction()))
+										 !IsSubTransaction(), num_events))
 				break;			/* all fired */
 		}
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 411a66d..20af94f 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -36,6 +36,8 @@ typedef struct TriggerData
 	Trigger    *tg_trigger;
 	Buffer		tg_trigtuplebuf;
 	Buffer		tg_newtuplebuf;
+	int			tg_tot_num_events;
+	int			tg_event_num;
 } TriggerData;
 
 /*
