Commit b813554d authored by Bruce Momjian's avatar Bruce Momjian

Improve documentation of trigger firing queue handling, cleanup.

Neil Conway
parent 2132ac89
<!-- $Header: /cvsroot/pgsql/doc/src/sgml/ref/set_constraints.sgml,v 1.4 2002/06/03 01:10:38 momjian Exp $ --> <!-- $Header: /cvsroot/pgsql/doc/src/sgml/ref/set_constraints.sgml,v 1.5 2002/08/17 12:15:48 momjian 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>
...@@ -22,10 +22,32 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">constraint</replaceable> ...@@ -22,10 +22,32 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">constraint</replaceable>
<para> <para>
<command>SET CONSTRAINTS</command> sets the behavior of constraint <command>SET CONSTRAINTS</command> sets the behavior of constraint
evaluation in the current transaction. In evaluation in the current transaction. In <option>IMMEDIATE
<option>IMMEDIATE</option> mode, constraints are checked at the end </option> mode, constraints are checked at the end of each
of each statement. In <option>DEFERRED</option> mode, constraints statement. In <option>DEFERRED</option> mode, constraints are not
are not checked until transaction commit. checked until transaction commit.
</para>
<note>
<para>
This command only alters the behavior of constraints within the
current transaction. Thus, if you execute this command outside
of an explicit transaction block (such as one started with
<command>BEGIN</command>), 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 <option>INITIALLY DEFERRED</option> or
<option>INITIALLY IMMEDIATE</option> when you create the constraint.
</para>
</note>
<para>
When you change the mode of a constraint to be <option>IMMEDIATE
</option>, the new constraint mode takes effect retroactively:
any outstanding data modifications that would have been checked
at the end of the transaction (when using
<option>DEFERRED</option>) are instead checked during the
execution of the <command>SET CONSTRAINTS</command> command.
</para> </para>
<para> <para>
...@@ -52,7 +74,11 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">constraint</replaceable> ...@@ -52,7 +74,11 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">constraint</replaceable>
<para> <para>
<command>SET CONSTRAINTS</command> is defined in <command>SET CONSTRAINTS</command> is defined in
<acronym>SQL92</acronym> and <acronym>SQL99</acronym>. <acronym>SQL92</acronym> and <acronym>SQL99</acronym>. The
implementation in <productname>PostgreSQL</productname> complies
with the behavior defined in the standard, except for the
<productname>PostgreSQL</productname> limitation that <command>SET
CONSTRAINTS</command> cannot be applied to check or unique constraints.
</para> </para>
</refsect2> </refsect2>
</refsect1> </refsect1>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.125 2002/08/13 17:22:08 petere Exp $ * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.126 2002/08/17 12:15:48 momjian Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1266,12 +1266,10 @@ static MemoryContext deftrig_cxt = NULL; ...@@ -1266,12 +1266,10 @@ static MemoryContext deftrig_cxt = NULL;
* state IMMEDIATE or DEFERRED. * state IMMEDIATE or DEFERRED.
* ---------- * ----------
*/ */
static bool deftrig_dfl_all_isset = false;
static bool deftrig_dfl_all_isdeferred = false;
static List *deftrig_dfl_trigstates = NIL; static List *deftrig_dfl_trigstates = NIL;
static bool deftrig_all_isset; static bool deftrig_all_isset = false;
static bool deftrig_all_isdeferred; static bool deftrig_all_isdeferred = false;
static List *deftrig_trigstates; static List *deftrig_trigstates;
/* ---------- /* ----------
...@@ -1702,8 +1700,11 @@ DeferredTriggerBeginXact(void) ...@@ -1702,8 +1700,11 @@ DeferredTriggerBeginXact(void)
ALLOCSET_DEFAULT_MAXSIZE); ALLOCSET_DEFAULT_MAXSIZE);
oldcxt = MemoryContextSwitchTo(deftrig_cxt); oldcxt = MemoryContextSwitchTo(deftrig_cxt);
deftrig_all_isset = deftrig_dfl_all_isset; deftrig_all_isset = false;
deftrig_all_isdeferred = deftrig_dfl_all_isdeferred; /*
* If unspecified, constraints default to IMMEDIATE, per SQL
*/
deftrig_all_isdeferred = false;
deftrig_trigstates = NIL; deftrig_trigstates = NIL;
foreach(l, deftrig_dfl_trigstates) foreach(l, deftrig_dfl_trigstates)
...@@ -1793,59 +1794,30 @@ DeferredTriggerAbortXact(void) ...@@ -1793,59 +1794,30 @@ DeferredTriggerAbortXact(void)
/* ---------- /* ----------
* DeferredTriggerSetState() * DeferredTriggerSetState()
* *
* Called for the users SET CONSTRAINTS ... utility command. * Called for the SET CONSTRAINTS ... utility command.
* ---------- * ----------
*/ */
void void
DeferredTriggerSetState(ConstraintsSetStmt *stmt) DeferredTriggerSetState(ConstraintsSetStmt *stmt)
{ {
Relation tgrel;
List *l; List *l;
List *ls;
List *loid = NIL;
MemoryContext oldcxt;
bool found;
DeferredTriggerStatus state;
/* /*
* Handle SET CONSTRAINTS ALL ... * If called outside a transaction block, we can safely return: this
* command cannot effect any subsequent transactions, and there
* are no "session-level" trigger settings.
*/ */
if (stmt->constraints == NIL)
{
if (!IsTransactionBlock()) if (!IsTransactionBlock())
{ return;
/*
* ... outside of a transaction block
*
* Drop all information about individual trigger states per
* session.
*/
l = deftrig_dfl_trigstates;
while (l != NIL)
{
List *next = lnext(l);
pfree(lfirst(l));
pfree(l);
l = next;
}
deftrig_dfl_trigstates = NIL;
/* /*
* Set the session ALL state to known. * Handle SET CONSTRAINTS ALL ...
*/ */
deftrig_dfl_all_isset = true; if (stmt->constraints == NIL)
deftrig_dfl_all_isdeferred = stmt->deferred;
return;
}
else
{ {
/* /*
* ... inside of a transaction block * Drop all per-transaction information about individual trigger
* * states.
* Drop all information about individual trigger states per
* transaction.
*/ */
l = deftrig_trigstates; l = deftrig_trigstates;
while (l != NIL) while (l != NIL)
...@@ -1859,14 +1831,19 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -1859,14 +1831,19 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
deftrig_trigstates = NIL; deftrig_trigstates = NIL;
/* /*
* Set the per transaction ALL state to known. * Set the per-transaction ALL state to known.
*/ */
deftrig_all_isset = true; deftrig_all_isset = true;
deftrig_all_isdeferred = stmt->deferred; deftrig_all_isdeferred = stmt->deferred;
return;
}
} }
else
{
Relation tgrel;
MemoryContext oldcxt;
bool found;
DeferredTriggerStatus state;
List *ls;
List *loid = NIL;
/* ---------- /* ----------
* Handle SET CONSTRAINTS constraint-name [, ...] * Handle SET CONSTRAINTS constraint-name [, ...]
...@@ -1891,8 +1868,7 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -1891,8 +1868,7 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
/* /*
* Setup to scan pg_trigger by tgconstrname ... * Setup to scan pg_trigger by tgconstrname ...
*/ */
ScanKeyEntryInitialize(&skey, ScanKeyEntryInitialize(&skey, (bits16) 0x0,
(bits16) 0x0,
(AttrNumber) Anum_pg_trigger_tgconstrname, (AttrNumber) Anum_pg_trigger_tgconstrname,
(RegProcedure) F_NAMEEQ, (RegProcedure) F_NAMEEQ,
PointerGetDatum(cname)); PointerGetDatum(cname));
...@@ -1937,45 +1913,6 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -1937,45 +1913,6 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
} }
heap_close(tgrel, AccessShareLock); heap_close(tgrel, AccessShareLock);
if (!IsTransactionBlock())
{
/*
* Outside of a transaction block set the trigger states of
* individual triggers on session level.
*/
oldcxt = MemoryContextSwitchTo(deftrig_gcxt);
foreach(l, loid)
{
found = false;
foreach(ls, deftrig_dfl_trigstates)
{
state = (DeferredTriggerStatus) lfirst(ls);
if (state->dts_tgoid == (Oid) lfirsti(l))
{
state->dts_tgisdeferred = stmt->deferred;
found = true;
break;
}
}
if (!found)
{
state = (DeferredTriggerStatus)
palloc(sizeof(DeferredTriggerStatusData));
state->dts_tgoid = (Oid) lfirsti(l);
state->dts_tgisdeferred = stmt->deferred;
deftrig_dfl_trigstates =
lappend(deftrig_dfl_trigstates, state);
}
}
MemoryContextSwitchTo(oldcxt);
return;
}
else
{
/* /*
* Inside of a transaction block set the trigger states of * Inside of a transaction block set the trigger states of
* individual triggers on transaction level. * individual triggers on transaction level.
...@@ -2008,9 +1945,17 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -2008,9 +1945,17 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
} }
MemoryContextSwitchTo(oldcxt); MemoryContextSwitchTo(oldcxt);
return;
} }
/*
* 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().
*/
} }
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.170 2002/08/15 16:36:05 momjian Exp $ * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.171 2002/08/17 12:15:49 momjian Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -215,9 +215,8 @@ ProcessUtility(Node *parsetree, ...@@ -215,9 +215,8 @@ ProcessUtility(Node *parsetree,
BeginTransactionBlock(); BeginTransactionBlock();
/* /*
* Currently, the only option that can be set is * Currently, the only option that can be set by
* the transaction isolation level by START * START TRANSACTION is the isolation level.
* TRANSACTION.
*/ */
if (stmt->options) if (stmt->options)
{ {
......
...@@ -945,3 +945,80 @@ ERROR: Unable to identify an operator '=' for types 'inet' and 'integer' ...@@ -945,3 +945,80 @@ ERROR: Unable to identify an operator '=' for types 'inet' and 'integer'
drop table pktable; drop table pktable;
ERROR: table "pktable" does not exist ERROR: table "pktable" does not exist
drop table pktable_base; drop table pktable_base;
--
-- Deferrable constraints
-- (right now, only FOREIGN KEY constraints can be deferred)
--
-- deferrable, explicitely deferred
CREATE TABLE pktable (
id INT4 PRIMARY KEY,
other INT4
);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'pktable_pkey' for table 'pktable'
CREATE TABLE fktable (
id INT4 PRIMARY KEY,
fk INT4 REFERENCES pktable DEFERRABLE
);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'fktable_pkey' for table 'fktable'
NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)
-- default to immediate: should fail
INSERT INTO fktable VALUES (5, 10);
ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
-- explicitely defer the constraint
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
INSERT INTO fktable VALUES (10, 15);
INSERT INTO pktable VALUES (15, 0); -- make the FK insert valid
COMMIT;
DROP TABLE fktable, pktable;
-- deferrable, initially deferred
CREATE TABLE pktable (
id INT4 PRIMARY KEY,
other INT4
);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'pktable_pkey' for table 'pktable'
CREATE TABLE fktable (
id INT4 PRIMARY KEY,
fk INT4 REFERENCES pktable DEFERRABLE INITIALLY DEFERRED
);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'fktable_pkey' for table 'fktable'
NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)
-- default to deferred, should succeed
BEGIN;
INSERT INTO fktable VALUES (100, 200);
INSERT INTO pktable VALUES (200, 500); -- make the FK insert valid
COMMIT;
-- default to deferred, explicitely make immediate
BEGIN;
SET CONSTRAINTS ALL IMMEDIATE;
-- should fail
INSERT INTO fktable VALUES (500, 1000);
ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
COMMIT;
DROP TABLE fktable, pktable;
-- tricky behavior: according to SQL99, if a deferred constraint is set
-- to 'immediate' mode, it should be checked for validity *immediately*,
-- not when the current transaction commits (i.e. the mode change applies
-- retroactively)
CREATE TABLE pktable (
id INT4 PRIMARY KEY,
other INT4
);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'pktable_pkey' for table 'pktable'
CREATE TABLE fktable (
id INT4 PRIMARY KEY,
fk INT4 REFERENCES pktable DEFERRABLE
);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index 'fktable_pkey' for table 'fktable'
NOTICE: CREATE TABLE will create implicit trigger(s) for FOREIGN KEY check(s)
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
-- should succeed, for now
INSERT INTO fktable VALUES (1000, 2000);
-- should cause transaction abort, due to preceding error
SET CONSTRAINTS ALL IMMEDIATE;
ERROR: $1 referential integrity violation - key referenced from fktable not found in pktable
INSERT INTO pktable VALUES (2000, 3); -- too late
ERROR: current transaction is aborted, queries ignored until end of transaction block
COMMIT;
DROP TABLE fktable, pktable;
...@@ -583,3 +583,94 @@ create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), forei ...@@ -583,3 +583,94 @@ create table pktable(ptest1 inet, ptest2 inet, primary key(base1, ptest1), forei
drop table pktable; drop table pktable;
drop table pktable_base; drop table pktable_base;
--
-- Deferrable constraints
-- (right now, only FOREIGN KEY constraints can be deferred)
--
-- deferrable, explicitely deferred
CREATE TABLE pktable (
id INT4 PRIMARY KEY,
other INT4
);
CREATE TABLE fktable (
id INT4 PRIMARY KEY,
fk INT4 REFERENCES pktable DEFERRABLE
);
-- default to immediate: should fail
INSERT INTO fktable VALUES (5, 10);
-- explicitely defer the constraint
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
INSERT INTO fktable VALUES (10, 15);
INSERT INTO pktable VALUES (15, 0); -- make the FK insert valid
COMMIT;
DROP TABLE fktable, pktable;
-- deferrable, initially deferred
CREATE TABLE pktable (
id INT4 PRIMARY KEY,
other INT4
);
CREATE TABLE fktable (
id INT4 PRIMARY KEY,
fk INT4 REFERENCES pktable DEFERRABLE INITIALLY DEFERRED
);
-- default to deferred, should succeed
BEGIN;
INSERT INTO fktable VALUES (100, 200);
INSERT INTO pktable VALUES (200, 500); -- make the FK insert valid
COMMIT;
-- default to deferred, explicitely make immediate
BEGIN;
SET CONSTRAINTS ALL IMMEDIATE;
-- should fail
INSERT INTO fktable VALUES (500, 1000);
COMMIT;
DROP TABLE fktable, pktable;
-- tricky behavior: according to SQL99, if a deferred constraint is set
-- to 'immediate' mode, it should be checked for validity *immediately*,
-- not when the current transaction commits (i.e. the mode change applies
-- retroactively)
CREATE TABLE pktable (
id INT4 PRIMARY KEY,
other INT4
);
CREATE TABLE fktable (
id INT4 PRIMARY KEY,
fk INT4 REFERENCES pktable DEFERRABLE
);
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
-- should succeed, for now
INSERT INTO fktable VALUES (1000, 2000);
-- should cause transaction abort, due to preceding error
SET CONSTRAINTS ALL IMMEDIATE;
INSERT INTO pktable VALUES (2000, 3); -- too late
COMMIT;
DROP TABLE fktable, pktable;
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