-<!-- $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">
<refmeta>
<refentrytitle id="SQL-SET-CONSTRAINTS-title">SET CONSTRAINTS</refentrytitle>
<para>
<command>SET CONSTRAINTS</command> sets the behavior of constraint
- evaluation in the current transaction. In
- <option>IMMEDIATE</option> mode, constraints are checked at the end
- of each statement. In <option>DEFERRED</option> mode, constraints
- are not checked until transaction commit.
+ evaluation in the current transaction. In <option>IMMEDIATE
+ </option> mode, constraints are checked at the end of each
+ statement. In <option>DEFERRED</option> mode, constraints are not
+ 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>
<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>
</refsect2>
</refsect1>
* Portions Copyright (c) 1994, Regents of the University of California
*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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 bool deftrig_all_isset;
-static bool deftrig_all_isdeferred;
+static bool deftrig_all_isset = false;
+static bool deftrig_all_isdeferred = false;
static List *deftrig_trigstates;
/* ----------
ALLOCSET_DEFAULT_MAXSIZE);
oldcxt = MemoryContextSwitchTo(deftrig_cxt);
- deftrig_all_isset = deftrig_dfl_all_isset;
- deftrig_all_isdeferred = deftrig_dfl_all_isdeferred;
+ deftrig_all_isset = false;
+ /*
+ * If unspecified, constraints default to IMMEDIATE, per SQL
+ */
+ deftrig_all_isdeferred = false;
deftrig_trigstates = NIL;
foreach(l, deftrig_dfl_trigstates)
/* ----------
* DeferredTriggerSetState()
*
- * Called for the users SET CONSTRAINTS ... utility command.
+ * Called for the SET CONSTRAINTS ... utility command.
* ----------
*/
void
DeferredTriggerSetState(ConstraintsSetStmt *stmt)
{
- Relation tgrel;
List *l;
- List *ls;
- List *loid = NIL;
- MemoryContext oldcxt;
- bool found;
- DeferredTriggerStatus state;
+
+ /*
+ * 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 (!IsTransactionBlock())
+ return;
/*
* Handle SET CONSTRAINTS ALL ...
*/
if (stmt->constraints == NIL)
{
- if (!IsTransactionBlock())
- {
- /*
- * ... 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.
- */
- deftrig_dfl_all_isset = true;
- deftrig_dfl_all_isdeferred = stmt->deferred;
-
- return;
- }
- else
+ /*
+ * Drop all per-transaction information about individual trigger
+ * states.
+ */
+ l = deftrig_trigstates;
+ while (l != NIL)
{
- /*
- * ... inside of a transaction block
- *
- * Drop all information about individual trigger states per
- * transaction.
- */
- l = deftrig_trigstates;
- while (l != NIL)
- {
- List *next = lnext(l);
-
- pfree(lfirst(l));
- pfree(l);
- l = next;
- }
- deftrig_trigstates = NIL;
-
- /*
- * Set the per transaction ALL state to known.
- */
- deftrig_all_isset = true;
- deftrig_all_isdeferred = stmt->deferred;
+ List *next = lnext(l);
- return;
+ pfree(lfirst(l));
+ pfree(l);
+ l = next;
}
- }
-
- /* ----------
- * Handle SET CONSTRAINTS constraint-name [, ...]
- * First lookup all trigger Oid's for the constraint names.
- * ----------
- */
- tgrel = heap_openr(TriggerRelationName, AccessShareLock);
-
- foreach(l, stmt->constraints)
- {
- char *cname = strVal(lfirst(l));
- ScanKeyData skey;
- SysScanDesc tgscan;
- HeapTuple htup;
-
- /*
- * Check that only named constraints are set explicitly
- */
- if (strlen(cname) == 0)
- elog(ERROR, "unnamed constraints cannot be set explicitly");
+ deftrig_trigstates = NIL;
/*
- * Setup to scan pg_trigger by tgconstrname ...
+ * Set the per-transaction ALL state to known.
*/
- ScanKeyEntryInitialize(&skey,
- (bits16) 0x0,
- (AttrNumber) Anum_pg_trigger_tgconstrname,
- (RegProcedure) F_NAMEEQ,
- PointerGetDatum(cname));
-
- tgscan = systable_beginscan(tgrel, TriggerConstrNameIndex, true,
- SnapshotNow, 1, &skey);
-
- /*
- * ... and search for the constraint trigger row
+ deftrig_all_isset = true;
+ deftrig_all_isdeferred = stmt->deferred;
+ }
+ else
+ {
+ Relation tgrel;
+ MemoryContext oldcxt;
+ bool found;
+ DeferredTriggerStatus state;
+ List *ls;
+ List *loid = NIL;
+
+ /* ----------
+ * Handle SET CONSTRAINTS constraint-name [, ...]
+ * First lookup all trigger Oid's for the constraint names.
+ * ----------
*/
- found = false;
+ tgrel = heap_openr(TriggerRelationName, AccessShareLock);
- while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
+ foreach(l, stmt->constraints)
{
- Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
- Oid constr_oid;
+ char *cname = strVal(lfirst(l));
+ ScanKeyData skey;
+ SysScanDesc tgscan;
+ HeapTuple htup;
/*
- * If we found some, check that they fit the deferrability but
- * skip ON <event> RESTRICT ones, since they are silently
- * never deferrable.
+ * Check that only named constraints are set explicitly
*/
- if (stmt->deferred && !pg_trigger->tgdeferrable &&
- pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_UPD &&
- pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_DEL)
- elog(ERROR, "Constraint '%s' is not deferrable",
- cname);
-
- AssertTupleDescHasOid(tgrel->rd_att);
- constr_oid = HeapTupleGetOid(htup);
- loid = lappendi(loid, constr_oid);
- found = true;
- }
-
- systable_endscan(tgscan);
+ if (strlen(cname) == 0)
+ elog(ERROR, "unnamed constraints cannot be set explicitly");
- /*
- * Not found ?
- */
- if (!found)
- elog(ERROR, "Constraint '%s' does not exist", cname);
- }
- heap_close(tgrel, AccessShareLock);
+ /*
+ * Setup to scan pg_trigger by tgconstrname ...
+ */
+ ScanKeyEntryInitialize(&skey, (bits16) 0x0,
+ (AttrNumber) Anum_pg_trigger_tgconstrname,
+ (RegProcedure) F_NAMEEQ,
+ PointerGetDatum(cname));
- if (!IsTransactionBlock())
- {
- /*
- * Outside of a transaction block set the trigger states of
- * individual triggers on session level.
- */
- oldcxt = MemoryContextSwitchTo(deftrig_gcxt);
+ tgscan = systable_beginscan(tgrel, TriggerConstrNameIndex, true,
+ SnapshotNow, 1, &skey);
- foreach(l, loid)
- {
+ /*
+ * ... and search for the constraint trigger row
+ */
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)
+
+ while (HeapTupleIsValid(htup = systable_getnext(tgscan)))
{
- state = (DeferredTriggerStatus)
- palloc(sizeof(DeferredTriggerStatusData));
- state->dts_tgoid = (Oid) lfirsti(l);
- state->dts_tgisdeferred = stmt->deferred;
+ Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(htup);
+ Oid constr_oid;
- deftrig_dfl_trigstates =
- lappend(deftrig_dfl_trigstates, state);
+ /*
+ * If we found some, check that they fit the deferrability but
+ * skip ON <event> RESTRICT ones, since they are silently
+ * never deferrable.
+ */
+ if (stmt->deferred && !pg_trigger->tgdeferrable &&
+ pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_UPD &&
+ pg_trigger->tgfoid != F_RI_FKEY_RESTRICT_DEL)
+ elog(ERROR, "Constraint '%s' is not deferrable",
+ cname);
+
+ AssertTupleDescHasOid(tgrel->rd_att);
+ constr_oid = HeapTupleGetOid(htup);
+ loid = lappendi(loid, constr_oid);
+ found = true;
}
- }
- MemoryContextSwitchTo(oldcxt);
+ systable_endscan(tgscan);
+
+ /*
+ * Not found ?
+ */
+ if (!found)
+ elog(ERROR, "Constraint '%s' does not exist", cname);
+ }
+ heap_close(tgrel, AccessShareLock);
- return;
- }
- else
- {
/*
* Inside of a transaction block set the trigger states of
* individual triggers on transaction level.
}
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().
+ */
}
drop table pktable;
ERROR: table "pktable" does not exist
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;
drop table pktable;
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;