Commit b339d1ff authored by Tom Lane's avatar Tom Lane

Fire non-deferred AFTER triggers immediately upon query completion,

rather than when returning to the idle loop.  This makes no particular
difference for interactively-issued queries, but it makes a big difference
for queries issued within functions: trigger execution now occurs before
the calling function is allowed to proceed.  This responds to numerous
complaints about nonintuitive behavior of foreign key checking, such as
http://archives.postgresql.org/pgsql-bugs/2004-09/msg00020.php, and
appears to be required by the SQL99 spec.
Also take the opportunity to simplify the data structures used for the
pending-trigger list, rename them for more clarity, and squeeze out a
bit of space.
parent 856d1faa
<!-- $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"> <refentry id="SQL-SET-CONSTRAINTS">
<refmeta> <refmeta>
<refentrytitle id="SQL-SET-CONSTRAINTS-title">SET CONSTRAINTS</refentrytitle> <refentrytitle id="SQL-SET-CONSTRAINTS-title">SET CONSTRAINTS</refentrytitle>
...@@ -34,13 +34,13 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ... ...@@ -34,13 +34,13 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
<para> <para>
Upon creation, a constraint is given one of three Upon creation, a constraint is given one of three
characteristics: <literal>INITIALLY DEFERRED</literal>, characteristics: <literal>DEFERRABLE INITIALLY DEFERRED</literal>,
<literal>INITIALLY IMMEDIATE DEFERRABLE</literal>, or <literal>DEFERRABLE INITIALLY IMMEDIATE</literal>, or
<literal>INITIALLY IMMEDIATE NOT DEFERRABLE</literal>. The third <literal>NOT DEFERRABLE</literal>. The third
class is not affected by the <command>SET CONSTRAINTS</command> class is always <literal>IMMEDIATE</literal> and is not affected by the
command. The first two classes start every transaction in the <command>SET CONSTRAINTS</command> command. The first two classes start
indicated mode, but their behavior can be changed within a transaction every transaction in the indicated mode, but their behavior can be changed
by <command>SET CONSTRAINTS</command>. within a transaction by <command>SET CONSTRAINTS</command>.
</para> </para>
<para> <para>
...@@ -52,19 +52,22 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ... ...@@ -52,19 +52,22 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
</para> </para>
<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 to <literal>IMMEDIATE</literal>, the new mode takes effect
retroactively: any outstanding data modifications that would have retroactively: any outstanding data modifications that would have
been checked at the end of the transaction are instead checked during the been checked at the end of the transaction are instead checked during the
execution of the <command>SET CONSTRAINTS</command> command. execution of the <command>SET CONSTRAINTS</command> command.
If any such constraint is violated, the <command>SET CONSTRAINTS</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>
<para> <para>
Currently, only foreign key constraints are affected by this Currently, only foreign key constraints are affected by this
setting. Check and unique constraints are always effectively setting. Check and unique constraints are always effectively
initially immediate not deferrable. not deferrable.
</para> </para>
</refsect1> </refsect1>
...@@ -76,11 +79,7 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ... ...@@ -76,11 +79,7 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
current transaction. Thus, if you execute this command outside of a current transaction. Thus, if you execute this command outside of a
transaction block transaction block
(<command>BEGIN</command>/<command>COMMIT</command> pair), it will (<command>BEGIN</command>/<command>COMMIT</command> pair), it will
not appear to have any effect. If you wish to change the behavior not appear to have any effect.
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.
</para> </para>
</refsect1> </refsect1>
......
<!-- <!--
$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"> <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 ...@@ -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). whitespace (which has always been ignored).
</para> </para>
</listitem> </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> </itemizedlist>
</para> </para>
</sect2> </sect2>
...@@ -1424,6 +1434,18 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp ...@@ -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> <title>Server-Side Language Changes</title>
<itemizedlist> <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> <listitem>
<para> <para>
Allow function parameters to be declared with names (Dennis Bjorklund) 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 ...@@ -1483,7 +1505,7 @@ $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.294 2004/08/30 00:47:31 tgl Exp
<listitem> <listitem>
<para> <para>
New plperl server-side language (Command Prompt, Andrew Dunstan) Major overhaul of plperl server-side language (Command Prompt, Andrew Dunstan)
</para> </para>
</listitem> </listitem>
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * 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); ...@@ -138,7 +138,6 @@ static void CleanupSubTransaction(void);
static void StartAbortedSubTransaction(void); static void StartAbortedSubTransaction(void);
static void PushTransaction(void); static void PushTransaction(void);
static void PopTransaction(void); static void PopTransaction(void);
static void CommitTransactionToLevel(int level);
static char *CleanupAbortedSubTransactions(bool returnName); static char *CleanupAbortedSubTransactions(bool returnName);
static void AtSubAbort_Memory(void); static void AtSubAbort_Memory(void);
...@@ -1219,7 +1218,7 @@ StartTransaction(void) ...@@ -1219,7 +1218,7 @@ StartTransaction(void)
*/ */
AtStart_Inval(); AtStart_Inval();
AtStart_Cache(); AtStart_Cache();
DeferredTriggerBeginXact(); AfterTriggerBeginXact();
/* /*
* done with start processing, set current transaction state to "in * done with start processing, set current transaction state to "in
...@@ -1253,7 +1252,7 @@ CommitTransaction(void) ...@@ -1253,7 +1252,7 @@ CommitTransaction(void)
* committed. He'll invoke all trigger deferred until XACT before we * committed. He'll invoke all trigger deferred until XACT before we
* really start on committing the transaction. * really start on committing the transaction.
*/ */
DeferredTriggerEndXact(); AfterTriggerEndXact();
/* /*
* Similarly, let ON COMMIT management do its thing before we start to * Similarly, let ON COMMIT management do its thing before we start to
...@@ -1454,7 +1453,7 @@ AbortTransaction(void) ...@@ -1454,7 +1453,7 @@ AbortTransaction(void)
/* /*
* do abort processing * do abort processing
*/ */
DeferredTriggerAbortXact(); AfterTriggerAbortXact();
AtAbort_Portals(); AtAbort_Portals();
AtEOXact_LargeObject(false); /* 'false' means it's abort */ AtEOXact_LargeObject(false); /* 'false' means it's abort */
AtAbort_Notify(); AtAbort_Notify();
...@@ -1672,12 +1671,6 @@ CommitTransactionCommand(void) ...@@ -1672,12 +1671,6 @@ CommitTransactionCommand(void)
* default state. * default state.
*/ */
case TBLOCK_END: 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(); CommitTransaction();
s->blockState = TBLOCK_DEFAULT; s->blockState = TBLOCK_DEFAULT;
break; break;
...@@ -1732,11 +1725,10 @@ CommitTransactionCommand(void) ...@@ -1732,11 +1725,10 @@ CommitTransactionCommand(void)
break; break;
/* /*
* We were issued a RELEASE command, so we end the current * We were issued a COMMIT or RELEASE command, so we end the
* subtransaction and return to the parent transaction. * current subtransaction and return to the parent transaction.
* * Lather, rinse, and repeat until we get out of all SUBEND'ed
* Since RELEASE can exit multiple levels of subtransaction, we * subtransaction levels.
* must loop here until we get out of all SUBEND'ed levels.
*/ */
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
do do
...@@ -1745,6 +1737,13 @@ CommitTransactionCommand(void) ...@@ -1745,6 +1737,13 @@ CommitTransactionCommand(void)
PopTransaction(); PopTransaction();
s = CurrentTransactionState; /* changed by pop */ s = CurrentTransactionState; /* changed by pop */
} while (s->blockState == TBLOCK_SUBEND); } 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; break;
/* /*
...@@ -2238,7 +2237,6 @@ EndTransactionBlock(void) ...@@ -2238,7 +2237,6 @@ EndTransactionBlock(void)
* the default state. * the default state.
*/ */
case TBLOCK_INPROGRESS: case TBLOCK_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
s->blockState = TBLOCK_END; s->blockState = TBLOCK_END;
result = true; result = true;
break; break;
...@@ -2254,6 +2252,22 @@ EndTransactionBlock(void) ...@@ -2254,6 +2252,22 @@ EndTransactionBlock(void)
s->blockState = TBLOCK_ENDABORT; s->blockState = TBLOCK_ENDABORT;
break; 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 * Here we are inside an aborted subtransaction. Go to the
* "abort the whole tree" state so that * "abort the whole tree" state so that
...@@ -2699,8 +2713,12 @@ ReleaseCurrentSubTransaction(void) ...@@ -2699,8 +2713,12 @@ ReleaseCurrentSubTransaction(void)
if (s->blockState != TBLOCK_SUBINPROGRESS) if (s->blockState != TBLOCK_SUBINPROGRESS)
elog(ERROR, "ReleaseCurrentSubTransaction: unexpected state %s", elog(ERROR, "ReleaseCurrentSubTransaction: unexpected state %s",
BlockStateAsString(s->blockState)); BlockStateAsString(s->blockState));
Assert(s->state == TRANS_INPROGRESS);
MemoryContextSwitchTo(CurTransactionContext); MemoryContextSwitchTo(CurTransactionContext);
CommitTransactionToLevel(GetCurrentTransactionNestLevel()); CommitSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
Assert(s->state == TRANS_INPROGRESS);
} }
/* /*
...@@ -2827,28 +2845,6 @@ AbortOutOfAnyTransaction(void) ...@@ -2827,28 +2845,6 @@ AbortOutOfAnyTransaction(void)
Assert(s->parent == NULL); 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? * IsTransactionBlock --- are we within a transaction block?
*/ */
...@@ -2975,7 +2971,7 @@ StartSubTransaction(void) ...@@ -2975,7 +2971,7 @@ StartSubTransaction(void)
*/ */
AtSubStart_Inval(); AtSubStart_Inval();
AtSubStart_Notify(); AtSubStart_Notify();
DeferredTriggerBeginSubXact(); AfterTriggerBeginSubXact();
s->state = TRANS_INPROGRESS; s->state = TRANS_INPROGRESS;
...@@ -3011,7 +3007,7 @@ CommitSubTransaction(void) ...@@ -3011,7 +3007,7 @@ CommitSubTransaction(void)
AtSubCommit_childXids(); AtSubCommit_childXids();
/* Post-commit cleanup */ /* Post-commit cleanup */
DeferredTriggerEndSubXact(true); AfterTriggerEndSubXact(true);
AtSubCommit_Portals(s->parent->transactionIdData, AtSubCommit_Portals(s->parent->transactionIdData,
s->parent->curTransactionOwner); s->parent->curTransactionOwner);
AtEOSubXact_LargeObject(true, s->transactionIdData, AtEOSubXact_LargeObject(true, s->transactionIdData,
...@@ -3101,7 +3097,7 @@ AbortSubTransaction(void) ...@@ -3101,7 +3097,7 @@ AbortSubTransaction(void)
*/ */
AtSubAbort_Memory(); AtSubAbort_Memory();
DeferredTriggerEndSubXact(false); AfterTriggerEndSubXact(false);
AtSubAbort_Portals(s->parent->transactionIdData, AtSubAbort_Portals(s->parent->transactionIdData,
s->parent->curTransactionOwner); s->parent->curTransactionOwner);
AtEOSubXact_LargeObject(false, s->transactionIdData, AtEOSubXact_LargeObject(false, s->transactionIdData,
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -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 * Check BEFORE STATEMENT insertion triggers. It's debateable whether
* we should do this for COPY, since it's not really an "INSERT" * 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, ...@@ -1974,6 +1979,11 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
*/ */
ExecASInsertTriggers(estate, resultRelInfo); ExecASInsertTriggers(estate, resultRelInfo);
/*
* Handle queued AFTER triggers
*/
AfterTriggerEndQuery();
pfree(values); pfree(values);
pfree(nulls); pfree(nulls);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994-5, Regents of the University of California * Portions Copyright (c) 1994-5, Regents of the University of California
* *
* IDENTIFICATION * 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 @@ ...@@ -18,6 +18,7 @@
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/explain.h" #include "commands/explain.h"
#include "commands/prepare.h" #include "commands/prepare.h"
#include "commands/trigger.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "executor/instrument.h" #include "executor/instrument.h"
#include "lib/stringinfo.h" #include "lib/stringinfo.h"
...@@ -206,6 +207,10 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt, ...@@ -206,6 +207,10 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
gettimeofday(&starttime, NULL); gettimeofday(&starttime, NULL);
/* If analyzing, we need to cope with queued triggers */
if (stmt->analyze)
AfterTriggerBeginQuery();
/* call ExecutorStart to prepare the plan for execution */ /* call ExecutorStart to prepare the plan for execution */
ExecutorStart(queryDesc, false, !stmt->analyze); ExecutorStart(queryDesc, false, !stmt->analyze);
...@@ -255,12 +260,16 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt, ...@@ -255,12 +260,16 @@ ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
} }
/* /*
* Close down the query and free resources. Include time for this in * Close down the query and free resources; also run any queued
* the total runtime. * AFTER triggers. Include time for this in the total runtime.
*/ */
gettimeofday(&starttime, NULL); gettimeofday(&starttime, NULL);
ExecutorEnd(queryDesc); ExecutorEnd(queryDesc);
if (stmt->analyze)
AfterTriggerEndQuery();
FreeQueryDesc(queryDesc); FreeQueryDesc(queryDesc);
CommandCounterIncrement(); CommandCounterIncrement();
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -273,6 +273,7 @@ PortalCleanup(Portal portal)
{ {
CurrentResourceOwner = portal->resowner; CurrentResourceOwner = portal->resowner;
ExecutorEnd(queryDesc); ExecutorEnd(queryDesc);
/* we do not need AfterTriggerEndQuery() here */
} }
PG_CATCH(); PG_CATCH();
{ {
...@@ -373,6 +374,7 @@ PersistHoldablePortal(Portal portal) ...@@ -373,6 +374,7 @@ PersistHoldablePortal(Portal portal)
*/ */
portal->queryDesc = NULL; /* prevent double shutdown */ portal->queryDesc = NULL; /* prevent double shutdown */
ExecutorEnd(queryDesc); ExecutorEnd(queryDesc);
/* we do not need AfterTriggerEndQuery() here */
/* /*
* Reset the position in the result set: ideally, this could be * Reset the position in the result set: ideally, this could be
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * 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, ...@@ -48,7 +48,7 @@ static HeapTuple GetTupleForTrigger(EState *estate,
static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
FmgrInfo *finfo, FmgrInfo *finfo,
MemoryContext per_tuple_context); 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); bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
...@@ -1219,8 +1219,8 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo) ...@@ -1219,8 +1219,8 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0) if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT, AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
false, NULL, NULL); false, NULL, NULL);
} }
HeapTuple HeapTuple
...@@ -1272,8 +1272,8 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, ...@@ -1272,8 +1272,8 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0) if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT, AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
true, NULL, trigtuple); true, NULL, trigtuple);
} }
void void
...@@ -1332,8 +1332,8 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo) ...@@ -1332,8 +1332,8 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0) if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE, AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
false, NULL, NULL); false, NULL, NULL);
} }
bool bool
...@@ -1399,8 +1399,8 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, ...@@ -1399,8 +1399,8 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
(CommandId) 0, (CommandId) 0,
NULL); NULL);
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE, AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
true, trigtuple, NULL); true, trigtuple, NULL);
heap_freetuple(trigtuple); heap_freetuple(trigtuple);
} }
} }
...@@ -1461,8 +1461,8 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo) ...@@ -1461,8 +1461,8 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0) if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE, AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
false, NULL, NULL); false, NULL, NULL);
} }
HeapTuple HeapTuple
...@@ -1535,8 +1535,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ...@@ -1535,8 +1535,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
(CommandId) 0, (CommandId) 0,
NULL); NULL);
DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE, AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
true, trigtuple, newtuple); true, trigtuple, newtuple);
heap_freetuple(trigtuple); heap_freetuple(trigtuple);
} }
} }
...@@ -1635,87 +1635,35 @@ ltrmark:; ...@@ -1635,87 +1635,35 @@ ltrmark:;
/* ---------- /* ----------
* Deferred trigger stuff * After-trigger stuff
* *
* The DeferredTriggersData struct holds data about pending deferred * The AfterTriggersData struct holds data about pending AFTER trigger events
* trigger events during the current transaction tree. The struct and * during the current transaction tree. (BEFORE triggers are fired
* most of its subsidiary data are kept in TopTransactionContext; however * 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 * the individual event records are kept in CurTransactionContext, so that
* they will easily go away during subtransaction abort. * they will easily go away during subtransaction abort.
* *
* DeferredTriggersData has the following fields: * 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
* state keeps track of the deferred state of each trigger * but thread the events manually.
* (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.)
* *
* XXX We need to be able to save the per-event data in a file if it grows too * XXX We need to be able to save the per-event data in a file if it grows too
* large. * large.
* ---------- * ----------
*/ */
/* Per-item data */ /* Per-trigger SET CONSTRAINT status */
typedef struct DeferredTriggerEventItem typedef struct SetConstraintTriggerData
{
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
{ {
Oid dts_tgoid; Oid sct_tgoid;
bool dts_tgisdeferred; bool sct_tgisdeferred;
} DeferredTriggerStatusData; } SetConstraintTriggerData;
typedef struct DeferredTriggerStatusData *DeferredTriggerStatus;
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. * We make this a single palloc'd object so it can be copied and freed easily.
* *
...@@ -1724,62 +1672,148 @@ typedef struct DeferredTriggerStatusData *DeferredTriggerStatus; ...@@ -1724,62 +1672,148 @@ typedef struct DeferredTriggerStatusData *DeferredTriggerStatus;
* *
* trigstates[] stores per-trigger tgisdeferred settings. * trigstates[] stores per-trigger tgisdeferred settings.
*/ */
typedef struct DeferredTriggerStateData typedef struct SetConstraintStateData
{ {
bool all_isset; bool all_isset;
bool all_isdeferred; bool all_isdeferred;
int numstates; /* number of trigstates[] entries in use */ int numstates; /* number of trigstates[] entries in use */
int numalloc; /* allocated size of trigstates[] */ int numalloc; /* allocated size of trigstates[] */
DeferredTriggerStatusData trigstates[1]; /* VARIABLE LENGTH ARRAY */ SetConstraintTriggerData trigstates[1]; /* VARIABLE LENGTH ARRAY */
} DeferredTriggerStateData; } 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, * All per-transaction data for the AFTER TRIGGERS module.
MemoryContext per_tuple_context); *
static DeferredTriggerState DeferredTriggerStateCreate(int numalloc); * AfterTriggersData has the following fields:
static DeferredTriggerState DeferredTriggerStateCopy(DeferredTriggerState state); *
static DeferredTriggerState DeferredTriggerStateAddItem(DeferredTriggerState state, * 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); Oid tgoid, bool tgisdeferred);
/* ---------- /* ----------
* deferredTriggerCheckState() * afterTriggerCheckState()
* *
* Returns true if the trigger identified by tgoid is actually * Returns true if the trigger identified by tgoid is actually
* in state DEFERRED. * in state DEFERRED.
* ---------- * ----------
*/ */
static bool static bool
deferredTriggerCheckState(Oid tgoid, int32 itemstate) afterTriggerCheckState(Oid tgoid, TriggerEvent eventstate)
{ {
DeferredTriggerState state = deferredTriggers->state; SetConstraintState state = afterTriggers->state;
int i; int i;
/* /*
* For not-deferrable triggers (i.e. normal AFTER ROW triggers and * For not-deferrable triggers (i.e. normal AFTER ROW triggers and
* constraints declared NOT DEFERRABLE), the state is always false. * constraints declared NOT DEFERRABLE), the state is always false.
*/ */
if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0) if ((eventstate & AFTER_TRIGGER_DEFERRABLE) == 0)
return false; return false;
/* /*
...@@ -1787,8 +1821,8 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate) ...@@ -1787,8 +1821,8 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
*/ */
for (i = 0; i < state->numstates; i++) for (i = 0; i < state->numstates; i++)
{ {
if (state->trigstates[i].dts_tgoid == tgoid) if (state->trigstates[i].sct_tgoid == tgoid)
return state->trigstates[i].dts_tgisdeferred; return state->trigstates[i].sct_tgisdeferred;
} }
/* /*
...@@ -1800,37 +1834,43 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate) ...@@ -1800,37 +1834,43 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
/* /*
* Otherwise return the default state for the trigger. * 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 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 */ /* first list entry */
deferredTriggers->events = event; events->head = event;
deferredTriggers->tail_thisxact = event; events->tail = event;
} }
else else
{ {
deferredTriggers->tail_thisxact->dte_next = event; events->tail->ate_next = event;
deferredTriggers->tail_thisxact = event; events->tail = event;
} }
} }
/* ---------- /* ----------
* DeferredTriggerExecute() * AfterTriggerExecute()
* *
* Fetch the required tuples back from the heap and fire one * Fetch the required tuples back from the heap and fire one
* single trigger function. * single trigger function.
...@@ -1840,7 +1880,6 @@ deferredTriggerAddEvent(DeferredTriggerEvent event) ...@@ -1840,7 +1880,6 @@ deferredTriggerAddEvent(DeferredTriggerEvent event)
* fmgr lookup cache space at the caller level. * fmgr lookup cache space at the caller level.
* *
* event: event currently being fired. * event: event currently being fired.
* itemno: item within event currently being fired.
* rel: open relation for event. * rel: open relation for event.
* trigdesc: working copy of rel's trigger info. * trigdesc: working copy of rel's trigger info.
* finfo: array of fmgr lookup cache entries (one per trigger in trigdesc). * finfo: array of fmgr lookup cache entries (one per trigger in trigdesc).
...@@ -1848,11 +1887,11 @@ deferredTriggerAddEvent(DeferredTriggerEvent event) ...@@ -1848,11 +1887,11 @@ deferredTriggerAddEvent(DeferredTriggerEvent event)
* ---------- * ----------
*/ */
static void static void
DeferredTriggerExecute(DeferredTriggerEvent event, int itemno, AfterTriggerExecute(AfterTriggerEvent event,
Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo, 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; TriggerData LocTriggerData;
HeapTupleData oldtuple; HeapTupleData oldtuple;
HeapTupleData newtuple; HeapTupleData newtuple;
...@@ -1864,26 +1903,26 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno, ...@@ -1864,26 +1903,26 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
/* /*
* Fetch the required OLD and NEW tuples. * 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)) 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)) 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 * Setup the trigger information
*/ */
LocTriggerData.type = T_TriggerData; LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = (event->dte_event & TRIGGER_EVENT_OPMASK) | LocTriggerData.tg_event =
(event->dte_event & TRIGGER_EVENT_ROW); event->ate_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW);
LocTriggerData.tg_relation = rel; LocTriggerData.tg_relation = rel;
LocTriggerData.tg_trigger = NULL; LocTriggerData.tg_trigger = NULL;
...@@ -1898,7 +1937,7 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno, ...@@ -1898,7 +1937,7 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
if (LocTriggerData.tg_trigger == NULL) if (LocTriggerData.tg_trigger == NULL)
elog(ERROR, "could not find trigger %u", tgoid); 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: case TRIGGER_EVENT_INSERT:
LocTriggerData.tg_trigtuple = &newtuple; LocTriggerData.tg_trigtuple = &newtuple;
...@@ -1916,6 +1955,8 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno, ...@@ -1916,6 +1955,8 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
break; break;
} }
MemoryContextReset(per_tuple_context);
/* /*
* Call the trigger and throw away any eventually returned updated * Call the trigger and throw away any eventually returned updated
* tuple. * tuple.
...@@ -1929,290 +1970,409 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno, ...@@ -1929,290 +1970,409 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
/* /*
* Release buffers * Release buffers
*/ */
if (ItemPointerIsValid(&(event->dte_oldctid))) if (ItemPointerIsValid(&(event->ate_oldctid)))
ReleaseBuffer(oldbuffer); ReleaseBuffer(oldbuffer);
if (ItemPointerIsValid(&(event->dte_newctid))) if (ItemPointerIsValid(&(event->ate_newctid)))
ReleaseBuffer(newbuffer); 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 * Scan the given event list for events that are marked as to be fired
* should be invoked now and do so. * 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 static void
deferredTriggerInvokeEvents(bool immediate_only) afterTriggerInvokeEvents(AfterTriggerEventList *events,
CommandId firing_id,
bool delete_ok)
{ {
DeferredTriggerEvent event, AfterTriggerEvent event,
prev_event; prev_event;
MemoryContext per_tuple_context; MemoryContext per_tuple_context;
Relation rel = NULL; Relation rel = NULL;
TriggerDesc *trigdesc = NULL; TriggerDesc *trigdesc = NULL;
FmgrInfo *finfo = 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 */ /* Make a per-tuple memory context for trigger function calls */
per_tuple_context = per_tuple_context =
AllocSetContextCreate(CurrentMemoryContext, AllocSetContextCreate(CurrentMemoryContext,
"DeferredTriggerTupleContext", "AfterTriggerTupleContext",
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE); ALLOCSET_DEFAULT_MAXSIZE);
/* prev_event = NULL;
* If immediate_only is true, then the only events that could need event = events->head;
* 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;
}
while (event != NULL) while (event != NULL)
{ {
bool still_deferred_ones = false; AfterTriggerEvent next_event;
DeferredTriggerEvent next_event;
int i;
/* /*
* Skip executing cancelled events, and events already done, * Is it one for me to fire?
* unless they were done by a subtransaction that later aborted.
*/ */
if (!(event->dte_event & TRIGGER_DEFERRED_CANCELED) && if ((event->ate_event & AFTER_TRIGGER_IN_PROGRESS) &&
!(event->dte_event & TRIGGER_DEFERRED_DONE && event->ate_firing_id == firing_id)
TransactionIdIsValid(event->dte_done_xid) &&
!TransactionIdDidAbort(event->dte_done_xid)))
{ {
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 && if (rel)
TransactionIdIsValid(event->dte_item[i].dti_done_xid) && heap_close(rel, NoLock);
!(TransactionIdDidAbort(event->dte_item[i].dti_done_xid))) if (trigdesc)
continue; FreeTriggerDesc(trigdesc);
if (finfo)
pfree(finfo);
/* /*
* This trigger item hasn't been called yet. Check if we * We assume that an appropriate lock is still held by
* should call it now. * the executor, so grab no new lock here.
*/ */
if (immediate_only && rel = heap_open(event->ate_relid, NoLock);
deferredTriggerCheckState(event->dte_item[i].dti_tgoid,
event->dte_item[i].dti_state))
{
still_deferred_ones = true;
continue;
}
/* /*
* So let's fire it... but first, open the correct * Copy relation's trigger info so that we have a
* relation if this is not the same relation as before. * stable copy no matter what the called triggers do.
*/ */
if (rel == NULL || rel->rd_id != event->dte_relid) trigdesc = CopyTriggerDesc(rel->trigdesc);
{
if (rel) if (trigdesc == NULL) /* should not happen */
heap_close(rel, NoLock); elog(ERROR, "relation %u has no triggers",
FreeTriggerDesc(trigdesc); event->ate_relid);
if (finfo)
pfree(finfo); /*
* Allocate space to cache fmgr lookup info for
/* * triggers.
* We assume that an appropriate lock is still held by */
* the executor, so grab no new lock here. finfo = (FmgrInfo *)
*/ palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
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));
}
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(); * Mark the event as done.
} /* end loop over items within event */ */
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 * 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 */ /* Delink it from list and free it */
prev_event = event; if (prev_event)
prev_event->ate_next = next_event;
else
events->head = next_event;
pfree(event);
} }
else else
{ {
/* /* Keep it in list */
* We can drop an item if it's done, but only if we're not prev_event = event;
* 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;
}
} }
event = next_event; event = next_event;
} }
/* Update list tail pointer in case we just deleted tail event */ /* Update list tail pointer in case we just deleted tail event */
deferredTriggers->tail_thisxact = prev_event; events->tail = prev_event;
/* Set the immediate event pointer for next time */
deferredTriggers->events_imm = prev_event;
/* Release working resources */ /* Release working resources */
if (rel) if (rel)
heap_close(rel, NoLock); heap_close(rel, NoLock);
FreeTriggerDesc(trigdesc); if (trigdesc)
FreeTriggerDesc(trigdesc);
if (finfo) if (finfo)
pfree(finfo); pfree(finfo);
MemoryContextDelete(per_tuple_context); MemoryContextDelete(per_tuple_context);
} }
/* ---------- /* ----------
* DeferredTriggerBeginXact() * AfterTriggerBeginXact()
* *
* Called at transaction start (either BEGIN or implicit for single * Called at transaction start (either BEGIN or implicit for single
* statement outside of transaction block). * statement outside of transaction block).
* ---------- * ----------
*/ */
void 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, 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); if (afterTriggers->query_depth >= afterTriggers->maxquerydepth)
deferredTriggers->events = NULL; {
deferredTriggers->events_imm = NULL; /* repalloc will keep the stack in the same context */
deferredTriggers->tail_thisxact = NULL; int new_alloc = afterTriggers->maxquerydepth * 2;
deferredTriggers->tail_stack = NULL;
deferredTriggers->imm_stack = NULL; afterTriggers->query_stack = (AfterTriggerEventList *)
deferredTriggers->state_stack = NULL; repalloc(afterTriggers->query_stack,
deferredTriggers->numalloc = 0; 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 * Called after one query has been completely processed. At this time
* processed. At this time we invoke all outstanding IMMEDIATE triggers. * we invoke all AFTER IMMEDIATE trigger events queued by the query, and
* transfer deferred trigger events to the global deferred-trigger list.
* ---------- * ----------
*/ */
void 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) events = &afterTriggers->query_stack[afterTriggers->query_depth];
return; 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 * Called just before the current transaction is committed. At this
* time we invoke all DEFERRED triggers and tidy up. * time we invoke all DEFERRED triggers and tidy up.
* ---------- * ----------
*/ */
void 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) events = &afterTriggers->events;
return; 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 * Since all the info is in TopTransactionContext or children thereof, we
* need do nothing special to reclaim memory. * need do nothing special to reclaim memory.
*/ */
deferredTriggers = NULL; afterTriggers = NULL;
} }
/* ---------- /* ----------
* DeferredTriggerAbortXact() * AfterTriggerAbortXact()
* *
* The current transaction has entered the abort state. * The current transaction has entered the abort state.
* All outstanding triggers are canceled so we simply throw * All outstanding triggers are canceled so we simply throw
...@@ -2220,139 +2380,147 @@ DeferredTriggerEndXact(void) ...@@ -2220,139 +2380,147 @@ DeferredTriggerEndXact(void)
* ---------- * ----------
*/ */
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; 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 * Since all the info is in TopTransactionContext or children thereof, we
* need do nothing special to reclaim memory. * need do nothing special to reclaim memory.
*/ */
deferredTriggers = NULL; afterTriggers = NULL;
} }
/* /*
* DeferredTriggerBeginSubXact() * AfterTriggerBeginSubXact()
* *
* Start a subtransaction. * Start a subtransaction.
*/ */
void void
DeferredTriggerBeginSubXact(void) AfterTriggerBeginSubXact(void)
{ {
int my_level = GetCurrentTransactionNestLevel(); 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; return;
/* /*
* Allocate more space in the stacks if needed. * 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; MemoryContext old_cxt;
old_cxt = MemoryContextSwitchTo(TopTransactionContext); old_cxt = MemoryContextSwitchTo(TopTransactionContext);
#define DEFTRIG_INITALLOC 8 #define DEFTRIG_INITALLOC 8
deferredTriggers->tail_stack = (DeferredTriggerEvent *) afterTriggers->state_stack = (SetConstraintState *)
palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent)); palloc(DEFTRIG_INITALLOC * sizeof(SetConstraintState));
deferredTriggers->imm_stack = (DeferredTriggerEvent *) afterTriggers->events_stack = (AfterTriggerEventList *)
palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent)); palloc(DEFTRIG_INITALLOC * sizeof(AfterTriggerEventList));
deferredTriggers->state_stack = (DeferredTriggerState *) afterTriggers->depth_stack = (int *)
palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerState)); palloc(DEFTRIG_INITALLOC * sizeof(int));
deferredTriggers->numalloc = DEFTRIG_INITALLOC; afterTriggers->firing_stack = (CommandId *)
palloc(DEFTRIG_INITALLOC * sizeof(CommandId));
afterTriggers->maxtransdepth = DEFTRIG_INITALLOC;
MemoryContextSwitchTo(old_cxt); MemoryContextSwitchTo(old_cxt);
} }
else else
{ {
/* repalloc will keep the stacks in the same context */ /* repalloc will keep the stacks in the same context */
int new_alloc = deferredTriggers->numalloc * 2; int new_alloc = afterTriggers->maxtransdepth * 2;
deferredTriggers->tail_stack = (DeferredTriggerEvent *) afterTriggers->state_stack = (SetConstraintState *)
repalloc(deferredTriggers->tail_stack, repalloc(afterTriggers->state_stack,
new_alloc * sizeof(DeferredTriggerEvent)); new_alloc * sizeof(SetConstraintState));
deferredTriggers->imm_stack = (DeferredTriggerEvent *) afterTriggers->events_stack = (AfterTriggerEventList *)
repalloc(deferredTriggers->imm_stack, repalloc(afterTriggers->events_stack,
new_alloc * sizeof(DeferredTriggerEvent)); new_alloc * sizeof(AfterTriggerEventList));
deferredTriggers->state_stack = (DeferredTriggerState *) afterTriggers->depth_stack = (int *)
repalloc(deferredTriggers->state_stack, repalloc(afterTriggers->depth_stack,
new_alloc * sizeof(DeferredTriggerState)); new_alloc * sizeof(int));
deferredTriggers->numalloc = new_alloc; 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; afterTriggers->state_stack[my_level] = NULL;
deferredTriggers->imm_stack[my_level] = deferredTriggers->events_imm; afterTriggers->events_stack[my_level] = afterTriggers->events;
/* State is not saved until/unless changed */ afterTriggers->depth_stack[my_level] = afterTriggers->query_depth;
deferredTriggers->state_stack[my_level] = NULL; afterTriggers->firing_stack[my_level] = afterTriggers->firing_counter;
} }
/* /*
* DeferredTriggerEndSubXact() * AfterTriggerEndSubXact()
* *
* The current subtransaction is ending. * The current subtransaction is ending.
*/ */
void void
DeferredTriggerEndSubXact(bool isCommit) AfterTriggerEndSubXact(bool isCommit)
{ {
int my_level = GetCurrentTransactionNestLevel(); 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; return;
/* /*
* Pop the prior state if needed. * Pop the prior state if needed.
*/ */
Assert(my_level < deferredTriggers->numalloc); Assert(my_level < afterTriggers->maxtransdepth);
if (isCommit) if (isCommit)
{ {
/* If we saved a prior state, we don't need it anymore */ /* 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) if (state != NULL)
pfree(state); pfree(state);
/* this avoids double pfree if error later: */ /* 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 else
{ {
/* /*
* Aborting --- restore the pointers from the stacks. * Aborting --- restore the pointers from the stacks.
*/ */
deferredTriggers->tail_thisxact = afterTriggers->events = afterTriggers->events_stack[my_level];
deferredTriggers->tail_stack[my_level]; afterTriggers->query_depth = afterTriggers->depth_stack[my_level];
deferredTriggers->events_imm =
deferredTriggers->imm_stack[my_level];
/* /*
* Cleanup the head and the tail of the list. * Cleanup the tail of the list.
*/ */
if (deferredTriggers->tail_thisxact == NULL) if (afterTriggers->events.tail != NULL)
deferredTriggers->events = NULL; afterTriggers->events.tail->ate_next = NULL;
else
deferredTriggers->tail_thisxact->dte_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. * CurTransactionContext will be reset shortly.
*/ */
...@@ -2360,24 +2528,46 @@ DeferredTriggerEndSubXact(bool isCommit) ...@@ -2360,24 +2528,46 @@ DeferredTriggerEndSubXact(bool isCommit)
* Restore the trigger state. If the saved state is NULL, then * Restore the trigger state. If the saved state is NULL, then
* this subxact didn't save it, so it doesn't need restoring. * 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) if (state != NULL)
{ {
pfree(deferredTriggers->state); pfree(afterTriggers->state);
deferredTriggers->state = state; afterTriggers->state = state;
} }
/* this avoids double pfree if error later: */ /* 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 static SetConstraintState
DeferredTriggerStateCreate(int numalloc) SetConstraintStateCreate(int numalloc)
{ {
DeferredTriggerState state; SetConstraintState state;
/* Behave sanely with numalloc == 0 */ /* Behave sanely with numalloc == 0 */
if (numalloc <= 0) if (numalloc <= 0)
...@@ -2386,10 +2576,10 @@ DeferredTriggerStateCreate(int numalloc) ...@@ -2386,10 +2576,10 @@ DeferredTriggerStateCreate(int numalloc)
/* /*
* We assume that zeroing will correctly initialize the state values. * We assume that zeroing will correctly initialize the state values.
*/ */
state = (DeferredTriggerState) state = (SetConstraintState)
MemoryContextAllocZero(TopTransactionContext, MemoryContextAllocZero(TopTransactionContext,
sizeof(DeferredTriggerStateData) + sizeof(SetConstraintStateData) +
(numalloc - 1) *sizeof(DeferredTriggerStatusData)); (numalloc - 1) *sizeof(SetConstraintTriggerData));
state->numalloc = numalloc; state->numalloc = numalloc;
...@@ -2397,67 +2587,67 @@ DeferredTriggerStateCreate(int numalloc) ...@@ -2397,67 +2587,67 @@ DeferredTriggerStateCreate(int numalloc)
} }
/* /*
* Copy a DeferredTriggerState * Copy a SetConstraintState
*/ */
static DeferredTriggerState static SetConstraintState
DeferredTriggerStateCopy(DeferredTriggerState origstate) SetConstraintStateCopy(SetConstraintState origstate)
{ {
DeferredTriggerState state; SetConstraintState state;
state = DeferredTriggerStateCreate(origstate->numstates); state = SetConstraintStateCreate(origstate->numstates);
state->all_isset = origstate->all_isset; state->all_isset = origstate->all_isset;
state->all_isdeferred = origstate->all_isdeferred; state->all_isdeferred = origstate->all_isdeferred;
state->numstates = origstate->numstates; state->numstates = origstate->numstates;
memcpy(state->trigstates, origstate->trigstates, memcpy(state->trigstates, origstate->trigstates,
origstate->numstates * sizeof(DeferredTriggerStatusData)); origstate->numstates * sizeof(SetConstraintTriggerData));
return state; 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). * pointer to the state object (it will change if we have to repalloc).
*/ */
static DeferredTriggerState static SetConstraintState
DeferredTriggerStateAddItem(DeferredTriggerState state, SetConstraintStateAddItem(SetConstraintState state,
Oid tgoid, bool tgisdeferred) Oid tgoid, bool tgisdeferred)
{ {
if (state->numstates >= state->numalloc) if (state->numstates >= state->numalloc)
{ {
int newalloc = state->numalloc * 2; int newalloc = state->numalloc * 2;
newalloc = Max(newalloc, 8); /* in case original has size 0 */ newalloc = Max(newalloc, 8); /* in case original has size 0 */
state = (DeferredTriggerState) state = (SetConstraintState)
repalloc(state, repalloc(state,
sizeof(DeferredTriggerStateData) + sizeof(SetConstraintStateData) +
(newalloc - 1) *sizeof(DeferredTriggerStatusData)); (newalloc - 1) *sizeof(SetConstraintTriggerData));
state->numalloc = newalloc; state->numalloc = newalloc;
Assert(state->numstates < state->numalloc); Assert(state->numstates < state->numalloc);
} }
state->trigstates[state->numstates].dts_tgoid = tgoid; state->trigstates[state->numstates].sct_tgoid = tgoid;
state->trigstates[state->numstates].dts_tgisdeferred = tgisdeferred; state->trigstates[state->numstates].sct_tgisdeferred = tgisdeferred;
state->numstates++; state->numstates++;
return state; return state;
} }
/* ---------- /* ----------
* DeferredTriggerSetState() * AfterTriggerSetState()
* *
* Called for the SET CONSTRAINTS ... utility command. * Execute the SET CONSTRAINTS ... utility command.
* ---------- * ----------
*/ */
void void
DeferredTriggerSetState(ConstraintsSetStmt *stmt) AfterTriggerSetState(ConstraintsSetStmt *stmt)
{ {
int my_level = GetCurrentTransactionNestLevel(); 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; return;
/* /*
...@@ -2466,10 +2656,10 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -2466,10 +2656,10 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
* aborts. * aborts.
*/ */
if (my_level > 1 && if (my_level > 1 &&
deferredTriggers->state_stack[my_level] == NULL) afterTriggers->state_stack[my_level] == NULL)
{ {
deferredTriggers->state_stack[my_level] = afterTriggers->state_stack[my_level] =
DeferredTriggerStateCopy(deferredTriggers->state); SetConstraintStateCopy(afterTriggers->state);
} }
/* /*
...@@ -2480,13 +2670,13 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -2480,13 +2670,13 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
/* /*
* Forget any previous SET CONSTRAINTS commands in this transaction. * 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. * Set the per-transaction ALL state to known.
*/ */
deferredTriggers->state->all_isset = true; afterTriggers->state->all_isset = true;
deferredTriggers->state->all_isdeferred = stmt->deferred; afterTriggers->state->all_isdeferred = stmt->deferred;
} }
else else
{ {
...@@ -2569,29 +2759,28 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -2569,29 +2759,28 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
heap_close(tgrel, AccessShareLock); heap_close(tgrel, AccessShareLock);
/* /*
* Inside of a transaction block set the trigger states of * Set the trigger states of individual triggers for this xact.
* individual triggers on transaction level.
*/ */
foreach(l, oidlist) foreach(l, oidlist)
{ {
Oid tgoid = lfirst_oid(l); Oid tgoid = lfirst_oid(l);
DeferredTriggerState state = deferredTriggers->state; SetConstraintState state = afterTriggers->state;
bool found = false; bool found = false;
int i; int i;
for (i = 0; i < state->numstates; 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; found = true;
break; break;
} }
} }
if (!found) if (!found)
{ {
deferredTriggers->state = afterTriggers->state =
DeferredTriggerStateAddItem(state, tgoid, stmt->deferred); SetConstraintStateAddItem(state, tgoid, stmt->deferred);
} }
} }
} }
...@@ -2600,20 +2789,35 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -2600,20 +2789,35 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
* SQL99 requires that when a constraint is set to IMMEDIATE, any * SQL99 requires that when a constraint is set to IMMEDIATE, any
* deferred checks against that constraint must be made when the SET * deferred checks against that constraint must be made when the SET
* CONSTRAINTS command is executed -- i.e. the effects of the SET * CONSTRAINTS command is executed -- i.e. the effects of the SET
* CONSTRAINTS command applies retroactively. This happens "for free" * CONSTRAINTS command apply retroactively. We've updated the
* since we have already made the necessary modifications to the * constraints state, so scan the list of previously deferred events
* constraints, and deferredTriggerEndQuery() is called by * to fire any that have now become immediate.
* finish_xact_command(). But we must reset *
* deferredTriggerInvokeEvents' tail pointer to make it rescan the * Obviously, if this was SET ... DEFERRED then it can't have converted
* entire list, in case some deferred events are now immediately * any unfired events to immediate, so we need do nothing in that case.
* invokable.
*/ */
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. * Called by ExecA[RS]...Triggers() to add the event to the queue.
* *
...@@ -2622,23 +2826,20 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -2622,23 +2826,20 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
* ---------- * ----------
*/ */
static void static void
DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup) HeapTuple oldtup, HeapTuple newtup)
{ {
Relation rel = relinfo->ri_RelationDesc; Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
MemoryContext oldcxt; AfterTriggerEvent new_event;
DeferredTriggerEvent new_event;
int new_size;
int i; int i;
int ntriggers; int ntriggers;
int n_enabled_triggers = 0;
int *tgindx; int *tgindx;
ItemPointerData oldctid; ItemPointerData oldctid;
ItemPointerData newctid; ItemPointerData newctid;
if (deferredTriggers == NULL) if (afterTriggers == NULL)
elog(ERROR, "DeferredTriggerSaveEvent() called outside of transaction"); elog(ERROR, "AfterTriggerSaveEvent() called outside of transaction");
/* /*
* Get the CTID's of OLD and NEW * Get the CTID's of OLD and NEW
...@@ -2652,6 +2853,9 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, ...@@ -2652,6 +2853,9 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
else else
ItemPointerSetInvalid(&(newctid)); ItemPointerSetInvalid(&(newctid));
/*
* Scan the appropriate set of triggers
*/
if (row_trigger) if (row_trigger)
{ {
ntriggers = trigdesc->n_after_row[event]; ntriggers = trigdesc->n_after_row[event];
...@@ -2663,137 +2867,80 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, ...@@ -2663,137 +2867,80 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
tgindx = trigdesc->tg_after_statement[event]; 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++) for (i = 0; i < ntriggers; i++)
{ {
Trigger *trigger = &trigdesc->triggers[tgindx[i]]; Trigger *trigger = &trigdesc->triggers[tgindx[i]];
if (trigger->tgenabled) /* Ignore disabled triggers */
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]];
if (!trigger->tgenabled) if (!trigger->tgenabled)
continue; continue;
ev_item = &(new_event->dte_item[i]); /*
ev_item->dti_tgoid = trigger->tgoid; * If it is an RI UPDATE trigger, and the referenced keys have
ev_item->dti_done_xid = InvalidTransactionId; * not changed, short-circuit queuing of the event; there's no
ev_item->dti_state = * need to fire the trigger.
((trigger->tgdeferrable) ? */
TRIGGER_DEFERRED_DEFERRABLE : 0) | if ((event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE)
((trigger->tginitdeferred) ? {
TRIGGER_DEFERRED_INITDEFERRED : 0); bool is_ri_trigger;
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);
switch (event & TRIGGER_EVENT_OPMASK) switch (trigger->tgfoid)
{ {
case TRIGGER_EVENT_INSERT: case F_RI_FKEY_NOACTION_UPD:
/* nothing to do */ case F_RI_FKEY_CASCADE_UPD:
break; 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;
}
/* if (is_ri_trigger)
* Check if one of the referenced keys is changed.
*/
for (i = 0; i < ntriggers; i++)
{ {
Trigger *trigger = &trigdesc->triggers[tgindx[i]];
bool is_ri_trigger;
bool key_unchanged;
TriggerData LocTriggerData; 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.type = T_TriggerData;
LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE; LocTriggerData.tg_event =
TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW;
LocTriggerData.tg_relation = rel; LocTriggerData.tg_relation = rel;
LocTriggerData.tg_trigtuple = oldtup; LocTriggerData.tg_trigtuple = oldtup;
LocTriggerData.tg_newtuple = newtup; LocTriggerData.tg_newtuple = newtup;
LocTriggerData.tg_trigger = trigger; LocTriggerData.tg_trigger = trigger;
key_unchanged = RI_FKey_keyequal_upd(&LocTriggerData); if (RI_FKey_keyequal_upd(&LocTriggerData))
if (key_unchanged)
{ {
/* /* key unchanged, so skip queuing this event */
* The key hasn't changed, so no need later to invoke continue;
* the trigger at all.
*/
new_event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE;
new_event->dte_item[i].dti_done_xid = GetCurrentTransactionId();
} }
} }
}
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 */ * Add the new event to the queue.
break; */
afterTriggerAddEvent(new_event);
} }
/*
* Add the new event to the queue.
*/
deferredTriggerAddEvent(new_event);
} }
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -17,6 +17,7 @@
#include "access/heapam.h" #include "access/heapam.h"
#include "catalog/pg_proc.h" #include "catalog/pg_proc.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/trigger.h"
#include "executor/execdefs.h" #include "executor/execdefs.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "executor/functions.h" #include "executor/functions.h"
...@@ -273,7 +274,10 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) ...@@ -273,7 +274,10 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
/* Utility commands don't need Executor. */ /* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY) if (es->qd->operation != CMD_UTILITY)
{
AfterTriggerBeginQuery();
ExecutorStart(es->qd, false, false); ExecutorStart(es->qd, false, false);
}
es->status = F_EXEC_RUN; es->status = F_EXEC_RUN;
} }
...@@ -316,7 +320,10 @@ postquel_end(execution_state *es) ...@@ -316,7 +320,10 @@ postquel_end(execution_state *es)
/* Utility commands don't need Executor. */ /* Utility commands don't need Executor. */
if (es->qd->operation != CMD_UTILITY) if (es->qd->operation != CMD_UTILITY)
{
ExecutorEnd(es->qd); ExecutorEnd(es->qd);
AfterTriggerEndQuery();
}
FreeQueryDesc(es->qd); FreeQueryDesc(es->qd);
es->qd = NULL; es->qd = NULL;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 @@ ...@@ -16,6 +16,7 @@
#include "access/printtup.h" #include "access/printtup.h"
#include "catalog/heap.h" #include "catalog/heap.h"
#include "commands/trigger.h"
#include "executor/spi_priv.h" #include "executor/spi_priv.h"
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
...@@ -1434,6 +1435,8 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit, ...@@ -1434,6 +1435,8 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
ResetUsage(); ResetUsage();
#endif #endif
AfterTriggerBeginQuery();
ExecutorStart(queryDesc, useCurrentSnapshot, false); ExecutorStart(queryDesc, useCurrentSnapshot, false);
ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount); ExecutorRun(queryDesc, ForwardScanDirection, (long) tcount);
...@@ -1447,6 +1450,11 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit, ...@@ -1447,6 +1450,11 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
elog(ERROR, "consistency check on SPI tuple count failed"); elog(ERROR, "consistency check on SPI tuple count failed");
} }
ExecutorEnd(queryDesc);
/* Take care of any queued AFTER triggers */
AfterTriggerEndQuery();
if (queryDesc->dest->mydest == SPI) if (queryDesc->dest->mydest == SPI)
{ {
SPI_processed = _SPI_current->processed; SPI_processed = _SPI_current->processed;
...@@ -1459,8 +1467,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit, ...@@ -1459,8 +1467,6 @@ _SPI_pquery(QueryDesc *queryDesc, bool runit,
res = SPI_OK_UTILITY; res = SPI_OK_UTILITY;
} }
ExecutorEnd(queryDesc);
FreeQueryDesc(queryDesc); FreeQueryDesc(queryDesc);
#ifdef SPI_EXECUTOR_STATS #ifdef SPI_EXECUTOR_STATS
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* this is the "main" module of the postgres backend and * this is the "main" module of the postgres backend and
...@@ -1823,9 +1823,6 @@ finish_xact_command(void) ...@@ -1823,9 +1823,6 @@ finish_xact_command(void)
{ {
if (xact_started) if (xact_started)
{ {
/* Invoke IMMEDIATE constraint triggers */
DeferredTriggerEndQuery();
/* Cancel any active statement timeout before committing */ /* Cancel any active statement timeout before committing */
disable_sig_alarm(true); disable_sig_alarm(true);
......
...@@ -8,13 +8,14 @@ ...@@ -8,13 +8,14 @@
* *
* *
* IDENTIFICATION * 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 "postgres.h"
#include "commands/trigger.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
...@@ -136,6 +137,11 @@ ProcessQuery(Query *parsetree, ...@@ -136,6 +137,11 @@ ProcessQuery(Query *parsetree,
*/ */
queryDesc = CreateQueryDesc(parsetree, plan, dest, params, false); queryDesc = CreateQueryDesc(parsetree, plan, dest, params, false);
/*
* Set up to collect AFTER triggers
*/
AfterTriggerBeginQuery();
/* /*
* Call ExecStart to prepare the plan for execution * Call ExecStart to prepare the plan for execution
*/ */
...@@ -185,6 +191,9 @@ ProcessQuery(Query *parsetree, ...@@ -185,6 +191,9 @@ ProcessQuery(Query *parsetree,
*/ */
ExecutorEnd(queryDesc); ExecutorEnd(queryDesc);
/* And take care of any queued AFTER triggers */
AfterTriggerEndQuery();
FreeQueryDesc(queryDesc); FreeQueryDesc(queryDesc);
} }
...@@ -290,6 +299,13 @@ PortalStart(Portal portal, ParamListInfo params) ...@@ -290,6 +299,13 @@ PortalStart(Portal portal, ParamListInfo params)
params, params,
false); 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 * Call ExecStart to prepare the plan for execution
*/ */
...@@ -1144,8 +1160,8 @@ DoPortalRunFetch(Portal portal, ...@@ -1144,8 +1160,8 @@ DoPortalRunFetch(Portal portal,
return PortalRunSelect(portal, false, 1L, dest); return PortalRunSelect(portal, false, 1L, dest);
} }
else else
/* count == 0 */
{ {
/* count == 0 */
/* Rewind to start, return zero rows */ /* Rewind to start, return zero rows */
DoPortalRewind(portal); DoPortalRewind(portal);
return PortalRunSelect(portal, true, 0L, dest); return PortalRunSelect(portal, true, 0L, dest);
...@@ -1173,8 +1189,8 @@ DoPortalRunFetch(Portal portal, ...@@ -1173,8 +1189,8 @@ DoPortalRunFetch(Portal portal,
return PortalRunSelect(portal, false, 1L, dest); return PortalRunSelect(portal, false, 1L, dest);
} }
else else
/* count == 0 */
{ {
/* count == 0 */
/* Same as FETCH FORWARD 0, so fall out of switch */ /* Same as FETCH FORWARD 0, so fall out of switch */
fdirection = FETCH_FORWARD; fdirection = FETCH_FORWARD;
} }
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -912,7 +912,7 @@ ProcessUtility(Node *parsetree,
break; break;
case T_ConstraintsSetStmt: case T_ConstraintsSetStmt:
DeferredTriggerSetState((ConstraintsSetStmt *) parsetree); AfterTriggerSetState((ConstraintsSetStmt *) parsetree);
break; break;
case T_CreateGroupStmt: case T_CreateGroupStmt:
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
* *
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * 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) ...@@ -2454,7 +2454,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
* *
* Check if we have a key change on update. * 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". * trigger queue manager to detect "triggered data change violation".
* ---------- * ----------
*/ */
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -45,11 +45,12 @@ typedef struct TriggerData
#define TRIGGER_EVENT_ROW 0x00000004 #define TRIGGER_EVENT_ROW 0x00000004
#define TRIGGER_EVENT_BEFORE 0x00000008 #define TRIGGER_EVENT_BEFORE 0x00000008
#define TRIGGER_DEFERRED_DONE 0x00000010 /* More TriggerEvent flags, used only within trigger.c */
#define TRIGGER_DEFERRED_CANCELED 0x00000020
#define TRIGGER_DEFERRED_DEFERRABLE 0x00000040 #define AFTER_TRIGGER_DONE 0x00000010
#define TRIGGER_DEFERRED_INITDEFERRED 0x00000080 #define AFTER_TRIGGER_IN_PROGRESS 0x00000020
#define TRIGGER_DEFERRED_HAS_BEFORE 0x00000100 #define AFTER_TRIGGER_DEFERRABLE 0x00000040
#define AFTER_TRIGGER_INITDEFERRED 0x00000080
#define TRIGGER_FIRED_BY_INSERT(event) \ #define TRIGGER_FIRED_BY_INSERT(event) \
(((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \ (((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
...@@ -151,14 +152,15 @@ extern void ExecARUpdateTriggers(EState *estate, ...@@ -151,14 +152,15 @@ extern void ExecARUpdateTriggers(EState *estate,
ItemPointer tupleid, ItemPointer tupleid,
HeapTuple newtuple); HeapTuple newtuple);
extern void DeferredTriggerBeginXact(void); extern void AfterTriggerBeginXact(void);
extern void DeferredTriggerEndQuery(void); extern void AfterTriggerBeginQuery(void);
extern void DeferredTriggerEndXact(void); extern void AfterTriggerEndQuery(void);
extern void DeferredTriggerAbortXact(void); extern void AfterTriggerEndXact(void);
extern void DeferredTriggerBeginSubXact(void); extern void AfterTriggerAbortXact(void);
extern void DeferredTriggerEndSubXact(bool isCommit); extern void AfterTriggerBeginSubXact(void);
extern void AfterTriggerEndSubXact(bool isCommit);
extern void DeferredTriggerSetState(ConstraintsSetStmt *stmt); extern void AfterTriggerSetState(ConstraintsSetStmt *stmt);
/* /*
......
...@@ -646,6 +646,7 @@ SELECT * from FKTABLE; ...@@ -646,6 +646,7 @@ SELECT * from FKTABLE;
UPDATE PKTABLE set ptest2=5 where ptest2=2; UPDATE PKTABLE set ptest2=5 where ptest2=2;
ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3" 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". 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 -- Try to update something that will set default
UPDATE PKTABLE set ptest1=0, ptest2=5, ptest3=10 where ptest2=2; UPDATE PKTABLE set ptest1=0, ptest2=5, ptest3=10 where ptest2=2;
UPDATE PKTABLE set ptest2=10 where ptest2=4; UPDATE PKTABLE set ptest2=10 where ptest2=4;
......
...@@ -1935,3 +1935,74 @@ select * from foo; ...@@ -1935,3 +1935,74 @@ select * from foo;
20 20
(2 rows) (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();
...@@ -1699,3 +1699,54 @@ select blockme(); ...@@ -1699,3 +1699,54 @@ select blockme();
reset statement_timeout; reset statement_timeout;
select * from foo; 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();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment