Fire non-deferred AFTER triggers immediately upon query completion,
authorTom Lane
Fri, 10 Sep 2004 18:40:09 +0000 (18:40 +0000)
committerTom Lane
Fri, 10 Sep 2004 18:40:09 +0000 (18:40 +0000)
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.

17 files changed:
doc/src/sgml/ref/set_constraints.sgml
doc/src/sgml/release.sgml
src/backend/access/transam/xact.c
src/backend/commands/copy.c
src/backend/commands/explain.c
src/backend/commands/portalcmds.c
src/backend/commands/trigger.c
src/backend/executor/functions.c
src/backend/executor/spi.c
src/backend/tcop/postgres.c
src/backend/tcop/pquery.c
src/backend/tcop/utility.c
src/backend/utils/adt/ri_triggers.c
src/include/commands/trigger.h
src/test/regress/expected/foreign_key.out
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index 0c976c93e258fc072c923084628a077099c0dd39..3bcde91f386914e4013a2c42096599b06518f5d0 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
  
   SET CONSTRAINTS
@@ -34,13 +34,13 @@ SET CONSTRAINTS { ALL | name [, ...
 
   
    Upon creation, a constraint is given one of three
-   characteristics: INITIALLY DEFERRED,
-   INITIALLY IMMEDIATE DEFERRABLE, or
-   INITIALLY IMMEDIATE NOT DEFERRABLE. The third
-   class is not affected by the SET CONSTRAINTS
-   command.  The first two classes start every transaction in the
-   indicated mode, but their behavior can be changed within a transaction
-   by SET CONSTRAINTS.
+   characteristics: DEFERRABLE INITIALLY DEFERRED,
+   DEFERRABLE INITIALLY IMMEDIATE, or
+   NOT DEFERRABLE. The third
+   class is always IMMEDIATE and is not affected by the
+   SET CONSTRAINTS command.  The first two classes start
+   every transaction in the indicated mode, but their behavior can be changed
+   within a transaction by SET CONSTRAINTS.
   
 
   
@@ -52,19 +52,22 @@ SET CONSTRAINTS { ALL | name [, ...
   
 
   
-   When you change the mode of a constraint from DEFERRED
+   When SET CONSTRAINTS changes the mode of a constraint
+   from DEFERRED
    to IMMEDIATE, 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 SET CONSTRAINTS command.
    If any such constraint is violated, the SET CONSTRAINTS
-   fails (and does not change the constraint mode).
+   fails (and does not change the constraint mode).  Thus, SET
+   CONSTRAINTS can be used to force checking of constraints to
+   occur at a specific point in a transaction.
   
 
   
    Currently, only foreign key constraints are affected by this
    setting. Check and unique constraints are always effectively
-   initially immediate not deferrable.
+   not deferrable.
   
  
 
@@ -76,11 +79,7 @@ SET CONSTRAINTS { ALL | name [, ...
    current transaction. Thus, if you execute this command outside of a
    transaction block
    (BEGIN/COMMIT pair), it will
-   not appear to have any effect.  If you wish to change the behavior
-   of a constraint without needing to issue a SET
-   CONSTRAINTS command in every transaction, specify
-   INITIALLY DEFERRED or INITIALLY
-   IMMEDIATE when you create the constraint.
+   not appear to have any effect.
   
  
 
index b742f675e30ff6835a598110825299d4c675d126..ddf93a04ff996df42d2be7a0e59e1c73db866de0 100644 (file)
@@ -1,5 +1,5 @@
 
 
 
@@ -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).
       
      
+
+     
+      
+       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.
+      
+     
     
    
   
@@ -1424,6 +1434,18 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp
    Server-Side Language Changes
    
 
+    
+     
+      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.
+     
+    
+
     
      
       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
 
     
      
-      New plperl server-side language (Command Prompt, Andrew Dunstan)
+      Major overhaul of plperl server-side language (Command Prompt, Andrew Dunstan)
      
     
 
index 92c9de0ea755182c7c0e482a540613417e6233f0..47c501c393e31f3716c9067c35cff20a88ddd67e 100644 (file)
@@ -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,
index 5793c0b2bbba0a0b36649a99c7f5e5f32d6d59bb..b25c8eee98ca3028f4f9abd40dd04e27ea4757e3 100644 (file)
@@ -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);
 
index 7ad3596fac6b7d314158c04c1a1d80f4c6ba3cdb..9b7cf1ae4912d4386f6656bf8e9add43db145754 100644 (file)
@@ -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();
index 08b1401354731d753c2b3023907271086b8859fa..390e20aa2e9703ef72b9e2ef26e18903e21ff8ae 100644 (file)
@@ -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
index 0efc4afb58c83244da81242a2d3a1b239ec50ef6..5480fce189f83d878cbde0ad46ee5609941ef6a6 100644 (file)
@@ -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);
 }
index 2a9e5d88a9e5914be481c062876ffc3694177674..3611c85a5fc6eca196ae90db06709392a5733498 100644 (file)
@@ -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;
index 4ffb27b01392e28ffb167f4db681a45671f59182..636eed31eeeeaf9434bad73e5f82d8af39f30a22 100644 (file)
@@ -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
index a3a96efae9c35a783a8d635a59419845b43ed748..50364bd79d37770415aba485dc1db7bf12fa4432 100644 (file)
@@ -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);
 
index 5d9fe611f017784868dfa28bf9da30b38098943a..fca98fc0fa568dafe3f29d30cf1deace537f1184 100644 (file)
@@ -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;
            }
index 517266b649c9b6ea68643b2e759426142c77384e..6aec17bf9a58c1fd8d5947a12693618f3a74a538 100644 (file)
@@ -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:
index 8aaa38ddb69e2b5e2587199fd57530bd542ffce4..9c32d57c11121009b10fabdf3fc6ed0de11f2a01 100644 (file)
@@ -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".
  * ----------
  */
index f23889668c1969c445bd4c69c519f3e96ae19b99..7d5569018ae3eac0db6da173bf8e0b714455b72e 100644 (file)
@@ -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);
 
 
 /*
index b291000add1ba1c9dd1699bd7547b0e99a5ee71b..e954a232f1bf165eb1c6a864f9c4fa84e1d47ed1 100644 (file)
@@ -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;
index 73962dbb37a5c6a24cf79e797686538e8c9a5c5d..50d72830fb46a43fcd7f65509f915596449f62fa 100644 (file)
@@ -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();
index 948a02ac0e214c833c9727055274d752b8c3fd67..a8951cd6efa44aa4c2598764352a003a252e6c74 100644 (file)
@@ -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();