Fire non-deferred AFTER triggers immediately upon query completion,
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 10 Sep 2004 18:40:09 +0000 (18:40 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_constraints.sgml,v 1.11 2004/09/08 20:47:37 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_constraints.sgml,v 1.12 2004/09/10 18:39:53 tgl Exp $ -->
 <refentry id="SQL-SET-CONSTRAINTS">
  <refmeta>
   <refentrytitle id="SQL-SET-CONSTRAINTS-title">SET CONSTRAINTS</refentrytitle>
@@ -34,13 +34,13 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
 
   <para>
    Upon creation, a constraint is given one of three
-   characteristics: <literal>INITIALLY DEFERRED</literal>,
-   <literal>INITIALLY IMMEDIATE DEFERRABLE</literal>, or
-   <literal>INITIALLY IMMEDIATE NOT DEFERRABLE</literal>. The third
-   class is not affected by the <command>SET CONSTRAINTS</command>
-   command.  The first two classes start every transaction in the
-   indicated mode, but their behavior can be changed within a transaction
-   by <command>SET CONSTRAINTS</command>.
+   characteristics: <literal>DEFERRABLE INITIALLY DEFERRED</literal>,
+   <literal>DEFERRABLE INITIALLY IMMEDIATE</literal>, or
+   <literal>NOT DEFERRABLE</literal>. The third
+   class is always <literal>IMMEDIATE</literal> and is not affected by the
+   <command>SET CONSTRAINTS</command> command.  The first two classes start
+   every transaction in the indicated mode, but their behavior can be changed
+   within a transaction by <command>SET CONSTRAINTS</command>.
   </para>
 
   <para>
@@ -52,19 +52,22 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
   </para>
 
   <para>
-   When you change the mode of a constraint from <literal>DEFERRED</literal>
+   When <command>SET CONSTRAINTS</command> changes the mode of a constraint
+   from <literal>DEFERRED</literal>
    to <literal>IMMEDIATE</literal>, the new mode takes effect
    retroactively: any outstanding data modifications that would have
    been checked at the end of the transaction are instead checked during the
    execution of the <command>SET CONSTRAINTS</command> command.
    If any such constraint is violated, the <command>SET CONSTRAINTS</command>
-   fails (and does not change the constraint mode).
+   fails (and does not change the constraint mode).  Thus, <command>SET
+   CONSTRAINTS</command> can be used to force checking of constraints to
+   occur at a specific point in a transaction.
   </para>
 
   <para>
    Currently, only foreign key constraints are affected by this
    setting. Check and unique constraints are always effectively
-   initially immediate not deferrable.
+   not deferrable.
   </para>
  </refsect1>
 
@@ -76,11 +79,7 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
    current transaction. Thus, if you execute this command outside of a
    transaction block
    (<command>BEGIN</command>/<command>COMMIT</command> pair), it will
-   not appear to have any effect.  If you wish to change the behavior
-   of a constraint without needing to issue a <command>SET
-   CONSTRAINTS</command> command in every transaction, specify
-   <literal>INITIALLY DEFERRED</literal> or <literal>INITIALLY
-   IMMEDIATE</literal> when you create the constraint.
+   not appear to have any effect.
   </para>
  </refsect1>
 
index b742f675e30ff6835a598110825299d4c675d126..ddf93a04ff996df42d2be7a0e59e1c73db866de0 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.295 2004/09/10 18:39:54 tgl Exp $
 -->
 
 <appendix id="release">
@@ -336,6 +336,16 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp
        whitespace (which has always been ignored).
       </para>
      </listitem>
+
+     <listitem>
+      <para>
+       Non-deferred AFTER triggers are now fired immediately after completion
+       of the triggering query, rather than upon finishing the current
+       interactive command.  This makes a difference when the triggering query
+       occurred within a function: the trigger is invoked before the function
+       proceeds to its next operation.
+      </para>
+     </listitem>
     </itemizedlist>
    </para>
   </sect2>
@@ -1424,6 +1434,18 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp
    <title>Server-Side Language Changes</title>
    <itemizedlist>
 
+    <listitem>
+     <para>
+      Non-deferred AFTER triggers are now fired immediately after completion
+      of the triggering query, rather than upon finishing the current
+      interactive command.  This makes a difference when the triggering query
+      occurred within a function: the trigger is invoked before the function
+      proceeds to its next operation.  For example, if a function inserts
+      a new row into a table, any non-deferred foreign key checks occur
+      before proceeding with the function.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       Allow function parameters to be declared with names (Dennis Bjorklund)
@@ -1483,7 +1505,7 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp
 
     <listitem>
      <para>
-      New plperl server-side language (Command Prompt, Andrew Dunstan)
+      Major overhaul of plperl server-side language (Command Prompt, Andrew Dunstan)
      </para>
     </listitem>
 
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();