class="parameter">refcolumn is omitted, the
- primary key of the
- class="parameter">reftable is used. The
- referenced columns must be the columns of a unique or primary
- key constraint in the referenced table. Note that foreign key
- constraints cannot be defined between temporary tables and
+ primary key of the reftable
+ is used. The referenced columns must be the columns of a non-deferrable
+ unique or primary key constraint in the referenced table. Note that
+ foreign key constraints cannot be defined between temporary tables and
permanent tables.
after every command. Checking of constraints that are
deferrable can be postponed until the end of the transaction
(using the command).
- NOT DEFERRABLE is the default. Only foreign
- key constraints currently accept this clause. All other
- constraint types are not deferrable.
+ NOT DEFERRABLE is the default.
+ Currently, only UNIQUE>, PRIMARY KEY>, and
+ REFERENCES> (foreign key) constraints accept this
+ clause. NOT NULL> and CHECK> constraints are not
+ deferrable.
+
+
Non-deferred Uniqueness Constraints
+
+ When a UNIQUE> or PRIMARY KEY> constraint is
+ not deferrable,
PostgreSQL checks for
+ uniqueness immediately whenever a row is inserted or modified.
+ The SQL standard says that uniqueness should be enforced only at
+ the end of the statement; this makes a difference when, for example,
+ a single command updates multiple key values. To obtain
+ standard-compliant behavior, declare the constraint as
+ DEFERRABLE> but not deferred (i.e., INITIALLY
+ IMMEDIATE>). Be aware that this can be significantly slower than
+ immediate uniqueness checking.
+
+
+
Column Check Constraints
-
+
SET CONSTRAINTS
SET CONSTRAINTS with a list of constraint names changes
the mode of just those constraints (which must all be deferrable). The
current schema search path is used to find the first matching name if
- no schema name is specified. SET CONSTRAINTS ALL
+ no schema name is specified. SET CONSTRAINTS ALL
changes the mode of all deferrable constraints.
- Currently, only foreign key constraints are affected by this
- setting. Check and unique constraints are always effectively
- not deferrable. Triggers that are declared as constraint
- triggers> are also affected.
+ Currently, only UNIQUE>, PRIMARY KEY>, and
+ REFERENCES> (foreign key) constraints are affected by this
+ setting. NOT NULL> and CHECK> constraints are
+ always checked immediately when a row is inserted or modified
+ (not> at the end of the statement).
+ Uniqueness constraints that have not been declared DEFERRABLE>
+ are also checked immediately.
+
+
+ The firing of triggers that are declared as constraint triggers>
+ is also controlled by this setting — they fire at the same time
+ that the associated constraint should be checked.
This command complies with the behavior defined in the SQL
standard, except for the limitation that, in
PostgreSQL, it only applies to
- foreign-key constraints.
+ foreign-key and uniqueness constraints.
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.22 2009/06/11 14:48:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.23 2009/07/29 20:56:17 tgl Exp $
*-------------------------------------------------------------------------
*/
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
- bool checkUnique = PG_GETARG_BOOL(5);
+ IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
#endif
GinState ginstate;
MemoryContext oldCtx;
MemoryContext insertCtx;
- uint32 res = 0;
int i;
insertCtx = AllocSetContextCreate(CurrentMemoryContext,
memset(&collector, 0, sizeof(GinTupleCollector));
for (i = 0; i < ginstate.origTupdesc->natts; i++)
if (!isnull[i])
- res += ginHeapTupleFastCollect(index, &ginstate, &collector,
+ ginHeapTupleFastCollect(index, &ginstate, &collector,
(OffsetNumber) (i + 1), values[i], ht_ctid);
ginHeapTupleFastInsert(index, &ginstate, &collector);
{
for (i = 0; i < ginstate.origTupdesc->natts; i++)
if (!isnull[i])
- res += ginHeapTupleInsert(index, &ginstate,
+ ginHeapTupleInsert(index, &ginstate,
(OffsetNumber) (i + 1), values[i], ht_ctid);
}
MemoryContextSwitchTo(oldCtx);
MemoryContextDelete(insertCtx);
- PG_RETURN_BOOL(res > 0);
+ PG_RETURN_BOOL(false);
}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/gist/gist.c,v 1.156 2009/01/01 17:23:34 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/gist/gist.c,v 1.157 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
- bool checkUnique = PG_GETARG_BOOL(5);
+ IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
#endif
IndexTuple itup;
GISTSTATE giststate;
MemoryContextSwitchTo(oldCtx);
MemoryContextDelete(insertCtx);
- PG_RETURN_BOOL(true);
+ PG_RETURN_BOOL(false);
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.112 2009/06/11 14:48:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.113 2009/07/29 20:56:18 tgl Exp $
*
* NOTES
* This file contains only the public interface routines.
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
- bool checkUnique = PG_GETARG_BOOL(5);
+ IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
#endif
IndexTuple itup;
pfree(itup);
- PG_RETURN_BOOL(true);
+ PG_RETURN_BOOL(false);
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.94 2009/07/22 01:21:22 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.95 2009/07/29 20:56:18 tgl Exp $
*
*
* INTERFACE ROUTINES
*/
index_insert(toastidx, t_values, t_isnull,
&(toasttup->t_self),
- toastrel, toastidx->rd_index->indisunique);
+ toastrel,
+ toastidx->rd_index->indisunique ?
+ UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
/*
* Free memory
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/index/indexam.c,v 1.114 2009/06/11 14:48:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/index/indexam.c,v 1.115 2009/07/29 20:56:18 tgl Exp $
*
* INTERFACE ROUTINES
* index_open - open an index relation by relation OID
bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
- bool check_uniqueness)
+ IndexUniqueCheck checkUnique)
{
FmgrInfo *procedure;
PointerGetDatum(isnull),
PointerGetDatum(heap_t_ctid),
PointerGetDatum(heapRelation),
- BoolGetDatum(check_uniqueness)));
+ Int32GetDatum((int32) checkUnique)));
}
/*
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.170 2009/06/11 14:48:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.171 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static Buffer _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf);
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
- Relation heapRel, Buffer buf, OffsetNumber ioffset,
- ScanKey itup_scankey);
+ Relation heapRel, Buffer buf, OffsetNumber offset,
+ ScanKey itup_scankey,
+ IndexUniqueCheck checkUnique, bool *is_unique);
static void _bt_findinsertloc(Relation rel,
Buffer *bufptr,
OffsetNumber *offsetptr,
*
* This routine is called by the public interface routines, btbuild
* and btinsert. By here, itup is filled in, including the TID.
+ *
+ * If checkUnique is UNIQUE_CHECK_NO or UNIQUE_CHECK_PARTIAL, this
+ * will allow duplicates. Otherwise (UNIQUE_CHECK_YES or
+ * UNIQUE_CHECK_EXISTING) it will throw error for a duplicate.
+ * For UNIQUE_CHECK_EXISTING we merely run the duplicate check, and
+ * don't actually insert.
+ *
+ * The result value is only significant for UNIQUE_CHECK_PARTIAL:
+ * it must be TRUE if the entry is known unique, else FALSE.
+ * (In the current implementation we'll also return TRUE after a
+ * successful UNIQUE_CHECK_YES or UNIQUE_CHECK_EXISTING call, but
+ * that's just a coding artifact.)
*/
-void
+bool
_bt_doinsert(Relation rel, IndexTuple itup,
- bool index_is_unique, Relation heapRel)
+ IndexUniqueCheck checkUnique, Relation heapRel)
{
+ bool is_unique = false;
int natts = rel->rd_rel->relnatts;
ScanKey itup_scankey;
BTStack stack;
*
* If we must wait for another xact, we release the lock while waiting,
* and then must start over completely.
+ *
+ * For a partial uniqueness check, we don't wait for the other xact.
+ * Just let the tuple in and return false for possibly non-unique,
+ * or true for definitely unique.
*/
- if (index_is_unique)
+ if (checkUnique != UNIQUE_CHECK_NO)
{
TransactionId xwait;
offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
- xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey);
+ xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
+ checkUnique, &is_unique);
if (TransactionIdIsValid(xwait))
{
}
}
- /* do the insertion */
- _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
- _bt_insertonpg(rel, buf, stack, itup, offset, false);
+ if (checkUnique != UNIQUE_CHECK_EXISTING)
+ {
+ /* do the insertion */
+ _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
+ _bt_insertonpg(rel, buf, stack, itup, offset, false);
+ }
+ else
+ {
+ /* just release the buffer */
+ _bt_relbuf(rel, buf);
+ }
/* be tidy */
_bt_freestack(stack);
_bt_freeskey(itup_scankey);
+
+ return is_unique;
}
/*
* Returns InvalidTransactionId if there is no conflict, else an xact ID
* we must wait for to see if it commits a conflicting tuple. If an actual
* conflict is detected, no return --- just ereport().
+ *
+ * However, if checkUnique == UNIQUE_CHECK_PARTIAL, we always return
+ * InvalidTransactionId because we don't want to wait. In this case we
+ * set *is_unique to false if there is a potential conflict, and the
+ * core code must redo the uniqueness check later.
*/
static TransactionId
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
- Buffer buf, OffsetNumber offset, ScanKey itup_scankey)
+ Buffer buf, OffsetNumber offset, ScanKey itup_scankey,
+ IndexUniqueCheck checkUnique, bool *is_unique)
{
TupleDesc itupdesc = RelationGetDescr(rel);
int natts = rel->rd_rel->relnatts;
Page page;
BTPageOpaque opaque;
Buffer nbuf = InvalidBuffer;
+ bool found = false;
+
+ /* Assume unique until we find a duplicate */
+ *is_unique = true;
InitDirtySnapshot(SnapshotDirty);
curitup = (IndexTuple) PageGetItem(page, curitemid);
htid = curitup->t_tid;
+ /*
+ * If we are doing a recheck, we expect to find the tuple we
+ * are rechecking. It's not a duplicate, but we have to keep
+ * scanning.
+ */
+ if (checkUnique == UNIQUE_CHECK_EXISTING &&
+ ItemPointerCompare(&htid, &itup->t_tid) == 0)
+ {
+ found = true;
+ }
+
/*
* We check the whole HOT-chain to see if there is any tuple
* that satisfies SnapshotDirty. This is necessary because we
* have just a single index entry for the entire chain.
*/
- if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
+ else if (heap_hot_search(&htid, heapRel, &SnapshotDirty,
+ &all_dead))
{
- /* it is a duplicate */
- TransactionId xwait =
- (TransactionIdIsValid(SnapshotDirty.xmin)) ?
- SnapshotDirty.xmin : SnapshotDirty.xmax;
+ TransactionId xwait;
+
+ /*
+ * It is a duplicate. If we are only doing a partial
+ * check, then don't bother checking if the tuple is
+ * being updated in another transaction. Just return
+ * the fact that it is a potential conflict and leave
+ * the full check till later.
+ */
+ if (checkUnique == UNIQUE_CHECK_PARTIAL)
+ {
+ if (nbuf != InvalidBuffer)
+ _bt_relbuf(rel, nbuf);
+ *is_unique = false;
+ return InvalidTransactionId;
+ }
/*
* If this tuple is being updated by other transaction
* then we have to wait for its commit/abort.
*/
+ xwait = (TransactionIdIsValid(SnapshotDirty.xmin)) ?
+ SnapshotDirty.xmin : SnapshotDirty.xmax;
+
if (TransactionIdIsValid(xwait))
{
if (nbuf != InvalidBuffer)
break;
}
+ /*
+ * This is a definite conflict.
+ */
ereport(ERROR,
(errcode(ERRCODE_UNIQUE_VIOLATION),
errmsg("duplicate key value violates unique constraint \"%s\"",
}
}
+ /*
+ * If we are doing a recheck then we should have found the tuple we
+ * are checking. Otherwise there's something very wrong --- probably,
+ * the index is on a non-immutable expression.
+ */
+ if (checkUnique == UNIQUE_CHECK_EXISTING && !found)
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("failed to re-find tuple within index \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("This may be because of a non-immutable index expression.")));
+
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.171 2009/06/11 14:48:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.172 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
bool *isnull = (bool *) PG_GETARG_POINTER(2);
ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
- bool checkUnique = PG_GETARG_BOOL(5);
+ IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
+ bool result;
IndexTuple itup;
/* generate an index tuple */
itup = index_form_tuple(RelationGetDescr(rel), values, isnull);
itup->t_tid = *ht_ctid;
- _bt_doinsert(rel, itup, checkUnique, heapRel);
+ result = _bt_doinsert(rel, itup, checkUnique, heapRel);
pfree(itup);
- PG_RETURN_BOOL(true);
+ PG_RETURN_BOOL(result);
}
/*
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.96 2009/01/01 17:23:36 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.97 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
NULL,
$10,
NULL, NIL,
- false, false, false,
+ false, false, false, false, false,
false, false, true, false, false);
do_end();
}
NULL,
$11,
NULL, NIL,
- true, false, false,
+ true, false, false, false, false,
false, false, true, false, false);
do_end();
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.319 2009/07/28 02:56:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.320 2009/07/29 20:56:18 tgl Exp $
*
*
* INTERFACE ROUTINES
#include "catalog/pg_operator.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_tablespace.h"
+#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "commands/tablecmds.h"
+#include "commands/trigger.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/var.h"
+#include "parser/parser.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
Oid *classOids,
int16 *coloptions,
bool primary,
+ bool immediate,
bool isvalid);
static void index_update_stats(Relation rel, bool hasindex, bool isprimary,
Oid reltoastidxid, double reltuples);
Oid *classOids,
int16 *coloptions,
bool primary,
+ bool immediate,
bool isvalid)
{
int2vector *indkey;
values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs);
values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique);
values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
+ values[Anum_pg_index_indimmediate - 1] = BoolGetDatum(immediate);
values[Anum_pg_index_indisclustered - 1] = BoolGetDatum(false);
values[Anum_pg_index_indisvalid - 1] = BoolGetDatum(isvalid);
values[Anum_pg_index_indcheckxmin - 1] = BoolGetDatum(false);
* reloptions: AM-specific options
* isprimary: index is a PRIMARY KEY
* isconstraint: index is owned by a PRIMARY KEY or UNIQUE constraint
+ * deferrable: constraint is DEFERRABLE
+ * initdeferred: constraint is INITIALLY DEFERRED
* allow_system_table_mods: allow table to be a system catalog
* skip_build: true to skip the index_build() step for the moment; caller
* must do it later (typically via reindex_index())
Datum reloptions,
bool isprimary,
bool isconstraint,
+ bool deferrable,
+ bool initdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent)
* ----------------
*/
UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
- classObjectId, coloptions, isprimary, !concurrent);
+ classObjectId, coloptions, isprimary,
+ !deferrable,
+ !concurrent);
/*
* Register constraint and dependencies for the index.
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
- false, /* isDeferrable */
- false, /* isDeferred */
+ deferrable,
+ initdeferred,
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+
+ /*
+ * If the constraint is deferrable, create the deferred uniqueness
+ * checking trigger. (The trigger will be given an internal
+ * dependency on the constraint by CreateTrigger, so there's no
+ * need to do anything more here.)
+ */
+ if (deferrable)
+ {
+ RangeVar *heapRel;
+ CreateTrigStmt *trigger;
+
+ heapRel = makeRangeVar(get_namespace_name(namespaceId),
+ pstrdup(RelationGetRelationName(heapRelation)),
+ -1);
+
+ trigger = makeNode(CreateTrigStmt);
+ trigger->trigname = pstrdup(indexRelationName);
+ trigger->relation = heapRel;
+ trigger->funcname = SystemFuncName("unique_key_recheck");
+ trigger->args = NIL;
+ trigger->before = false;
+ trigger->row = true;
+ trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
+ trigger->isconstraint = true;
+ trigger->deferrable = true;
+ trigger->initdeferred = initdeferred;
+ trigger->constrrel = NULL;
+
+ (void) CreateTrigger(trigger, conOid, indexRelationId,
+ isprimary ? "PK_ConstraintTrigger" :
+ "Unique_ConstraintTrigger",
+ false);
+ }
}
else
{
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
}
+
+ /* Non-constraint indexes can't be deferrable */
+ Assert(!deferrable);
+ Assert(!initdeferred);
}
/* Store dependency on operator classes */
DEPENDENCY_AUTO);
}
}
+ else
+ {
+ /* Bootstrap mode - assert we weren't asked for constraint support */
+ Assert(!isconstraint);
+ Assert(!deferrable);
+ Assert(!initdeferred);
+ }
/*
* Advance the command counter so that we can see the newly-entered
isnull,
&rootTuple,
heapRelation,
- indexInfo->ii_Unique);
+ indexInfo->ii_Unique ?
+ UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
state->tups_inserted += 1;
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.117 2009/01/01 17:23:37 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.118 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
- relationDescs[i]->rd_index->indisunique);
+ relationDescs[i]->rd_index->indisunique ?
+ UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
}
ExecDropSingleTupleTableSlot(slot);
F696 Additional translation documentation NO
F701 Referential update actions YES
F711 ALTER domain YES
-F721 Deferrable constraints NO foreign keys only
+F721 Deferrable constraints NO foreign and unique keys only
F731 INSERT column privileges YES
F741 Referential MATCH types NO no partial match yet
F751 View CHECK enhancements NO
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.17 2009/06/11 20:46:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.18 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
BTREE_AM_OID,
rel->rd_rel->reltablespace,
classObjectId, coloptions, (Datum) 0,
- true, false, true, false, false);
+ true, false, false, false,
+ true, false, false);
/*
* Store the toast table's OID in the parent relation's pg_class row
# Makefile for backend/commands
#
# IDENTIFICATION
-# $PostgreSQL: pgsql/src/backend/commands/Makefile,v 1.39 2008/12/19 16:25:17 petere Exp $
+# $PostgreSQL: pgsql/src/backend/commands/Makefile,v 1.40 2009/07/29 20:56:18 tgl Exp $
#
#-------------------------------------------------------------------------
include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
- conversioncmds.o copy.o \
+ constraint.o conversioncmds.o copy.o \
dbcommands.o define.o discard.o explain.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * constraint.c
+ * PostgreSQL CONSTRAINT support code.
+ *
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * $PostgreSQL: pgsql/src/backend/commands/constraint.c,v 1.1 2009/07/29 20:56:18 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/index.h"
+#include "commands/trigger.h"
+#include "executor/executor.h"
+#include "utils/builtins.h"
+#include "utils/tqual.h"
+
+
+/*
+ * unique_key_recheck - trigger function to do a deferred uniqueness check.
+ *
+ * This is invoked as an AFTER ROW trigger for both INSERT and UPDATE,
+ * for any rows recorded as potentially violating a deferrable unique
+ * constraint.
+ *
+ * This may be an end-of-statement check, a commit-time check, or a
+ * check triggered by a SET CONSTRAINTS command.
+ */
+Datum
+unique_key_recheck(PG_FUNCTION_ARGS)
+{
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+ const char *funcname = "unique_key_recheck";
+ HeapTuple new_row;
+ ItemPointerData tmptid;
+ Relation indexRel;
+ IndexInfo *indexInfo;
+ EState *estate;
+ ExprContext *econtext;
+ TupleTableSlot *slot;
+ Datum values[INDEX_MAX_KEYS];
+ bool isnull[INDEX_MAX_KEYS];
+
+ /*
+ * Make sure this is being called as an AFTER ROW trigger. Note:
+ * translatable error strings are shared with ri_triggers.c, so
+ * resist the temptation to fold the function name into them.
+ */
+ if (!CALLED_AS_TRIGGER(fcinfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ errmsg("function \"%s\" was not called by trigger manager",
+ funcname)));
+
+ if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
+ !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ errmsg("function \"%s\" must be fired AFTER ROW",
+ funcname)));
+
+ /*
+ * Get the new data that was inserted/updated.
+ */
+ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ new_row = trigdata->tg_trigtuple;
+ else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ new_row = trigdata->tg_newtuple;
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ errmsg("function \"%s\" must be fired for INSERT or UPDATE",
+ funcname)));
+ new_row = NULL; /* keep compiler quiet */
+ }
+
+ /*
+ * If the new_row is now dead (ie, inserted and then deleted within our
+ * transaction), we can skip the check. However, we have to be careful,
+ * because this trigger gets queued only in response to index insertions;
+ * which means it does not get queued for HOT updates. The row we are
+ * called for might now be dead, but have a live HOT child, in which case
+ * we still need to make the uniqueness check. Therefore we have to use
+ * heap_hot_search, not just HeapTupleSatisfiesVisibility as is done in
+ * the comparable test in RI_FKey_check.
+ *
+ * This might look like just an optimization, because the index AM will
+ * make this identical test before throwing an error. But it's actually
+ * needed for correctness, because the index AM will also throw an error
+ * if it doesn't find the index entry for the row. If the row's dead then
+ * it's possible the index entry has also been marked dead, and even
+ * removed.
+ */
+ tmptid = new_row->t_self;
+ if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
+ {
+ /*
+ * All rows in the HOT chain are dead, so skip the check.
+ */
+ return PointerGetDatum(NULL);
+ }
+
+ /*
+ * Open the index, acquiring a RowExclusiveLock, just as if we were
+ * going to update it. (This protects against possible changes of the
+ * index schema, not against concurrent updates.)
+ */
+ indexRel = index_open(trigdata->tg_trigger->tgconstrindid,
+ RowExclusiveLock);
+ indexInfo = BuildIndexInfo(indexRel);
+
+ /*
+ * The heap tuple must be put into a slot for FormIndexDatum.
+ */
+ slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation));
+
+ ExecStoreTuple(new_row, slot, InvalidBuffer, false);
+
+ /*
+ * Typically the index won't have expressions, but if it does we need
+ * an EState to evaluate them.
+ */
+ if (indexInfo->ii_Expressions != NIL)
+ {
+ estate = CreateExecutorState();
+ econtext = GetPerTupleExprContext(estate);
+ econtext->ecxt_scantuple = slot;
+ }
+ else
+ estate = NULL;
+
+ /*
+ * Form the index values and isnull flags for the index entry that
+ * we need to check.
+ *
+ * Note: if the index uses functions that are not as immutable as they
+ * are supposed to be, this could produce an index tuple different from
+ * the original. The index AM can catch such errors by verifying that
+ * it finds a matching index entry with the tuple's TID.
+ */
+ FormIndexDatum(indexInfo, slot, estate, values, isnull);
+
+ /*
+ * Now do the uniqueness check. This is not a real insert; it is a
+ * check that the index entry that has already been inserted is unique.
+ */
+ index_insert(indexRel, values, isnull, &(new_row->t_self),
+ trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
+
+ /*
+ * If that worked, then this index entry is unique, and we are done.
+ */
+ if (estate != NULL)
+ FreeExecutorState(estate);
+
+ ExecDropSingleTupleTableSlot(slot);
+
+ index_close(indexRel, RowExclusiveLock);
+
+ return PointerGetDatum(NULL);
+}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.315 2009/07/25 17:04:19 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.316 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
if (!skip_tuple)
{
+ List *recheckIndexes = NIL;
+
/* Place tuple in tuple slot */
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
- ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
+ recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+ estate, false);
/* AFTER ROW INSERT Triggers */
- ExecARInsertTriggers(estate, resultRelInfo, tuple);
+ ExecARInsertTriggers(estate, resultRelInfo, tuple,
+ recheckIndexes);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.186 2009/07/16 06:33:42 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.187 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* 'primary': mark the index as a primary key in the catalogs.
* 'isconstraint': index is for a PRIMARY KEY or UNIQUE constraint,
* so build a pg_constraint entry for it.
+ * 'deferrable': constraint is DEFERRABLE.
+ * 'initdeferred': constraint is INITIALLY DEFERRED.
* 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
* 'check_rights': check for CREATE rights in the namespace. (This should
* be true except when ALTER is deleting/recreating an index.)
bool unique,
bool primary,
bool isconstraint,
+ bool deferrable,
+ bool initdeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
indexRelationId =
index_create(relationId, indexRelationName, indexRelationId,
indexInfo, accessMethodId, tablespaceId, classObjectId,
- coloptions, reloptions, primary, isconstraint,
+ coloptions, reloptions, primary,
+ isconstraint, deferrable, initdeferred,
allowSystemTableMods, skip_build, concurrent);
return; /* We're done, in the standard case */
indexRelationId =
index_create(relationId, indexRelationName, indexRelationId,
indexInfo, accessMethodId, tablespaceId, classObjectId,
- coloptions, reloptions, primary, isconstraint,
+ coloptions, reloptions, primary,
+ isconstraint, deferrable, initdeferred,
allowSystemTableMods, true, concurrent);
/*
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.292 2009/07/28 02:56:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.293 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->deferrable,
+ stmt->initdeferred,
true, /* is_alter_table */
check_rights,
skip_build,
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
if (indexStruct->indisprimary)
{
+ /*
+ * Refuse to use a deferrable primary key. This is per SQL spec,
+ * and there would be a lot of interesting semantic problems if
+ * we tried to allow it.
+ */
+ if (!indexStruct->indimmediate)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot use a deferrable primary key for referenced table \"%s\"",
+ RelationGetRelationName(pkrel))));
+
*indexOid = indexoid;
break;
}
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
/*
- * Must have the right number of columns; must be unique and not a
- * partial index; forget it if there are any expressions, too
+ * Must have the right number of columns; must be unique (non
+ * deferrable) and not a partial index; forget it if there are any
+ * expressions, too
*/
if (indexStruct->indnatts == numattrs &&
- indexStruct->indisunique &&
+ indexStruct->indisunique && indexStruct->indimmediate &&
heap_attisnull(indexTuple, Anum_pg_index_indpred) &&
heap_attisnull(indexTuple, Anum_pg_index_indexprs))
{
fk_trigger->constrrel = fkconstraint->pktable;
fk_trigger->args = NIL;
- (void) CreateTrigger(fk_trigger, constraintOid, indexOid, false);
+ (void) CreateTrigger(fk_trigger, constraintOid, indexOid,
+ "RI_ConstraintTrigger", false);
/* Make changes-so-far visible */
CommandCounterIncrement();
}
fk_trigger->args = NIL;
- (void) CreateTrigger(fk_trigger, constraintOid, indexOid, false);
+ (void) CreateTrigger(fk_trigger, constraintOid, indexOid,
+ "RI_ConstraintTrigger", false);
/* Make changes-so-far visible */
CommandCounterIncrement();
}
fk_trigger->args = NIL;
- (void) CreateTrigger(fk_trigger, constraintOid, indexOid, false);
+ (void) CreateTrigger(fk_trigger, constraintOid, indexOid,
+ "RI_ConstraintTrigger", false);
}
/*
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.249 2009/07/28 02:56:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.250 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
- bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
+ bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
+ List *recheckIndexes);
/*
* indexOid, if nonzero, is the OID of an index associated with the constraint.
* We do nothing with this except store it into pg_trigger.tgconstrindid.
*
+ * prefix is NULL for user-created triggers. For internally generated
+ * constraint triggers, it is a prefix string to use in building the
+ * trigger name. (stmt->trigname is the constraint name in such cases.)
+ *
* If checkPermissions is true we require ACL_TRIGGER permissions on the
* relation. If not, the caller already checked permissions. (This is
* currently redundant with constraintOid being zero, but it's clearer to
*/
Oid
CreateTrigger(CreateTrigStmt *stmt,
- Oid constraintOid, Oid indexOid,
+ Oid constraintOid, Oid indexOid, const char *prefix,
bool checkPermissions)
{
int16 tgtype;
trigoid = GetNewOid(tgrel);
/*
- * If trigger is for an RI constraint, the passed-in name is the
- * constraint name; save that and build a unique trigger name to avoid
- * collisions with user-selected trigger names.
+ * If trigger is for a constraint, stmt->trigname is the constraint
+ * name; save that and build a unique trigger name based on the supplied
+ * prefix, to avoid collisions with user-selected trigger names.
*/
- if (OidIsValid(constraintOid))
+ if (prefix != NULL)
{
snprintf(constrtrigname, sizeof(constrtrigname),
- "RI_ConstraintTrigger_%u", trigoid);
+ "%s_%u", prefix, trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
else if (stmt->isconstraint)
{
- /* constraint trigger: trigger name is also constraint name */
+ /* user constraint trigger: trigger name is also constraint name */
trigname = stmt->trigname;
constrname = stmt->trigname;
}
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
- false, NULL, NULL);
+ false, NULL, NULL, NIL);
}
HeapTuple
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
- HeapTuple trigtuple)
+ HeapTuple trigtuple, List *recheckIndexes)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
- true, NULL, trigtuple);
+ true, NULL, trigtuple, recheckIndexes);
}
void
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
- false, NULL, NULL);
+ false, NULL, NULL, NIL);
}
bool
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
- true, trigtuple, NULL);
+ true, trigtuple, NULL, NIL);
heap_freetuple(trigtuple);
}
}
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
- false, NULL, NULL);
+ false, NULL, NULL, NIL);
}
HeapTuple
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
- ItemPointer tupleid, HeapTuple newtuple)
+ ItemPointer tupleid, HeapTuple newtuple,
+ List *recheckIndexes)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
- true, trigtuple, newtuple);
+ true, trigtuple, newtuple, recheckIndexes);
heap_freetuple(trigtuple);
}
}
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
- false, NULL, NULL);
+ false, NULL, NULL, NIL);
}
/* ----------
* AfterTriggerSaveEvent()
*
- * Called by ExecA[RS]...Triggers() to add the event to the queue.
+ * Called by ExecA[RS]...Triggers() to queue up the triggers that should
+ * be fired for an event.
*
- * NOTE: should be called only if we've determined that an event must
- * be added to the queue.
+ * NOTE: this is called whenever there are any triggers associated with
+ * the event (even if they are disabled). This function decides which
+ * triggers actually need to be queued.
* ----------
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
- HeapTuple oldtup, HeapTuple newtup)
+ HeapTuple oldtup, HeapTuple newtup,
+ List *recheckIndexes)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
}
}
+ /*
+ * If the trigger is a deferred unique constraint check trigger,
+ * only queue it if the unique constraint was potentially violated,
+ * which we know from index insertion time.
+ */
+ if (trigger->tgfoid == F_UNIQUE_KEY_RECHECK)
+ {
+ if (!list_member_oid(recheckIndexes, trigger->tgconstrindid))
+ continue; /* Uniqueness definitely not violated */
+ }
+
/*
* Fill in event structure and add it to the current query's queue.
*/
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.326 2009/06/11 20:46:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.327 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
Oid newId;
+ List *recheckIndexes = NIL;
/*
* get the heap tuple out of the tuple table slot, making sure we have a
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
- ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
+ recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+ estate, false);
/* AFTER ROW INSERT Triggers */
- ExecARInsertTriggers(estate, resultRelInfo, tuple);
+ ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
HTSU_Result result;
ItemPointerData update_ctid;
TransactionId update_xmax;
+ List *recheckIndexes = NIL;
/*
* abort the operation if not running transactions
* If it's a HOT update, we mustn't insert new index entries.
*/
if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
- ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
+ recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+ estate, false);
/* AFTER ROW UPDATE Triggers */
- ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
+ ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
+ recheckIndexes);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.160 2009/07/18 19:15:41 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.161 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* doesn't provide the functionality needed by the
* executor.. -cim 9/27/89
*
+ * This returns a list of OIDs for any unique indexes
+ * whose constraint check was deferred and which had
+ * potential (unconfirmed) conflicts.
+ *
* CAUTION: this must not be called for a HOT update.
* We can't defend against that here for lack of info.
* Should we change the API to make it safer?
* ----------------------------------------------------------------
*/
-void
+List *
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
- bool is_vacuum)
+ bool is_vacuum_full)
{
+ List *result = NIL;
ResultRelInfo *resultRelInfo;
int i;
int numIndices;
*/
for (i = 0; i < numIndices; i++)
{
+ Relation indexRelation = relationDescs[i];
IndexInfo *indexInfo;
+ IndexUniqueCheck checkUnique;
+ bool isUnique;
- if (relationDescs[i] == NULL)
+ if (indexRelation == NULL)
continue;
indexInfo = indexInfoArray[i];
isnull);
/*
- * The index AM does the rest. Note we suppress unique-index checks
- * if we are being called from VACUUM, since VACUUM may need to move
- * dead tuples that have the same keys as live ones.
+ * The index AM does the rest, including uniqueness checking.
+ *
+ * For an immediate-mode unique index, we just tell the index AM to
+ * throw error if not unique.
+ *
+ * For a deferrable unique index, we tell the index AM to just detect
+ * possible non-uniqueness, and we add the index OID to the result
+ * list if further checking is needed.
+ *
+ * Special hack: we suppress unique-index checks if we are being
+ * called from VACUUM FULL, since VACUUM FULL may need to move dead
+ * tuples that have the same keys as live ones.
*/
- index_insert(relationDescs[i], /* index relation */
- values, /* array of index Datums */
- isnull, /* null flags */
- tupleid, /* tid of heap tuple */
- heapRelation,
- relationDescs[i]->rd_index->indisunique && !is_vacuum);
+ if (is_vacuum_full || !indexRelation->rd_index->indisunique)
+ checkUnique = UNIQUE_CHECK_NO;
+ else if (indexRelation->rd_index->indimmediate)
+ checkUnique = UNIQUE_CHECK_YES;
+ else
+ checkUnique = UNIQUE_CHECK_PARTIAL;
+
+ isUnique =
+ index_insert(indexRelation, /* index relation */
+ values, /* array of index Datums */
+ isnull, /* null flags */
+ tupleid, /* tid of heap tuple */
+ heapRelation, /* heap relation */
+ checkUnique); /* type of uniqueness check to do */
+
+ if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
+ {
+ /*
+ * The tuple potentially violates the uniqueness constraint,
+ * so make a note of the index so that we can re-check it later.
+ */
+ result = lappend_oid(result, RelationGetRelid(indexRelation));
+ }
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
+
+ return result;
}
/*
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.435 2009/07/26 23:34:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.436 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
COPY_NODE_FIELD(keys);
COPY_NODE_FIELD(options);
COPY_STRING_FIELD(indexspace);
+ COPY_SCALAR_FIELD(deferrable);
+ COPY_SCALAR_FIELD(initdeferred);
return newnode;
}
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
+ COPY_SCALAR_FIELD(deferrable);
+ COPY_SCALAR_FIELD(initdeferred);
COPY_SCALAR_FIELD(concurrent);
return newnode;
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.358 2009/07/26 23:34:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.359 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
+ COMPARE_SCALAR_FIELD(deferrable);
+ COMPARE_SCALAR_FIELD(initdeferred);
COMPARE_SCALAR_FIELD(concurrent);
return true;
COMPARE_NODE_FIELD(keys);
COMPARE_NODE_FIELD(options);
COMPARE_STRING_FIELD(indexspace);
+ COMPARE_SCALAR_FIELD(deferrable);
+ COMPARE_SCALAR_FIELD(initdeferred);
return true;
}
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.361 2009/07/16 06:33:42 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.362 2009/07/29 20:56:19 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
+ WRITE_BOOL_FIELD(deferrable);
+ WRITE_BOOL_FIELD(initdeferred);
WRITE_BOOL_FIELD(concurrent);
}
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
+ WRITE_BOOL_FIELD(deferrable);
+ WRITE_BOOL_FIELD(initdeferred);
break;
case CONSTR_UNIQUE:
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
+ WRITE_BOOL_FIELD(deferrable);
+ WRITE_BOOL_FIELD(initdeferred);
break;
case CONSTR_CHECK:
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.673 2009/07/26 23:34:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.674 2009/07/29 20:56:19 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| NULL_P
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE opt_definition OptConsTableSpace
n->keys = NULL;
n->options = $2;
n->indexspace = $3;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| PRIMARY KEY opt_definition OptConsTableSpace
n->keys = NULL;
n->options = $3;
n->indexspace = $4;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| CHECK '(' a_expr ')'
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| DEFAULT b_expr
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| REFERENCES qualified_name opt_column_list key_match key_actions
;
ConstraintElem:
- CHECK '(' a_expr ')'
+ CHECK '(' a_expr ')' ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_CHECK;
n->raw_expr = $3;
n->cooked_expr = NULL;
n->indexspace = NULL;
+ if ($5 != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CHECK constraints cannot be deferred"),
+ parser_errposition(@5)));
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
+ ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_UNIQUE;
n->keys = $3;
n->options = $5;
n->indexspace = $6;
+ n->deferrable = ($7 & 1) != 0;
+ n->initdeferred = ($7 & 2) != 0;
$$ = (Node *)n;
}
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
+ ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_PRIMARY;
n->keys = $4;
n->options = $6;
n->indexspace = $7;
+ n->deferrable = ($8 & 1) != 0;
+ n->initdeferred = ($8 & 2) != 0;
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.23 2009/07/16 06:33:43 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.24 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/pg_constraint.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
/*
* If the index is marked PRIMARY, it's certainly from a constraint; else,
- * if it's not marked UNIQUE, it certainly isn't; else, we have to search
- * pg_depend to see if there's an associated unique constraint.
+ * if it's not marked UNIQUE, it certainly isn't. If it is or might be
+ * from a constraint, we have to fetch the constraint to check for
+ * deferrability attributes.
*/
- if (index->primary)
- index->isconstraint = true;
- else if (!index->unique)
- index->isconstraint = false;
+ if (index->primary || index->unique)
+ {
+ Oid constraintId = get_index_constraint(source_relid);
+
+ if (OidIsValid(constraintId))
+ {
+ HeapTuple ht_constr;
+ Form_pg_constraint conrec;
+
+ ht_constr = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(constraintId),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u",
+ constraintId);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ index->isconstraint = true;
+ index->deferrable = conrec->condeferrable;
+ index->initdeferred = conrec->condeferred;
+
+ ReleaseSysCache(ht_constr);
+ }
+ else
+ index->isconstraint = false;
+ }
else
- index->isconstraint = OidIsValid(get_index_constraint(source_relid));
+ index->isconstraint = false;
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
if (equal(index->indexParams, priorindex->indexParams) &&
equal(index->whereClause, priorindex->whereClause) &&
- strcmp(index->accessMethod, priorindex->accessMethod) == 0)
+ strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
+ index->deferrable == priorindex->deferrable &&
+ index->initdeferred == priorindex->initdeferred)
{
priorindex->unique |= index->unique;
*/
}
index->isconstraint = true;
+ index->deferrable = constraint->deferrable;
+ index->initdeferred = constraint->initdeferred;
if (constraint->name != NULL)
index->idxname = pstrdup(constraint->name);
* to attach constraint attributes to their primary constraint nodes
* and detect inconsistent/misplaced constraint attributes.
*
- * NOTE: currently, attributes are only supported for FOREIGN KEY primary
- * constraints, but someday they ought to be supported for other constraints.
+ * NOTE: currently, attributes are only supported for FOREIGN KEY, UNIQUE,
+ * and PRIMARY KEY constraints, but someday they ought to be supported
+ * for other constraint types.
*/
static void
transformConstraintAttrs(List *constraintList)
bool saw_initially = false;
ListCell *clist;
+#define SUPPORTS_ATTRS(node) \
+ ((node) != NULL && \
+ (IsA((node), FkConstraint) || \
+ (IsA((node), Constraint) && \
+ (((Constraint *) (node))->contype == CONSTR_PRIMARY || \
+ ((Constraint *) (node))->contype == CONSTR_UNIQUE))))
+
foreach(clist, constraintList)
{
Node *node = lfirst(clist);
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
- if (lastprimarynode == NULL ||
- !IsA(lastprimarynode, FkConstraint))
+ if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
- ((FkConstraint *) lastprimarynode)->deferrable = true;
+ if (IsA(lastprimarynode, FkConstraint))
+ ((FkConstraint *) lastprimarynode)->deferrable = true;
+ else
+ ((Constraint *) lastprimarynode)->deferrable = true;
break;
+
case CONSTR_ATTR_NOT_DEFERRABLE:
- if (lastprimarynode == NULL ||
- !IsA(lastprimarynode, FkConstraint))
+ if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
- ((FkConstraint *) lastprimarynode)->deferrable = false;
- if (saw_initially &&
- ((FkConstraint *) lastprimarynode)->initdeferred)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
+ if (IsA(lastprimarynode, FkConstraint))
+ {
+ ((FkConstraint *) lastprimarynode)->deferrable = false;
+ if (saw_initially &&
+ ((FkConstraint *) lastprimarynode)->initdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
+ }
+ else
+ {
+ ((Constraint *) lastprimarynode)->deferrable = false;
+ if (saw_initially &&
+ ((Constraint *) lastprimarynode)->initdeferred)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
+ }
break;
+
case CONSTR_ATTR_DEFERRED:
- if (lastprimarynode == NULL ||
- !IsA(lastprimarynode, FkConstraint))
+ if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
- ((FkConstraint *) lastprimarynode)->initdeferred = true;
/*
* If only INITIALLY DEFERRED appears, assume DEFERRABLE
*/
- if (!saw_deferrability)
- ((FkConstraint *) lastprimarynode)->deferrable = true;
- else if (!((FkConstraint *) lastprimarynode)->deferrable)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
+ if (IsA(lastprimarynode, FkConstraint))
+ {
+ ((FkConstraint *) lastprimarynode)->initdeferred = true;
+
+ if (!saw_deferrability)
+ ((FkConstraint *) lastprimarynode)->deferrable = true;
+ else if (!((FkConstraint *) lastprimarynode)->deferrable)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
+ }
+ else
+ {
+ ((Constraint *) lastprimarynode)->initdeferred = true;
+
+ if (!saw_deferrability)
+ ((Constraint *) lastprimarynode)->deferrable = true;
+ else if (!((Constraint *) lastprimarynode)->deferrable)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
+ }
break;
+
case CONSTR_ATTR_IMMEDIATE:
- if (lastprimarynode == NULL ||
- !IsA(lastprimarynode, FkConstraint))
+ if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
- ((FkConstraint *) lastprimarynode)->initdeferred = false;
+ if (IsA(lastprimarynode, FkConstraint))
+ ((FkConstraint *) lastprimarynode)->initdeferred = false;
+ else
+ ((Constraint *) lastprimarynode)->initdeferred = false;
break;
+
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.312 2009/07/28 02:56:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.313 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->deferrable,
+ stmt->initdeferred,
false, /* is_alter_table */
true, /* check_rights */
false, /* skip_build */
case T_CreateTrigStmt:
CreateTrigger((CreateTrigStmt *) parsetree,
- InvalidOid, InvalidOid, true);
+ InvalidOid, InvalidOid, NULL, true);
break;
case T_DropPropertyStmt:
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.304 2009/07/24 21:08:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.305 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
if (string)
appendStringInfo(&buf, " ON DELETE %s", string);
- if (conForm->condeferrable)
- appendStringInfo(&buf, " DEFERRABLE");
- if (conForm->condeferred)
- appendStringInfo(&buf, " INITIALLY DEFERRED");
-
break;
}
case CONSTRAINT_PRIMARY:
break;
}
+ if (conForm->condeferrable)
+ appendStringInfo(&buf, " DEFERRABLE");
+ if (conForm->condeferred)
+ appendStringInfo(&buf, " INITIALLY DEFERRED");
+
/* Cleanup */
ReleaseSysCache(tup);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.287 2009/06/11 14:49:05 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.288 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* Check to see if it is a unique, non-partial btree index on OID */
if (index->indnatts == 1 &&
- index->indisunique &&
+ index->indisunique && index->indimmediate &&
index->indkey.values[0] == ObjectIdAttributeNumber &&
index->indclass.values[0] == OID_BTREE_OPS_OID &&
heap_attisnull(htup, Anum_pg_index_indpred))
* by PostgreSQL
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.542 2009/07/23 22:59:40 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.543 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
i_indisclustered,
i_contype,
i_conname,
+ i_condeferrable,
+ i_condeferred,
i_contableoid,
i_conoid,
i_tablespace,
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
+ "c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
+ "c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
"c.contype, c.conname, "
+ "c.condeferrable, c.condeferred, "
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"NULL AS tablespace, "
"CASE WHEN i.indisprimary THEN 'p'::char "
"ELSE '0'::char END AS contype, "
"t.relname AS conname, "
+ "false AS condeferrable, "
+ "false AS condeferred, "
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
"CASE WHEN i.indisprimary THEN 'p'::char "
"ELSE '0'::char END AS contype, "
"t.relname AS conname, "
+ "false AS condeferrable, "
+ "false AS condeferred, "
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
i_indisclustered = PQfnumber(res, "indisclustered");
i_contype = PQfnumber(res, "contype");
i_conname = PQfnumber(res, "conname");
+ i_condeferrable = PQfnumber(res, "condeferrable");
+ i_condeferred = PQfnumber(res, "condeferred");
i_contableoid = PQfnumber(res, "contableoid");
i_conoid = PQfnumber(res, "conoid");
i_tablespace = PQfnumber(res, "tablespace");
constrinfo[j].condef = NULL;
constrinfo[j].confrelid = InvalidOid;
constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
+ constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
+ constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
constrinfo[j].condef = strdup(PQgetvalue(res, j, i_condef));
constrinfo[j].confrelid = atooid(PQgetvalue(res, j, i_confrelid));
constrinfo[j].conindex = 0;
+ constrinfo[j].condeferrable = false;
+ constrinfo[j].condeferred = false;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
}
constrinfo[i].condef = strdup(PQgetvalue(res, i, i_consrc));
constrinfo[i].confrelid = InvalidOid;
constrinfo[i].conindex = 0;
+ constrinfo[i].condeferrable = false;
+ constrinfo[i].condeferred = false;
constrinfo[i].conislocal = true;
constrinfo[i].separate = false;
constrs[j].condef = strdup(PQgetvalue(res, j, 3));
constrs[j].confrelid = InvalidOid;
constrs[j].conindex = 0;
+ constrs[j].condeferrable = false;
+ constrs[j].condeferred = false;
constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
constrs[j].separate = false;
if (indxinfo->options && strlen(indxinfo->options) > 0)
appendPQExpBuffer(q, " WITH (%s)", indxinfo->options);
+ if (coninfo->condeferrable)
+ {
+ appendPQExpBuffer(q, " DEFERRABLE");
+ if (coninfo->condeferred)
+ appendPQExpBuffer(q, " INITIALLY DEFERRED");
+ }
+
appendPQExpBuffer(q, ";\n");
/* If the index is clustered, we need to record that. */
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.154 2009/06/11 14:49:07 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.155 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* struct ConstraintInfo is used for all constraint types. However we
* use a different objType for foreign key constraints, to make it easier
* to sort them the way we want.
+ *
+ * Note: condeferrable and condeferred are currently only valid for
+ * unique/primary-key constraints. Otherwise that info is in condef.
*/
typedef struct _constraintInfo
{
char *condef; /* definition, if CHECK or FOREIGN KEY */
Oid confrelid; /* referenced table, if FOREIGN KEY */
DumpId conindex; /* identifies associated index if any */
+ bool condeferrable; /* TRUE if constraint is DEFERRABLE */
+ bool condeferred; /* TRUE if constraint is INITIALLY DEFERRED */
bool conislocal; /* TRUE if constraint has local definition */
bool separate; /* TRUE if must dump as separate item */
} ConstraintInfo;
*
* Copyright (c) 2000-2009, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.225 2009/07/20 03:46:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/describe.c,v 1.226 2009/07/29 20:56:19 tgl Exp $
*/
#include "postgres_fe.h"
printfPQExpBuffer(&buf,
"SELECT i.indisunique, i.indisprimary, i.indisclustered, ");
if (pset.sversion >= 80200)
- appendPQExpBuffer(&buf, "i.indisvalid, ");
+ appendPQExpBuffer(&buf, "i.indisvalid,\n");
else
- appendPQExpBuffer(&buf, "true as indisvalid, ");
- appendPQExpBuffer(&buf, "a.amname, c2.relname,\n"
- " pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n"
+ appendPQExpBuffer(&buf, "true AS indisvalid,\n");
+ if (pset.sversion >= 80500)
+ appendPQExpBuffer(&buf,
+ " (NOT i.indimmediate) AND "
+ "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE "
+ "d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass AND "
+ "d.objid = i.indexrelid AND "
+ "d.refclassid = 'pg_catalog.pg_constraint'::pg_catalog.regclass AND "
+ "d.refobjid = con.oid AND d.deptype = 'i' AND "
+ "con.condeferrable) AS condeferrable,\n"
+ " (NOT i.indimmediate) AND "
+ "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE "
+ "d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass AND "
+ "d.objid = i.indexrelid AND "
+ "d.refclassid = 'pg_catalog.pg_constraint'::pg_catalog.regclass AND "
+ "d.refobjid = con.oid AND d.deptype = 'i' AND "
+ "con.condeferred) AS condeferred,\n");
+ else
+ appendPQExpBuffer(&buf,
+ " false AS condeferrable, false AS condeferred,\n");
+ appendPQExpBuffer(&buf, " a.amname, c2.relname, "
+ "pg_catalog.pg_get_expr(i.indpred, i.indrelid, true)\n"
"FROM pg_catalog.pg_index i, pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_am a\n"
"WHERE i.indexrelid = c.oid AND c.oid = '%s' AND c.relam = a.oid\n"
"AND i.indrelid = c2.oid",
char *indisprimary = PQgetvalue(result, 0, 1);
char *indisclustered = PQgetvalue(result, 0, 2);
char *indisvalid = PQgetvalue(result, 0, 3);
- char *indamname = PQgetvalue(result, 0, 4);
- char *indtable = PQgetvalue(result, 0, 5);
- char *indpred = PQgetvalue(result, 0, 6);
+ char *deferrable = PQgetvalue(result, 0, 4);
+ char *deferred = PQgetvalue(result, 0, 5);
+ char *indamname = PQgetvalue(result, 0, 6);
+ char *indtable = PQgetvalue(result, 0, 7);
+ char *indpred = PQgetvalue(result, 0, 8);
if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, "));
if (strcmp(indisvalid, "t") != 0)
appendPQExpBuffer(&tmpbuf, _(", invalid"));
+ if (strcmp(deferrable, "t") == 0)
+ appendPQExpBuffer(&tmpbuf, _(", deferrable"));
+
+ if (strcmp(deferred, "t") == 0)
+ appendPQExpBuffer(&tmpbuf, _(", initially deferred"));
+
printTableAddFooter(&cont, tmpbuf.data);
add_tablespace_footer(&cont, tableinfo.relkind,
tableinfo.tablespace, true);
else
appendPQExpBuffer(&buf, "true as indisvalid, ");
appendPQExpBuffer(&buf, "pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)");
+ if (pset.sversion >= 80500)
+ appendPQExpBuffer(&buf,
+ ",\n (NOT i.indimmediate) AND "
+ "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE "
+ "d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass AND "
+ "d.objid = i.indexrelid AND "
+ "d.refclassid = 'pg_catalog.pg_constraint'::pg_catalog.regclass AND "
+ "d.refobjid = con.oid AND d.deptype = 'i' AND "
+ "con.condeferrable) AS condeferrable"
+ ",\n (NOT i.indimmediate) AND "
+ "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE "
+ "d.classid = 'pg_catalog.pg_class'::pg_catalog.regclass AND "
+ "d.objid = i.indexrelid AND "
+ "d.refclassid = 'pg_catalog.pg_constraint'::pg_catalog.regclass AND "
+ "d.refobjid = con.oid AND d.deptype = 'i' AND "
+ "con.condeferred) AS condeferred");
+ else
+ appendPQExpBuffer(&buf, ", false AS condeferrable, false AS condeferred");
if (pset.sversion >= 80000)
appendPQExpBuffer(&buf, ", c2.reltablespace");
appendPQExpBuffer(&buf,
if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
appendPQExpBuffer(&buf, " INVALID");
+ if (strcmp(PQgetvalue(result, i, 6), "t") == 0)
+ appendPQExpBuffer(&buf, " DEFERRABLE");
+
+ if (strcmp(PQgetvalue(result, i, 7), "t") == 0)
+ appendPQExpBuffer(&buf, " INITIALLY DEFERRED");
+
printTableAddFooter(&cont, buf.data);
/* Print tablespace of the index on the same line */
if (pset.sversion >= 80000)
add_tablespace_footer(&cont, 'i',
- atooid(PQgetvalue(result, i, 6)),
+ atooid(PQgetvalue(result, i, 8)),
false);
}
}
PQclear(result);
}
- /* print triggers (but ignore foreign-key triggers) */
+ /* print triggers (but ignore RI and unique constraint triggers) */
if (tableinfo.hastriggers)
{
printfPQExpBuffer(&buf,
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/genam.h,v 1.78 2009/06/11 14:49:08 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/genam.h,v 1.79 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
typedef struct IndexScanDescData *IndexScanDesc;
typedef struct SysScanDescData *SysScanDesc;
+/*
+ * Enumeration specifying the type of uniqueness check to perform in
+ * index_insert().
+ *
+ * UNIQUE_CHECK_YES is the traditional Postgres immediate check, possibly
+ * blocking to see if a conflicting transaction commits.
+ *
+ * For deferrable unique constraints, UNIQUE_CHECK_PARTIAL is specified at
+ * insertion time. The index AM should test if the tuple is unique, but
+ * should not throw error, block, or prevent the insertion if the tuple
+ * appears not to be unique. We'll recheck later when it is time for the
+ * constraint to be enforced. The AM must return true if the tuple is
+ * known unique, false if it is possibly non-unique. In the "true" case
+ * it is safe to omit the later recheck.
+ *
+ * When it is time to recheck the deferred constraint, a pseudo-insertion
+ * call is made with UNIQUE_CHECK_EXISTING. The tuple is already in the
+ * index in this case, so it should not be inserted again. Rather, just
+ * check for conflicting live tuples (possibly blocking).
+ */
+typedef enum IndexUniqueCheck
+{
+ UNIQUE_CHECK_NO, /* Don't do any uniqueness checking */
+ UNIQUE_CHECK_YES, /* Enforce uniqueness at insertion time */
+ UNIQUE_CHECK_PARTIAL, /* Test uniqueness, but no error */
+ UNIQUE_CHECK_EXISTING /* Check if existing tuple is unique */
+} IndexUniqueCheck;
+
/*
* generalized index_ interface routines (in indexam.c)
Datum *values, bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
- bool check_uniqueness);
+ IndexUniqueCheck checkUnique);
extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/nbtree.h,v 1.124 2009/06/11 14:49:08 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/nbtree.h,v 1.125 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* prototypes for functions in nbtinsert.c
*/
-extern void _bt_doinsert(Relation rel, IndexTuple itup,
- bool index_is_unique, Relation heapRel);
+extern bool _bt_doinsert(Relation rel, IndexTuple itup,
+ IndexUniqueCheck checkUnique, Relation heapRel);
extern Buffer _bt_getstackbuf(Relation rel, BTStack stack, int access);
extern void _bt_insert_parent(Relation rel, Buffer buf, Buffer rbuf,
BTStack stack, bool is_root, bool is_only);
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.533 2009/07/28 02:56:30 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.534 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200907271
+#define CATALOG_VERSION_NO 200907291
#endif
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.77 2009/01/01 17:23:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.78 2009/07/29 20:56:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
Datum reloptions,
bool isprimary,
bool isconstraint,
+ bool deferrable,
+ bool initdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent);
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.148 2009/06/11 14:49:09 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.149 2009/07/29 20:56:20 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
{ 0, {"indnatts"}, 21, -1, 2, 3, 0, -1, -1, true, 'p', 's', true, false, false, true, 0, { 0 } }, \
{ 0, {"indisunique"}, 16, -1, 1, 4, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
{ 0, {"indisprimary"}, 16, -1, 1, 5, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
-{ 0, {"indisclustered"}, 16, -1, 1, 6, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
-{ 0, {"indisvalid"}, 16, -1, 1, 7, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
-{ 0, {"indcheckxmin"}, 16, -1, 1, 8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
-{ 0, {"indisready"}, 16, -1, 1, 9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
-{ 0, {"indkey"}, 22, -1, -1, 10, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
-{ 0, {"indclass"}, 30, -1, -1, 11, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
-{ 0, {"indoption"}, 22, -1, -1, 12, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
-{ 0, {"indexprs"}, 25, -1, -1, 13, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
-{ 0, {"indpred"}, 25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
+{ 0, {"indimmediate"}, 16, -1, 1, 6, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indisclustered"}, 16, -1, 1, 7, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indisvalid"}, 16, -1, 1, 8, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indcheckxmin"}, 16, -1, 1, 9, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indisready"}, 16, -1, 1, 10, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indkey"}, 22, -1, -1, 11, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indclass"}, 30, -1, -1, 12, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indoption"}, 22, -1, -1, 13, 1, -1, -1, false, 'p', 'i', true, false, false, true, 0, { 0 } }, \
+{ 0, {"indexprs"}, 25, -1, -1, 14, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }, \
+{ 0, {"indpred"}, 25, -1, -1, 15, 0, -1, -1, false, 'x', 'i', false, false, false, true, 0, { 0 } }
#endif /* PG_ATTRIBUTE_H */
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_index.h,v 1.47 2009/01/01 17:23:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_index.h,v 1.48 2009/07/29 20:56:20 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
int2 indnatts; /* number of columns in index */
bool indisunique; /* is this a unique index? */
bool indisprimary; /* is this index for primary key? */
+ bool indimmediate; /* is uniqueness enforced immediately? */
bool indisclustered; /* is this the index last clustered by? */
bool indisvalid; /* is this index valid for use by queries? */
bool indcheckxmin; /* must we wait for xmin to be old? */
* compiler constants for pg_index
* ----------------
*/
-#define Natts_pg_index 14
+#define Natts_pg_index 15
#define Anum_pg_index_indexrelid 1
#define Anum_pg_index_indrelid 2
#define Anum_pg_index_indnatts 3
#define Anum_pg_index_indisunique 4
#define Anum_pg_index_indisprimary 5
-#define Anum_pg_index_indisclustered 6
-#define Anum_pg_index_indisvalid 7
-#define Anum_pg_index_indcheckxmin 8
-#define Anum_pg_index_indisready 9
-#define Anum_pg_index_indkey 10
-#define Anum_pg_index_indclass 11
-#define Anum_pg_index_indoption 12
-#define Anum_pg_index_indexprs 13
-#define Anum_pg_index_indpred 14
+#define Anum_pg_index_indimmediate 6
+#define Anum_pg_index_indisclustered 7
+#define Anum_pg_index_indisvalid 8
+#define Anum_pg_index_indcheckxmin 9
+#define Anum_pg_index_indisready 10
+#define Anum_pg_index_indkey 11
+#define Anum_pg_index_indclass 12
+#define Anum_pg_index_indoption 13
+#define Anum_pg_index_indexprs 14
+#define Anum_pg_index_indpred 15
/*
* Index AMs that support ordered scans must support these two indoption
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.546 2009/07/07 18:49:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.547 2009/07/29 20:56:20 tgl Exp $
*
* NOTES
* The script catalog/genbki.sh reads this file and generates .bki
DATA(insert OID = 1619 ( pg_typeof PGNSP PGUID 12 1 0 0 f f f f f s 1 0 2206 "2276" _null_ _null_ _null_ _null_ pg_typeof _null_ _null_ _null_ ));
DESCR("returns the type of the argument");
+/* Deferrable unique constraint trigger */
+DATA(insert OID = 1250 ( unique_key_recheck PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_recheck _null_ _null_ _null_ ));
+DESCR("deferred UNIQUE constraint check");
+
/* Generic referential integrity constraint triggers */
DATA(insert OID = 1644 ( RI_FKey_check_ins PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ RI_FKey_check_ins _null_ _null_ _null_ ));
DESCR("referential integrity FOREIGN KEY ... REFERENCES");
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.95 2009/07/16 06:33:45 petere Exp $
+ * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.96 2009/07/29 20:56:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
bool unique,
bool primary,
bool isconstraint,
+ bool deferrable,
+ bool initdeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.74 2009/07/28 02:56:31 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.75 2009/07/29 20:56:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#define TRIGGER_DISABLED 'D'
extern Oid CreateTrigger(CreateTrigStmt *stmt,
- Oid constraintOid, Oid indexOid,
+ Oid constraintOid, Oid indexOid, const char *prefix,
bool checkPermissions);
extern void DropTrigger(Oid relid, const char *trigname,
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
- HeapTuple trigtuple);
+ HeapTuple trigtuple,
+ List *recheckIndexes);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
- HeapTuple newtuple);
+ HeapTuple newtuple,
+ List *recheckIndexes);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.157 2009/07/22 17:00:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.158 2009/07/29 20:56:20 tgl Exp $
*
*-------------------------------------------------------------------------
*/
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
-extern void ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
- EState *estate, bool is_vacuum);
+extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
+ EState *estate, bool is_vacuum_full);
extern void RegisterExprContextCallback(ExprContext *econtext,
ExprContextCallbackFunction function,
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.398 2009/07/26 23:34:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.399 2009/07/29 20:56:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Constraint attributes (DEFERRABLE etc) are initially represented as
* separate Constraint nodes for simplicity of parsing. parse_utilcmd.c makes
* a pass through the constraints list to attach the info to the appropriate
- * FkConstraint node (and, perhaps, someday to other kinds of constraints).
+ * Constraint and FkConstraint nodes.
* ----------
*/
List *options; /* options from WITH clause */
char *indexspace; /* index tablespace for PKEY/UNIQUE
* constraints; NULL for default */
+ bool deferrable; /* DEFERRABLE */
+ bool initdeferred; /* INITIALLY DEFERRED */
} Constraint;
/* ----------
/* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
int16 events; /* INSERT/UPDATE/DELETE/TRUNCATE */
- /* The following are used for referential */
- /* integrity constraint triggers */
- bool isconstraint; /* This is an RI trigger */
+ /* The following are used for constraint triggers (RI and unique checks) */
+ bool isconstraint; /* This is a constraint trigger */
bool deferrable; /* [NOT] DEFERRABLE */
bool initdeferred; /* INITIALLY {DEFERRED|IMMEDIATE} */
- RangeVar *constrrel; /* opposite relation */
+ RangeVar *constrrel; /* opposite relation, if RI trigger */
} CreateTrigStmt;
/* ----------------------
bool unique; /* is index unique? */
bool primary; /* is index on primary key? */
bool isconstraint; /* is it from a CONSTRAINT clause? */
+ bool deferrable; /* is the constraint DEFERRABLE? */
+ bool initdeferred; /* is the constraint INITIALLY DEFERRED? */
bool concurrent; /* should this be a concurrent index build? */
} IndexStmt;
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.334 2009/07/16 06:33:46 petere Exp $
+ * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.335 2009/07/29 20:56:21 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* access/transam/twophase.c */
extern Datum pg_prepared_xact(PG_FUNCTION_ARGS);
+/* commands/constraint.c */
+extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
+
/* commands/prepare.c */
extern Datum pg_prepared_statement(PG_FUNCTION_ARGS);
WHERE relhasoids
AND ((nspname ~ '^pg_') IS NOT FALSE)
AND NOT EXISTS (SELECT 1 FROM pg_index i WHERE indrelid = c.oid
- AND indkey[0] = -2 AND indnatts = 1 AND indisunique);
+ AND indkey[0] = -2 AND indnatts = 1
+ AND indisunique AND indimmediate);
relname | nspname
---------+---------
(0 rows)
DROP TABLE UNIQUE_TBL;
+--
+-- Deferrable unique constraints
+--
+
+CREATE TABLE unique_tbl (i int UNIQUE DEFERRABLE, t text);
+
+INSERT INTO unique_tbl VALUES (0, 'one');
+INSERT INTO unique_tbl VALUES (1, 'two');
+INSERT INTO unique_tbl VALUES (2, 'tree');
+INSERT INTO unique_tbl VALUES (3, 'four');
+INSERT INTO unique_tbl VALUES (4, 'five');
+
+BEGIN;
+
+-- default is immediate so this should fail right away
+UPDATE unique_tbl SET i = 1 WHERE i = 0;
+
+ROLLBACK;
+
+-- check is done at end of statement, so this should succeed
+UPDATE unique_tbl SET i = i+1;
+
+SELECT * FROM unique_tbl;
+
+-- explicitly defer the constraint
+BEGIN;
+
+SET CONSTRAINTS unique_tbl_i_key DEFERRED;
+
+INSERT INTO unique_tbl VALUES (3, 'three');
+DELETE FROM unique_tbl WHERE t = 'tree'; -- makes constraint valid again
+
+COMMIT; -- should succeed
+
+SELECT * FROM unique_tbl;
+
+-- try adding an initially deferred constraint
+ALTER TABLE unique_tbl DROP CONSTRAINT unique_tbl_i_key;
+ALTER TABLE unique_tbl ADD CONSTRAINT unique_tbl_i_key
+ UNIQUE (i) DEFERRABLE INITIALLY DEFERRED;
+
+BEGIN;
+
+INSERT INTO unique_tbl VALUES (1, 'five');
+INSERT INTO unique_tbl VALUES (5, 'one');
+UPDATE unique_tbl SET i = 4 WHERE i = 2;
+UPDATE unique_tbl SET i = 2 WHERE i = 4 AND t = 'four';
+DELETE FROM unique_tbl WHERE i = 1 AND t = 'one';
+DELETE FROM unique_tbl WHERE i = 5 AND t = 'five';
+
+COMMIT;
+
+SELECT * FROM unique_tbl;
+
+-- should fail at commit-time
+BEGIN;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+COMMIT; -- should fail
+
+-- make constraint check immediate
+BEGIN;
+
+SET CONSTRAINTS ALL IMMEDIATE;
+
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should fail
+
+COMMIT;
+
+-- forced check when SET CONSTRAINTS is called
+BEGIN;
+
+SET CONSTRAINTS ALL DEFERRED;
+
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+
+SET CONSTRAINTS ALL IMMEDIATE; -- should fail
+
+COMMIT;
+
+-- test a HOT update that invalidates the conflicting tuple.
+-- the trigger should still fire and catch the violation
+
+BEGIN;
+
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+UPDATE unique_tbl SET t = 'THREE' WHERE i = 3 AND t = 'Three';
+
+COMMIT; -- should fail
+
+SELECT * FROM unique_tbl;
+
+-- test a HOT update that modifies the newly inserted tuple,
+-- but should succeed because we then remove the other conflicting tuple.
+
+BEGIN;
+
+INSERT INTO unique_tbl VALUES(3, 'tree'); -- should succeed for now
+UPDATE unique_tbl SET t = 'threex' WHERE t = 'tree';
+DELETE FROM unique_tbl WHERE t = 'three';
+
+SELECT * FROM unique_tbl;
+
+COMMIT;
+
+SELECT * FROM unique_tbl;
+
+DROP TABLE unique_tbl;
(5 rows)
DROP TABLE UNIQUE_TBL;
+--
+-- Deferrable unique constraints
+--
+CREATE TABLE unique_tbl (i int UNIQUE DEFERRABLE, t text);
+NOTICE: CREATE TABLE / UNIQUE will create implicit index "unique_tbl_i_key" for table "unique_tbl"
+INSERT INTO unique_tbl VALUES (0, 'one');
+INSERT INTO unique_tbl VALUES (1, 'two');
+INSERT INTO unique_tbl VALUES (2, 'tree');
+INSERT INTO unique_tbl VALUES (3, 'four');
+INSERT INTO unique_tbl VALUES (4, 'five');
+BEGIN;
+-- default is immediate so this should fail right away
+UPDATE unique_tbl SET i = 1 WHERE i = 0;
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+ROLLBACK;
+-- check is done at end of statement, so this should succeed
+UPDATE unique_tbl SET i = i+1;
+SELECT * FROM unique_tbl;
+ i | t
+---+------
+ 1 | one
+ 2 | two
+ 3 | tree
+ 4 | four
+ 5 | five
+(5 rows)
+
+-- explicitly defer the constraint
+BEGIN;
+SET CONSTRAINTS unique_tbl_i_key DEFERRED;
+INSERT INTO unique_tbl VALUES (3, 'three');
+DELETE FROM unique_tbl WHERE t = 'tree'; -- makes constraint valid again
+COMMIT; -- should succeed
+SELECT * FROM unique_tbl;
+ i | t
+---+-------
+ 1 | one
+ 2 | two
+ 4 | four
+ 5 | five
+ 3 | three
+(5 rows)
+
+-- try adding an initially deferred constraint
+ALTER TABLE unique_tbl DROP CONSTRAINT unique_tbl_i_key;
+ALTER TABLE unique_tbl ADD CONSTRAINT unique_tbl_i_key
+ UNIQUE (i) DEFERRABLE INITIALLY DEFERRED;
+NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "unique_tbl_i_key" for table "unique_tbl"
+BEGIN;
+INSERT INTO unique_tbl VALUES (1, 'five');
+INSERT INTO unique_tbl VALUES (5, 'one');
+UPDATE unique_tbl SET i = 4 WHERE i = 2;
+UPDATE unique_tbl SET i = 2 WHERE i = 4 AND t = 'four';
+DELETE FROM unique_tbl WHERE i = 1 AND t = 'one';
+DELETE FROM unique_tbl WHERE i = 5 AND t = 'five';
+COMMIT;
+SELECT * FROM unique_tbl;
+ i | t
+---+-------
+ 3 | three
+ 1 | five
+ 5 | one
+ 4 | two
+ 2 | four
+(5 rows)
+
+-- should fail at commit-time
+BEGIN;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+COMMIT; -- should fail
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+-- make constraint check immediate
+BEGIN;
+SET CONSTRAINTS ALL IMMEDIATE;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should fail
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+COMMIT;
+-- forced check when SET CONSTRAINTS is called
+BEGIN;
+SET CONSTRAINTS ALL DEFERRED;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+SET CONSTRAINTS ALL IMMEDIATE; -- should fail
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+COMMIT;
+-- test a HOT update that invalidates the conflicting tuple.
+-- the trigger should still fire and catch the violation
+BEGIN;
+INSERT INTO unique_tbl VALUES (3, 'Three'); -- should succeed for now
+UPDATE unique_tbl SET t = 'THREE' WHERE i = 3 AND t = 'Three';
+COMMIT; -- should fail
+ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+SELECT * FROM unique_tbl;
+ i | t
+---+-------
+ 3 | three
+ 1 | five
+ 5 | one
+ 4 | two
+ 2 | four
+(5 rows)
+
+-- test a HOT update that modifies the newly inserted tuple,
+-- but should succeed because we then remove the other conflicting tuple.
+BEGIN;
+INSERT INTO unique_tbl VALUES(3, 'tree'); -- should succeed for now
+UPDATE unique_tbl SET t = 'threex' WHERE t = 'tree';
+DELETE FROM unique_tbl WHERE t = 'three';
+SELECT * FROM unique_tbl;
+ i | t
+---+--------
+ 1 | five
+ 5 | one
+ 4 | two
+ 2 | four
+ 3 | threex
+(5 rows)
+
+COMMIT;
+SELECT * FROM unique_tbl;
+ i | t
+---+--------
+ 1 | five
+ 5 | one
+ 4 | two
+ 2 | four
+ 3 | threex
+(5 rows)
+
+DROP TABLE unique_tbl;
WHERE relhasoids
AND ((nspname ~ '^pg_') IS NOT FALSE)
AND NOT EXISTS (SELECT 1 FROM pg_index i WHERE indrelid = c.oid
- AND indkey[0] = -2 AND indnatts = 1 AND indisunique);
+ AND indkey[0] = -2 AND indnatts = 1
+ AND indisunique AND indimmediate);