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
......
This diff is collapsed.
...@@ -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