From b339d1fff6c2f14776af29a35c8550b222ca70b2 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 10 Sep 2004 18:40:09 +0000
Subject: [PATCH] Fire non-deferred AFTER triggers immediately upon query
 completion, rather than when returning to the idle loop.  This makes no
 particular difference for interactively-issued queries, but it makes a big
 difference for queries issued within functions: trigger execution now occurs
 before the calling function is allowed to proceed.  This responds to numerous
 complaints about nonintuitive behavior of foreign key checking, such as
 http://archives.postgresql.org/pgsql-bugs/2004-09/msg00020.php, and appears
 to be required by the SQL99 spec. Also take the opportunity to simplify the
 data structures used for the pending-trigger list, rename them for more
 clarity, and squeeze out a bit of space.

---
 doc/src/sgml/ref/set_constraints.sgml     |   31 +-
 doc/src/sgml/release.sgml                 |   26 +-
 src/backend/access/transam/xact.c         |   82 +-
 src/backend/commands/copy.c               |   12 +-
 src/backend/commands/explain.c            |   15 +-
 src/backend/commands/portalcmds.c         |    4 +-
 src/backend/commands/trigger.c            | 1195 ++++++++++++---------
 src/backend/executor/functions.c          |    9 +-
 src/backend/executor/spi.c                |   12 +-
 src/backend/tcop/postgres.c               |    5 +-
 src/backend/tcop/pquery.c                 |   22 +-
 src/backend/tcop/utility.c                |    4 +-
 src/backend/utils/adt/ri_triggers.c       |    4 +-
 src/include/commands/trigger.h            |   28 +-
 src/test/regress/expected/foreign_key.out |    1 +
 src/test/regress/expected/plpgsql.out     |   71 ++
 src/test/regress/sql/plpgsql.sql          |   51 +
 17 files changed, 954 insertions(+), 618 deletions(-)

diff --git a/doc/src/sgml/ref/set_constraints.sgml b/doc/src/sgml/ref/set_constraints.sgml
index 0c976c93e25..3bcde91f386 100644
--- a/doc/src/sgml/ref/set_constraints.sgml
+++ b/doc/src/sgml/ref/set_constraints.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_constraints.sgml,v 1.11 2004/09/08 20:47:37 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_constraints.sgml,v 1.12 2004/09/10 18:39:53 tgl Exp $ -->
 <refentry id="SQL-SET-CONSTRAINTS">
  <refmeta>
   <refentrytitle id="SQL-SET-CONSTRAINTS-title">SET CONSTRAINTS</refentrytitle>
@@ -34,13 +34,13 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
 
   <para>
    Upon creation, a constraint is given one of three
-   characteristics: <literal>INITIALLY DEFERRED</literal>,
-   <literal>INITIALLY IMMEDIATE DEFERRABLE</literal>, or
-   <literal>INITIALLY IMMEDIATE NOT DEFERRABLE</literal>. The third
-   class is not affected by the <command>SET CONSTRAINTS</command>
-   command.  The first two classes start every transaction in the
-   indicated mode, but their behavior can be changed within a transaction
-   by <command>SET CONSTRAINTS</command>.
+   characteristics: <literal>DEFERRABLE INITIALLY DEFERRED</literal>,
+   <literal>DEFERRABLE INITIALLY IMMEDIATE</literal>, or
+   <literal>NOT DEFERRABLE</literal>. The third
+   class is always <literal>IMMEDIATE</literal> and is not affected by the
+   <command>SET CONSTRAINTS</command> command.  The first two classes start
+   every transaction in the indicated mode, but their behavior can be changed
+   within a transaction by <command>SET CONSTRAINTS</command>.
   </para>
 
   <para>
@@ -52,19 +52,22 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
   </para>
 
   <para>
-   When you change the mode of a constraint from <literal>DEFERRED</literal>
+   When <command>SET CONSTRAINTS</command> changes the mode of a constraint
+   from <literal>DEFERRED</literal>
    to <literal>IMMEDIATE</literal>, the new mode takes effect
    retroactively: any outstanding data modifications that would have
    been checked at the end of the transaction are instead checked during the
    execution of the <command>SET CONSTRAINTS</command> command.
    If any such constraint is violated, the <command>SET CONSTRAINTS</command>
-   fails (and does not change the constraint mode).
+   fails (and does not change the constraint mode).  Thus, <command>SET
+   CONSTRAINTS</command> can be used to force checking of constraints to
+   occur at a specific point in a transaction.
   </para>
 
   <para>
    Currently, only foreign key constraints are affected by this
    setting. Check and unique constraints are always effectively
-   initially immediate not deferrable.
+   not deferrable.
   </para>
  </refsect1>
 
@@ -76,11 +79,7 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
    current transaction. Thus, if you execute this command outside of a
    transaction block
    (<command>BEGIN</command>/<command>COMMIT</command> pair), it will
-   not appear to have any effect.  If you wish to change the behavior
-   of a constraint without needing to issue a <command>SET
-   CONSTRAINTS</command> command in every transaction, specify
-   <literal>INITIALLY DEFERRED</literal> or <literal>INITIALLY
-   IMMEDIATE</literal> when you create the constraint.
+   not appear to have any effect.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml
index b742f675e30..ddf93a04ff9 100644
--- a/doc/src/sgml/release.sgml
+++ b/doc/src/sgml/release.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.295 2004/09/10 18:39:54 tgl Exp $
 -->
 
 <appendix id="release">
@@ -336,6 +336,16 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp
        whitespace (which has always been ignored).
       </para>
      </listitem>
+
+     <listitem>
+      <para>
+       Non-deferred AFTER triggers are now fired immediately after completion
+       of the triggering query, rather than upon finishing the current
+       interactive command.  This makes a difference when the triggering query
+       occurred within a function: the trigger is invoked before the function
+       proceeds to its next operation.
+      </para>
+     </listitem>
     </itemizedlist>
    </para>
   </sect2>
@@ -1424,6 +1434,18 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp
    <title>Server-Side Language Changes</title>
    <itemizedlist>
 
+    <listitem>
+     <para>
+      Non-deferred AFTER triggers are now fired immediately after completion
+      of the triggering query, rather than upon finishing the current
+      interactive command.  This makes a difference when the triggering query
+      occurred within a function: the trigger is invoked before the function
+      proceeds to its next operation.  For example, if a function inserts
+      a new row into a table, any non-deferred foreign key checks occur
+      before proceeding with the function.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       Allow function parameters to be declared with names (Dennis Bjorklund)
@@ -1483,7 +1505,7 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp
 
     <listitem>
      <para>
-      New plperl server-side language (Command Prompt, Andrew Dunstan)
+      Major overhaul of plperl server-side language (Command Prompt, Andrew Dunstan)
      </para>
     </listitem>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 92c9de0ea75..47c501c393e 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.186 2004/09/06 17:56:04 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.187 2004/09/10 18:39:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -138,7 +138,6 @@ static void CleanupSubTransaction(void);
 static void StartAbortedSubTransaction(void);
 static void PushTransaction(void);
 static void PopTransaction(void);
-static void CommitTransactionToLevel(int level);
 static char *CleanupAbortedSubTransactions(bool returnName);
 
 static void AtSubAbort_Memory(void);
@@ -1219,7 +1218,7 @@ StartTransaction(void)
 	 */
 	AtStart_Inval();
 	AtStart_Cache();
-	DeferredTriggerBeginXact();
+	AfterTriggerBeginXact();
 
 	/*
 	 * done with start processing, set current transaction state to "in
@@ -1253,7 +1252,7 @@ CommitTransaction(void)
 	 * committed. He'll invoke all trigger deferred until XACT before we
 	 * really start on committing the transaction.
 	 */
-	DeferredTriggerEndXact();
+	AfterTriggerEndXact();
 
 	/*
 	 * Similarly, let ON COMMIT management do its thing before we start to
@@ -1454,7 +1453,7 @@ AbortTransaction(void)
 	/*
 	 * do abort processing
 	 */
-	DeferredTriggerAbortXact();
+	AfterTriggerAbortXact();
 	AtAbort_Portals();
 	AtEOXact_LargeObject(false);	/* 'false' means it's abort */
 	AtAbort_Notify();
@@ -1672,12 +1671,6 @@ CommitTransactionCommand(void)
 			 * default state.
 			 */
 		case TBLOCK_END:
-			/* commit all open subtransactions */
-			if (s->nestingLevel > 1)
-				CommitTransactionToLevel(2);
-			s = CurrentTransactionState;
-			Assert(s->parent == NULL);
-			/* and now the outer transaction */
 			CommitTransaction();
 			s->blockState = TBLOCK_DEFAULT;
 			break;
@@ -1732,11 +1725,10 @@ CommitTransactionCommand(void)
 			break;
 
 			/*
-			 * We were issued a RELEASE command, so we end the current
-			 * subtransaction and return to the parent transaction.
-			 *
-			 * Since RELEASE can exit multiple levels of subtransaction, we
-			 * must loop here until we get out of all SUBEND'ed levels.
+			 * We were issued a COMMIT or RELEASE command, so we end the
+			 * current subtransaction and return to the parent transaction.
+			 * Lather, rinse, and repeat until we get out of all SUBEND'ed
+			 * subtransaction levels.
 			 */
 		case TBLOCK_SUBEND:
 			do
@@ -1745,6 +1737,13 @@ CommitTransactionCommand(void)
 				PopTransaction();
 				s = CurrentTransactionState;	/* changed by pop */
 			} while (s->blockState == TBLOCK_SUBEND);
+			/* If we had a COMMIT command, finish off the main xact too */
+			if (s->blockState == TBLOCK_END)
+			{
+				Assert(s->parent == NULL);
+				CommitTransaction();
+				s->blockState = TBLOCK_DEFAULT;
+			}
 			break;
 
 			/*
@@ -2238,7 +2237,6 @@ EndTransactionBlock(void)
 			 * the default state.
 			 */
 		case TBLOCK_INPROGRESS:
-		case TBLOCK_SUBINPROGRESS:
 			s->blockState = TBLOCK_END;
 			result = true;
 			break;
@@ -2254,6 +2252,22 @@ EndTransactionBlock(void)
 			s->blockState = TBLOCK_ENDABORT;
 			break;
 
+			/*
+			 * We are in a live subtransaction block.  Set up to subcommit
+			 * all open subtransactions and then commit the main transaction.
+			 */
+		case TBLOCK_SUBINPROGRESS:
+			while (s->parent != NULL)
+			{
+				Assert(s->blockState == TBLOCK_SUBINPROGRESS);
+				s->blockState = TBLOCK_SUBEND;
+				s = s->parent;
+			}
+			Assert(s->blockState == TBLOCK_INPROGRESS);
+			s->blockState = TBLOCK_END;
+			result = true;
+			break;
+
 			/*
 			 * Here we are inside an aborted subtransaction.  Go to the
 			 * "abort the whole tree" state so that
@@ -2699,8 +2713,12 @@ ReleaseCurrentSubTransaction(void)
 	if (s->blockState != TBLOCK_SUBINPROGRESS)
 		elog(ERROR, "ReleaseCurrentSubTransaction: unexpected state %s",
 			 BlockStateAsString(s->blockState));
+	Assert(s->state == TRANS_INPROGRESS);
 	MemoryContextSwitchTo(CurTransactionContext);
-	CommitTransactionToLevel(GetCurrentTransactionNestLevel());
+	CommitSubTransaction();
+	PopTransaction();
+	s = CurrentTransactionState; /* changed by pop */
+	Assert(s->state == TRANS_INPROGRESS);
 }
 
 /*
@@ -2827,28 +2845,6 @@ AbortOutOfAnyTransaction(void)
 	Assert(s->parent == NULL);
 }
 
-/*
- * CommitTransactionToLevel
- *
- * Commit everything from the current transaction level
- * up to the specified level (inclusive).
- */
-static void
-CommitTransactionToLevel(int level)
-{
-	TransactionState s = CurrentTransactionState;
-
-	Assert(s->state == TRANS_INPROGRESS);
-
-	while (s->nestingLevel >= level)
-	{
-		CommitSubTransaction();
-		PopTransaction();
-		s = CurrentTransactionState;	/* changed by pop */
-		Assert(s->state == TRANS_INPROGRESS);
-	}
-}
-
 /*
  * IsTransactionBlock --- are we within a transaction block?
  */
@@ -2975,7 +2971,7 @@ StartSubTransaction(void)
 	 */
 	AtSubStart_Inval();
 	AtSubStart_Notify();
-	DeferredTriggerBeginSubXact();
+	AfterTriggerBeginSubXact();
 
 	s->state = TRANS_INPROGRESS;
 
@@ -3011,7 +3007,7 @@ CommitSubTransaction(void)
 	AtSubCommit_childXids();
 
 	/* Post-commit cleanup */
-	DeferredTriggerEndSubXact(true);
+	AfterTriggerEndSubXact(true);
 	AtSubCommit_Portals(s->parent->transactionIdData,
 						s->parent->curTransactionOwner);
 	AtEOSubXact_LargeObject(true, s->transactionIdData,
@@ -3101,7 +3097,7 @@ AbortSubTransaction(void)
 	 */
 	AtSubAbort_Memory();
 
-	DeferredTriggerEndSubXact(false);
+	AfterTriggerEndSubXact(false);
 	AtSubAbort_Portals(s->parent->transactionIdData,
 					   s->parent->curTransactionOwner);
 	AtEOSubXact_LargeObject(false, s->transactionIdData,
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 5793c0b2bbb..b25c8eee98c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.230 2004/08/29 05:06:41 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.231 2004/09/10 18:39:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1610,6 +1610,11 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 		}
 	}
 
+	/*
+	 * Prepare to catch AFTER triggers.
+	 */
+	AfterTriggerBeginQuery();
+
 	/*
 	 * Check BEFORE STATEMENT insertion triggers. It's debateable whether
 	 * we should do this for COPY, since it's not really an "INSERT"
@@ -1974,6 +1979,11 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 	 */
 	ExecASInsertTriggers(estate, resultRelInfo);
 
+	/*
+	 * Handle queued AFTER triggers
+	 */
+	AfterTriggerEndQuery();
+
 	pfree(values);
 	pfree(nulls);
 
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7ad3596fac6..9b7cf1ae491 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.124 2004/08/29 05:06:41 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.125 2004/09/10 18:39:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,6 +18,7 @@
 #include "catalog/pg_type.h"
 #include "commands/explain.h"
 #include "commands/prepare.h"
+#include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/instrument.h"
 #include "lib/stringinfo.h"
@@ -206,6 +207,10 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
 
 	gettimeofday(&starttime, NULL);
 
+	/* If analyzing, we need to cope with queued triggers */
+	if (stmt->analyze)
+		AfterTriggerBeginQuery();
+
 	/* call ExecutorStart to prepare the plan for execution */
 	ExecutorStart(queryDesc, false, !stmt->analyze);
 
@@ -255,12 +260,16 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
 	}
 
 	/*
-	 * Close down the query and free resources.  Include time for this in
-	 * the total runtime.
+	 * Close down the query and free resources; also run any queued
+	 * AFTER triggers.  Include time for this in the total runtime.
 	 */
 	gettimeofday(&starttime, NULL);
 
 	ExecutorEnd(queryDesc);
+
+	if (stmt->analyze)
+		AfterTriggerEndQuery();
+
 	FreeQueryDesc(queryDesc);
 
 	CommandCounterIncrement();
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 08b14013547..390e20aa2e9 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.33 2004/08/29 05:06:41 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.34 2004/09/10 18:39:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -273,6 +273,7 @@ PortalCleanup(Portal portal)
 			{
 				CurrentResourceOwner = portal->resowner;
 				ExecutorEnd(queryDesc);
+				/* we do not need AfterTriggerEndQuery() here */
 			}
 			PG_CATCH();
 			{
@@ -373,6 +374,7 @@ PersistHoldablePortal(Portal portal)
 		 */
 		portal->queryDesc = NULL;		/* prevent double shutdown */
 		ExecutorEnd(queryDesc);
+		/* we do not need AfterTriggerEndQuery() here */
 
 		/*
 		 * Reset the position in the result set: ideally, this could be
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 0efc4afb58c..5480fce189f 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.171 2004/09/08 23:47:58 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.172 2004/09/10 18:39:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,7 +48,7 @@ static HeapTuple GetTupleForTrigger(EState *estate,
 static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
 					FmgrInfo *finfo,
 					MemoryContext per_tuple_context);
-static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
+static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
 				   bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
 
 
@@ -1219,8 +1219,8 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
-		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
-								 false, NULL, NULL);
+		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
+							  false, NULL, NULL);
 }
 
 HeapTuple
@@ -1272,8 +1272,8 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
-		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
-								 true, NULL, trigtuple);
+		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
+							  true, NULL, trigtuple);
 }
 
 void
@@ -1332,8 +1332,8 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
-		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
-								 false, NULL, NULL);
+		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
+							  false, NULL, NULL);
 }
 
 bool
@@ -1399,8 +1399,8 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 												   (CommandId) 0,
 												   NULL);
 
-		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
-								 true, trigtuple, NULL);
+		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
+							  true, trigtuple, NULL);
 		heap_freetuple(trigtuple);
 	}
 }
@@ -1461,8 +1461,8 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
 	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
-		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
-								 false, NULL, NULL);
+		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
+							  false, NULL, NULL);
 }
 
 HeapTuple
@@ -1535,8 +1535,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 												   (CommandId) 0,
 												   NULL);
 
-		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
-								 true, trigtuple, newtuple);
+		AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
+							  true, trigtuple, newtuple);
 		heap_freetuple(trigtuple);
 	}
 }
@@ -1635,87 +1635,35 @@ ltrmark:;
 
 
 /* ----------
- * Deferred trigger stuff
+ * After-trigger stuff
  *
- * The DeferredTriggersData struct holds data about pending deferred
- * trigger events during the current transaction tree.	The struct and
- * most of its subsidiary data are kept in TopTransactionContext; however
+ * The AfterTriggersData struct holds data about pending AFTER trigger events
+ * during the current transaction tree.  (BEFORE triggers are fired
+ * immediately so we don't need any persistent state about them.)  The struct
+ * and most of its subsidiary data are kept in TopTransactionContext; however
  * the individual event records are kept in CurTransactionContext, so that
  * they will easily go away during subtransaction abort.
  *
- * DeferredTriggersData has the following fields:
- *
- * state keeps track of the deferred state of each trigger
- * (including the global state).  This is saved and restored across
- * failed subtransactions.
- *
- * events is the head of the list of events.
- *
- * tail_thisxact points to the tail of the list, for the current
- * transaction (whether main transaction or subtransaction).  We always
- * append to the list using this pointer.
- *
- * events_imm points to the last element scanned by the last
- * deferredTriggerInvokeEvents call.  We can use this to avoid rescanning
- * unnecessarily; if it's NULL, the scan should start at the head of the
- * list.  Its name comes from the fact that it's set to the last event fired
- * by the last call to immediate triggers.
- *
- * tail_stack and imm_stack are stacks of pointer, which hold the pointers
- * to the tail and the "immediate" events as of the start of a subtransaction.
- * We use to revert them when aborting the subtransaction.
- *
- * state_stack is a stack of pointers to saved copies of the deferred-trigger
- * state data; each subtransaction level that modifies that state first
- * saves a copy, which we use to restore the state if we abort.
- *
- * We use GetCurrentTransactionNestLevel() to determine the correct array
- * index in these stacks.  numalloc is the number of allocated entries in
- * each stack.  (By not keeping our own stack pointer, we can avoid trouble
- * in cases where errors during subxact abort cause multiple invocations
- * of DeferredTriggerEndSubXact() at the same nesting depth.)
+ * Because the list of pending events can grow large, we go to some effort
+ * to minimize memory consumption.  We do not use the generic List mechanism
+ * but thread the events manually.
  *
  * XXX We need to be able to save the per-event data in a file if it grows too
  * large.
  * ----------
  */
 
-/* Per-item data */
-typedef struct DeferredTriggerEventItem
-{
-	Oid			dti_tgoid;
-	TransactionId dti_done_xid;
-	int32		dti_state;
-} DeferredTriggerEventItem;
-
-typedef struct DeferredTriggerEventData *DeferredTriggerEvent;
-
-/* Per-event data */
-typedef struct DeferredTriggerEventData
-{
-	DeferredTriggerEvent dte_next;		/* list link */
-	int32		dte_event;
-	Oid			dte_relid;
-	TransactionId dte_done_xid;
-	ItemPointerData dte_oldctid;
-	ItemPointerData dte_newctid;
-	int32		dte_n_items;
-	/* dte_item is actually a variable-size array, of length dte_n_items */
-	DeferredTriggerEventItem dte_item[1];
-} DeferredTriggerEventData;
-
-/* Per-trigger status data */
-typedef struct DeferredTriggerStatusData
+/* Per-trigger SET CONSTRAINT status */
+typedef struct SetConstraintTriggerData
 {
-	Oid			dts_tgoid;
-	bool		dts_tgisdeferred;
-} DeferredTriggerStatusData;
-
-typedef struct DeferredTriggerStatusData *DeferredTriggerStatus;
+	Oid			sct_tgoid;
+	bool		sct_tgisdeferred;
+} SetConstraintTriggerData;
 
+typedef struct SetConstraintTriggerData *SetConstraintTrigger;
 
 /*
- * Trigger deferral status data.
+ * SET CONSTRAINT intra-transaction status.
  *
  * We make this a single palloc'd object so it can be copied and freed easily.
  *
@@ -1724,62 +1672,148 @@ typedef struct DeferredTriggerStatusData *DeferredTriggerStatus;
  *
  * trigstates[] stores per-trigger tgisdeferred settings.
  */
-typedef struct DeferredTriggerStateData
+typedef struct SetConstraintStateData
 {
 	bool		all_isset;
 	bool		all_isdeferred;
 	int			numstates;		/* number of trigstates[] entries in use */
 	int			numalloc;		/* allocated size of trigstates[] */
-	DeferredTriggerStatusData trigstates[1];	/* VARIABLE LENGTH ARRAY */
-} DeferredTriggerStateData;
+	SetConstraintTriggerData trigstates[1];	/* VARIABLE LENGTH ARRAY */
+} SetConstraintStateData;
 
-typedef DeferredTriggerStateData *DeferredTriggerState;
+typedef SetConstraintStateData *SetConstraintState;
 
-/* Per-transaction data */
-typedef struct DeferredTriggersData
-{
-	DeferredTriggerState state;
-	DeferredTriggerEvent events;
-	DeferredTriggerEvent tail_thisxact;
-	DeferredTriggerEvent events_imm;
-	DeferredTriggerEvent *tail_stack;
-	DeferredTriggerEvent *imm_stack;
-	DeferredTriggerState *state_stack;
-	int			numalloc;
-} DeferredTriggersData;
 
-typedef DeferredTriggersData *DeferredTriggers;
+/*
+ * Per-trigger-event data
+ *
+ * Note: ate_firing_id is meaningful when either AFTER_TRIGGER_DONE
+ * or AFTER_TRIGGER_IN_PROGRESS is set.  It indicates which trigger firing
+ * cycle the trigger was or will be fired in.
+ */
+typedef struct AfterTriggerEventData *AfterTriggerEvent;
 
-static DeferredTriggers deferredTriggers;
+typedef struct AfterTriggerEventData
+{
+	AfterTriggerEvent ate_next;			/* list link */
+	TriggerEvent ate_event;				/* event type and status bits */
+	CommandId	ate_firing_id;			/* ID for firing cycle */
+	Oid			ate_tgoid;				/* the trigger's ID */
+	Oid			ate_relid;				/* the relation it's on */
+	ItemPointerData ate_oldctid;		/* specific tuple(s) involved */
+	ItemPointerData ate_newctid;
+} AfterTriggerEventData;
+
+/* A list of events */
+typedef struct AfterTriggerEventList
+{
+	AfterTriggerEvent head;
+	AfterTriggerEvent tail;
+} AfterTriggerEventList;
 
 
-static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
-					Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
-					   MemoryContext per_tuple_context);
-static DeferredTriggerState DeferredTriggerStateCreate(int numalloc);
-static DeferredTriggerState DeferredTriggerStateCopy(DeferredTriggerState state);
-static DeferredTriggerState DeferredTriggerStateAddItem(DeferredTriggerState state,
+/*
+ * All per-transaction data for the AFTER TRIGGERS module.
+ *
+ * AfterTriggersData has the following fields:
+ *
+ * firing_counter is incremented for each call of afterTriggerInvokeEvents.
+ * We mark firable events with the current firing cycle's ID so that we can
+ * tell which ones to work on.  This ensures sane behavior if a trigger
+ * function chooses to do SET CONSTRAINTS: the inner SET CONSTRAINTS will
+ * only fire those events that weren't already scheduled for firing.
+ *
+ * state keeps track of the transaction-local effects of SET CONSTRAINTS.
+ * This is saved and restored across failed subtransactions.
+ *
+ * events is the current list of deferred events.  This is global across
+ * all subtransactions of the current transaction.  In a subtransaction
+ * abort, we know that the events added by the subtransaction are at the
+ * end of the list, so it is relatively easy to discard them.
+ *
+ * query_depth is the current depth of nested AfterTriggerBeginQuery calls
+ * (-1 when the stack is empty).
+ *
+ * query_stack[query_depth] is a list of AFTER trigger events queued by the
+ * current query (and the query_stack entries below it are lists of trigger
+ * events queued by calling queries).  None of these are valid until the
+ * matching AfterTriggerEndQuery call occurs.  At that point we fire
+ * immediate-mode triggers, and append any deferred events to the main events
+ * list.
+ *
+ * maxquerydepth is just the allocated length of query_stack.
+ *
+ * state_stack is a stack of pointers to saved copies of the SET CONSTRAINTS
+ * state data; each subtransaction level that modifies that state first
+ * saves a copy, which we use to restore the state if we abort.
+ *
+ * events_stack is a stack of copies of the events head/tail pointers,
+ * which we use to restore those values during subtransaction abort.
+ *
+ * depth_stack is a stack of copies of subtransaction-start-time query_depth,
+ * which we similarly use to clean up at subtransaction abort.
+ *
+ * firing_stack is a stack of copies of subtransaction-start-time
+ * firing_counter.  We use this to recognize which deferred triggers were
+ * fired (or marked for firing) within an aborted subtransaction.
+ *
+ * We use GetCurrentTransactionNestLevel() to determine the correct array
+ * index in these stacks.  maxtransdepth is the number of allocated entries in
+ * each stack.  (By not keeping our own stack pointer, we can avoid trouble
+ * in cases where errors during subxact abort cause multiple invocations
+ * of AfterTriggerEndSubXact() at the same nesting depth.)
+ */
+typedef struct AfterTriggersData
+{
+	CommandId	firing_counter;			/* next firing ID to assign */
+	SetConstraintState state;			/* the active S C state */
+	AfterTriggerEventList events;		/* deferred-event list */
+	int			query_depth;			/* current query list index */
+	AfterTriggerEventList *query_stack;	/* events pending from each query */
+	int			maxquerydepth;			/* allocated len of above array */
+
+	/* these fields are just for resetting at subtrans abort: */
+
+	SetConstraintState *state_stack;	/* stacked S C states */
+	AfterTriggerEventList *events_stack; /* stacked list pointers */
+	int		   *depth_stack;			/* stacked query_depths */
+	CommandId  *firing_stack;			/* stacked firing_counters */
+	int			maxtransdepth;			/* allocated len of above arrays */
+} AfterTriggersData;
+
+typedef AfterTriggersData *AfterTriggers;
+
+static AfterTriggers afterTriggers;
+
+
+static void AfterTriggerExecute(AfterTriggerEvent event,
+								Relation rel, TriggerDesc *trigdesc,
+								FmgrInfo *finfo,
+								MemoryContext per_tuple_context);
+static SetConstraintState SetConstraintStateCreate(int numalloc);
+static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
+static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
 							Oid tgoid, bool tgisdeferred);
 
 
 /* ----------
- * deferredTriggerCheckState()
+ * afterTriggerCheckState()
  *
  *	Returns true if the trigger identified by tgoid is actually
  *	in state DEFERRED.
  * ----------
  */
 static bool
-deferredTriggerCheckState(Oid tgoid, int32 itemstate)
+afterTriggerCheckState(Oid tgoid, TriggerEvent eventstate)
 {
-	DeferredTriggerState state = deferredTriggers->state;
+	SetConstraintState state = afterTriggers->state;
 	int			i;
 
 	/*
 	 * For not-deferrable triggers (i.e. normal AFTER ROW triggers and
 	 * constraints declared NOT DEFERRABLE), the state is always false.
 	 */
-	if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0)
+	if ((eventstate & AFTER_TRIGGER_DEFERRABLE) == 0)
 		return false;
 
 	/*
@@ -1787,8 +1821,8 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
 	 */
 	for (i = 0; i < state->numstates; i++)
 	{
-		if (state->trigstates[i].dts_tgoid == tgoid)
-			return state->trigstates[i].dts_tgisdeferred;
+		if (state->trigstates[i].sct_tgoid == tgoid)
+			return state->trigstates[i].sct_tgisdeferred;
 	}
 
 	/*
@@ -1800,37 +1834,43 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
 	/*
 	 * Otherwise return the default state for the trigger.
 	 */
-	return ((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0);
+	return ((eventstate & AFTER_TRIGGER_INITDEFERRED) != 0);
 }
 
 
 /* ----------
- * deferredTriggerAddEvent()
+ * afterTriggerAddEvent()
  *
- *	Add a new trigger event to the queue.
+ *	Add a new trigger event to the current query's queue.
  * ----------
  */
 static void
-deferredTriggerAddEvent(DeferredTriggerEvent event)
+afterTriggerAddEvent(AfterTriggerEvent event)
 {
-	Assert(event->dte_next == NULL);
+	AfterTriggerEventList *events;
+
+	Assert(event->ate_next == NULL);
 
-	if (deferredTriggers->tail_thisxact == NULL)
+	/* Must be inside a query */
+	Assert(afterTriggers->query_depth >= 0);
+
+	events = &afterTriggers->query_stack[afterTriggers->query_depth];
+	if (events->tail == NULL)
 	{
 		/* first list entry */
-		deferredTriggers->events = event;
-		deferredTriggers->tail_thisxact = event;
+		events->head = event;
+		events->tail = event;
 	}
 	else
 	{
-		deferredTriggers->tail_thisxact->dte_next = event;
-		deferredTriggers->tail_thisxact = event;
+		events->tail->ate_next = event;
+		events->tail = event;
 	}
 }
 
 
 /* ----------
- * DeferredTriggerExecute()
+ * AfterTriggerExecute()
  *
  *	Fetch the required tuples back from the heap and fire one
  *	single trigger function.
@@ -1840,7 +1880,6 @@ deferredTriggerAddEvent(DeferredTriggerEvent event)
  *	fmgr lookup cache space at the caller level.
  *
  *	event: event currently being fired.
- *	itemno: item within event currently being fired.
  *	rel: open relation for event.
  *	trigdesc: working copy of rel's trigger info.
  *	finfo: array of fmgr lookup cache entries (one per trigger in trigdesc).
@@ -1848,11 +1887,11 @@ deferredTriggerAddEvent(DeferredTriggerEvent event)
  * ----------
  */
 static void
-DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
+AfterTriggerExecute(AfterTriggerEvent event,
 					Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
-					   MemoryContext per_tuple_context)
+					MemoryContext per_tuple_context)
 {
-	Oid			tgoid = event->dte_item[itemno].dti_tgoid;
+	Oid			tgoid = event->ate_tgoid;
 	TriggerData LocTriggerData;
 	HeapTupleData oldtuple;
 	HeapTupleData newtuple;
@@ -1864,26 +1903,26 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
 	/*
 	 * Fetch the required OLD and NEW tuples.
 	 */
-	if (ItemPointerIsValid(&(event->dte_oldctid)))
+	if (ItemPointerIsValid(&(event->ate_oldctid)))
 	{
-		ItemPointerCopy(&(event->dte_oldctid), &(oldtuple.t_self));
+		ItemPointerCopy(&(event->ate_oldctid), &(oldtuple.t_self));
 		if (!heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer, false, NULL))
-			elog(ERROR, "failed to fetch old tuple for deferred trigger");
+			elog(ERROR, "failed to fetch old tuple for AFTER trigger");
 	}
 
-	if (ItemPointerIsValid(&(event->dte_newctid)))
+	if (ItemPointerIsValid(&(event->ate_newctid)))
 	{
-		ItemPointerCopy(&(event->dte_newctid), &(newtuple.t_self));
+		ItemPointerCopy(&(event->ate_newctid), &(newtuple.t_self));
 		if (!heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer, false, NULL))
-			elog(ERROR, "failed to fetch new tuple for deferred trigger");
+			elog(ERROR, "failed to fetch new tuple for AFTER trigger");
 	}
 
 	/*
 	 * Setup the trigger information
 	 */
 	LocTriggerData.type = T_TriggerData;
-	LocTriggerData.tg_event = (event->dte_event & TRIGGER_EVENT_OPMASK) |
-		(event->dte_event & TRIGGER_EVENT_ROW);
+	LocTriggerData.tg_event =
+		event->ate_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW);
 	LocTriggerData.tg_relation = rel;
 
 	LocTriggerData.tg_trigger = NULL;
@@ -1898,7 +1937,7 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
 	if (LocTriggerData.tg_trigger == NULL)
 		elog(ERROR, "could not find trigger %u", tgoid);
 
-	switch (event->dte_event & TRIGGER_EVENT_OPMASK)
+	switch (event->ate_event & TRIGGER_EVENT_OPMASK)
 	{
 		case TRIGGER_EVENT_INSERT:
 			LocTriggerData.tg_trigtuple = &newtuple;
@@ -1916,6 +1955,8 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
 			break;
 	}
 
+	MemoryContextReset(per_tuple_context);
+
 	/*
 	 * Call the trigger and throw away any eventually returned updated
 	 * tuple.
@@ -1929,290 +1970,409 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
 	/*
 	 * Release buffers
 	 */
-	if (ItemPointerIsValid(&(event->dte_oldctid)))
+	if (ItemPointerIsValid(&(event->ate_oldctid)))
 		ReleaseBuffer(oldbuffer);
-	if (ItemPointerIsValid(&(event->dte_newctid)))
+	if (ItemPointerIsValid(&(event->ate_newctid)))
 		ReleaseBuffer(newbuffer);
 }
 
 
+/*
+ * afterTriggerMarkEvents()
+ *
+ *	Scan the given event list for not yet invoked events.  Mark the ones
+ *	that can be invoked now with the current firing ID.
+ *
+ *	If move_list isn't NULL, events that are not to be invoked now are
+ *	removed from the given list and appended to move_list.
+ *
+ *	When immediate_only is TRUE, do not invoke currently-deferred triggers.
+ *	(This will be FALSE only at main transaction exit.)
+ *
+ *	Returns TRUE if any invokable events were found.
+ */
+static bool
+afterTriggerMarkEvents(AfterTriggerEventList *events,
+					   AfterTriggerEventList *move_list,
+					   bool immediate_only)
+{
+	bool		found = false;
+	AfterTriggerEvent event,
+				prev_event;
+
+	prev_event = NULL;
+	event = events->head;
+
+	while (event != NULL)
+	{
+		bool		defer_it = false;
+		AfterTriggerEvent next_event;
+
+		if (!(event->ate_event &
+			  (AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS)))
+		{
+			/*
+			 * This trigger hasn't been called or scheduled yet. Check if we
+			 * should call it now.
+			 */
+			if (immediate_only &&
+				afterTriggerCheckState(event->ate_tgoid, event->ate_event))
+			{
+				defer_it = true;
+			}
+			else
+			{
+				/*
+				 * Mark it as to be fired in this firing cycle.
+				 */
+				event->ate_firing_id = afterTriggers->firing_counter;
+				event->ate_event |= AFTER_TRIGGER_IN_PROGRESS;
+				found = true;
+			}
+		}
+
+		/*
+		 * If it's deferred, move it to move_list, if requested.
+		 */
+		next_event = event->ate_next;
+
+		if (defer_it && move_list != NULL)
+		{
+			/* Delink it from input list */
+			if (prev_event)
+				prev_event->ate_next = next_event;
+			else
+				events->head = next_event;
+			/* and add it to move_list */
+			event->ate_next = NULL;
+			if (move_list->tail == NULL)
+			{
+				/* first list entry */
+				move_list->head = event;
+				move_list->tail = event;
+			}
+			else
+			{
+				move_list->tail->ate_next = event;
+				move_list->tail = event;
+			}
+		}
+		else
+		{
+			/* Keep it in input list */
+			prev_event = event;
+		}
+
+		event = next_event;
+	}
+
+	/* Update list tail pointer in case we moved tail event */
+	events->tail = prev_event;
+
+	return found;
+}
+
 /* ----------
- * deferredTriggerInvokeEvents()
+ * afterTriggerInvokeEvents()
  *
- *	Scan the event queue for not yet invoked triggers. Check if they
- *	should be invoked now and do so.
+ *	Scan the given event list for events that are marked as to be fired
+ *	in the current firing cycle, and fire them.
+ *
+ *	When delete_ok is TRUE, it's okay to delete fully-processed events.
+ *	The events list pointers are updated.
  * ----------
  */
 static void
-deferredTriggerInvokeEvents(bool immediate_only)
+afterTriggerInvokeEvents(AfterTriggerEventList *events,
+						 CommandId firing_id,
+						 bool delete_ok)
 {
-	DeferredTriggerEvent event,
+	AfterTriggerEvent event,
 				prev_event;
 	MemoryContext per_tuple_context;
 	Relation	rel = NULL;
 	TriggerDesc *trigdesc = NULL;
 	FmgrInfo   *finfo = NULL;
 
-	/*
-	 * If immediate_only is true, we remove fully-processed events from
-	 * the event queue to recycle space.  If immediate_only is false, we
-	 * are going to discard the whole event queue on return anyway, so no
-	 * need to bother with "retail" pfree's.
-	 *
-	 * If immediate_only is true, we need only scan from where the end of the
-	 * queue was at the previous deferredTriggerInvokeEvents call; any
-	 * non-deferred events before that point are already fired. (But if
-	 * the deferral state changes, we must reset the saved position to the
-	 * beginning of the queue, so as to process all events once with the
-	 * new states.	See DeferredTriggerSetState.)
-	 */
-
 	/* Make a per-tuple memory context for trigger function calls */
 	per_tuple_context =
 		AllocSetContextCreate(CurrentMemoryContext,
-							  "DeferredTriggerTupleContext",
+							  "AfterTriggerTupleContext",
 							  ALLOCSET_DEFAULT_MINSIZE,
 							  ALLOCSET_DEFAULT_INITSIZE,
 							  ALLOCSET_DEFAULT_MAXSIZE);
 
-	/*
-	 * If immediate_only is true, then the only events that could need
-	 * firing are those since events_imm.  (But if events_imm is NULL, we
-	 * must scan the entire list.)
-	 */
-	if (immediate_only && deferredTriggers->events_imm != NULL)
-	{
-		prev_event = deferredTriggers->events_imm;
-		event = prev_event->dte_next;
-	}
-	else
-	{
-		prev_event = NULL;
-		event = deferredTriggers->events;
-	}
+	prev_event = NULL;
+	event = events->head;
 
 	while (event != NULL)
 	{
-		bool		still_deferred_ones = false;
-		DeferredTriggerEvent next_event;
-		int			i;
+		AfterTriggerEvent next_event;
 
 		/*
-		 * Skip executing cancelled events, and events already done,
-		 * unless they were done by a subtransaction that later aborted.
+		 * Is it one for me to fire?
 		 */
-		if (!(event->dte_event & TRIGGER_DEFERRED_CANCELED) &&
-			!(event->dte_event & TRIGGER_DEFERRED_DONE &&
-			  TransactionIdIsValid(event->dte_done_xid) &&
-			  !TransactionIdDidAbort(event->dte_done_xid)))
+		if ((event->ate_event & AFTER_TRIGGER_IN_PROGRESS) &&
+			event->ate_firing_id == firing_id)
 		{
-			MemoryContextReset(per_tuple_context);
-
 			/*
-			 * Check each trigger item in the event.
+			 * So let's fire it... but first, open the correct
+			 * relation if this is not the same relation as before.
 			 */
-			for (i = 0; i < event->dte_n_items; i++)
+			if (rel == NULL || rel->rd_id != event->ate_relid)
 			{
-				if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE &&
-				 TransactionIdIsValid(event->dte_item[i].dti_done_xid) &&
-				!(TransactionIdDidAbort(event->dte_item[i].dti_done_xid)))
-					continue;
+				if (rel)
+					heap_close(rel, NoLock);
+				if (trigdesc)
+					FreeTriggerDesc(trigdesc);
+				if (finfo)
+					pfree(finfo);
 
 				/*
-				 * This trigger item hasn't been called yet. Check if we
-				 * should call it now.
+				 * We assume that an appropriate lock is still held by
+				 * the executor, so grab no new lock here.
 				 */
-				if (immediate_only &&
-				  deferredTriggerCheckState(event->dte_item[i].dti_tgoid,
-											event->dte_item[i].dti_state))
-				{
-					still_deferred_ones = true;
-					continue;
-				}
+				rel = heap_open(event->ate_relid, NoLock);
 
 				/*
-				 * So let's fire it... but first, open the correct
-				 * relation if this is not the same relation as before.
+				 * Copy relation's trigger info so that we have a
+				 * stable copy no matter what the called triggers do.
 				 */
-				if (rel == NULL || rel->rd_id != event->dte_relid)
-				{
-					if (rel)
-						heap_close(rel, NoLock);
-					FreeTriggerDesc(trigdesc);
-					if (finfo)
-						pfree(finfo);
-
-					/*
-					 * We assume that an appropriate lock is still held by
-					 * the executor, so grab no new lock here.
-					 */
-					rel = heap_open(event->dte_relid, NoLock);
-
-					/*
-					 * Copy relation's trigger info so that we have a
-					 * stable copy no matter what the called triggers do.
-					 */
-					trigdesc = CopyTriggerDesc(rel->trigdesc);
-
-					if (trigdesc == NULL)		/* should not happen */
-						elog(ERROR, "relation %u has no triggers",
-							 event->dte_relid);
-
-					/*
-					 * Allocate space to cache fmgr lookup info for
-					 * triggers.
-					 */
-					finfo = (FmgrInfo *)
-						palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
-				}
+				trigdesc = CopyTriggerDesc(rel->trigdesc);
+
+				if (trigdesc == NULL)		/* should not happen */
+					elog(ERROR, "relation %u has no triggers",
+						 event->ate_relid);
+
+				/*
+				 * Allocate space to cache fmgr lookup info for
+				 * triggers.
+				 */
+				finfo = (FmgrInfo *)
+					palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
+			}
 
-				DeferredTriggerExecute(event, i, rel, trigdesc, finfo,
-									   per_tuple_context);
+			/*
+			 * Fire it.  Note that the AFTER_TRIGGER_IN_PROGRESS flag is still
+			 * set, so recursive examinations of the event list won't try
+			 * to re-fire it.
+			 */
+			AfterTriggerExecute(event, rel, trigdesc, finfo,
+								per_tuple_context);
 
-				event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
-				event->dte_item[i].dti_done_xid = GetCurrentTransactionId();
-			}					/* end loop over items within event */
+			/*
+			 * Mark the event as done.
+			 */
+			event->ate_event &= ~AFTER_TRIGGER_IN_PROGRESS;
+			event->ate_event |= AFTER_TRIGGER_DONE;
 		}
 
 		/*
-		 * If it's now completely done, throw it away.
+		 * If it's now done, throw it away, if allowed.
 		 *
-		 * NB: it's possible the trigger calls above added more events to the
+		 * NB: it's possible the trigger call above added more events to the
 		 * queue, or that calls we will do later will want to add more, so
-		 * we have to be careful about maintaining list validity here.
+		 * we have to be careful about maintaining list validity at all
+		 * points here.
 		 */
-		next_event = event->dte_next;
+		next_event = event->ate_next;
 
-		if (still_deferred_ones || !immediate_only)
+		if ((event->ate_event & AFTER_TRIGGER_DONE) && delete_ok)
 		{
-			/* Not done, keep in list */
-			prev_event = event;
+			/* Delink it from list and free it */
+			if (prev_event)
+				prev_event->ate_next = next_event;
+			else
+				events->head = next_event;
+			pfree(event);
 		}
 		else
 		{
-			/*
-			 * We can drop an item if it's done, but only if we're not
-			 * inside a subtransaction because it could abort later on. We
-			 * will want to check the item again if it does.
-			 */
-			if (!IsSubTransaction())
-			{
-				/* delink it from list and free it */
-				if (prev_event)
-					prev_event->dte_next = next_event;
-				else
-					deferredTriggers->events = next_event;
-				pfree(event);
-			}
-			else
-			{
-				/*
-				 * Mark the event-as-a-whole done, but keep it in the list.
-				 */
-				event->dte_event |= TRIGGER_DEFERRED_DONE;
-				event->dte_done_xid = GetCurrentTransactionId();
-				prev_event = event;
-			}
+			/* Keep it in list */
+			prev_event = event;
 		}
 
 		event = next_event;
 	}
 
 	/* Update list tail pointer in case we just deleted tail event */
-	deferredTriggers->tail_thisxact = prev_event;
-
-	/* Set the immediate event pointer for next time */
-	deferredTriggers->events_imm = prev_event;
+	events->tail = prev_event;
 
 	/* Release working resources */
 	if (rel)
 		heap_close(rel, NoLock);
-	FreeTriggerDesc(trigdesc);
+	if (trigdesc)
+		FreeTriggerDesc(trigdesc);
 	if (finfo)
 		pfree(finfo);
 	MemoryContextDelete(per_tuple_context);
 }
 
+
 /* ----------
- * DeferredTriggerBeginXact()
+ * AfterTriggerBeginXact()
  *
  *	Called at transaction start (either BEGIN or implicit for single
  *	statement outside of transaction block).
  * ----------
  */
 void
-DeferredTriggerBeginXact(void)
+AfterTriggerBeginXact(void)
 {
-	Assert(deferredTriggers == NULL);
+	Assert(afterTriggers == NULL);
+
+	/*
+	 * Build empty after-trigger state structure
+	 */
+	afterTriggers = (AfterTriggers)
+		MemoryContextAlloc(TopTransactionContext,
+						   sizeof(AfterTriggersData));
+
+	afterTriggers->firing_counter = FirstCommandId;
+	afterTriggers->state = SetConstraintStateCreate(8);
+	afterTriggers->events.head = NULL;
+	afterTriggers->events.tail = NULL;
+	afterTriggers->query_depth = -1;
 
-	deferredTriggers = (DeferredTriggers)
+	/* We initialize the query stack to a reasonable size */
+	afterTriggers->query_stack = (AfterTriggerEventList *)
 		MemoryContextAlloc(TopTransactionContext,
-						   sizeof(DeferredTriggersData));
+						   8 * sizeof(AfterTriggerEventList));
+	afterTriggers->maxquerydepth = 8;
+
+	/* Subtransaction stack is empty until/unless needed */
+	afterTriggers->state_stack = NULL;
+	afterTriggers->events_stack = NULL;
+	afterTriggers->depth_stack = NULL;
+	afterTriggers->firing_stack = NULL;
+	afterTriggers->maxtransdepth = 0;
+}
+
+
+/* ----------
+ * AfterTriggerBeginQuery()
+ *
+ *	Called just before we start processing a single query within a
+ *	transaction (or subtransaction).  Set up to record AFTER trigger
+ *	events queued by the query.  Note that it is allowed to have
+ *	nested queries within a (sub)transaction.
+ * ----------
+ */
+void
+AfterTriggerBeginQuery(void)
+{
+	/* Must be inside a transaction */
+	Assert(afterTriggers != NULL);
+
+	/* Increase the query stack depth */
+	afterTriggers->query_depth++;
 
 	/*
-	 * If unspecified, constraints default to IMMEDIATE, per SQL
+	 * Allocate more space in the query stack if needed.
 	 */
-	deferredTriggers->state = DeferredTriggerStateCreate(8);
-	deferredTriggers->events = NULL;
-	deferredTriggers->events_imm = NULL;
-	deferredTriggers->tail_thisxact = NULL;
-	deferredTriggers->tail_stack = NULL;
-	deferredTriggers->imm_stack = NULL;
-	deferredTriggers->state_stack = NULL;
-	deferredTriggers->numalloc = 0;
+	if (afterTriggers->query_depth >= afterTriggers->maxquerydepth)
+	{
+		/* repalloc will keep the stack in the same context */
+		int		new_alloc = afterTriggers->maxquerydepth * 2;
+
+		afterTriggers->query_stack = (AfterTriggerEventList *)
+			repalloc(afterTriggers->query_stack,
+					 new_alloc * sizeof(AfterTriggerEventList));
+		afterTriggers->maxquerydepth = new_alloc;
+	}
+
+	/* Initialize this query's list to empty */
+	afterTriggers->query_stack[afterTriggers->query_depth].head = NULL;
+	afterTriggers->query_stack[afterTriggers->query_depth].tail = NULL;
 }
 
 
 /* ----------
- * DeferredTriggerEndQuery()
+ * AfterTriggerEndQuery()
  *
- *	Called after one query sent down by the user has completely been
- *	processed. At this time we invoke all outstanding IMMEDIATE triggers.
+ *	Called after one query has been completely processed. At this time
+ *	we invoke all AFTER IMMEDIATE trigger events queued by the query, and
+ *	transfer deferred trigger events to the global deferred-trigger list.
  * ----------
  */
 void
-DeferredTriggerEndQuery(void)
+AfterTriggerEndQuery(void)
 {
+	AfterTriggerEventList *events;
+
+	/* Must be inside a transaction */
+	Assert(afterTriggers != NULL);
+
+	/* Must be inside a query, too */
+	Assert(afterTriggers->query_depth >= 0);
+
 	/*
-	 * Ignore call if we aren't in a transaction.
+	 * Process all immediate-mode triggers queued by the query, and move
+	 * the deferred ones to the main list of deferred events.
+	 *
+	 * Notice that we decide which ones will be fired, and put the deferred
+	 * ones on the main list, before anything is actually fired.  This
+	 * ensures reasonably sane behavior if a trigger function does
+	 * SET CONSTRAINTS ... IMMEDIATE: all events we have decided to defer
+	 * will be available for it to fire.
+	 *
+	 * If we find no firable events, we don't have to increment firing_counter.
 	 */
-	if (deferredTriggers == NULL)
-		return;
+	events = &afterTriggers->query_stack[afterTriggers->query_depth];
+	if (afterTriggerMarkEvents(events, &afterTriggers->events, true))
+	{
+		CommandId		firing_id = afterTriggers->firing_counter++;
+
+		/* OK to delete the immediate events after processing them */
+		afterTriggerInvokeEvents(events, firing_id, true);
+	}
 
-	deferredTriggerInvokeEvents(true);
+	afterTriggers->query_depth--;
 }
 
 
 /* ----------
- * DeferredTriggerEndXact()
+ * AfterTriggerEndXact()
  *
  *	Called just before the current transaction is committed. At this
  *	time we invoke all DEFERRED triggers and tidy up.
  * ----------
  */
 void
-DeferredTriggerEndXact(void)
+AfterTriggerEndXact(void)
 {
+	AfterTriggerEventList *events;
+
+	/* Must be inside a transaction */
+	Assert(afterTriggers != NULL);
+
+	/* ... but not inside a query */
+	Assert(afterTriggers->query_depth == -1);
+
 	/*
-	 * Ignore call if we aren't in a transaction.
+	 * Run all the remaining triggers.  Loop until they are all gone,
+	 * just in case some trigger queues more for us to do.
 	 */
-	if (deferredTriggers == NULL)
-		return;
+	events = &afterTriggers->events;
+	while (afterTriggerMarkEvents(events, NULL, false))
+	{
+		CommandId		firing_id = afterTriggers->firing_counter++;
 
-	deferredTriggerInvokeEvents(false);
+		afterTriggerInvokeEvents(events, firing_id, true);
+	}
 
 	/*
-	 * Forget everything we know about deferred triggers.
+	 * Forget everything we know about AFTER triggers.
 	 *
 	 * Since all the info is in TopTransactionContext or children thereof, we
 	 * need do nothing special to reclaim memory.
 	 */
-	deferredTriggers = NULL;
+	afterTriggers = NULL;
 }
 
 
 /* ----------
- * DeferredTriggerAbortXact()
+ * AfterTriggerAbortXact()
  *
  *	The current transaction has entered the abort state.
  *	All outstanding triggers are canceled so we simply throw
@@ -2220,139 +2380,147 @@ DeferredTriggerEndXact(void)
  * ----------
  */
 void
-DeferredTriggerAbortXact(void)
+AfterTriggerAbortXact(void)
 {
 	/*
-	 * Ignore call if we aren't in a transaction.
+	 * Ignore call if we aren't in a transaction.  (Need this to survive
+	 * repeat call in case of error during transaction abort.)
 	 */
-	if (deferredTriggers == NULL)
+	if (afterTriggers == NULL)
 		return;
 
 	/*
-	 * Forget everything we know about deferred triggers.
+	 * Forget everything we know about AFTER triggers.
 	 *
 	 * Since all the info is in TopTransactionContext or children thereof, we
 	 * need do nothing special to reclaim memory.
 	 */
-	deferredTriggers = NULL;
+	afterTriggers = NULL;
 }
 
 /*
- * DeferredTriggerBeginSubXact()
+ * AfterTriggerBeginSubXact()
  *
  *	Start a subtransaction.
  */
 void
-DeferredTriggerBeginSubXact(void)
+AfterTriggerBeginSubXact(void)
 {
 	int			my_level = GetCurrentTransactionNestLevel();
 
 	/*
-	 * Ignore call if the transaction is in aborted state.
+	 * Ignore call if the transaction is in aborted state.  (Probably
+	 * shouldn't happen?)
 	 */
-	if (deferredTriggers == NULL)
+	if (afterTriggers == NULL)
 		return;
 
 	/*
 	 * Allocate more space in the stacks if needed.
 	 */
-	while (my_level >= deferredTriggers->numalloc)
+	while (my_level >= afterTriggers->maxtransdepth)
 	{
-		if (deferredTriggers->numalloc == 0)
+		if (afterTriggers->maxtransdepth == 0)
 		{
 			MemoryContext old_cxt;
 
 			old_cxt = MemoryContextSwitchTo(TopTransactionContext);
 
 #define DEFTRIG_INITALLOC 8
-			deferredTriggers->tail_stack = (DeferredTriggerEvent *)
-				palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent));
-			deferredTriggers->imm_stack = (DeferredTriggerEvent *)
-				palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent));
-			deferredTriggers->state_stack = (DeferredTriggerState *)
-				palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerState));
-			deferredTriggers->numalloc = DEFTRIG_INITALLOC;
+			afterTriggers->state_stack = (SetConstraintState *)
+				palloc(DEFTRIG_INITALLOC * sizeof(SetConstraintState));
+			afterTriggers->events_stack = (AfterTriggerEventList *)
+				palloc(DEFTRIG_INITALLOC * sizeof(AfterTriggerEventList));
+			afterTriggers->depth_stack = (int *)
+				palloc(DEFTRIG_INITALLOC * sizeof(int));
+			afterTriggers->firing_stack = (CommandId *)
+				palloc(DEFTRIG_INITALLOC * sizeof(CommandId));
+			afterTriggers->maxtransdepth = DEFTRIG_INITALLOC;
 
 			MemoryContextSwitchTo(old_cxt);
 		}
 		else
 		{
 			/* repalloc will keep the stacks in the same context */
-			int		new_alloc = deferredTriggers->numalloc * 2;
-
-			deferredTriggers->tail_stack = (DeferredTriggerEvent *)
-				repalloc(deferredTriggers->tail_stack,
-						 new_alloc * sizeof(DeferredTriggerEvent));
-			deferredTriggers->imm_stack = (DeferredTriggerEvent *)
-				repalloc(deferredTriggers->imm_stack,
-						 new_alloc * sizeof(DeferredTriggerEvent));
-			deferredTriggers->state_stack = (DeferredTriggerState *)
-				repalloc(deferredTriggers->state_stack,
-						 new_alloc * sizeof(DeferredTriggerState));
-			deferredTriggers->numalloc = new_alloc;
+			int		new_alloc = afterTriggers->maxtransdepth * 2;
+
+			afterTriggers->state_stack = (SetConstraintState *)
+				repalloc(afterTriggers->state_stack,
+						 new_alloc * sizeof(SetConstraintState));
+			afterTriggers->events_stack = (AfterTriggerEventList *)
+				repalloc(afterTriggers->events_stack,
+						 new_alloc * sizeof(AfterTriggerEventList));
+			afterTriggers->depth_stack = (int *)
+				repalloc(afterTriggers->depth_stack,
+						 new_alloc * sizeof(int));
+			afterTriggers->firing_stack = (CommandId *)
+				repalloc(afterTriggers->firing_stack,
+						 new_alloc * sizeof(CommandId));
+			afterTriggers->maxtransdepth = new_alloc;
 		}
 	}
 
 	/*
-	 * Push the current information into the stack.
+	 * Push the current information into the stack.  The SET CONSTRAINTS
+	 * state is not saved until/unless changed.
 	 */
-	deferredTriggers->tail_stack[my_level] = deferredTriggers->tail_thisxact;
-	deferredTriggers->imm_stack[my_level] = deferredTriggers->events_imm;
-	/* State is not saved until/unless changed */
-	deferredTriggers->state_stack[my_level] = NULL;
+	afterTriggers->state_stack[my_level] = NULL;
+	afterTriggers->events_stack[my_level] = afterTriggers->events;
+	afterTriggers->depth_stack[my_level] = afterTriggers->query_depth;
+	afterTriggers->firing_stack[my_level] = afterTriggers->firing_counter;
 }
 
 /*
- * DeferredTriggerEndSubXact()
+ * AfterTriggerEndSubXact()
  *
  *	The current subtransaction is ending.
  */
 void
-DeferredTriggerEndSubXact(bool isCommit)
+AfterTriggerEndSubXact(bool isCommit)
 {
 	int			my_level = GetCurrentTransactionNestLevel();
-	DeferredTriggerState state;
+	SetConstraintState state;
+	AfterTriggerEvent event;
+	CommandId	subxact_firing_id;
 
 	/*
-	 * Ignore call if the transaction is in aborted state.
+	 * Ignore call if the transaction is in aborted state.  (Probably unneeded)
 	 */
-	if (deferredTriggers == NULL)
+	if (afterTriggers == NULL)
 		return;
 
 	/*
 	 * Pop the prior state if needed.
 	 */
-	Assert(my_level < deferredTriggers->numalloc);
+	Assert(my_level < afterTriggers->maxtransdepth);
 
 	if (isCommit)
 	{
 		/* If we saved a prior state, we don't need it anymore */
-		state = deferredTriggers->state_stack[my_level];
+		state = afterTriggers->state_stack[my_level];
 		if (state != NULL)
 			pfree(state);
 		/* this avoids double pfree if error later: */
-		deferredTriggers->state_stack[my_level] = NULL;
+		afterTriggers->state_stack[my_level] = NULL;
+		Assert(afterTriggers->query_depth ==
+			   afterTriggers->depth_stack[my_level]);
 	}
 	else
 	{
 		/*
 		 * Aborting --- restore the pointers from the stacks.
 		 */
-		deferredTriggers->tail_thisxact =
-			deferredTriggers->tail_stack[my_level];
-		deferredTriggers->events_imm =
-			deferredTriggers->imm_stack[my_level];
+		afterTriggers->events = afterTriggers->events_stack[my_level];
+		afterTriggers->query_depth = afterTriggers->depth_stack[my_level];
 
 		/*
-		 * Cleanup the head and the tail of the list.
+		 * Cleanup the tail of the list.
 		 */
-		if (deferredTriggers->tail_thisxact == NULL)
-			deferredTriggers->events = NULL;
-		else
-			deferredTriggers->tail_thisxact->dte_next = NULL;
+		if (afterTriggers->events.tail != NULL)
+			afterTriggers->events.tail->ate_next = NULL;
 
 		/*
-		 * We don't need to free the items, since the
+		 * We don't need to free the subtransaction's items, since the
 		 * CurTransactionContext will be reset shortly.
 		 */
 
@@ -2360,24 +2528,46 @@ DeferredTriggerEndSubXact(bool isCommit)
 		 * Restore the trigger state.  If the saved state is NULL, then
 		 * this subxact didn't save it, so it doesn't need restoring.
 		 */
-		state = deferredTriggers->state_stack[my_level];
+		state = afterTriggers->state_stack[my_level];
 		if (state != NULL)
 		{
-			pfree(deferredTriggers->state);
-			deferredTriggers->state = state;
+			pfree(afterTriggers->state);
+			afterTriggers->state = state;
 		}
 		/* this avoids double pfree if error later: */
-		deferredTriggers->state_stack[my_level] = NULL;
+		afterTriggers->state_stack[my_level] = NULL;
+
+		/*
+		 * Scan for any remaining deferred events that were marked DONE
+		 * or IN PROGRESS by this subxact or a child, and un-mark them.
+		 * We can recognize such events because they have a firing ID
+		 * greater than or equal to the firing_counter value we saved at
+		 * subtransaction start.  (This essentially assumes that the
+		 * current subxact includes all subxacts started after it.)
+		 */
+		subxact_firing_id = afterTriggers->firing_stack[my_level];
+		for (event = afterTriggers->events.head;
+			 event != NULL;
+			 event = event->ate_next)
+		{
+			if (event->ate_event &
+				(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS))
+			{
+				if (event->ate_firing_id >= subxact_firing_id)
+					event->ate_event &=
+						~(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS);
+			}
+		}
 	}
 }
 
 /*
- * Create an empty DeferredTriggerState with room for numalloc trigstates
+ * Create an empty SetConstraintState with room for numalloc trigstates
  */
-static DeferredTriggerState
-DeferredTriggerStateCreate(int numalloc)
+static SetConstraintState
+SetConstraintStateCreate(int numalloc)
 {
-	DeferredTriggerState state;
+	SetConstraintState state;
 
 	/* Behave sanely with numalloc == 0 */
 	if (numalloc <= 0)
@@ -2386,10 +2576,10 @@ DeferredTriggerStateCreate(int numalloc)
 	/*
 	 * We assume that zeroing will correctly initialize the state values.
 	 */
-	state = (DeferredTriggerState)
+	state = (SetConstraintState)
 		MemoryContextAllocZero(TopTransactionContext,
-							   sizeof(DeferredTriggerStateData) +
-					  (numalloc - 1) *sizeof(DeferredTriggerStatusData));
+							   sizeof(SetConstraintStateData) +
+					  (numalloc - 1) *sizeof(SetConstraintTriggerData));
 
 	state->numalloc = numalloc;
 
@@ -2397,67 +2587,67 @@ DeferredTriggerStateCreate(int numalloc)
 }
 
 /*
- * Copy a DeferredTriggerState
+ * Copy a SetConstraintState
  */
-static DeferredTriggerState
-DeferredTriggerStateCopy(DeferredTriggerState origstate)
+static SetConstraintState
+SetConstraintStateCopy(SetConstraintState origstate)
 {
-	DeferredTriggerState state;
+	SetConstraintState state;
 
-	state = DeferredTriggerStateCreate(origstate->numstates);
+	state = SetConstraintStateCreate(origstate->numstates);
 
 	state->all_isset = origstate->all_isset;
 	state->all_isdeferred = origstate->all_isdeferred;
 	state->numstates = origstate->numstates;
 	memcpy(state->trigstates, origstate->trigstates,
-		   origstate->numstates * sizeof(DeferredTriggerStatusData));
+		   origstate->numstates * sizeof(SetConstraintTriggerData));
 
 	return state;
 }
 
 /*
- * Add a per-trigger item to a DeferredTriggerState.  Returns possibly-changed
+ * Add a per-trigger item to a SetConstraintState.  Returns possibly-changed
  * pointer to the state object (it will change if we have to repalloc).
  */
-static DeferredTriggerState
-DeferredTriggerStateAddItem(DeferredTriggerState state,
-							Oid tgoid, bool tgisdeferred)
+static SetConstraintState
+SetConstraintStateAddItem(SetConstraintState state,
+						  Oid tgoid, bool tgisdeferred)
 {
 	if (state->numstates >= state->numalloc)
 	{
 		int			newalloc = state->numalloc * 2;
 
 		newalloc = Max(newalloc, 8);	/* in case original has size 0 */
-		state = (DeferredTriggerState)
+		state = (SetConstraintState)
 			repalloc(state,
-					 sizeof(DeferredTriggerStateData) +
-					 (newalloc - 1) *sizeof(DeferredTriggerStatusData));
+					 sizeof(SetConstraintStateData) +
+					 (newalloc - 1) *sizeof(SetConstraintTriggerData));
 		state->numalloc = newalloc;
 		Assert(state->numstates < state->numalloc);
 	}
 
-	state->trigstates[state->numstates].dts_tgoid = tgoid;
-	state->trigstates[state->numstates].dts_tgisdeferred = tgisdeferred;
+	state->trigstates[state->numstates].sct_tgoid = tgoid;
+	state->trigstates[state->numstates].sct_tgisdeferred = tgisdeferred;
 	state->numstates++;
 
 	return state;
 }
 
 /* ----------
- * DeferredTriggerSetState()
+ * AfterTriggerSetState()
  *
- *	Called for the SET CONSTRAINTS ... utility command.
+ *	Execute the SET CONSTRAINTS ... utility command.
  * ----------
  */
 void
-DeferredTriggerSetState(ConstraintsSetStmt *stmt)
+AfterTriggerSetState(ConstraintsSetStmt *stmt)
 {
 	int			my_level = GetCurrentTransactionNestLevel();
 
 	/*
-	 * Ignore call if we aren't in a transaction.
+	 * Ignore call if we aren't in a transaction.  (Shouldn't happen?)
 	 */
-	if (deferredTriggers == NULL)
+	if (afterTriggers == NULL)
 		return;
 
 	/*
@@ -2466,10 +2656,10 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
 	 * aborts.
 	 */
 	if (my_level > 1 &&
-		deferredTriggers->state_stack[my_level] == NULL)
+		afterTriggers->state_stack[my_level] == NULL)
 	{
-		deferredTriggers->state_stack[my_level] =
-			DeferredTriggerStateCopy(deferredTriggers->state);
+		afterTriggers->state_stack[my_level] =
+			SetConstraintStateCopy(afterTriggers->state);
 	}
 
 	/*
@@ -2480,13 +2670,13 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
 		/*
 		 * Forget any previous SET CONSTRAINTS commands in this transaction.
 		 */
-		deferredTriggers->state->numstates = 0;
+		afterTriggers->state->numstates = 0;
 
 		/*
 		 * Set the per-transaction ALL state to known.
 		 */
-		deferredTriggers->state->all_isset = true;
-		deferredTriggers->state->all_isdeferred = stmt->deferred;
+		afterTriggers->state->all_isset = true;
+		afterTriggers->state->all_isdeferred = stmt->deferred;
 	}
 	else
 	{
@@ -2569,29 +2759,28 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
 		heap_close(tgrel, AccessShareLock);
 
 		/*
-		 * Inside of a transaction block set the trigger states of
-		 * individual triggers on transaction level.
+		 * Set the trigger states of individual triggers for this xact.
 		 */
 		foreach(l, oidlist)
 		{
 			Oid			tgoid = lfirst_oid(l);
-			DeferredTriggerState state = deferredTriggers->state;
+			SetConstraintState state = afterTriggers->state;
 			bool		found = false;
 			int			i;
 
 			for (i = 0; i < state->numstates; i++)
 			{
-				if (state->trigstates[i].dts_tgoid == tgoid)
+				if (state->trigstates[i].sct_tgoid == tgoid)
 				{
-					state->trigstates[i].dts_tgisdeferred = stmt->deferred;
+					state->trigstates[i].sct_tgisdeferred = stmt->deferred;
 					found = true;
 					break;
 				}
 			}
 			if (!found)
 			{
-				deferredTriggers->state =
-					DeferredTriggerStateAddItem(state, tgoid, stmt->deferred);
+				afterTriggers->state =
+					SetConstraintStateAddItem(state, tgoid, stmt->deferred);
 			}
 		}
 	}
@@ -2600,20 +2789,35 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
 	 * SQL99 requires that when a constraint is set to IMMEDIATE, any
 	 * deferred checks against that constraint must be made when the SET
 	 * CONSTRAINTS command is executed -- i.e. the effects of the SET
-	 * CONSTRAINTS command applies retroactively. This happens "for free"
-	 * since we have already made the necessary modifications to the
-	 * constraints, and deferredTriggerEndQuery() is called by
-	 * finish_xact_command().  But we must reset
-	 * deferredTriggerInvokeEvents' tail pointer to make it rescan the
-	 * entire list, in case some deferred events are now immediately
-	 * invokable.
+	 * CONSTRAINTS command apply retroactively.  We've updated the
+	 * constraints state, so scan the list of previously deferred events
+	 * to fire any that have now become immediate.
+	 *
+	 * Obviously, if this was SET ... DEFERRED then it can't have converted
+	 * any unfired events to immediate, so we need do nothing in that case.
 	 */
-	deferredTriggers->events_imm = NULL;
+	if (!stmt->deferred)
+	{
+		AfterTriggerEventList *events = &afterTriggers->events;
+
+		if (afterTriggerMarkEvents(events, NULL, true))
+		{
+			CommandId		firing_id = afterTriggers->firing_counter++;
+
+			/*
+			 * We can delete fired events if we are at top transaction
+			 * level, but we'd better not if inside a subtransaction, since
+			 * the subtransaction could later get rolled back.
+			 */
+			afterTriggerInvokeEvents(events, firing_id,
+									 !IsSubTransaction());
+		}
+	}
 }
 
 
 /* ----------
- * DeferredTriggerSaveEvent()
+ * AfterTriggerSaveEvent()
  *
  *	Called by ExecA[RS]...Triggers() to add the event to the queue.
  *
@@ -2622,23 +2826,20 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
  * ----------
  */
 static void
-DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
-						 HeapTuple oldtup, HeapTuple newtup)
+AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
+					  HeapTuple oldtup, HeapTuple newtup)
 {
 	Relation	rel = relinfo->ri_RelationDesc;
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
-	MemoryContext oldcxt;
-	DeferredTriggerEvent new_event;
-	int			new_size;
+	AfterTriggerEvent new_event;
 	int			i;
 	int			ntriggers;
-	int			n_enabled_triggers = 0;
 	int		   *tgindx;
 	ItemPointerData oldctid;
 	ItemPointerData newctid;
 
-	if (deferredTriggers == NULL)
-		elog(ERROR, "DeferredTriggerSaveEvent() called outside of transaction");
+	if (afterTriggers == NULL)
+		elog(ERROR, "AfterTriggerSaveEvent() called outside of transaction");
 
 	/*
 	 * Get the CTID's of OLD and NEW
@@ -2652,6 +2853,9 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
 	else
 		ItemPointerSetInvalid(&(newctid));
 
+	/*
+	 * Scan the appropriate set of triggers
+	 */
 	if (row_trigger)
 	{
 		ntriggers = trigdesc->n_after_row[event];
@@ -2663,137 +2867,80 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
 		tgindx = trigdesc->tg_after_statement[event];
 	}
 
-	/*
-	 * Count the number of triggers that are actually enabled. Since we
-	 * only add enabled triggers to the queue, we only need allocate
-	 * enough space to hold them (and not any disabled triggers that may
-	 * be associated with the relation).
-	 */
 	for (i = 0; i < ntriggers; i++)
 	{
 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
 
-		if (trigger->tgenabled)
-			n_enabled_triggers++;
-	}
-
-	/*
-	 * If all the triggers on this relation are disabled, we're done.
-	 */
-	if (n_enabled_triggers == 0)
-		return;
-
-	/*
-	 * Create a new event.	We use the CurTransactionContext so the event
-	 * will automatically go away if the subtransaction aborts.
-	 */
-	oldcxt = MemoryContextSwitchTo(CurTransactionContext);
-
-	new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
-		n_enabled_triggers * sizeof(DeferredTriggerEventItem);
-
-	new_event = (DeferredTriggerEvent) palloc(new_size);
-	new_event->dte_next = NULL;
-	new_event->dte_event = event & TRIGGER_EVENT_OPMASK;
-	new_event->dte_done_xid = InvalidTransactionId;
-	if (row_trigger)
-		new_event->dte_event |= TRIGGER_EVENT_ROW;
-	new_event->dte_relid = rel->rd_id;
-	ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
-	ItemPointerCopy(&newctid, &(new_event->dte_newctid));
-	new_event->dte_n_items = ntriggers;
-	for (i = 0; i < ntriggers; i++)
-	{
-		DeferredTriggerEventItem *ev_item;
-		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
-
+		/* Ignore disabled triggers */
 		if (!trigger->tgenabled)
 			continue;
 
-		ev_item = &(new_event->dte_item[i]);
-		ev_item->dti_tgoid = trigger->tgoid;
-		ev_item->dti_done_xid = InvalidTransactionId;
-		ev_item->dti_state =
-			((trigger->tgdeferrable) ?
-			 TRIGGER_DEFERRED_DEFERRABLE : 0) |
-			((trigger->tginitdeferred) ?
-			 TRIGGER_DEFERRED_INITDEFERRED : 0);
-
-		if (row_trigger && (trigdesc->n_before_row[event] > 0))
-			ev_item->dti_state |= TRIGGER_DEFERRED_HAS_BEFORE;
-		else if (!row_trigger && (trigdesc->n_before_statement[event] > 0))
-			ev_item->dti_state |= TRIGGER_DEFERRED_HAS_BEFORE;
-	}
-
-	MemoryContextSwitchTo(oldcxt);
+		/*
+		 * If it is an RI UPDATE trigger, and the referenced keys have
+		 * not changed, short-circuit queuing of the event; there's no
+		 * need to fire the trigger.
+		 */
+		if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE)
+		{
+			bool		is_ri_trigger;
 
-	switch (event & TRIGGER_EVENT_OPMASK)
-	{
-		case TRIGGER_EVENT_INSERT:
-			/* nothing to do */
-			break;
+			switch (trigger->tgfoid)
+			{
+				case F_RI_FKEY_NOACTION_UPD:
+				case F_RI_FKEY_CASCADE_UPD:
+				case F_RI_FKEY_RESTRICT_UPD:
+				case F_RI_FKEY_SETNULL_UPD:
+				case F_RI_FKEY_SETDEFAULT_UPD:
+					is_ri_trigger = true;
+					break;
 
-		case TRIGGER_EVENT_UPDATE:
+				default:
+					is_ri_trigger = false;
+					break;
+			}
 
-			/*
-			 * Check if one of the referenced keys is changed.
-			 */
-			for (i = 0; i < ntriggers; i++)
+			if (is_ri_trigger)
 			{
-				Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
-				bool		is_ri_trigger;
-				bool		key_unchanged;
 				TriggerData LocTriggerData;
 
-				/*
-				 * We are interested in RI_FKEY triggers only.
-				 */
-				switch (trigger->tgfoid)
-				{
-					case F_RI_FKEY_NOACTION_UPD:
-					case F_RI_FKEY_CASCADE_UPD:
-					case F_RI_FKEY_RESTRICT_UPD:
-					case F_RI_FKEY_SETNULL_UPD:
-					case F_RI_FKEY_SETDEFAULT_UPD:
-						is_ri_trigger = true;
-						break;
-
-					default:
-						is_ri_trigger = false;
-						break;
-				}
-				if (!is_ri_trigger)
-					continue;
-
 				LocTriggerData.type = T_TriggerData;
-				LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE;
+				LocTriggerData.tg_event =
+					TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW;
 				LocTriggerData.tg_relation = rel;
 				LocTriggerData.tg_trigtuple = oldtup;
 				LocTriggerData.tg_newtuple = newtup;
 				LocTriggerData.tg_trigger = trigger;
 
-				key_unchanged = RI_FKey_keyequal_upd(&LocTriggerData);
-
-				if (key_unchanged)
+				if (RI_FKey_keyequal_upd(&LocTriggerData))
 				{
-					/*
-					 * The key hasn't changed, so no need later to invoke
-					 * the trigger at all.
-					 */
-					new_event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
-					new_event->dte_item[i].dti_done_xid = GetCurrentTransactionId();
+					/* key unchanged, so skip queuing this event */
+					continue;
 				}
 			}
+		}
 
-			break;
+		/*
+		 * Create a new event.	We use the CurTransactionContext so the event
+		 * will automatically go away if the subtransaction aborts.
+		 */
+		new_event = (AfterTriggerEvent)
+			MemoryContextAlloc(CurTransactionContext,
+							   sizeof(AfterTriggerEventData));
+		new_event->ate_next = NULL;
+		new_event->ate_event =
+			(event & TRIGGER_EVENT_OPMASK) |
+			(row_trigger ? TRIGGER_EVENT_ROW : 0) |
+			(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
+			(trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
+		new_event->ate_firing_id = 0;
+		new_event->ate_tgoid = trigger->tgoid;
+		new_event->ate_relid = rel->rd_id;
+		ItemPointerCopy(&oldctid, &(new_event->ate_oldctid));
+		ItemPointerCopy(&newctid, &(new_event->ate_newctid));
 
-		case TRIGGER_EVENT_DELETE:
-			/* nothing to do */
-			break;
+		/*
+		 * Add the new event to the queue.
+		 */
+		afterTriggerAddEvent(new_event);
 	}
-
-	/*
-	 * Add the new event to the queue.
-	 */
-	deferredTriggerAddEvent(new_event);
 }
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 2a9e5d88a9e..3611c85a5fc 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.87 2004/09/06 18:10:38 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.88 2004/09/10 18:39:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,6 +17,7 @@
 #include "access/heapam.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/trigger.h"
 #include "executor/execdefs.h"
 #include "executor/executor.h"
 #include "executor/functions.h"
@@ -273,7 +274,10 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
 
 	/* Utility commands don't need Executor. */
 	if (es->qd->operation != CMD_UTILITY)
+	{
+		AfterTriggerBeginQuery();
 		ExecutorStart(es->qd, false, false);
+	}
 
 	es->status = F_EXEC_RUN;
 }
@@ -316,7 +320,10 @@ postquel_end(execution_state *es)
 
 	/* Utility commands don't need Executor. */
 	if (es->qd->operation != CMD_UTILITY)
+	{
 		ExecutorEnd(es->qd);
+		AfterTriggerEndQuery();
+	}
 
 	FreeQueryDesc(es->qd);
 	es->qd = NULL;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 4ffb27b0139..636eed31eee 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.125 2004/08/29 05:06:42 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.126 2004/09/10 18:39:57 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,6 +16,7 @@
 
 #include "access/printtup.h"
 #include "catalog/heap.h"
+#include "commands/trigger.h"
 #include "executor/spi_priv.h"
 #include "tcop/tcopprot.h"
 #include "utils/lsyscache.h"
@@ -1434,6 +1435,8 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
 		ResetUsage();
 #endif
 
+	AfterTriggerBeginQuery();
+
 	ExecutorStart(queryDesc, useCurrentSnapshot, false);
 
 	ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
@@ -1447,6 +1450,11 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
 			elog(ERROR, "consistency check on SPI tuple count failed");
 	}
 
+	ExecutorEnd(queryDesc);
+
+	/* Take care of any queued AFTER triggers */
+	AfterTriggerEndQuery();
+
 	if (queryDesc->dest->mydest == SPI)
 	{
 		SPI_processed = _SPI_current->processed;
@@ -1459,8 +1467,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
 		res = SPI_OK_UTILITY;
 	}
 
-	ExecutorEnd(queryDesc);
-
 	FreeQueryDesc(queryDesc);
 
 #ifdef SPI_EXECUTOR_STATS
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index a3a96efae9c..50364bd79d3 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.430 2004/08/29 05:06:49 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.431 2004/09/10 18:39:59 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -1823,9 +1823,6 @@ finish_xact_command(void)
 {
 	if (xact_started)
 	{
-		/* Invoke IMMEDIATE constraint triggers */
-		DeferredTriggerEndQuery();
-
 		/* Cancel any active statement timeout before committing */
 		disable_sig_alarm(true);
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 5d9fe611f01..fca98fc0fa5 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,13 +8,14 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.85 2004/08/29 05:06:49 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.86 2004/09/10 18:40:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
 #include "postgres.h"
 
+#include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "tcop/tcopprot.h"
@@ -136,6 +137,11 @@ ProcessQuery(Query *parsetree,
 	 */
 	queryDesc = CreateQueryDesc(parsetree, plan, dest, params, false);
 
+	/*
+	 * Set up to collect AFTER triggers
+	 */
+	AfterTriggerBeginQuery();
+
 	/*
 	 * Call ExecStart to prepare the plan for execution
 	 */
@@ -185,6 +191,9 @@ ProcessQuery(Query *parsetree,
 	 */
 	ExecutorEnd(queryDesc);
 
+	/* And take care of any queued AFTER triggers */
+	AfterTriggerEndQuery();
+
 	FreeQueryDesc(queryDesc);
 }
 
@@ -290,6 +299,13 @@ PortalStart(Portal portal, ParamListInfo params)
 											params,
 											false);
 
+				/*
+				 * We do *not* call AfterTriggerBeginQuery() here.  We
+				 * assume that a SELECT cannot queue any triggers.  It
+				 * would be messy to support triggers since the execution
+				 * of the portal may be interleaved with other queries.
+				 */
+
 				/*
 				 * Call ExecStart to prepare the plan for execution
 				 */
@@ -1144,8 +1160,8 @@ DoPortalRunFetch(Portal portal,
 				return PortalRunSelect(portal, false, 1L, dest);
 			}
 			else
-/* count == 0 */
 			{
+				/* count == 0 */
 				/* Rewind to start, return zero rows */
 				DoPortalRewind(portal);
 				return PortalRunSelect(portal, true, 0L, dest);
@@ -1173,8 +1189,8 @@ DoPortalRunFetch(Portal portal,
 				return PortalRunSelect(portal, false, 1L, dest);
 			}
 			else
-/* count == 0 */
 			{
+				/* count == 0 */
 				/* Same as FETCH FORWARD 0, so fall out of switch */
 				fdirection = FETCH_FORWARD;
 			}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 517266b649c..6aec17bf9a5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.228 2004/08/29 05:06:49 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.229 2004/09/10 18:40:00 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -912,7 +912,7 @@ ProcessUtility(Node *parsetree,
 			break;
 
 		case T_ConstraintsSetStmt:
-			DeferredTriggerSetState((ConstraintsSetStmt *) parsetree);
+			AfterTriggerSetState((ConstraintsSetStmt *) parsetree);
 			break;
 
 		case T_CreateGroupStmt:
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 8aaa38ddb69..9c32d57c111 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -17,7 +17,7 @@
  *
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.71 2004/08/29 05:06:49 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ri_triggers.c,v 1.72 2004/09/10 18:40:04 tgl Exp $
  *
  * ----------
  */
@@ -2454,7 +2454,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
  *
  *	Check if we have a key change on update.
  *
- *	This is not a real trigger procedure. It is used by the deferred
+ *	This is not a real trigger procedure. It is used by the AFTER
  *	trigger queue manager to detect "triggered data change violation".
  * ----------
  */
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index f23889668c1..7d5569018ae 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.48 2004/08/29 05:06:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.49 2004/09/10 18:40:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,11 +45,12 @@ typedef struct TriggerData
 #define TRIGGER_EVENT_ROW				0x00000004
 #define TRIGGER_EVENT_BEFORE			0x00000008
 
-#define TRIGGER_DEFERRED_DONE			0x00000010
-#define TRIGGER_DEFERRED_CANCELED		0x00000020
-#define TRIGGER_DEFERRED_DEFERRABLE		0x00000040
-#define TRIGGER_DEFERRED_INITDEFERRED	0x00000080
-#define TRIGGER_DEFERRED_HAS_BEFORE		0x00000100
+/* More TriggerEvent flags, used only within trigger.c */
+
+#define AFTER_TRIGGER_DONE				0x00000010
+#define AFTER_TRIGGER_IN_PROGRESS		0x00000020
+#define AFTER_TRIGGER_DEFERRABLE		0x00000040
+#define AFTER_TRIGGER_INITDEFERRED		0x00000080
 
 #define TRIGGER_FIRED_BY_INSERT(event)	\
 		(((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
@@ -151,14 +152,15 @@ extern void ExecARUpdateTriggers(EState *estate,
 					 ItemPointer tupleid,
 					 HeapTuple newtuple);
 
-extern void DeferredTriggerBeginXact(void);
-extern void DeferredTriggerEndQuery(void);
-extern void DeferredTriggerEndXact(void);
-extern void DeferredTriggerAbortXact(void);
-extern void DeferredTriggerBeginSubXact(void);
-extern void DeferredTriggerEndSubXact(bool isCommit);
+extern void AfterTriggerBeginXact(void);
+extern void AfterTriggerBeginQuery(void);
+extern void AfterTriggerEndQuery(void);
+extern void AfterTriggerEndXact(void);
+extern void AfterTriggerAbortXact(void);
+extern void AfterTriggerBeginSubXact(void);
+extern void AfterTriggerEndSubXact(bool isCommit);
 
-extern void DeferredTriggerSetState(ConstraintsSetStmt *stmt);
+extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
 
 
 /*
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index b291000add1..e954a232f1b 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -646,6 +646,7 @@ SELECT * from FKTABLE;
 UPDATE PKTABLE set ptest2=5 where ptest2=2;
 ERROR:  insert or update on table "fktable" violates foreign key constraint "constrname3"
 DETAIL:  Key (ftest1,ftest2,ftest3)=(1,-1,3) is not present in table "pktable".
+CONTEXT:  SQL query "UPDATE ONLY "public"."fktable" SET "ftest2" = DEFAULT WHERE "ftest1" = $1 AND "ftest2" = $2 AND "ftest3" = $3"
 -- Try to update something that will set default
 UPDATE PKTABLE set ptest1=0, ptest2=5, ptest3=10 where ptest2=2;
 UPDATE PKTABLE set ptest2=10 where ptest2=4;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 73962dbb37a..50d72830fb4 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1935,3 +1935,74 @@ select * from foo;
  20
 (2 rows)
 
+--
+-- test foreign key error trapping
+--
+create temp table master(f1 int primary key);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "master_pkey" for table "master"
+create temp table slave(f1 int references master deferrable);
+insert into master values(1);
+insert into slave values(1);
+insert into slave values(2);	-- fails
+ERROR:  insert or update on table "slave" violates foreign key constraint "slave_f1_fkey"
+DETAIL:  Key (f1)=(2) is not present in table "master".
+create function trap_foreign_key(int) returns int as $$
+begin
+	begin	-- start a subtransaction
+		insert into slave values($1);
+	exception
+		when foreign_key_violation then
+			raise notice 'caught foreign_key_violation';
+			return 0;
+	end;
+	return 1;
+end$$ language plpgsql;
+create function trap_foreign_key_2() returns int as $$
+begin
+	begin	-- start a subtransaction
+		set constraints all immediate;
+	exception
+		when foreign_key_violation then
+			raise notice 'caught foreign_key_violation';
+			return 0;
+	end;
+	return 1;
+end$$ language plpgsql;
+select trap_foreign_key(1);
+ trap_foreign_key 
+------------------
+                1
+(1 row)
+
+select trap_foreign_key(2);	-- detects FK violation
+NOTICE:  caught foreign_key_violation
+ trap_foreign_key 
+------------------
+                0
+(1 row)
+
+begin;
+  set constraints all deferred;
+  select trap_foreign_key(2);	-- should not detect FK violation
+ trap_foreign_key 
+------------------
+                1
+(1 row)
+
+  savepoint x;
+    set constraints all immediate; -- fails
+ERROR:  insert or update on table "slave" violates foreign key constraint "slave_f1_fkey"
+DETAIL:  Key (f1)=(2) is not present in table "master".
+  rollback to x;
+  select trap_foreign_key_2();  -- detects FK violation
+NOTICE:  caught foreign_key_violation
+ trap_foreign_key_2 
+--------------------
+                  0
+(1 row)
+
+commit;				-- still fails
+ERROR:  insert or update on table "slave" violates foreign key constraint "slave_f1_fkey"
+DETAIL:  Key (f1)=(2) is not present in table "master".
+drop function trap_foreign_key(int);
+drop function trap_foreign_key_2();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 948a02ac0e2..a8951cd6efa 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -1699,3 +1699,54 @@ select blockme();
 reset statement_timeout;
 
 select * from foo;
+
+--
+-- test foreign key error trapping
+--
+
+create temp table master(f1 int primary key);
+
+create temp table slave(f1 int references master deferrable);
+
+insert into master values(1);
+insert into slave values(1);
+insert into slave values(2);	-- fails
+
+create function trap_foreign_key(int) returns int as $$
+begin
+	begin	-- start a subtransaction
+		insert into slave values($1);
+	exception
+		when foreign_key_violation then
+			raise notice 'caught foreign_key_violation';
+			return 0;
+	end;
+	return 1;
+end$$ language plpgsql;
+
+create function trap_foreign_key_2() returns int as $$
+begin
+	begin	-- start a subtransaction
+		set constraints all immediate;
+	exception
+		when foreign_key_violation then
+			raise notice 'caught foreign_key_violation';
+			return 0;
+	end;
+	return 1;
+end$$ language plpgsql;
+
+select trap_foreign_key(1);
+select trap_foreign_key(2);	-- detects FK violation
+
+begin;
+  set constraints all deferred;
+  select trap_foreign_key(2);	-- should not detect FK violation
+  savepoint x;
+    set constraints all immediate; -- fails
+  rollback to x;
+  select trap_foreign_key_2();  -- detects FK violation
+commit;				-- still fails
+
+drop function trap_foreign_key(int);
+drop function trap_foreign_key_2();
-- 
GitLab