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