WIP: Deferrable unique constraints
This is a feature I would find useful, and I wanted to learn more
about the database internals, so I thought I would give it a go as an
exercise.
I added 2 new index insert modes to support deferrable. During the
initial index insert, if the constraint is deferrable, it does a
"partial" check of uniqueness. This is a non-blocking check which
allows duplicates in, but can distinguish between definitely unique
and potentially non-unique. For potential uniqueness violations a
deferred trigger is queued to do a full check at the end of the
statement or transaction, or when SET CONSTRAINTS is called. The
trigger then replays the insert in a "fake insert" mode, which doesn't
actually insert anything - it just checks that what is already there
is unique, waiting for other transactions if necessary.
This approach works well if the number of potential conflicts is
small. For example, if uniqueness is not actually violated, there is
no significant penalty for the deferred check, since no triggers are
queued:
pgdevel=# CREATE TABLE foo(a int UNIQUE DEFERRABLE INITIALLY DEFERRED);
NOTICE: CREATE TABLE / UNIQUE will create implicit index "foo_a_key"
for table "foo"
CREATE TABLE
Time: 79.131 ms
pgdevel=# INSERT INTO foo (SELECT * FROM generate_series(0, 1999999,
2)); -- 1M rows
INSERT 0 1000000
Time: 11403.906 ms
pgdevel=# BEGIN;
BEGIN
Time: 0.145 ms
pgdevel=# UPDATE foo SET a=a+1; -- Uniqueness never violated
UPDATE 1000000
Time: 21258.245 ms
pgdevel=# COMMIT;
COMMIT
Time: 83.267 ms
compared with:
pgdevel=# CREATE TABLE foo(a int UNIQUE);
NOTICE: CREATE TABLE / UNIQUE will create implicit index "foo_a_key"
for table "foo"
CREATE TABLE
Time: 110.277 ms
pgdevel=# INSERT INTO foo (SELECT * FROM generate_series(0, 1999999,
2)); -- 1M rows
INSERT 0 1000000
Time: 11196.600 ms
pgdevel=# BEGIN;
BEGIN
Time: 0.146 ms
pgdevel=# UPDATE foo SET a=a+1; -- Uniqueness never violated
UPDATE 1000000
Time: 21054.430 ms
pgdevel=# COMMIT;
COMMIT
Time: 186.174 ms
However, if there are lots of temporarily non-unique values, the
performance impact is much greater, since the unique check is repeated
for each row at commit time:
pgdevel=# CREATE TABLE foo(a int UNIQUE DEFERRABLE INITIALLY DEFERRED);
NOTICE: CREATE TABLE / UNIQUE will create implicit index "foo_a_key"
for table "foo"
CREATE TABLE
Time: 64.244 ms
pgdevel=# INSERT INTO foo (SELECT * FROM generate_series(0, 1999999,
2)); -- 1M rows
INSERT 0 1000000
Time: 11621.438 ms
pgdevel=# BEGIN;
BEGIN
Time: 0.146 ms
pgdevel=# UPDATE foo SET a=a+2; -- Uniqueness temporarily violated
UPDATE 1000000
Time: 21807.128 ms
pgdevel=# COMMIT;
COMMIT
Time: 14974.916 ms
And similarly for an "immediate" check:
pgdevel=# CREATE TABLE foo(a int UNIQUE DEFERRABLE INITIALLY IMMEDIATE);
NOTICE: CREATE TABLE / UNIQUE will create implicit index "foo_a_key"
for table "foo"
CREATE TABLE
Time: 44.477 ms
pgdevel=# INSERT INTO foo (SELECT * FROM generate_series(0, 1999999,
2)); -- 1M rows
INSERT 0 1000000
Time: 12083.089 ms
pgdevel=# UPDATE foo SET a=a+2;
UPDATE 1000000
Time: 37173.210 ms
Also, as for FKs and other queued triggers, this doesn't scale because
the queue is held in memory.
Curing the scalability problem by spooling the queue to disk shouldn't
be too hard to do, but that doesn't address the problem that if a
significant proportion of rows from the table need to be checked, it
is far quicker to scan the whole index once than check row by row.
In fact, with this data on my machine, a single scan takes around
0.5sec, so on that basis a full scan would be quicker if more than
around 3% of the data is flagged as potentially non-unique, although
the planner might be a better judge of that.
I can't see a way of coding something that can switch over to a full
scan, without first recording all the potential violations. Then I'm
not entirely sure how best to do the switch-over logic.
- Dean
Attachments:
deferrable_unique.patchapplication/octet-stream; name=deferrable_unique.patchDownload
*** ./src/backend/access/heap/tuptoaster.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/heap/tuptoaster.c 2009-07-04 17:11:08.000000000 +0100
***************
*** 1225,1231 ****
*/
index_insert(toastidx, t_values, t_isnull,
&(toasttup->t_self),
! toastrel, toastidx->rd_index->indisunique);
/*
* Free memory
--- 1225,1233 ----
*/
index_insert(toastidx, t_values, t_isnull,
&(toasttup->t_self),
! toastrel,
! toastidx->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
/*
* Free memory
*** ./src/backend/access/index/indexam.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/index/indexam.c 2009-07-04 17:08:26.000000000 +0100
***************
*** 185,191 ****
bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! bool check_uniqueness)
{
FmgrInfo *procedure;
--- 185,191 ----
bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! IndexUniqueCheck check_uniqueness)
{
FmgrInfo *procedure;
***************
*** 201,207 ****
PointerGetDatum(isnull),
PointerGetDatum(heap_t_ctid),
PointerGetDatum(heapRelation),
! BoolGetDatum(check_uniqueness)));
}
/*
--- 201,207 ----
PointerGetDatum(isnull),
PointerGetDatum(heap_t_ctid),
PointerGetDatum(heapRelation),
! Int32GetDatum((int32) check_uniqueness)));
}
/*
*** ./src/backend/access/nbtree/nbtinsert.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/nbtree/nbtinsert.c 2009-07-05 23:02:09.000000000 +0100
***************
*** 50,56 ****
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
Relation heapRel, Buffer buf, OffsetNumber ioffset,
! ScanKey itup_scankey);
static void _bt_findinsertloc(Relation rel,
Buffer *bufptr,
OffsetNumber *offsetptr,
--- 50,57 ----
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
Relation heapRel, Buffer buf, OffsetNumber ioffset,
! ScanKey itup_scankey, IndexUniqueCheck unique_check,
! bool *is_unique);
static void _bt_findinsertloc(Relation rel,
Buffer *bufptr,
OffsetNumber *offsetptr,
***************
*** 85,95 ****
*
* This routine is called by the public interface routines, btbuild
* and btinsert. By here, itup is filled in, including the TID.
*/
! void
_bt_doinsert(Relation rel, IndexTuple itup,
! bool index_is_unique, Relation heapRel)
{
int natts = rel->rd_rel->relnatts;
ScanKey itup_scankey;
BTStack stack;
--- 86,107 ----
*
* This routine is called by the public interface routines, btbuild
* and btinsert. By here, itup is filled in, including the TID.
+ *
+ * The return value is always true, unless unique_check is
+ * UNIQUE_CHECK_PARTIAL, in which case it may return false if the
+ * tuple is potentially non-unique. This is only a partial uniqueness
+ * check, which doesn't block on other transactions, so it may report
+ * false conflicts.
+ *
+ * If full uniqueness checking is being done (UNIQUE_CHECK_YES or
+ * UNIQUE_CHECK_EXISTING) then this will error if duplicates are
+ * found.
*/
! bool
_bt_doinsert(Relation rel, IndexTuple itup,
! IndexUniqueCheck unique_check, Relation heapRel)
{
+ bool ok = true;
int natts = rel->rd_rel->relnatts;
ScanKey itup_scankey;
BTStack stack;
***************
*** 134,165 ****
*
* If we must wait for another xact, we release the lock while waiting,
* and then must start over completely.
*/
! if (index_is_unique)
{
TransactionId xwait;
offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
! xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey);
! if (TransactionIdIsValid(xwait))
{
! /* Have to wait for the other guy ... */
! _bt_relbuf(rel, buf);
! XactLockTableWait(xwait);
! /* start over... */
! _bt_freestack(stack);
! goto top;
}
}
! /* do the insertion */
! _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
! _bt_insertonpg(rel, buf, stack, itup, offset, false);
/* be tidy */
_bt_freestack(stack);
_bt_freeskey(itup_scankey);
}
/*
--- 146,209 ----
*
* If we must wait for another xact, we release the lock while waiting,
* and then must start over completely.
+ *
+ * For a partial uniqueness check, 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 (unique_check != UNIQUE_CHECK_NO)
{
TransactionId xwait;
+ bool is_unique;
offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
! xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
! unique_check, &is_unique);
! if (!is_unique)
{
! if (TransactionIdIsValid(xwait))
! {
! /* Have to wait for the other guy ... */
! _bt_relbuf(rel, buf);
! XactLockTableWait(xwait);
! /* start over... */
! _bt_freestack(stack);
! goto top;
! }
!
! /*
! * This is a partial uniqueness check and it appears to be a
! * duplicate. Proceed with the insert, but return false - the
! * caller will need to re-check the constraint later.
! */
! ok = false;
}
}
! /*
! * If unique_check is UNIQUE_CHECK_EXISTING, then this is a re-check
! * of a previous potential conflict, rather than a real insert. So
! * either the check above will have thrown an error or the insert has
! * been done already and uniquess is satisfied. Don't re-insert.
! */
! if (unique_check == UNIQUE_CHECK_EXISTING)
! {
! /* just release the buffer */
! _bt_relbuf(rel, buf);
! }
! else
! {
! /* do the insertion */
! _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
! _bt_insertonpg(rel, buf, stack, itup, offset, false);
! }
/* be tidy */
_bt_freestack(stack);
_bt_freeskey(itup_scankey);
+
+ return ok;
}
/*
***************
*** 169,182 ****
* also point to end-of-page, which means that the first tuple to check
* is the first tuple on the next page.
*
! * 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().
*/
static TransactionId
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
! Buffer buf, OffsetNumber offset, ScanKey itup_scankey)
{
TupleDesc itupdesc = RelationGetDescr(rel);
int natts = rel->rd_rel->relnatts;
SnapshotData SnapshotDirty;
--- 213,234 ----
* also point to end-of-page, which means that the first tuple to check
* is the first tuple on the next page.
*
! * For a full uniqueness check, this 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().
! *
! * For a partial uniqueness check, we just set is_unique to false,
! * indicating a potential conflict. _bt_doinsert() will then not block,
! * but just go ahead with the insert. The proper check will be done later
! * (at the end of the statement or transaction).
*/
static TransactionId
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
! Buffer buf, OffsetNumber offset, ScanKey itup_scankey,
! IndexUniqueCheck unique_check, bool *is_unique)
{
+ TransactionId xwait = InvalidTransactionId;
TupleDesc itupdesc = RelationGetDescr(rel);
int natts = rel->rd_rel->relnatts;
SnapshotData SnapshotDirty;
***************
*** 184,189 ****
--- 236,242 ----
Page page;
BTPageOpaque opaque;
Buffer nbuf = InvalidBuffer;
+ bool found = false;
InitDirtySnapshot(SnapshotDirty);
***************
*** 191,196 ****
--- 244,252 ----
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
maxoff = PageGetMaxOffsetNumber(page);
+ /* Assume unique unless we find a duplicate */
+ *is_unique = true;
+
/*
* Scan over all equal tuples, looking for live conflicts.
*/
***************
*** 247,263 ****
*/
if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
{
! /* it is a duplicate */
! TransactionId xwait =
! (TransactionIdIsValid(SnapshotDirty.xmin)) ?
! SnapshotDirty.xmin : SnapshotDirty.xmax;
/*
! * If this tuple is being updated by other transaction
! * then we have to wait for its commit/abort.
*/
! if (TransactionIdIsValid(xwait))
{
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
/* Tell _bt_doinsert to wait... */
--- 303,357 ----
*/
if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
{
! /*
! * We have 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. This is used for deferrable
! * unique constraints.
! */
! if (unique_check == UNIQUE_CHECK_PARTIAL)
! {
! *is_unique = false;
! if (nbuf != InvalidBuffer)
! _bt_relbuf(rel, nbuf);
! return InvalidTransactionId;
! }
/*
! * Otherwise we need to do a proper check to see if
! * it really is a duplicate. First check if it is
! * being updated by another transaction, in which case
! * we may have to wait for it's commit/abort.
*/
! if (TransactionIdIsValid(SnapshotDirty.xmin) ||
! TransactionIdIsValid(SnapshotDirty.xmax))
{
+ xwait = TransactionIdIsValid(SnapshotDirty.xmin) ?
+ SnapshotDirty.xmin : SnapshotDirty.xmax;
+
+ /*
+ * If this is a deferred uniqueness check, of a
+ * tuple that we have already inserted, then there
+ * may be other conflicting tuples which we can
+ * see without waiting, so reset the snapshot and
+ * keep looking, but remember that we may need to
+ * wait on this one after all.
+ */
+ if (unique_check == UNIQUE_CHECK_EXISTING)
+ {
+ SnapshotDirty.xmin = InvalidTransactionId;
+ SnapshotDirty.xmax = InvalidTransactionId;
+
+ goto next_tuple;
+ }
+
+ /*
+ * Otherwise return the other transaction ID
+ * that we have to wait for.
+ */
+ *is_unique = false;
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
/* Tell _bt_doinsert to wait... */
***************
*** 265,270 ****
--- 359,377 ----
}
/*
+ * We have a definite match. If this is a deferred check,
+ * of a tuple that we have already inserted, the first
+ * match is not a conflict, so look for another. This
+ * is used for the final check of a deferrable uniquenes
+ * constraint.
+ */
+ if (unique_check == UNIQUE_CHECK_EXISTING && !found)
+ {
+ found = true;
+ goto next_tuple;
+ }
+
+ /*
* Otherwise we have a definite conflict. But before
* complaining, look to see if the tuple we want to insert
* is itself now committed dead --- if so, don't complain.
***************
*** 280,298 ****
* we have a unique key conflict. The other live tuple is
* not part of this chain because it had a different index
* entry.
*/
! htid = itup->t_tid;
! if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
! {
! /* Normal case --- it's still live */
! }
! else
{
! /*
! * It's been deleted, so no error, and no need to
! * continue searching
! */
! break;
}
ereport(ERROR,
--- 387,411 ----
* we have a unique key conflict. The other live tuple is
* not part of this chain because it had a different index
* entry.
+ *
+ * NOTE: Unique indexes for deferrable constraints can't
+ * be built concurrently, so we can skip this for those.
*/
! if (unique_check == UNIQUE_CHECK_YES)
{
! htid = itup->t_tid;
! if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
! {
! /* Normal case --- it's still live */
! }
! else
! {
! /*
! * It's been deleted, so no error, and no need to
! * continue searching
! */
! break;
! }
}
ereport(ERROR,
***************
*** 318,323 ****
--- 431,437 ----
}
}
+ next_tuple:
/*
* Advance to next tuple to continue checking.
*/
***************
*** 352,357 ****
--- 466,483 ----
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
+ /*
+ * If we reach here, then we didn't find a duplicate. However, in a
+ * deferred uniqueness check, we may have found 1 visible tuple plus
+ * another being updated in another transaction, in which case we need
+ * to wait for it's commit/abort.
+ */
+ if (found && TransactionIdIsValid(xwait))
+ {
+ *is_unique = false;
+ return xwait;
+ }
+
return InvalidTransactionId;
}
*** ./src/backend/access/nbtree/nbtree.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/nbtree/nbtree.c 2009-07-04 17:19:10.000000000 +0100
***************
*** 212,234 ****
Datum
btinsert(PG_FUNCTION_ARGS)
{
! Relation rel = (Relation) PG_GETARG_POINTER(0);
! Datum *values = (Datum *) PG_GETARG_POINTER(1);
! 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);
! 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);
pfree(itup);
! PG_RETURN_BOOL(true);
}
/*
--- 212,235 ----
Datum
btinsert(PG_FUNCTION_ARGS)
{
! Relation rel = (Relation) PG_GETARG_POINTER(0);
! Datum *values = (Datum *) PG_GETARG_POINTER(1);
! bool *isnull = (bool *) PG_GETARG_POINTER(2);
! ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
! Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! IndexTuple itup;
! bool ok;
/* generate an index tuple */
itup = index_form_tuple(RelationGetDescr(rel), values, isnull);
itup->t_tid = *ht_ctid;
! ok = _bt_doinsert(rel, itup, checkUnique, heapRel);
pfree(itup);
! PG_RETURN_BOOL(ok);
}
/*
*** ./src/backend/bootstrap/bootparse.y.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/bootstrap/bootparse.y 2009-07-04 16:54:25.000000000 +0100
***************
*** 266,272 ****
NULL,
$10,
NULL, NIL,
! false, false, false,
false, false, true, false, false);
do_end();
}
--- 266,272 ----
NULL,
$10,
NULL, NIL,
! false, false, false, false, false,
false, false, true, false, false);
do_end();
}
***************
*** 284,290 ****
NULL,
$11,
NULL, NIL,
! true, false, false,
false, false, true, false, false);
do_end();
}
--- 284,290 ----
NULL,
$11,
NULL, NIL,
! true, false, false, false, false,
false, false, true, false, false);
do_end();
}
*** ./src/backend/catalog/index.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/index.c 2009-07-04 19:13:10.000000000 +0100
***************
*** 40,53 ****
--- 40,57 ----
#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/gramparse.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
***************
*** 726,733 ****
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
! false, /* isDeferrable */
! false, /* isDeferred */
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
--- 730,737 ----
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
! indexInfo->ii_Deferrable,
! indexInfo->ii_InitDeferred,
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
***************
*** 753,758 ****
--- 757,807 ----
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+
+ /*
+ * If the constraint is deferrable, define the deferred uniqueness
+ * checking triggers. The index will temporarily allow duplicates and
+ * then these triggers will re-check for duplicates at the end of the
+ * statement or transaction. These triggers are only queued if
+ * potentially non-unique entries are detected during index insertion.
+ */
+ if (indexInfo->ii_Deferrable)
+ {
+ char *nsname = get_namespace_name(namespaceId);
+ RangeVar *heapRel = makeRangeVar(nsname, pstrdup(RelationGetRelationName(heapRelation)), -1);
+ RangeVar *indexRel = makeRangeVar(nsname, pstrdup(indexRelationName), -1);
+ CreateTrigStmt *trigger;
+
+ /*
+ * Advance the command counter so that we can see the
+ * newly-entered catalog tuples for the index.
+ */
+ CommandCounterIncrement();
+
+ trigger = makeNode(CreateTrigStmt);
+ trigger->trigname = pstrdup(indexRelationName);
+ trigger->relation = heapRel;
+ trigger->funcname = SystemFuncName("unique_key_check_ins");
+ trigger->args = NIL;
+ trigger->before = false;
+ trigger->row = true;
+ trigger->events = TRIGGER_TYPE_INSERT;
+ trigger->isconstraint = true;
+ trigger->deferrable = true;
+ trigger->initdeferred = indexInfo->ii_InitDeferred;
+ trigger->constrrel = indexRel;
+
+ (void) CreateTrigger(trigger, conOid, false);
+
+ CommandCounterIncrement();
+
+ trigger->funcname = SystemFuncName("unique_key_check_upd");
+ trigger->events = TRIGGER_TYPE_UPDATE;
+
+ (void) CreateTrigger(trigger, conOid, false);
+
+ CommandCounterIncrement();
+ }
}
else
{
***************
*** 1026,1031 ****
--- 1075,1104 ----
ii->ii_Concurrent = false;
ii->ii_BrokenHotChain = false;
+ /* fetch the details of the index constraint if any */
+ ii->ii_ConstraintId = get_index_constraint(RelationGetRelid(index));
+ if (OidIsValid(ii->ii_ConstraintId))
+ {
+ HeapTuple ht_constr = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(ii->ii_ConstraintId),
+ 0, 0, 0);
+ Form_pg_constraint conrec;
+
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u", ii->ii_ConstraintId);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ ii->ii_Deferrable = conrec->condeferrable;
+ ii->ii_InitDeferred = conrec->condeferred;
+
+ ReleaseSysCache(ht_constr);
+ }
+ else
+ {
+ ii->ii_Deferrable = false;
+ ii->ii_InitDeferred = false;
+ }
+
return ii;
}
***************
*** 2190,2196 ****
isnull,
&rootTuple,
heapRelation,
! indexInfo->ii_Unique);
state->tups_inserted += 1;
}
--- 2263,2270 ----
isnull,
&rootTuple,
heapRelation,
! indexInfo->ii_Unique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
state->tups_inserted += 1;
}
*** ./src/backend/catalog/indexing.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/indexing.c 2009-07-04 16:57:21.000000000 +0100
***************
*** 134,140 ****
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
! relationDescs[i]->rd_index->indisunique);
}
ExecDropSingleTupleTableSlot(slot);
--- 134,141 ----
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
! relationDescs[i]->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
}
ExecDropSingleTupleTableSlot(slot);
*** ./src/backend/catalog/toasting.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/toasting.c 2009-07-04 16:56:10.000000000 +0100
***************
*** 241,246 ****
--- 241,249 ----
indexInfo->ii_ReadyForInserts = true;
indexInfo->ii_Concurrent = false;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_ConstraintId = InvalidOid;
+ indexInfo->ii_Deferrable = false;
+ indexInfo->ii_InitDeferred = false;
classObjectId[0] = OID_BTREE_OPS_OID;
classObjectId[1] = INT4_BTREE_OPS_OID;
*** ./src/backend/commands/copy.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/copy.c 2009-07-04 18:15:59.000000000 +0100
***************
*** 2119,2124 ****
--- 2119,2126 ----
if (!skip_tuple)
{
+ List *problemIndexList = NIL;
+
/* Place tuple in tuple slot */
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
***************
*** 2130,2139 ****
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
! ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;
--- 2132,2141 ----
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple, problemIndexList);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;
*** ./src/backend/commands/indexcmds.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/indexcmds.c 2009-07-04 18:37:43.000000000 +0100
***************
*** 87,92 ****
--- 87,94 ----
* '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.
+ * 'constrDeferrable': constraint is DEFERRABLE (UNIQUE constraint).
+ * 'constrInitDeferred': constraint is INITIALLY DEFERRED (UNIQUE constraint).
* '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.)
***************
*** 107,112 ****
--- 109,116 ----
bool unique,
bool primary,
bool isconstraint,
+ bool constrDeferrable,
+ bool constrInitDeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
***************
*** 419,424 ****
--- 423,431 ----
indexInfo->ii_ReadyForInserts = !concurrent;
indexInfo->ii_Concurrent = concurrent;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_ConstraintId = InvalidOid; /* index_create() may create this */
+ indexInfo->ii_Deferrable = constrDeferrable;
+ indexInfo->ii_InitDeferred = constrInitDeferred;
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
*** ./src/backend/commands/tablecmds.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/tablecmds.c 2009-07-04 18:11:06.000000000 +0100
***************
*** 4371,4376 ****
--- 4371,4378 ----
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->constrDeferrable,
+ stmt->constrInitDeferred,
true, /* is_alter_table */
check_rights,
skip_build,
*** ./src/backend/commands/trigger.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/trigger.c 2009-07-05 22:55:24.000000000 +0100
***************
*** 63,69 ****
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
/*
--- 63,70 ----
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *problemIndexList);
/*
***************
*** 211,224 ****
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 (OidIsValid(constraintOid))
{
snprintf(constrtrigname, sizeof(constrtrigname),
! "RI_ConstraintTrigger_%u", trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
--- 212,258 ----
trigoid = GetNewOid(tgrel);
/*
! * If trigger is for a constraint, the passed-in name is the constraint
! * name; save that and build a unique trigger name based on the type of
! * the constraint to avoid collisions with user-selected trigger names.
*/
if (OidIsValid(constraintOid))
{
+ HeapTuple ht_constr = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(constraintOid),
+ 0, 0, 0);
+ Form_pg_constraint conrec;
+ char *prefix;
+
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u", constraintOid);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ /*
+ * Prefix for trigger name, based on constraint type.
+ */
+ switch (conrec->contype)
+ {
+ case CONSTRAINT_CHECK:
+ prefix = "Check_ConstraintTrigger";
+ break;
+ case CONSTRAINT_FOREIGN:
+ prefix = "RI_ConstraintTrigger";
+ break;
+ case CONSTRAINT_PRIMARY:
+ prefix = "PK_ConstraintTrigger";
+ break;
+ case CONSTRAINT_UNIQUE:
+ prefix = "Unique_ConstraintTrigger";
+ break;
+ default:
+ prefix = "ConstraintTrigger";
+ break;
+ }
+ ReleaseSysCache(ht_constr);
+
snprintf(constrtrigname, sizeof(constrtrigname),
! "%s_%u", prefix, trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
***************
*** 1639,1645 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL);
}
HeapTuple
--- 1673,1679 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL);
}
HeapTuple
***************
*** 1695,1707 ****
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
! HeapTuple trigtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple);
}
void
--- 1729,1741 ----
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
! HeapTuple trigtuple, List *problemIndexList)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, problemIndexList);
}
void
***************
*** 1770,1776 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL);
}
bool
--- 1804,1810 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL);
}
bool
***************
*** 1847,1853 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL);
heap_freetuple(trigtuple);
}
}
--- 1881,1887 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL);
heap_freetuple(trigtuple);
}
}
***************
*** 1918,1924 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL);
}
HeapTuple
--- 1952,1958 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL);
}
HeapTuple
***************
*** 1988,1994 ****
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
! ItemPointer tupleid, HeapTuple newtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 2022,2029 ----
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
! ItemPointer tupleid, HeapTuple newtuple,
! List *problemIndexList)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
***************
*** 1998,2004 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple);
heap_freetuple(trigtuple);
}
}
--- 2033,2039 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, problemIndexList);
heap_freetuple(trigtuple);
}
}
***************
*** 2069,2075 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL);
}
--- 2104,2110 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL);
}
***************
*** 3790,3796 ****
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
! HeapTuple oldtup, HeapTuple newtup)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 3825,3832 ----
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
! HeapTuple oldtup, HeapTuple newtup,
! List *problemIndexList)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
***************
*** 3951,3956 ****
--- 3987,4004 ----
}
/*
+ * If the trigger is a deferred unique constraint check trigger,
+ * only execute it if the unique constraint was potentially
+ * violated, which we know from index insertion time.
+ */
+ if (trigger->tgfoid == F_UNIQUE_KEY_CHECK_INS ||
+ trigger->tgfoid == F_UNIQUE_KEY_CHECK_UPD)
+ {
+ if (!list_member_oid(problemIndexList, trigger->tgconstrrelid))
+ continue; /* Uniqueness definitely not violated */
+ }
+
+ /*
* Fill in event structure and add it to the current query's queue.
*/
new_shared.ats_event =
*** ./src/backend/executor/execMain.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/executor/execMain.c 2009-07-04 17:45:41.000000000 +0100
***************
*** 1753,1758 ****
--- 1753,1759 ----
ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
Oid newId;
+ List *problemIndexList = NIL;
/*
* get the heap tuple out of the tuple table slot, making sure we have a
***************
*** 1834,1843 ****
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
! ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
--- 1835,1845 ----
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self),
! estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple, problemIndexList);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
***************
*** 1999,2004 ****
--- 2001,2007 ----
HTSU_Result result;
ItemPointerData update_ctid;
TransactionId update_xmax;
+ List *problemIndexList = NIL;
/*
* abort the operation if not running transactions
***************
*** 2132,2141 ****
* 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);
/* AFTER ROW UPDATE Triggers */
! ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
--- 2135,2145 ----
* If it's a HOT update, we mustn't insert new index entries.
*/
if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self),
! estate, false);
/* AFTER ROW UPDATE Triggers */
! ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple, problemIndexList);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
*** ./src/backend/executor/execUtils.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/executor/execUtils.c 2009-07-04 17:40:14.000000000 +0100
***************
*** 1030,1038 ****
* 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
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
--- 1030,1042 ----
* 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?
+ *
+ * This returns a list of OIDs for any unique indexes
+ * whose constraint check was deferred and which had
+ * potential (unconfirmed) conflicts.
* ----------------------------------------------------------------
*/
! List *
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
***************
*** 1047,1052 ****
--- 1051,1057 ----
ExprContext *econtext;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
+ List *result = NIL;
/*
* Get information from the result relation info structure.
***************
*** 1072,1077 ****
--- 1077,1084 ----
for (i = 0; i < numIndices; i++)
{
IndexInfo *indexInfo;
+ bool defer_unique_check;
+ bool ok;
if (relationDescs[i] == NULL)
continue;
***************
*** 1119,1137 ****
* 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.
*/
! 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);
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
}
/*
--- 1126,1165 ----
* 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.
+ *
+ * If there is a deferrable unique constraint associated with the
+ * index, then we don't enforce uniqueness here, we just record if
+ * the tuple was possibly not unique.
*/
! defer_unique_check = relationDescs[i]->rd_index->indisunique &&
! !is_vacuum && indexInfo->ii_Deferrable;
!
! ok = index_insert(relationDescs[i], /* index relation */
! values, /* array of index Datums */
! isnull, /* null flags */
! tupleid, /* tid of heap tuple */
! heapRelation,
! is_vacuum ? UNIQUE_CHECK_NO :
! defer_unique_check ? UNIQUE_CHECK_PARTIAL :
! relationDescs[i]->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
!
! if (defer_unique_check && !ok)
! {
! /*
! * The tuple is potentially violates the uniqueness constraint.
! * Make a note of this index so that we can re-check it later.
! */
! result = lappend_oid(result, relationDescs[i]->rd_index->indexrelid);
! }
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
+
+ return result;
}
/*
*** ./src/backend/nodes/copyfuncs.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c 2009-07-04 17:54:33.000000000 +0100
***************
*** 2091,2096 ****
--- 2091,2098 ----
COPY_NODE_FIELD(keys);
COPY_NODE_FIELD(options);
COPY_STRING_FIELD(indexspace);
+ COPY_SCALAR_FIELD(deferrable);
+ COPY_SCALAR_FIELD(initdeferred);
return newnode;
}
***************
*** 2509,2514 ****
--- 2511,2518 ----
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
+ COPY_SCALAR_FIELD(constrDeferrable);
+ COPY_SCALAR_FIELD(constrInitDeferred);
COPY_SCALAR_FIELD(concurrent);
return newnode;
*** ./src/backend/nodes/equalfuncs.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/nodes/equalfuncs.c 2009-07-04 17:52:16.000000000 +0100
***************
*** 1159,1164 ****
--- 1159,1166 ----
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
+ COMPARE_SCALAR_FIELD(constrDeferrable);
+ COMPARE_SCALAR_FIELD(constrInitDeferred);
COMPARE_SCALAR_FIELD(concurrent);
return true;
***************
*** 2068,2073 ****
--- 2070,2077 ----
COMPARE_NODE_FIELD(keys);
COMPARE_NODE_FIELD(options);
COMPARE_STRING_FIELD(indexspace);
+ COMPARE_SCALAR_FIELD(deferrable);
+ COMPARE_SCALAR_FIELD(initdeferred);
return true;
}
*** ./src/backend/nodes/outfuncs.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/nodes/outfuncs.c 2009-07-04 17:50:04.000000000 +0100
***************
*** 1734,1739 ****
--- 1734,1741 ----
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
+ WRITE_BOOL_FIELD(constrDeferrable);
+ WRITE_BOOL_FIELD(constrInitDeferred);
WRITE_BOOL_FIELD(concurrent);
}
***************
*** 2292,2297 ****
--- 2294,2301 ----
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
+ WRITE_BOOL_FIELD(deferrable);
+ WRITE_BOOL_FIELD(initdeferred);
break;
case CONSTR_CHECK:
*** ./src/backend/parser/gram.y.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/parser/gram.y 2009-07-04 17:37:32.000000000 +0100
***************
*** 2173,2178 ****
--- 2173,2180 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| NULL_P
***************
*** 2184,2189 ****
--- 2186,2193 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE opt_definition OptConsTableSpace
***************
*** 2196,2201 ****
--- 2200,2207 ----
n->keys = NULL;
n->options = $2;
n->indexspace = $3;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| PRIMARY KEY opt_definition OptConsTableSpace
***************
*** 2208,2213 ****
--- 2214,2221 ----
n->keys = NULL;
n->options = $3;
n->indexspace = $4;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| CHECK '(' a_expr ')'
***************
*** 2219,2224 ****
--- 2227,2234 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| DEFAULT b_expr
***************
*** 2230,2235 ****
--- 2240,2247 ----
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
***************
*** 2359,2367 ****
--- 2371,2382 ----
n->raw_expr = $3;
n->cooked_expr = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
+ ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_UNIQUE;
***************
*** 2371,2376 ****
--- 2386,2393 ----
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
***************
*** 2383,2388 ****
--- 2400,2407 ----
n->keys = $4;
n->options = $6;
n->indexspace = $7;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
*** ./src/backend/parser/parse_utilcmd.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/parser/parse_utilcmd.c 2009-07-04 17:25:31.000000000 +0100
***************
*** 34,39 ****
--- 34,40 ----
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_opclass.h"
+ #include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
***************
*** 809,815 ****
else if (!index->unique)
index->isconstraint = false;
else
! index->isconstraint = OidIsValid(get_index_constraint(source_relid));
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
--- 810,843 ----
else if (!index->unique)
index->isconstraint = false;
else
! {
! /*
! * The index is marked UNIQUE, so there may be an associated unique
! * constraint. If there is, we need to fetch its DEFERRABLE and
! * INITIALLY DEFERRED attributes.
! */
! Oid constraintId = get_index_constraint(source_relid);
!
! if (OidIsValid(constraintId))
! {
! HeapTuple ht_constr = SearchSysCache(CONSTROID,
! ObjectIdGetDatum(constraintId),
! 0, 0, 0);
! Form_pg_constraint conrec;
!
! if (!HeapTupleIsValid(ht_constr))
! elog(ERROR, "cache lookup failed for constraint %u", constraintId);
! conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
!
! index->isconstraint = true;
! index->constrDeferrable = conrec->condeferrable;
! index->constrInitDeferred = conrec->condeferred;
!
! ReleaseSysCache(ht_constr);
! }
! else
! index->isconstraint = false;
! }
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
***************
*** 1092,1097 ****
--- 1120,1127 ----
*/
}
index->isconstraint = true;
+ index->constrDeferrable = constraint->deferrable;
+ index->constrInitDeferred = constraint->initdeferred;
if (constraint->name != NULL)
index->idxname = pstrdup(constraint->name);
***************
*** 1853,1861 ****
* 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.
*/
static void
transformConstraintAttrs(List *constraintList)
{
--- 1883,1898 ----
* to attach constraint attributes to their primary constraint nodes
* and detect inconsistent/misplaced constraint attributes.
*
! * NOTE: currently, attributes are only supported for FOREIGN KEY and
! * UNIQUE primary constraints, but someday they ought to be supported
! * for other constraints.
*/
+ #define SUPPORTS_ATTRS(node) \
+ ((node) != NULL && \
+ (IsA((node), FkConstraint) || \
+ (IsA((node), Constraint) && \
+ ((Constraint *)(node))->contype == CONSTR_UNIQUE)))
+
static void
transformConstraintAttrs(List *constraintList)
{
***************
*** 1882,1889 ****
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
--- 1919,1925 ----
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
! if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
***************
*** 1892,1902 ****
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! ((FkConstraint *) lastprimarynode)->deferrable = true;
break;
case CONSTR_ATTR_NOT_DEFERRABLE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
--- 1928,1941 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! if (IsA(lastprimarynode, FkConstraint))
! ((FkConstraint *) lastprimarynode)->deferrable = true;
! else
! ((Constraint *) lastprimarynode)->deferrable = true;
break;
+
case CONSTR_ATTR_NOT_DEFERRABLE:
! if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
***************
*** 1905,1920 ****
(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")));
break;
case CONSTR_ATTR_DEFERRED:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
--- 1944,1971 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! 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 (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
***************
*** 1923,1943 ****
(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")));
break;
case CONSTR_ATTR_IMMEDIATE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
--- 1974,2009 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
/*
* If only INITIALLY DEFERRED appears, assume 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 (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
***************
*** 1946,1953 ****
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
! ((FkConstraint *) lastprimarynode)->initdeferred = false;
break;
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
--- 2012,2023 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
! if (IsA(lastprimarynode, FkConstraint))
! ((FkConstraint *) lastprimarynode)->initdeferred = false;
! else
! ((Constraint *) lastprimarynode)->initdeferred = false;
break;
+
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
*** ./src/backend/tcop/utility.c.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/backend/tcop/utility.c 2009-07-04 16:51:25.000000000 +0100
***************
*** 795,800 ****
--- 795,802 ----
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->constrDeferrable,
+ stmt->constrInitDeferred,
false, /* is_alter_table */
true, /* check_rights */
false, /* skip_build */
*** ./src/backend/utils/adt/ri_triggers.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/utils/adt/ri_triggers.c 2009-07-06 17:28:50.000000000 +0100
***************
*** 30,40 ****
--- 30,43 ----
#include "postgres.h"
+ #include "access/genam.h"
#include "access/xact.h"
+ #include "catalog/index.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
+ #include "executor/executor.h"
#include "executor/spi.h"
#include "parser/parse_coerce.h"
#include "parser/parse_relation.h"
***************
*** 4020,4022 ****
--- 4023,4174 ----
return RI_TRIGGER_NONE;
}
+
+ /* -------------------------------------------------------------------------
+ *
+ * Triggers to check deferred UNIQUE constraints.
+ *
+ * These probably belong in a new source file, since they actually have
+ * little in common with the referential integrity triggers.
+ *
+ * -------------------------------------------------------------------------
+ */
+
+ /* ----------
+ * unique_key_check -
+ *
+ * Deferred check of a uniqueness constraint (for both INSERT and UPDATE).
+ * ----------
+ */
+ static Datum
+ unique_key_check(PG_FUNCTION_ARGS)
+ {
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+ HeapTuple new_row;
+ Buffer new_row_buf;
+ Relation indexRel;
+ IndexInfo *indexInfo;
+ EState *estate;
+ ExprContext *econtext;
+ TupleTableSlot *slot;
+ Datum values[INDEX_MAX_KEYS];
+ bool isnull[INDEX_MAX_KEYS];
+
+ /*
+ * Get the new data that was inserted/updated.
+ */
+ if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ {
+ new_row = trigdata->tg_newtuple;
+ new_row_buf = trigdata->tg_newtuplebuf;
+ }
+ else
+ {
+ new_row = trigdata->tg_trigtuple;
+ new_row_buf = trigdata->tg_trigtuplebuf;
+ }
+
+ Assert(new_row_buf != InvalidBuffer);
+
+ /*
+ * Open the unique index, acquiring a RowExclusiveLock, as if we were
+ * going to update it. This does not actually protect us against
+ * concurrent insertions - that is done by the index itself which
+ * acquires a write lock on the target page during the uniqueness check.
+ */
+ indexRel = index_open(trigdata->tg_trigger->tgconstrrelid,
+ RowExclusiveLock);
+ indexInfo = BuildIndexInfo(indexRel);
+
+ /*
+ * Typically the index won't have expressions, but if it does we need
+ * an EState to evaluate them. Also need a slot for the tuple we are
+ * going to test.
+ */
+ if (indexInfo->ii_Expressions != NIL)
+ {
+ estate = CreateExecutorState();
+ econtext = GetPerTupleExprContext(estate);
+ }
+ else
+ {
+ estate = NULL;
+ econtext = NULL;
+ }
+
+ slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation));
+
+ if (econtext != NULL)
+ {
+ econtext->ecxt_scantuple = slot;
+ MemoryContextReset(econtext->ecxt_per_tuple_memory);
+ }
+
+ ExecStoreTuple(new_row, slot, InvalidBuffer, false);
+
+ /*
+ * Form the index values and is null flags for the index entry that
+ * we need to check.
+ */
+ 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.
+ */
+ ExecDropSingleTupleTableSlot(slot);
+
+ index_close(indexRel, RowExclusiveLock);
+
+ if (estate != NULL)
+ FreeExecutorState(estate);
+
+ return PointerGetDatum(NULL);
+ }
+
+
+ /* ----------
+ * unique_key_check_ins -
+ *
+ * Check uniqueness constraint at insert event.
+ *
+ * This applies only to UNIQUE constraints marked DEFERRABLE.
+ * ----------
+ */
+ Datum
+ unique_key_check_ins(PG_FUNCTION_ARGS)
+ {
+ /*
+ * Check that this is a valid trigger call on the right time and event.
+ */
+ ri_CheckTrigger(fcinfo, "unique_key_check_ins", RI_TRIGTYPE_INSERT);
+
+ return unique_key_check(fcinfo);
+ }
+
+
+ /* ----------
+ * unique_key_check_upd -
+ *
+ * Check uniquesness constraint at update event.
+ *
+ * This applies only to UNIQUE constraints marked DEFERRABLE.
+ * ----------
+ */
+ Datum
+ unique_key_check_upd(PG_FUNCTION_ARGS)
+ {
+ /*
+ * Check that this is a valid trigger call on the right time and event.
+ */
+ ri_CheckTrigger(fcinfo, "unique_key_check_upd", RI_TRIGTYPE_UPDATE);
+
+ return unique_key_check(fcinfo);
+ }
*** ./src/backend/utils/adt/ruleutils.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/utils/adt/ruleutils.c 2009-07-04 18:03:05.000000000 +0100
***************
*** 1096,1101 ****
--- 1096,1106 ----
quote_identifier(get_tablespace_name(tblspc)));
}
+ if (conForm->condeferrable)
+ appendStringInfo(&buf, " DEFERRABLE");
+ if (conForm->condeferred)
+ appendStringInfo(&buf, " INITIALLY DEFERRED");
+
break;
}
case CONSTRAINT_CHECK:
*** ./src/include/access/genam.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/access/genam.h 2009-07-04 16:25:47.000000000 +0100
***************
*** 85,90 ****
--- 85,118 ----
typedef struct IndexScanDescData *IndexScanDesc;
typedef struct SysScanDescData *SysScanDesc;
+ /*
+ * Enumeration specifying the type of uniqueness check to perform for
+ * index_insert().
+ *
+ * For deferrable unique constraints, UNIQUE_CHECK_PARTIAL is used at
+ * insertion time. This tests if the tuple is unique but doesn't error,
+ * block or prevent the insertion if the tuple appears not to be unique.
+ * Since it isn't blocking, it can't know for sure if the tuple is not
+ * unique (the other transaction might be rolled back). The return value
+ * of index_insert() in this case is true if the tuple is definitely
+ * unique and false if it is possibly non-unique.
+ *
+ * UNIQUE_CHECK_EXISTING is for deferred unqiue constraint checks. The
+ * tuple is already in the index when the check is performed, so it is
+ * not inserted again. This just checks if the tuple in the index is
+ * unique.
+ */
+ typedef enum IndexUniqueCheck
+ {
+ UNIQUE_CHECK_NO, /* Don't do any uniqueness checking */
+ UNIQUE_CHECK_YES, /* Enforce uniqueness at insertion time */
+ /* (possibly blocking on another xact) */
+ UNIQUE_CHECK_PARTIAL, /* Test uniqueness, but don't block and */
+ /* no error if potentially not unique */
+ UNIQUE_CHECK_EXISTING /* Don't do the insert, just check if */
+ /* the existing tuple is unique */
+ } IndexUniqueCheck;
+
/*
* generalized index_ interface routines (in indexam.c)
***************
*** 103,109 ****
Datum *values, bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! bool check_uniqueness);
extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
--- 131,137 ----
Datum *values, bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! IndexUniqueCheck check_uniqueness);
extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
*** ./src/include/access/nbtree.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/access/nbtree.h 2009-07-04 16:28:12.000000000 +0100
***************
*** 517,524 ****
/*
* prototypes for functions in nbtinsert.c
*/
! extern void _bt_doinsert(Relation rel, IndexTuple itup,
! bool index_is_unique, 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);
--- 517,524 ----
/*
* prototypes for functions in nbtinsert.c
*/
! extern bool _bt_doinsert(Relation rel, IndexTuple itup,
! IndexUniqueCheck unique_check, 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);
*** ./src/include/catalog/pg_proc.h.orig 2009-07-04 15:41:37.000000000 +0100
--- ./src/include/catalog/pg_proc.h 2009-07-04 16:07:27.000000000 +0100
***************
*** 2346,2351 ****
--- 2346,2357 ----
DATA(insert OID = 1655 ( RI_FKey_noaction_upd PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ RI_FKey_noaction_upd _null_ _null_ _null_ ));
DESCR("referential integrity ON UPDATE NO ACTION");
+ /* Deferrable unique constraint triggers */
+ DATA(insert OID = 1029 ( unique_key_check_ins PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_check_ins _null_ _null_ _null_ ));
+ DESCR("deferred check UNIQUE ... DEFERRABLE");
+ DATA(insert OID = 1030 ( unique_key_check_upd PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_check_upd _null_ _null_ _null_ ));
+ DESCR("deferred check UNIQUE ... DEFERRABLE");
+
DATA(insert OID = 1666 ( varbiteq PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "1562 1562" _null_ _null_ _null_ _null_ biteq _null_ _null_ _null_ ));
DESCR("equal");
DATA(insert OID = 1667 ( varbitne PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "1562 1562" _null_ _null_ _null_ _null_ bitne _null_ _null_ _null_ ));
*** ./src/include/commands/defrem.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/commands/defrem.h 2009-07-04 16:48:30.000000000 +0100
***************
*** 29,34 ****
--- 29,36 ----
bool unique,
bool primary,
bool isconstraint,
+ bool constrDeferrable,
+ bool constrInitDeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
*** ./src/include/commands/trigger.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/commands/trigger.h 2009-07-04 16:46:36.000000000 +0100
***************
*** 131,137 ****
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
! HeapTuple trigtuple);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
--- 131,138 ----
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
! HeapTuple trigtuple,
! List *problemIndexList);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
***************
*** 153,159 ****
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
! HeapTuple newtuple);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
--- 154,161 ----
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
! HeapTuple newtuple,
! List *problemIndexList);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
*** ./src/include/executor/executor.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/executor/executor.h 2009-07-04 16:32:11.000000000 +0100
***************
*** 301,307 ****
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern void ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum);
extern void RegisterExprContextCallback(ExprContext *econtext,
--- 301,307 ----
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum);
extern void RegisterExprContextCallback(ExprContext *econtext,
*** ./src/include/nodes/execnodes.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/nodes/execnodes.h 2009-07-04 16:34:51.000000000 +0100
***************
*** 44,49 ****
--- 44,52 ----
* ReadyForInserts is it valid for inserts?
* Concurrent are we doing a concurrent index build?
* BrokenHotChain did we detect any broken HOT chains?
+ * ConstraintId OID of constraint associated with this index
+ * Deferrable is the constraint DEFERRABLE?
+ * InitDeferred is the constraint INITIALLY DEFERRED?
*
* ii_Concurrent and ii_BrokenHotChain are used only during index build;
* they're conventionally set to false otherwise.
***************
*** 62,67 ****
--- 65,73 ----
bool ii_ReadyForInserts;
bool ii_Concurrent;
bool ii_BrokenHotChain;
+ Oid ii_ConstraintId;
+ bool ii_Deferrable;
+ bool ii_InitDeferred;
} IndexInfo;
/* ----------------
*** ./src/include/nodes/parsenodes.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/nodes/parsenodes.h 2009-07-04 16:40:06.000000000 +0100
***************
*** 1354,1360 ****
* 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).
* ----------
*/
--- 1354,1360 ----
* 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
! * Constraint and FkConstraint nodes.
* ----------
*/
***************
*** 1384,1389 ****
--- 1384,1391 ----
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;
/* ----------
***************
*** 1863,1868 ****
--- 1865,1872 ----
bool unique; /* is index unique? */
bool primary; /* is index on primary key? */
bool isconstraint; /* is it from a CONSTRAINT clause? */
+ bool constrDeferrable; /* is the constraint DEFERRABLE? */
+ bool constrInitDeferred; /* is the constraint INITIALLY DEFERRED? */
bool concurrent; /* should this be a concurrent index build? */
} IndexStmt;
*** ./src/include/utils/builtins.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/utils/builtins.h 2009-07-04 16:43:48.000000000 +0100
***************
*** 932,940 ****
extern Datum RI_FKey_setnull_upd(PG_FUNCTION_ARGS);
extern Datum RI_FKey_setdefault_del(PG_FUNCTION_ARGS);
extern Datum RI_FKey_setdefault_upd(PG_FUNCTION_ARGS);
!
! /* trigfuncs.c */
! extern Datum suppress_redundant_updates_trigger(PG_FUNCTION_ARGS);
/* encoding support functions */
extern Datum getdatabaseencoding(PG_FUNCTION_ARGS);
--- 932,939 ----
extern Datum RI_FKey_setnull_upd(PG_FUNCTION_ARGS);
extern Datum RI_FKey_setdefault_del(PG_FUNCTION_ARGS);
extern Datum RI_FKey_setdefault_upd(PG_FUNCTION_ARGS);
! extern Datum unique_key_check_ins(PG_FUNCTION_ARGS);
! extern Datum unique_key_check_upd(PG_FUNCTION_ARGS);
/* encoding support functions */
extern Datum getdatabaseencoding(PG_FUNCTION_ARGS);
First, I'm happy that you're working on this; I think it's important. I
am working on another index constraints feature that may have some
interaction:
http://archives.postgresql.org/pgsql-hackers/2009-07/msg00302.php
Let me know if you see any potential conflicts between our work.
On Tue, 2009-07-07 at 19:38 +0100, Dean Rasheed wrote:
For potential uniqueness violations a
deferred trigger is queued to do a full check at the end of the
statement or transaction, or when SET CONSTRAINTS is called. The
trigger then replays the insert in a "fake insert" mode, which doesn't
actually insert anything - it just checks that what is already there
is unique, waiting for other transactions if necessary.
What does the deferred trigger do? Do you need a "fake insert" mode or
can you use an index search instead?
I'm thinking that you could just store the TID of the tuple that causes
the potential violation in your list. Then, when you recheck the list,
for each potential violation, find the tuple from the TID, do a search
using the appropriate attributes, and if you get multiple results
there's a conflict.
Regards,
Jeff Davis
On Tue, 2009-07-07 at 19:38 +0100, Dean Rasheed wrote:
This approach works well if the number of potential conflicts is
small.
[...]
Curing the scalability problem by spooling the queue to disk shouldn't
be too hard to do, but that doesn't address the problem that if a
significant proportion of rows from the table need to be checked, it
is far quicker to scan the whole index once than check row by row.
Another approach that might be worth considering is to build a temporary
index and try to merge them at constraint-checking time. That might work
well for unique.
However, there are some potential issues. I didn't think this through
yet, but here is a quick list just to get some thoughts down:
1. It would be tricky to merge while checking constraints if we are
supporting more general constraints like in my proposal
( http://archives.postgresql.org/pgsql-hackers/2009-07/msg00302.php ).
2. Which indexes can be merged efficiently, and how much effort would it
take to make this work?
3. A related issue: making indexes mergeable would be useful for bulk
inserts as well.
4. At the end of the command, the index needs to work, meaning that
queries would have to search two indexes. That may be difficult (but
check the GIN fast insert code, which does something similar).
5. The temporary index still can't be enforcing constraints if they are
deferred, so it won't solve all the issues here.
Regards,
Jeff Davis
Jeff Davis wrote:
First, I'm happy that you're working on this; I think it's important. I
am working on another index constraints feature that may have some
interaction:http://archives.postgresql.org/pgsql-hackers/2009-07/msg00302.php
Let me know if you see any potential conflicts between our work.
Hi Jeff,
Yes, I've been following that other thread with interest. I don't think
that there is any conflict between the 2 patches, although there may be
some interaction.
My stuff is specific to UNIQUE, and my triggers make the assumption that
this is enforced by an index. The triggers don't actually care what kind
of index, although of course currently it can only be a btree.
I guess that your generalised constraints could be made deferrable in a
similar way, and would use different triggers to do the final check, so
no problem there.
On Tue, 2009-07-07 at 19:38 +0100, Dean Rasheed wrote:
For potential uniqueness violations a
deferred trigger is queued to do a full check at the end of the
statement or transaction, or when SET CONSTRAINTS is called. The
trigger then replays the insert in a "fake insert" mode, which doesn't
actually insert anything - it just checks that what is already there
is unique, waiting for other transactions if necessary.What does the deferred trigger do? Do you need a "fake insert" mode or
can you use an index search instead?
Well the point about the "fake insert" mode is that it has to be able to
block on another transaction, to see if it commits or rolls back a
potentially conflicting value.
ISTM that trying to do this with an index search would open up the
potential for all sorts of race conditions.
- Dean
Show quoted text
I'm thinking that you could just store the TID of the tuple that causes
the potential violation in your list. Then, when you recheck the list,
for each potential violation, find the tuple from the TID, do a search
using the appropriate attributes, and if you get multiple results
there's a conflict.Regards,
Jeff Davis
Jeff Davis wrote:
On Tue, 2009-07-07 at 19:38 +0100, Dean Rasheed wrote:
This approach works well if the number of potential conflicts is
small.[...]
Curing the scalability problem by spooling the queue to disk shouldn't
be too hard to do, but that doesn't address the problem that if a
significant proportion of rows from the table need to be checked, it
is far quicker to scan the whole index once than check row by row.Another approach that might be worth considering is to build a temporary
index and try to merge them at constraint-checking time. That might work
well for unique.
I'm not really sure what you mean by a "temporary index". Do you mean
one that you would just throw away at the end of the statement? That
seems a bit heavy-weight.
Also it seems too specific to unique constraints. I think it would be
better to cure the scalability issues for all constraints and triggers
in one place, in the after triggers queue code.
I had hoped that after doing deferrable unique constraints, I might
apply a similar approach to other constraints, eg. a deferrable check
constraint. In that case, an index doesn't help, and there is no choice
but to check the rows one at a time.
Unique (and also FK) are special, in that they have potentially more
optimal ways of checking them in bulk. ISTM that this is an orthogonal
concept to the issue of making the trigger queue scalable, except that
there ought to be an efficient way of discarding all the queued entries
for a particular constraint, if we decide to check it en masse (perhaps
a separate queue per constraint, or per trigger).
- Dean
Show quoted text
However, there are some potential issues. I didn't think this through
yet, but here is a quick list just to get some thoughts down:1. It would be tricky to merge while checking constraints if we are
supporting more general constraints like in my proposal
( http://archives.postgresql.org/pgsql-hackers/2009-07/msg00302.php ).2. Which indexes can be merged efficiently, and how much effort would it
take to make this work?3. A related issue: making indexes mergeable would be useful for bulk
inserts as well.4. At the end of the command, the index needs to work, meaning that
queries would have to search two indexes. That may be difficult (but
check the GIN fast insert code, which does something similar).5. The temporary index still can't be enforcing constraints if they are
deferred, so it won't solve all the issues here.Regards,
Jeff Davis
Here is an updated version of this patch which should apply to HEAD,
with updated docs, regression tests, pg_dump and psql \d.
It works well for small numbers of temporary uniqueness violations,
and at least as well (in fact about twice as fast) as deferred FK
checks for large numbers of deferred checks.
I propose trying to improve performance and scalability for large
numbers of deferred checks in a separate patch.
- Dean
Attachments:
deferrable_unique.patchapplication/octet-stream; name=deferrable_unique.patchDownload
*** ./doc/src/sgml/indexam.sgml.orig 2009-07-11 22:37:18.000000000 +0100
--- ./doc/src/sgml/indexam.sgml 2009-07-12 12:10:09.000000000 +0100
***************
*** 172,191 ****
bool *isnull,
ItemPointer heap_tid,
Relation heapRelation,
! bool check_uniqueness);
</programlisting>
Insert a new tuple into an existing index. The <literal>values</> and
<literal>isnull</> arrays give the key values to be indexed, and
<literal>heap_tid</> is the TID to be indexed.
If the access method supports unique indexes (its
<structname>pg_am</>.<structfield>amcanunique</> flag is true) then
! <literal>check_uniqueness</> might be true, in which case the access method
! must verify that there is no conflicting row; this is the only situation in
! which the access method normally needs the <literal>heapRelation</>
! parameter. See <xref linkend="index-unique-checks"> for details.
! The result is TRUE if an index entry was inserted, FALSE if not. (A FALSE
! result does not denote an error condition, but is used for cases such
! as an index method refusing to index a NULL.)
</para>
<para>
--- 172,195 ----
bool *isnull,
ItemPointer heap_tid,
Relation heapRelation,
! IndexUniqueCheck uniqueness_check);
</programlisting>
Insert a new tuple into an existing index. The <literal>values</> and
<literal>isnull</> arrays give the key values to be indexed, and
<literal>heap_tid</> is the TID to be indexed.
If the access method supports unique indexes (its
<structname>pg_am</>.<structfield>amcanunique</> flag is true) then
! <literal>uniqueness_check</> indicates that type of uniqueness check to
! perform to verify that there is no conflicting row. The type of uniqueness
! check varies depending on whether the uniqueness constraint is deferrable;
! see <xref linkend="index-unique-checks"> for more details. This is the only
! situation in which the access method normally needs the
! <literal>heapRelation</> parameter.
! The result is TRUE if an index entry was inserted, FALSE if not, or if
! uniqueness was potentially violated. (A FALSE result does not denote an
! error condition, but is used for cases such as an index method refusing
! to index a NULL, or noting that a non-NULL value might violate a deferred
! unique check.)
</para>
<para>
***************
*** 728,737 ****
</para>
<para>
! The main limitation of this scheme is that it has no convenient way
! to support deferred uniqueness checks.
! </para>
</sect1>
<sect1 id="index-cost-estimation">
--- 732,799 ----
</para>
<para>
! If the unique constraint is deferrable, then the final uniqueness check
! needs to be done later - either at the end of the statement, or when the
! transaction is committed, or as part of a <command>SET CONSTRAINTS</>
! operation. This is handled by passing one of the following enumeration
! values to the index insert access method:
!
! <programlisting>
! typedef enum IndexUniqueCheck
! {
! UNIQUE_CHECK_NO,
! UNIQUE_CHECK_YES,
! UNIQUE_CHECK_PARTIAL,
! UNIQUE_CHECK_EXISTING
! } IndexUniqueCheck;
! </programlisting>
!
! <itemizedlist>
! <listitem>
! <para>
! <literal>UNQIUE_CHECK_NO</> indicates that no uniqueness checking
! need be done (this is not a unique index).
! </para>
! </listitem>
! <listitem>
! <para>
! <literal>UNIQUE_CHECK_YES</> indicates that this is a non-deferrable
! unique index, and the uniqueness check must be done immediately, as
! described above.
! </para>
! </listitem>
! <listitem>
! <para>
! <literal>UNIQUE_CHECK_PARTIAL</> indicates that the uniqueness
! constraint is deferrable. <productname>PostgreSQL</productname>
! will use this mode to insert each row's index entry; the access
! method must allow duplicate entries into the index, and report any
! potential duplicates by returning false. All such rows will then be
! re-checked later when the deferred constraint check is triggered.
! </para>
+ <para>
+ The access method must identify any rows which might violate the
+ uniqueness constraint, but it is not an error for it to report false
+ positives. This allows the check to be done without waiting for other
+ transactions to finish; conflicts reported here are not treated as
+ errors and will be re-checked later, by which time they may no longer
+ be conflicts.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>UNIQUE_CHECK_EXISTING</> indicates that this is a deferred
+ check of a row which was reported as a potential uniqueness violation.
+ This is only used when a deferred check is triggered (after the index
+ entry has already been inserted). The access method is expected to
+ check the existing entry for uniqueness and raise an error if it is
+ not unique. It should not insert the entry again - this is a read-only
+ operation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
</sect1>
<sect1 id="index-cost-estimation">
*** ./doc/src/sgml/ref/create_table.sgml.orig 2009-07-11 22:21:03.000000000 +0100
--- ./doc/src/sgml/ref/create_table.sgml 2009-07-11 22:23:01.000000000 +0100
***************
*** 535,541 ****
deferrable can be postponed until the end of the transaction
(using the <xref linkend="sql-set-constraints" endterm="sql-set-constraints-title"> command).
<literal>NOT DEFERRABLE</literal> is the default. Only foreign
! key constraints currently accept this clause. All other
constraint types are not deferrable.
</para>
</listitem>
--- 535,541 ----
deferrable can be postponed until the end of the transaction
(using the <xref linkend="sql-set-constraints" endterm="sql-set-constraints-title"> command).
<literal>NOT DEFERRABLE</literal> is the default. Only foreign
! key and unique constraints currently accept this clause. All other
constraint types are not deferrable.
</para>
</listitem>
*** ./doc/src/sgml/ref/set_constraints.sgml.orig 2009-07-11 22:24:31.000000000 +0100
--- ./doc/src/sgml/ref/set_constraints.sgml 2009-07-11 22:27:37.000000000 +0100
***************
*** 66,73 ****
</para>
<para>
! Currently, only foreign key constraints are affected by this
! setting. Check and unique constraints are always effectively
not deferrable. Triggers that are declared as <quote>constraint
triggers</> are also affected.
</para>
--- 66,73 ----
</para>
<para>
! Currently, only foreign key and unique constraints are affected by this
! setting. Check constraints are always effectively
not deferrable. Triggers that are declared as <quote>constraint
triggers</> are also affected.
</para>
***************
*** 92,98 ****
This command complies with the behavior defined in the SQL
standard, except for the limitation that, in
<productname>PostgreSQL</productname>, it only applies to
! foreign-key constraints.
</para>
</refsect1>
--- 92,98 ----
This command complies with the behavior defined in the SQL
standard, except for the limitation that, in
<productname>PostgreSQL</productname>, it only applies to
! foreign-key and unique constraints.
</para>
</refsect1>
*** ./src/backend/access/heap/tuptoaster.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/heap/tuptoaster.c 2009-07-04 17:11:08.000000000 +0100
***************
*** 1225,1231 ****
*/
index_insert(toastidx, t_values, t_isnull,
&(toasttup->t_self),
! toastrel, toastidx->rd_index->indisunique);
/*
* Free memory
--- 1225,1233 ----
*/
index_insert(toastidx, t_values, t_isnull,
&(toasttup->t_self),
! toastrel,
! toastidx->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
/*
* Free memory
*** ./src/backend/access/index/indexam.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/index/indexam.c 2009-07-04 17:08:26.000000000 +0100
***************
*** 185,191 ****
bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! bool check_uniqueness)
{
FmgrInfo *procedure;
--- 185,191 ----
bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! IndexUniqueCheck check_uniqueness)
{
FmgrInfo *procedure;
***************
*** 201,207 ****
PointerGetDatum(isnull),
PointerGetDatum(heap_t_ctid),
PointerGetDatum(heapRelation),
! BoolGetDatum(check_uniqueness)));
}
/*
--- 201,207 ----
PointerGetDatum(isnull),
PointerGetDatum(heap_t_ctid),
PointerGetDatum(heapRelation),
! Int32GetDatum((int32) check_uniqueness)));
}
/*
*** ./src/backend/access/nbtree/nbtinsert.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/nbtree/nbtinsert.c 2009-07-05 23:02:09.000000000 +0100
***************
*** 50,56 ****
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
Relation heapRel, Buffer buf, OffsetNumber ioffset,
! ScanKey itup_scankey);
static void _bt_findinsertloc(Relation rel,
Buffer *bufptr,
OffsetNumber *offsetptr,
--- 50,57 ----
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
Relation heapRel, Buffer buf, OffsetNumber ioffset,
! ScanKey itup_scankey, IndexUniqueCheck unique_check,
! bool *is_unique);
static void _bt_findinsertloc(Relation rel,
Buffer *bufptr,
OffsetNumber *offsetptr,
***************
*** 85,95 ****
*
* This routine is called by the public interface routines, btbuild
* and btinsert. By here, itup is filled in, including the TID.
*/
! void
_bt_doinsert(Relation rel, IndexTuple itup,
! bool index_is_unique, Relation heapRel)
{
int natts = rel->rd_rel->relnatts;
ScanKey itup_scankey;
BTStack stack;
--- 86,107 ----
*
* This routine is called by the public interface routines, btbuild
* and btinsert. By here, itup is filled in, including the TID.
+ *
+ * The return value is always true, unless unique_check is
+ * UNIQUE_CHECK_PARTIAL, in which case it may return false if the
+ * tuple is potentially non-unique. This is only a partial uniqueness
+ * check, which doesn't block on other transactions, so it may report
+ * false conflicts.
+ *
+ * If full uniqueness checking is being done (UNIQUE_CHECK_YES or
+ * UNIQUE_CHECK_EXISTING) then this will error if duplicates are
+ * found.
*/
! bool
_bt_doinsert(Relation rel, IndexTuple itup,
! IndexUniqueCheck unique_check, Relation heapRel)
{
+ bool ok = true;
int natts = rel->rd_rel->relnatts;
ScanKey itup_scankey;
BTStack stack;
***************
*** 134,165 ****
*
* If we must wait for another xact, we release the lock while waiting,
* and then must start over completely.
*/
! if (index_is_unique)
{
TransactionId xwait;
offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
! xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey);
! if (TransactionIdIsValid(xwait))
{
! /* Have to wait for the other guy ... */
! _bt_relbuf(rel, buf);
! XactLockTableWait(xwait);
! /* start over... */
! _bt_freestack(stack);
! goto top;
}
}
! /* do the insertion */
! _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
! _bt_insertonpg(rel, buf, stack, itup, offset, false);
/* be tidy */
_bt_freestack(stack);
_bt_freeskey(itup_scankey);
}
/*
--- 146,209 ----
*
* If we must wait for another xact, we release the lock while waiting,
* and then must start over completely.
+ *
+ * For a partial uniqueness check, 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 (unique_check != UNIQUE_CHECK_NO)
{
TransactionId xwait;
+ bool is_unique;
offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
! xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
! unique_check, &is_unique);
! if (!is_unique)
{
! if (TransactionIdIsValid(xwait))
! {
! /* Have to wait for the other guy ... */
! _bt_relbuf(rel, buf);
! XactLockTableWait(xwait);
! /* start over... */
! _bt_freestack(stack);
! goto top;
! }
!
! /*
! * This is a partial uniqueness check and it appears to be a
! * duplicate. Proceed with the insert, but return false - the
! * caller will need to re-check the constraint later.
! */
! ok = false;
}
}
! /*
! * If unique_check is UNIQUE_CHECK_EXISTING, then this is a re-check
! * of a previous potential conflict, rather than a real insert. So
! * either the check above will have thrown an error or the insert has
! * been done already and uniquess is satisfied. Don't re-insert.
! */
! if (unique_check == UNIQUE_CHECK_EXISTING)
! {
! /* just release the buffer */
! _bt_relbuf(rel, buf);
! }
! else
! {
! /* do the insertion */
! _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
! _bt_insertonpg(rel, buf, stack, itup, offset, false);
! }
/* be tidy */
_bt_freestack(stack);
_bt_freeskey(itup_scankey);
+
+ return ok;
}
/*
***************
*** 169,182 ****
* also point to end-of-page, which means that the first tuple to check
* is the first tuple on the next page.
*
! * 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().
*/
static TransactionId
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
! Buffer buf, OffsetNumber offset, ScanKey itup_scankey)
{
TupleDesc itupdesc = RelationGetDescr(rel);
int natts = rel->rd_rel->relnatts;
SnapshotData SnapshotDirty;
--- 213,234 ----
* also point to end-of-page, which means that the first tuple to check
* is the first tuple on the next page.
*
! * For a full uniqueness check, this 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().
! *
! * For a partial uniqueness check, we just set is_unique to false,
! * indicating a potential conflict. _bt_doinsert() will then not block,
! * but just go ahead with the insert. The proper check will be done later
! * (at the end of the statement or transaction).
*/
static TransactionId
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
! Buffer buf, OffsetNumber offset, ScanKey itup_scankey,
! IndexUniqueCheck unique_check, bool *is_unique)
{
+ TransactionId xwait = InvalidTransactionId;
TupleDesc itupdesc = RelationGetDescr(rel);
int natts = rel->rd_rel->relnatts;
SnapshotData SnapshotDirty;
***************
*** 184,189 ****
--- 236,242 ----
Page page;
BTPageOpaque opaque;
Buffer nbuf = InvalidBuffer;
+ bool found = false;
InitDirtySnapshot(SnapshotDirty);
***************
*** 191,196 ****
--- 244,252 ----
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
maxoff = PageGetMaxOffsetNumber(page);
+ /* Assume unique unless we find a duplicate */
+ *is_unique = true;
+
/*
* Scan over all equal tuples, looking for live conflicts.
*/
***************
*** 247,263 ****
*/
if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
{
! /* it is a duplicate */
! TransactionId xwait =
! (TransactionIdIsValid(SnapshotDirty.xmin)) ?
! SnapshotDirty.xmin : SnapshotDirty.xmax;
/*
! * If this tuple is being updated by other transaction
! * then we have to wait for its commit/abort.
*/
! if (TransactionIdIsValid(xwait))
{
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
/* Tell _bt_doinsert to wait... */
--- 303,357 ----
*/
if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
{
! /*
! * We have 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. This is used for deferrable
! * unique constraints.
! */
! if (unique_check == UNIQUE_CHECK_PARTIAL)
! {
! *is_unique = false;
! if (nbuf != InvalidBuffer)
! _bt_relbuf(rel, nbuf);
! return InvalidTransactionId;
! }
/*
! * Otherwise we need to do a proper check to see if
! * it really is a duplicate. First check if it is
! * being updated by another transaction, in which case
! * we may have to wait for it's commit/abort.
*/
! if (TransactionIdIsValid(SnapshotDirty.xmin) ||
! TransactionIdIsValid(SnapshotDirty.xmax))
{
+ xwait = TransactionIdIsValid(SnapshotDirty.xmin) ?
+ SnapshotDirty.xmin : SnapshotDirty.xmax;
+
+ /*
+ * If this is a deferred uniqueness check, of a
+ * tuple that we have already inserted, then there
+ * may be other conflicting tuples which we can
+ * see without waiting, so reset the snapshot and
+ * keep looking, but remember that we may need to
+ * wait on this one after all.
+ */
+ if (unique_check == UNIQUE_CHECK_EXISTING)
+ {
+ SnapshotDirty.xmin = InvalidTransactionId;
+ SnapshotDirty.xmax = InvalidTransactionId;
+
+ goto next_tuple;
+ }
+
+ /*
+ * Otherwise return the other transaction ID
+ * that we have to wait for.
+ */
+ *is_unique = false;
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
/* Tell _bt_doinsert to wait... */
***************
*** 265,270 ****
--- 359,377 ----
}
/*
+ * We have a definite match. If this is a deferred check,
+ * of a tuple that we have already inserted, the first
+ * match is not a conflict, so look for another. This
+ * is used for the final check of a deferrable uniquenes
+ * constraint.
+ */
+ if (unique_check == UNIQUE_CHECK_EXISTING && !found)
+ {
+ found = true;
+ goto next_tuple;
+ }
+
+ /*
* Otherwise we have a definite conflict. But before
* complaining, look to see if the tuple we want to insert
* is itself now committed dead --- if so, don't complain.
***************
*** 280,298 ****
* we have a unique key conflict. The other live tuple is
* not part of this chain because it had a different index
* entry.
*/
! htid = itup->t_tid;
! if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
! {
! /* Normal case --- it's still live */
! }
! else
{
! /*
! * It's been deleted, so no error, and no need to
! * continue searching
! */
! break;
}
ereport(ERROR,
--- 387,411 ----
* we have a unique key conflict. The other live tuple is
* not part of this chain because it had a different index
* entry.
+ *
+ * NOTE: Unique indexes for deferrable constraints can't
+ * be built concurrently, so we can skip this for those.
*/
! if (unique_check == UNIQUE_CHECK_YES)
{
! htid = itup->t_tid;
! if (heap_hot_search(&htid, heapRel, SnapshotSelf, NULL))
! {
! /* Normal case --- it's still live */
! }
! else
! {
! /*
! * It's been deleted, so no error, and no need to
! * continue searching
! */
! break;
! }
}
ereport(ERROR,
***************
*** 318,323 ****
--- 431,437 ----
}
}
+ next_tuple:
/*
* Advance to next tuple to continue checking.
*/
***************
*** 352,357 ****
--- 466,483 ----
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
+ /*
+ * If we reach here, then we didn't find a duplicate. However, in a
+ * deferred uniqueness check, we may have found 1 visible tuple plus
+ * another being updated in another transaction, in which case we need
+ * to wait for it's commit/abort.
+ */
+ if (found && TransactionIdIsValid(xwait))
+ {
+ *is_unique = false;
+ return xwait;
+ }
+
return InvalidTransactionId;
}
*** ./src/backend/access/nbtree/nbtree.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/nbtree/nbtree.c 2009-07-04 17:19:10.000000000 +0100
***************
*** 212,234 ****
Datum
btinsert(PG_FUNCTION_ARGS)
{
! Relation rel = (Relation) PG_GETARG_POINTER(0);
! Datum *values = (Datum *) PG_GETARG_POINTER(1);
! 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);
! 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);
pfree(itup);
! PG_RETURN_BOOL(true);
}
/*
--- 212,235 ----
Datum
btinsert(PG_FUNCTION_ARGS)
{
! Relation rel = (Relation) PG_GETARG_POINTER(0);
! Datum *values = (Datum *) PG_GETARG_POINTER(1);
! bool *isnull = (bool *) PG_GETARG_POINTER(2);
! ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
! Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! IndexTuple itup;
! bool ok;
/* generate an index tuple */
itup = index_form_tuple(RelationGetDescr(rel), values, isnull);
itup->t_tid = *ht_ctid;
! ok = _bt_doinsert(rel, itup, checkUnique, heapRel);
pfree(itup);
! PG_RETURN_BOOL(ok);
}
/*
*** ./src/backend/bootstrap/bootparse.y.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/bootstrap/bootparse.y 2009-07-04 16:54:25.000000000 +0100
***************
*** 266,272 ****
NULL,
$10,
NULL, NIL,
! false, false, false,
false, false, true, false, false);
do_end();
}
--- 266,272 ----
NULL,
$10,
NULL, NIL,
! false, false, false, false, false,
false, false, true, false, false);
do_end();
}
***************
*** 284,290 ****
NULL,
$11,
NULL, NIL,
! true, false, false,
false, false, true, false, false);
do_end();
}
--- 284,290 ----
NULL,
$11,
NULL, NIL,
! true, false, false, false, false,
false, false, true, false, false);
do_end();
}
*** ./src/backend/catalog/index.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/index.c 2009-07-04 19:13:10.000000000 +0100
***************
*** 40,53 ****
--- 40,57 ----
#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/gramparse.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
***************
*** 726,733 ****
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
! false, /* isDeferrable */
! false, /* isDeferred */
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
--- 730,737 ----
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
! indexInfo->ii_Deferrable,
! indexInfo->ii_InitDeferred,
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
***************
*** 753,758 ****
--- 757,807 ----
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+
+ /*
+ * If the constraint is deferrable, define the deferred uniqueness
+ * checking triggers. The index will temporarily allow duplicates and
+ * then these triggers will re-check for duplicates at the end of the
+ * statement or transaction. These triggers are only queued if
+ * potentially non-unique entries are detected during index insertion.
+ */
+ if (indexInfo->ii_Deferrable)
+ {
+ char *nsname = get_namespace_name(namespaceId);
+ RangeVar *heapRel = makeRangeVar(nsname, pstrdup(RelationGetRelationName(heapRelation)), -1);
+ RangeVar *indexRel = makeRangeVar(nsname, pstrdup(indexRelationName), -1);
+ CreateTrigStmt *trigger;
+
+ /*
+ * Advance the command counter so that we can see the
+ * newly-entered catalog tuples for the index.
+ */
+ CommandCounterIncrement();
+
+ trigger = makeNode(CreateTrigStmt);
+ trigger->trigname = pstrdup(indexRelationName);
+ trigger->relation = heapRel;
+ trigger->funcname = SystemFuncName("unique_key_check_ins");
+ trigger->args = NIL;
+ trigger->before = false;
+ trigger->row = true;
+ trigger->events = TRIGGER_TYPE_INSERT;
+ trigger->isconstraint = true;
+ trigger->deferrable = true;
+ trigger->initdeferred = indexInfo->ii_InitDeferred;
+ trigger->constrrel = indexRel;
+
+ (void) CreateTrigger(trigger, conOid, false);
+
+ CommandCounterIncrement();
+
+ trigger->funcname = SystemFuncName("unique_key_check_upd");
+ trigger->events = TRIGGER_TYPE_UPDATE;
+
+ (void) CreateTrigger(trigger, conOid, false);
+
+ CommandCounterIncrement();
+ }
}
else
{
***************
*** 1026,1031 ****
--- 1075,1104 ----
ii->ii_Concurrent = false;
ii->ii_BrokenHotChain = false;
+ /* fetch the details of the index constraint if any */
+ ii->ii_ConstraintId = get_index_constraint(RelationGetRelid(index));
+ if (OidIsValid(ii->ii_ConstraintId))
+ {
+ HeapTuple ht_constr = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(ii->ii_ConstraintId),
+ 0, 0, 0);
+ Form_pg_constraint conrec;
+
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u", ii->ii_ConstraintId);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ ii->ii_Deferrable = conrec->condeferrable;
+ ii->ii_InitDeferred = conrec->condeferred;
+
+ ReleaseSysCache(ht_constr);
+ }
+ else
+ {
+ ii->ii_Deferrable = false;
+ ii->ii_InitDeferred = false;
+ }
+
return ii;
}
***************
*** 2190,2196 ****
isnull,
&rootTuple,
heapRelation,
! indexInfo->ii_Unique);
state->tups_inserted += 1;
}
--- 2263,2270 ----
isnull,
&rootTuple,
heapRelation,
! indexInfo->ii_Unique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
state->tups_inserted += 1;
}
*** ./src/backend/catalog/indexing.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/indexing.c 2009-07-04 16:57:21.000000000 +0100
***************
*** 134,140 ****
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
! relationDescs[i]->rd_index->indisunique);
}
ExecDropSingleTupleTableSlot(slot);
--- 134,141 ----
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
! relationDescs[i]->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
}
ExecDropSingleTupleTableSlot(slot);
*** ./src/backend/catalog/sql_features.txt.orig 2009-07-11 23:37:21.000000000 +0100
--- ./src/backend/catalog/sql_features.txt 2009-07-11 23:38:32.000000000 +0100
***************
*** 289,295 ****
F696 Additional translation documentation NO
F701 Referential update actions YES
F711 ALTER domain YES
! F721 Deferrable constraints NO foreign keys only
F731 INSERT column privileges YES
F741 Referential MATCH types NO no partial match yet
F751 View CHECK enhancements NO
--- 289,295 ----
F696 Additional translation documentation NO
F701 Referential update actions YES
F711 ALTER domain YES
! 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
*** ./src/backend/catalog/toasting.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/toasting.c 2009-07-04 16:56:10.000000000 +0100
***************
*** 241,246 ****
--- 241,249 ----
indexInfo->ii_ReadyForInserts = true;
indexInfo->ii_Concurrent = false;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_ConstraintId = InvalidOid;
+ indexInfo->ii_Deferrable = false;
+ indexInfo->ii_InitDeferred = false;
classObjectId[0] = OID_BTREE_OPS_OID;
classObjectId[1] = INT4_BTREE_OPS_OID;
*** ./src/backend/commands/copy.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/copy.c 2009-07-04 18:15:59.000000000 +0100
***************
*** 2119,2124 ****
--- 2119,2126 ----
if (!skip_tuple)
{
+ List *problemIndexList = NIL;
+
/* Place tuple in tuple slot */
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
***************
*** 2130,2139 ****
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
! ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;
--- 2132,2141 ----
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple, problemIndexList);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;
*** ./src/backend/commands/indexcmds.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/indexcmds.c 2009-07-04 18:37:43.000000000 +0100
***************
*** 87,92 ****
--- 87,94 ----
* '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.
+ * 'constrDeferrable': constraint is DEFERRABLE (UNIQUE constraint).
+ * 'constrInitDeferred': constraint is INITIALLY DEFERRED (UNIQUE constraint).
* '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.)
***************
*** 107,112 ****
--- 109,116 ----
bool unique,
bool primary,
bool isconstraint,
+ bool constrDeferrable,
+ bool constrInitDeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
***************
*** 419,424 ****
--- 423,431 ----
indexInfo->ii_ReadyForInserts = !concurrent;
indexInfo->ii_Concurrent = concurrent;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_ConstraintId = InvalidOid; /* index_create() may create this */
+ indexInfo->ii_Deferrable = constrDeferrable;
+ indexInfo->ii_InitDeferred = constrInitDeferred;
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
*** ./src/backend/commands/tablecmds.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/tablecmds.c 2009-07-04 18:11:06.000000000 +0100
***************
*** 4371,4376 ****
--- 4371,4378 ----
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->constrDeferrable,
+ stmt->constrInitDeferred,
true, /* is_alter_table */
check_rights,
skip_build,
*** ./src/backend/commands/trigger.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/trigger.c 2009-07-05 22:55:24.000000000 +0100
***************
*** 63,69 ****
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
/*
--- 63,70 ----
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *problemIndexList);
/*
***************
*** 211,224 ****
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 (OidIsValid(constraintOid))
{
snprintf(constrtrigname, sizeof(constrtrigname),
! "RI_ConstraintTrigger_%u", trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
--- 212,258 ----
trigoid = GetNewOid(tgrel);
/*
! * If trigger is for a constraint, the passed-in name is the constraint
! * name; save that and build a unique trigger name based on the type of
! * the constraint to avoid collisions with user-selected trigger names.
*/
if (OidIsValid(constraintOid))
{
+ HeapTuple ht_constr = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(constraintOid),
+ 0, 0, 0);
+ Form_pg_constraint conrec;
+ char *prefix;
+
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u", constraintOid);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ /*
+ * Prefix for trigger name, based on constraint type.
+ */
+ switch (conrec->contype)
+ {
+ case CONSTRAINT_CHECK:
+ prefix = "Check_ConstraintTrigger";
+ break;
+ case CONSTRAINT_FOREIGN:
+ prefix = "RI_ConstraintTrigger";
+ break;
+ case CONSTRAINT_PRIMARY:
+ prefix = "PK_ConstraintTrigger";
+ break;
+ case CONSTRAINT_UNIQUE:
+ prefix = "Unique_ConstraintTrigger";
+ break;
+ default:
+ prefix = "ConstraintTrigger";
+ break;
+ }
+ ReleaseSysCache(ht_constr);
+
snprintf(constrtrigname, sizeof(constrtrigname),
! "%s_%u", prefix, trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
***************
*** 1639,1645 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL);
}
HeapTuple
--- 1673,1679 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL);
}
HeapTuple
***************
*** 1695,1707 ****
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
! HeapTuple trigtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple);
}
void
--- 1729,1741 ----
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
! HeapTuple trigtuple, List *problemIndexList)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, problemIndexList);
}
void
***************
*** 1770,1776 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL);
}
bool
--- 1804,1810 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL);
}
bool
***************
*** 1847,1853 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL);
heap_freetuple(trigtuple);
}
}
--- 1881,1887 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL);
heap_freetuple(trigtuple);
}
}
***************
*** 1918,1924 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL);
}
HeapTuple
--- 1952,1958 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL);
}
HeapTuple
***************
*** 1988,1994 ****
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
! ItemPointer tupleid, HeapTuple newtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 2022,2029 ----
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
! ItemPointer tupleid, HeapTuple newtuple,
! List *problemIndexList)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
***************
*** 1998,2004 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple);
heap_freetuple(trigtuple);
}
}
--- 2033,2039 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, problemIndexList);
heap_freetuple(trigtuple);
}
}
***************
*** 2069,2075 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL);
}
--- 2104,2110 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL);
}
***************
*** 3790,3796 ****
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
! HeapTuple oldtup, HeapTuple newtup)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 3825,3832 ----
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
! HeapTuple oldtup, HeapTuple newtup,
! List *problemIndexList)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
***************
*** 3951,3956 ****
--- 3987,4004 ----
}
/*
+ * If the trigger is a deferred unique constraint check trigger,
+ * only execute it if the unique constraint was potentially
+ * violated, which we know from index insertion time.
+ */
+ if (trigger->tgfoid == F_UNIQUE_KEY_CHECK_INS ||
+ trigger->tgfoid == F_UNIQUE_KEY_CHECK_UPD)
+ {
+ if (!list_member_oid(problemIndexList, trigger->tgconstrrelid))
+ continue; /* Uniqueness definitely not violated */
+ }
+
+ /*
* Fill in event structure and add it to the current query's queue.
*/
new_shared.ats_event =
*** ./src/backend/executor/execMain.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/executor/execMain.c 2009-07-04 17:45:41.000000000 +0100
***************
*** 1753,1758 ****
--- 1753,1759 ----
ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
Oid newId;
+ List *problemIndexList = NIL;
/*
* get the heap tuple out of the tuple table slot, making sure we have a
***************
*** 1834,1843 ****
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
! ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
--- 1835,1845 ----
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self),
! estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple, problemIndexList);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
***************
*** 1999,2004 ****
--- 2001,2007 ----
HTSU_Result result;
ItemPointerData update_ctid;
TransactionId update_xmax;
+ List *problemIndexList = NIL;
/*
* abort the operation if not running transactions
***************
*** 2132,2141 ****
* 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);
/* AFTER ROW UPDATE Triggers */
! ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
--- 2135,2145 ----
* If it's a HOT update, we mustn't insert new index entries.
*/
if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self),
! estate, false);
/* AFTER ROW UPDATE Triggers */
! ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple, problemIndexList);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
*** ./src/backend/executor/execUtils.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/executor/execUtils.c 2009-07-12 10:12:39.000000000 +0100
***************
*** 1030,1038 ****
* 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
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
--- 1030,1042 ----
* 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?
+ *
+ * This returns a list of OIDs for any unique indexes
+ * whose constraint check was deferred and which had
+ * potential (unconfirmed) conflicts.
* ----------------------------------------------------------------
*/
! List *
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
***************
*** 1047,1052 ****
--- 1051,1057 ----
ExprContext *econtext;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
+ List *result = NIL;
/*
* Get information from the result relation info structure.
***************
*** 1072,1077 ****
--- 1077,1084 ----
for (i = 0; i < numIndices; i++)
{
IndexInfo *indexInfo;
+ bool deferUniqueCheck;
+ bool insertResult;
if (relationDescs[i] == NULL)
continue;
***************
*** 1119,1137 ****
* 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.
*/
! 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);
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
}
/*
--- 1126,1188 ----
* 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.
+ *
+ * If there is a deferrable unique constraint associated with the
+ * index, then we don't enforce uniqueness here, we just record if
+ * the tuple was possibly not unique.
*/
! deferUniqueCheck = relationDescs[i]->rd_index->indisunique &&
! !is_vacuum && indexInfo->ii_Deferrable;
!
! insertResult = index_insert(relationDescs[i], /* index relation */
! values, /* array of index Datums */
! isnull, /* null flags */
! tupleid, /* tid of heap tuple */
! heapRelation,
! is_vacuum ? UNIQUE_CHECK_NO :
! deferUniqueCheck ? UNIQUE_CHECK_PARTIAL :
! relationDescs[i]->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
!
! if (deferUniqueCheck && !insertResult)
! {
! /*
! * This check is currently pointless, but one day hash indexes
! * might support uniqueness, in which case a FALSE return value
! * might be due to NULLs. Since NULL never satisfies '=', this
! * can never be a uniqueness violation.
! */
! bool hasNulls = false;
! int j;
!
! for (j = 0; j < indexInfo->ii_NumIndexAttrs; j++)
! {
! if (isnull[j])
! {
! hasNulls = true;
! break;
! }
! }
!
! if (!hasNulls)
! {
! /*
! * 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,
! relationDescs[i]->rd_index->indexrelid);
! }
! }
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
+
+ return result;
}
/*
*** ./src/backend/nodes/copyfuncs.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c 2009-07-04 17:54:33.000000000 +0100
***************
*** 2091,2096 ****
--- 2091,2098 ----
COPY_NODE_FIELD(keys);
COPY_NODE_FIELD(options);
COPY_STRING_FIELD(indexspace);
+ COPY_SCALAR_FIELD(deferrable);
+ COPY_SCALAR_FIELD(initdeferred);
return newnode;
}
***************
*** 2509,2514 ****
--- 2511,2518 ----
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
+ COPY_SCALAR_FIELD(constrDeferrable);
+ COPY_SCALAR_FIELD(constrInitDeferred);
COPY_SCALAR_FIELD(concurrent);
return newnode;
*** ./src/backend/nodes/equalfuncs.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/nodes/equalfuncs.c 2009-07-04 17:52:16.000000000 +0100
***************
*** 1159,1164 ****
--- 1159,1166 ----
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
+ COMPARE_SCALAR_FIELD(constrDeferrable);
+ COMPARE_SCALAR_FIELD(constrInitDeferred);
COMPARE_SCALAR_FIELD(concurrent);
return true;
***************
*** 2068,2073 ****
--- 2070,2077 ----
COMPARE_NODE_FIELD(keys);
COMPARE_NODE_FIELD(options);
COMPARE_STRING_FIELD(indexspace);
+ COMPARE_SCALAR_FIELD(deferrable);
+ COMPARE_SCALAR_FIELD(initdeferred);
return true;
}
*** ./src/backend/nodes/outfuncs.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/nodes/outfuncs.c 2009-07-04 17:50:04.000000000 +0100
***************
*** 1734,1739 ****
--- 1734,1741 ----
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
+ WRITE_BOOL_FIELD(constrDeferrable);
+ WRITE_BOOL_FIELD(constrInitDeferred);
WRITE_BOOL_FIELD(concurrent);
}
***************
*** 2292,2297 ****
--- 2294,2301 ----
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
+ WRITE_BOOL_FIELD(deferrable);
+ WRITE_BOOL_FIELD(initdeferred);
break;
case CONSTR_CHECK:
*** ./src/backend/parser/gram.y.orig 2009-07-11 12:53:30.000000000 +0100
--- ./src/backend/parser/gram.y 2009-07-11 12:55:46.000000000 +0100
***************
*** 2173,2178 ****
--- 2173,2180 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| NULL_P
***************
*** 2184,2189 ****
--- 2186,2193 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE opt_definition OptConsTableSpace
***************
*** 2196,2201 ****
--- 2200,2207 ----
n->keys = NULL;
n->options = $2;
n->indexspace = $3;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| PRIMARY KEY opt_definition OptConsTableSpace
***************
*** 2208,2213 ****
--- 2214,2221 ----
n->keys = NULL;
n->options = $3;
n->indexspace = $4;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| CHECK '(' a_expr ')'
***************
*** 2219,2224 ****
--- 2227,2234 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| DEFAULT b_expr
***************
*** 2230,2235 ****
--- 2240,2247 ----
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
***************
*** 2359,2367 ****
--- 2371,2382 ----
n->raw_expr = $3;
n->cooked_expr = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
+ ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_UNIQUE;
***************
*** 2371,2376 ****
--- 2386,2393 ----
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
***************
*** 2383,2388 ****
--- 2400,2407 ----
n->keys = $4;
n->options = $6;
n->indexspace = $7;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
*** ./src/backend/parser/parse_utilcmd.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/parser/parse_utilcmd.c 2009-07-04 17:25:31.000000000 +0100
***************
*** 34,39 ****
--- 34,40 ----
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_opclass.h"
+ #include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
***************
*** 809,815 ****
else if (!index->unique)
index->isconstraint = false;
else
! index->isconstraint = OidIsValid(get_index_constraint(source_relid));
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
--- 810,843 ----
else if (!index->unique)
index->isconstraint = false;
else
! {
! /*
! * The index is marked UNIQUE, so there may be an associated unique
! * constraint. If there is, we need to fetch its DEFERRABLE and
! * INITIALLY DEFERRED attributes.
! */
! Oid constraintId = get_index_constraint(source_relid);
!
! if (OidIsValid(constraintId))
! {
! HeapTuple ht_constr = SearchSysCache(CONSTROID,
! ObjectIdGetDatum(constraintId),
! 0, 0, 0);
! Form_pg_constraint conrec;
!
! if (!HeapTupleIsValid(ht_constr))
! elog(ERROR, "cache lookup failed for constraint %u", constraintId);
! conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
!
! index->isconstraint = true;
! index->constrDeferrable = conrec->condeferrable;
! index->constrInitDeferred = conrec->condeferred;
!
! ReleaseSysCache(ht_constr);
! }
! else
! index->isconstraint = false;
! }
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
***************
*** 1092,1097 ****
--- 1120,1127 ----
*/
}
index->isconstraint = true;
+ index->constrDeferrable = constraint->deferrable;
+ index->constrInitDeferred = constraint->initdeferred;
if (constraint->name != NULL)
index->idxname = pstrdup(constraint->name);
***************
*** 1853,1861 ****
* 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.
*/
static void
transformConstraintAttrs(List *constraintList)
{
--- 1883,1898 ----
* to attach constraint attributes to their primary constraint nodes
* and detect inconsistent/misplaced constraint attributes.
*
! * NOTE: currently, attributes are only supported for FOREIGN KEY and
! * UNIQUE primary constraints, but someday they ought to be supported
! * for other constraints.
*/
+ #define SUPPORTS_ATTRS(node) \
+ ((node) != NULL && \
+ (IsA((node), FkConstraint) || \
+ (IsA((node), Constraint) && \
+ ((Constraint *)(node))->contype == CONSTR_UNIQUE)))
+
static void
transformConstraintAttrs(List *constraintList)
{
***************
*** 1882,1889 ****
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
--- 1919,1925 ----
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
! if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
***************
*** 1892,1902 ****
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! ((FkConstraint *) lastprimarynode)->deferrable = true;
break;
case CONSTR_ATTR_NOT_DEFERRABLE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
--- 1928,1941 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! if (IsA(lastprimarynode, FkConstraint))
! ((FkConstraint *) lastprimarynode)->deferrable = true;
! else
! ((Constraint *) lastprimarynode)->deferrable = true;
break;
+
case CONSTR_ATTR_NOT_DEFERRABLE:
! if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
***************
*** 1905,1920 ****
(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")));
break;
case CONSTR_ATTR_DEFERRED:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
--- 1944,1971 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! 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 (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
***************
*** 1923,1943 ****
(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")));
break;
case CONSTR_ATTR_IMMEDIATE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
--- 1974,2009 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
/*
* If only INITIALLY DEFERRED appears, assume 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 (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
***************
*** 1946,1953 ****
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
! ((FkConstraint *) lastprimarynode)->initdeferred = false;
break;
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
--- 2012,2023 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
! if (IsA(lastprimarynode, FkConstraint))
! ((FkConstraint *) lastprimarynode)->initdeferred = false;
! else
! ((Constraint *) lastprimarynode)->initdeferred = false;
break;
+
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
*** ./src/backend/tcop/utility.c.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/backend/tcop/utility.c 2009-07-04 16:51:25.000000000 +0100
***************
*** 795,800 ****
--- 795,802 ----
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->constrDeferrable,
+ stmt->constrInitDeferred,
false, /* is_alter_table */
true, /* check_rights */
false, /* skip_build */
*** ./src/backend/utils/adt/ri_triggers.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/utils/adt/ri_triggers.c 2009-07-07 10:24:12.000000000 +0100
***************
*** 30,40 ****
--- 30,43 ----
#include "postgres.h"
+ #include "access/genam.h"
#include "access/xact.h"
+ #include "catalog/index.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
+ #include "executor/executor.h"
#include "executor/spi.h"
#include "parser/parse_coerce.h"
#include "parser/parse_relation.h"
***************
*** 4020,4022 ****
--- 4023,4174 ----
return RI_TRIGGER_NONE;
}
+
+ /* -------------------------------------------------------------------------
+ *
+ * Triggers to check deferred UNIQUE constraints.
+ *
+ * These probably belong in a new source file, since they actually have
+ * little in common with the referential integrity triggers.
+ *
+ * -------------------------------------------------------------------------
+ */
+
+ /* ----------
+ * unique_key_check -
+ *
+ * Deferred check of a uniqueness constraint (for both INSERT and UPDATE).
+ * ----------
+ */
+ static Datum
+ unique_key_check(PG_FUNCTION_ARGS)
+ {
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+ HeapTuple new_row;
+ Buffer new_row_buf;
+ Relation indexRel;
+ IndexInfo *indexInfo;
+ EState *estate;
+ ExprContext *econtext;
+ TupleTableSlot *slot;
+ Datum values[INDEX_MAX_KEYS];
+ bool isnull[INDEX_MAX_KEYS];
+
+ /*
+ * Get the new data that was inserted/updated.
+ */
+ if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ {
+ new_row = trigdata->tg_newtuple;
+ new_row_buf = trigdata->tg_newtuplebuf;
+ }
+ else
+ {
+ new_row = trigdata->tg_trigtuple;
+ new_row_buf = trigdata->tg_trigtuplebuf;
+ }
+
+ Assert(new_row_buf != InvalidBuffer);
+
+ /*
+ * Open the unique index, acquiring a RowExclusiveLock, as if we were
+ * going to update it. This does not actually protect us against
+ * concurrent insertions - that is done by the index itself which
+ * acquires a write lock on the target page during the uniqueness check.
+ */
+ indexRel = index_open(trigdata->tg_trigger->tgconstrrelid,
+ RowExclusiveLock);
+ indexInfo = BuildIndexInfo(indexRel);
+
+ /*
+ * Typically the index won't have expressions, but if it does we need
+ * an EState to evaluate them. Also need a slot for the tuple we are
+ * going to test.
+ */
+ if (indexInfo->ii_Expressions != NIL)
+ {
+ estate = CreateExecutorState();
+ econtext = GetPerTupleExprContext(estate);
+ }
+ else
+ {
+ estate = NULL;
+ econtext = NULL;
+ }
+
+ slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation));
+
+ if (econtext != NULL)
+ {
+ econtext->ecxt_scantuple = slot;
+ MemoryContextReset(econtext->ecxt_per_tuple_memory);
+ }
+
+ ExecStoreTuple(new_row, slot, InvalidBuffer, false);
+
+ /*
+ * Form the index values and is null flags for the index entry that
+ * we need to check.
+ */
+ 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.
+ */
+ ExecDropSingleTupleTableSlot(slot);
+
+ index_close(indexRel, RowExclusiveLock);
+
+ if (estate != NULL)
+ FreeExecutorState(estate);
+
+ return PointerGetDatum(NULL);
+ }
+
+
+ /* ----------
+ * unique_key_check_ins -
+ *
+ * Check uniqueness constraint at insert event.
+ *
+ * This applies only to UNIQUE constraints marked DEFERRABLE.
+ * ----------
+ */
+ Datum
+ unique_key_check_ins(PG_FUNCTION_ARGS)
+ {
+ /*
+ * Check that this is a valid trigger call on the right time and event.
+ */
+ ri_CheckTrigger(fcinfo, "unique_key_check_ins", RI_TRIGTYPE_INSERT);
+
+ return unique_key_check(fcinfo);
+ }
+
+
+ /* ----------
+ * unique_key_check_upd -
+ *
+ * Check uniquesness constraint at update event.
+ *
+ * This applies only to UNIQUE constraints marked DEFERRABLE.
+ * ----------
+ */
+ Datum
+ unique_key_check_upd(PG_FUNCTION_ARGS)
+ {
+ /*
+ * Check that this is a valid trigger call on the right time and event.
+ */
+ ri_CheckTrigger(fcinfo, "unique_key_check_upd", RI_TRIGTYPE_UPDATE);
+
+ return unique_key_check(fcinfo);
+ }
*** ./src/backend/utils/adt/ruleutils.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/utils/adt/ruleutils.c 2009-07-04 18:03:05.000000000 +0100
***************
*** 1096,1101 ****
--- 1096,1106 ----
quote_identifier(get_tablespace_name(tblspc)));
}
+ if (conForm->condeferrable)
+ appendStringInfo(&buf, " DEFERRABLE");
+ if (conForm->condeferred)
+ appendStringInfo(&buf, " INITIALLY DEFERRED");
+
break;
}
case CONSTRAINT_CHECK:
*** ./src/bin/pg_dump/pg_dump.c.orig 2009-07-09 10:15:59.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.c 2009-07-09 12:31:14.000000000 +0100
***************
*** 3610,3616 ****
i_contableoid,
i_conoid,
i_tablespace,
! i_options;
int ntups;
for (i = 0; i < numTables; i++)
--- 3610,3618 ----
i_contableoid,
i_conoid,
i_tablespace,
! i_options,
! i_condeferrable,
! i_coninitdeferred;
int ntups;
for (i = 0; i < numTables; i++)
***************
*** 3651,3657 ****
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "array_to_string(t.reloptions, ', ') AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--- 3653,3661 ----
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "array_to_string(t.reloptions, ', ') AS options, "
! "c.condeferrable AS condeferrable, "
! "c.condeferred AS coninitdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
***************
*** 3677,3683 ****
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "null AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--- 3681,3689 ----
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
***************
*** 3703,3709 ****
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--- 3709,3717 ----
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
***************
*** 3731,3737 ****
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
--- 3739,3747 ----
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
***************
*** 3754,3760 ****
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
--- 3764,3772 ----
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
***************
*** 3780,3785 ****
--- 3792,3799 ----
i_conoid = PQfnumber(res, "conoid");
i_tablespace = PQfnumber(res, "tablespace");
i_options = PQfnumber(res, "options");
+ i_condeferrable = PQfnumber(res, "condeferrable");
+ i_coninitdeferred = PQfnumber(res, "coninitdeferred");
indxinfo = (IndxInfo *) malloc(ntups * sizeof(IndxInfo));
constrinfo = (ConstraintInfo *) malloc(ntups * sizeof(ConstraintInfo));
***************
*** 3838,3843 ****
--- 3852,3859 ----
constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
+ constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
+ constrinfo[j].coninitdeferred = *(PQgetvalue(res, j, i_coninitdeferred)) == 't';
indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
***************
*** 3942,3947 ****
--- 3958,3965 ----
constrinfo[j].conindex = 0;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
+ constrinfo[j].condeferrable = false; /* Part of condef */
+ constrinfo[j].coninitdeferred = false; /* Part of condef */
}
PQclear(res);
***************
*** 4026,4031 ****
--- 4044,4051 ----
constrinfo[i].conindex = 0;
constrinfo[i].conislocal = true;
constrinfo[i].separate = false;
+ constrinfo[i].condeferrable = false;
+ constrinfo[i].coninitdeferred = false;
/*
* Make the domain depend on the constraint, ensuring it won't be
***************
*** 5025,5030 ****
--- 5045,5052 ----
constrs[j].conindex = 0;
constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
constrs[j].separate = false;
+ constrs[j].condeferrable = false;
+ constrs[j].coninitdeferred = false;
constrs[j].dobj.dump = tbinfo->dobj.dump;
***************
*** 10293,10298 ****
--- 10315,10327 ----
if (indxinfo->options && strlen(indxinfo->options) > 0)
appendPQExpBuffer(q, " WITH (%s)", indxinfo->options);
+ if (coninfo->condeferrable)
+ {
+ appendPQExpBuffer(q, " DEFERRABLE");
+ if (coninfo->coninitdeferred)
+ appendPQExpBuffer(q, " INITIALLY DEFERRED");
+ }
+
appendPQExpBuffer(q, ";\n");
/* If the index is clustered, we need to record that. */
*** ./src/bin/pg_dump/pg_dump.h.orig 2009-07-09 10:15:54.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.h 2009-07-09 10:21:12.000000000 +0100
***************
*** 344,349 ****
--- 344,351 ----
DumpId conindex; /* identifies associated index if any */
bool conislocal; /* TRUE if constraint has local definition */
bool separate; /* TRUE if must dump as separate item */
+ bool condeferrable; /* TRUE if constraint is DEFERRABLE */
+ bool coninitdeferred; /* TRUE if constraint is INITIALLY DEFERRED */
} ConstraintInfo;
typedef struct _procLangInfo
*** ./src/bin/psql/describe.c.orig 2009-07-11 12:59:37.000000000 +0100
--- ./src/bin/psql/describe.c 2009-07-11 13:07:18.000000000 +0100
***************
*** 1331,1336 ****
--- 1331,1347 ----
appendPQExpBuffer(&buf, "i.indisvalid, ");
else
appendPQExpBuffer(&buf, "true as indisvalid, ");
+ if (pset.sversion >= 80500)
+ appendPQExpBuffer(&buf,
+ "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE d.objid = i.indexrelid "
+ "AND con.oid = d.refobjid AND con.condeferrable) "
+ "as condeferrable, EXISTS (SELECT 1 FROM "
+ "pg_catalog.pg_depend d, pg_catalog.pg_constraint con "
+ "WHERE d.objid = i.indexrelid AND con.oid = d.refobjid "
+ "AND con.condeferred) as condeferred, ");
+ else
+ appendPQExpBuffer(&buf, "false as condeferrable, false as condeferred, ");
appendPQExpBuffer(&buf, "a.amname, c2.relname,\n"
" 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"
***************
*** 1352,1360 ****
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);
if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, "));
--- 1363,1373 ----
char *indisprimary = PQgetvalue(result, 0, 1);
char *indisclustered = PQgetvalue(result, 0, 2);
char *indisvalid = PQgetvalue(result, 0, 3);
! 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, "));
***************
*** 1377,1382 ****
--- 1390,1401 ----
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);
***************
*** 1438,1443 ****
--- 1457,1473 ----
else
appendPQExpBuffer(&buf, "true as indisvalid, ");
appendPQExpBuffer(&buf, "pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)");
+ if (pset.sversion >= 80500)
+ appendPQExpBuffer(&buf,
+ ", EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE d.objid = i.indexrelid "
+ "AND con.oid = d.refobjid AND con.condeferrable) "
+ "as condeferrable, EXISTS (SELECT 1 FROM "
+ "pg_catalog.pg_depend d, pg_catalog.pg_constraint con "
+ "WHERE d.objid = i.indexrelid AND con.oid = d.refobjid "
+ "AND con.condeferred) as condeferred");
+ else
+ appendPQExpBuffer(&buf, ", false as condeferrable, false as condeferred");
if (pset.sversion >= 80000)
appendPQExpBuffer(&buf, ", c2.reltablespace");
appendPQExpBuffer(&buf,
***************
*** 1484,1495 ****
if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
appendPQExpBuffer(&buf, " INVALID");
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)),
false);
}
}
--- 1514,1531 ----
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, 8)),
false);
}
}
*** ./src/include/access/genam.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/access/genam.h 2009-07-04 16:25:47.000000000 +0100
***************
*** 85,90 ****
--- 85,118 ----
typedef struct IndexScanDescData *IndexScanDesc;
typedef struct SysScanDescData *SysScanDesc;
+ /*
+ * Enumeration specifying the type of uniqueness check to perform for
+ * index_insert().
+ *
+ * For deferrable unique constraints, UNIQUE_CHECK_PARTIAL is used at
+ * insertion time. This tests if the tuple is unique but doesn't error,
+ * block or prevent the insertion if the tuple appears not to be unique.
+ * Since it isn't blocking, it can't know for sure if the tuple is not
+ * unique (the other transaction might be rolled back). The return value
+ * of index_insert() in this case is true if the tuple is definitely
+ * unique and false if it is possibly non-unique.
+ *
+ * UNIQUE_CHECK_EXISTING is for deferred unqiue constraint checks. The
+ * tuple is already in the index when the check is performed, so it is
+ * not inserted again. This just checks if the tuple in the index is
+ * unique.
+ */
+ typedef enum IndexUniqueCheck
+ {
+ UNIQUE_CHECK_NO, /* Don't do any uniqueness checking */
+ UNIQUE_CHECK_YES, /* Enforce uniqueness at insertion time */
+ /* (possibly blocking on another xact) */
+ UNIQUE_CHECK_PARTIAL, /* Test uniqueness, but don't block and */
+ /* no error if potentially not unique */
+ UNIQUE_CHECK_EXISTING /* Don't do the insert, just check if */
+ /* the existing tuple is unique */
+ } IndexUniqueCheck;
+
/*
* generalized index_ interface routines (in indexam.c)
***************
*** 103,109 ****
Datum *values, bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! bool check_uniqueness);
extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
--- 131,137 ----
Datum *values, bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! IndexUniqueCheck check_uniqueness);
extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
*** ./src/include/access/nbtree.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/access/nbtree.h 2009-07-04 16:28:12.000000000 +0100
***************
*** 517,524 ****
/*
* prototypes for functions in nbtinsert.c
*/
! extern void _bt_doinsert(Relation rel, IndexTuple itup,
! bool index_is_unique, 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);
--- 517,524 ----
/*
* prototypes for functions in nbtinsert.c
*/
! extern bool _bt_doinsert(Relation rel, IndexTuple itup,
! IndexUniqueCheck unique_check, 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);
*** ./src/include/catalog/pg_proc.h.orig 2009-07-11 13:12:26.000000000 +0100
--- ./src/include/catalog/pg_proc.h 2009-07-11 13:13:47.000000000 +0100
***************
*** 2349,2354 ****
--- 2349,2360 ----
DATA(insert OID = 1655 ( RI_FKey_noaction_upd PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ RI_FKey_noaction_upd _null_ _null_ _null_ ));
DESCR("referential integrity ON UPDATE NO ACTION");
+ /* Deferrable unique constraint triggers */
+ DATA(insert OID = 1029 ( unique_key_check_ins PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_check_ins _null_ _null_ _null_ ));
+ DESCR("deferred check UNIQUE ... DEFERRABLE");
+ DATA(insert OID = 1030 ( unique_key_check_upd PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_check_upd _null_ _null_ _null_ ));
+ DESCR("deferred check UNIQUE ... DEFERRABLE");
+
DATA(insert OID = 1666 ( varbiteq PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "1562 1562" _null_ _null_ _null_ _null_ biteq _null_ _null_ _null_ ));
DESCR("equal");
DATA(insert OID = 1667 ( varbitne PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "1562 1562" _null_ _null_ _null_ _null_ bitne _null_ _null_ _null_ ));
*** ./src/include/commands/defrem.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/commands/defrem.h 2009-07-04 16:48:30.000000000 +0100
***************
*** 29,34 ****
--- 29,36 ----
bool unique,
bool primary,
bool isconstraint,
+ bool constrDeferrable,
+ bool constrInitDeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
*** ./src/include/commands/trigger.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/commands/trigger.h 2009-07-04 16:46:36.000000000 +0100
***************
*** 131,137 ****
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
! HeapTuple trigtuple);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
--- 131,138 ----
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
! HeapTuple trigtuple,
! List *problemIndexList);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
***************
*** 153,159 ****
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
! HeapTuple newtuple);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
--- 154,161 ----
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
! HeapTuple newtuple,
! List *problemIndexList);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
*** ./src/include/executor/executor.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/executor/executor.h 2009-07-04 16:32:11.000000000 +0100
***************
*** 301,307 ****
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern void ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum);
extern void RegisterExprContextCallback(ExprContext *econtext,
--- 301,307 ----
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum);
extern void RegisterExprContextCallback(ExprContext *econtext,
*** ./src/include/nodes/execnodes.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/nodes/execnodes.h 2009-07-04 16:34:51.000000000 +0100
***************
*** 44,49 ****
--- 44,52 ----
* ReadyForInserts is it valid for inserts?
* Concurrent are we doing a concurrent index build?
* BrokenHotChain did we detect any broken HOT chains?
+ * ConstraintId OID of constraint associated with this index
+ * Deferrable is the constraint DEFERRABLE?
+ * InitDeferred is the constraint INITIALLY DEFERRED?
*
* ii_Concurrent and ii_BrokenHotChain are used only during index build;
* they're conventionally set to false otherwise.
***************
*** 62,67 ****
--- 65,73 ----
bool ii_ReadyForInserts;
bool ii_Concurrent;
bool ii_BrokenHotChain;
+ Oid ii_ConstraintId;
+ bool ii_Deferrable;
+ bool ii_InitDeferred;
} IndexInfo;
/* ----------------
*** ./src/include/nodes/parsenodes.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/nodes/parsenodes.h 2009-07-04 16:40:06.000000000 +0100
***************
*** 1354,1360 ****
* 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).
* ----------
*/
--- 1354,1360 ----
* 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
! * Constraint and FkConstraint nodes.
* ----------
*/
***************
*** 1384,1389 ****
--- 1384,1391 ----
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;
/* ----------
***************
*** 1863,1868 ****
--- 1865,1872 ----
bool unique; /* is index unique? */
bool primary; /* is index on primary key? */
bool isconstraint; /* is it from a CONSTRAINT clause? */
+ bool constrDeferrable; /* is the constraint DEFERRABLE? */
+ bool constrInitDeferred; /* is the constraint INITIALLY DEFERRED? */
bool concurrent; /* should this be a concurrent index build? */
} IndexStmt;
*** ./src/include/utils/builtins.h.orig 2009-07-11 13:15:31.000000000 +0100
--- ./src/include/utils/builtins.h 2009-07-11 13:22:16.000000000 +0100
***************
*** 932,937 ****
--- 932,939 ----
extern Datum RI_FKey_setnull_upd(PG_FUNCTION_ARGS);
extern Datum RI_FKey_setdefault_del(PG_FUNCTION_ARGS);
extern Datum RI_FKey_setdefault_upd(PG_FUNCTION_ARGS);
+ extern Datum unique_key_check_ins(PG_FUNCTION_ARGS);
+ extern Datum unique_key_check_upd(PG_FUNCTION_ARGS);
/* trigfuncs.c */
extern Datum suppress_redundant_updates_trigger(PG_FUNCTION_ARGS);
*** ./src/test/regress/input/constraints.source.orig 2009-07-11 18:17:41.000000000 +0100
--- ./src/test/regress/input/constraints.source 2009-07-11 18:59:44.000000000 +0100
***************
*** 259,261 ****
--- 259,345 ----
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');
+
+ -- default is immediate so this should fail
+ UPDATE UNIQUE_TBL SET i = 1 WHERE i = 0;
+
+ -- check is done at end of statement, so this should succeed
+ UPDATE UNIQUE_TBL SET i = i+1;
+
+ -- 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 '' AS five, * FROM UNIQUE_TBL;
+
+ -- 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 '' AS five, * 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 which 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
+
+ DROP TABLE UNIQUE_TBL;
+
*** ./src/test/regress/output/constraints.source.orig 2009-07-11 19:07:40.000000000 +0100
--- ./src/test/regress/output/constraints.source 2009-07-11 19:08:46.000000000 +0100
***************
*** 375,377 ****
--- 375,457 ----
(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');
+ -- default is immediate so this should fail
+ UPDATE UNIQUE_TBL SET i = 1 WHERE i = 0;
+ ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+ -- check is done at end of statement, so this should succeed
+ UPDATE UNIQUE_TBL SET i = i+1;
+ -- 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 '' AS five, * FROM UNIQUE_TBL;
+ five | i | t
+ ------+---+-------
+ | 1 | one
+ | 2 | two
+ | 4 | four
+ | 5 | five
+ | 3 | three
+ (5 rows)
+
+ -- 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 '' AS five, * FROM UNIQUE_TBL;
+ five | 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 which 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"
+ DROP TABLE UNIQUE_TBL;
On Sun, 2009-07-12 at 14:14 +0100, Dean Rasheed wrote:
Here is an updated version of this patch which should apply to HEAD,
with updated docs, regression tests, pg_dump and psql \d.It works well for small numbers of temporary uniqueness violations,
and at least as well (in fact about twice as fast) as deferred FK
checks for large numbers of deferred checks.
I took a brief look at this. You're extending the index AM, and that
might not be necessary. It might be fine, but usually there is a lot of
discussion around the changing of important APIs, so it might be worth
looking at alternatives.
With the patch I'm working on for generalized index constraints, there
would be no need to extend the index AM. However, I don't expect my
mechanism to replace the existing unique btree constraints, because I
would expect the existing unique constraints to be faster (I haven't
tested yet, though).
Perhaps we could instead use the TRY/CATCH mechanism. It's generally
difficult to figure out from the code exactly what happened, but in this
case we have the error code ERRCODE_UNIQUE_VIOLATION. So, just check for
that error code rather than passing back a boolean. You might want to
change the signature of _bt_check_unique() so that it doesn't have to
raise the error inside, and you can raise the error from _bt_doinsert().
The only problem there is telling the btree AM whether or not to do the
insert or not (i.e. fake versus real insert). Perhaps you can just do
that with careful use of a global variable?
Sure, all of this is a little ugly, but we've already acknowledged that
there is some ugliness around the existing unique constraint and the
btree code that supports it (for one, the btree AM accesses the heap).
I propose trying to improve performance and scalability for large
numbers of deferred checks in a separate patch.
Would it be possible to just check how long the list of potential
conflicts is growing, and if it gets to big, just replace them all with
a "bulk check" event?
Regards,
Jeff Davis
On Tue, Jul 14, 2009 at 09:56:48AM -0700, Jeff Davis wrote:
On Sun, 2009-07-12 at 14:14 +0100, Dean Rasheed wrote:
Here is an updated version of this patch which should apply to HEAD,
with updated docs, regression tests, pg_dump and psql \d.It works well for small numbers of temporary uniqueness violations,
and at least as well (in fact about twice as fast) as deferred FK
checks for large numbers of deferred checks.I took a brief look at this. You're extending the index AM, and that
might not be necessary. It might be fine, but usually there is a lot of
discussion around the changing of important APIs, so it might be worth
looking at alternatives.With the patch I'm working on for generalized index constraints, there
would be no need to extend the index AM. However, I don't expect my
mechanism to replace the existing unique btree constraints, because I
would expect the existing unique constraints to be faster (I haven't
tested yet, though).Perhaps we could instead use the TRY/CATCH mechanism. It's generally
difficult to figure out from the code exactly what happened, but in this
case we have the error code ERRCODE_UNIQUE_VIOLATION. So, just check for
that error code rather than passing back a boolean. You might want to
change the signature of _bt_check_unique() so that it doesn't have to
raise the error inside, and you can raise the error from _bt_doinsert().The only problem there is telling the btree AM whether or not to do the
insert or not (i.e. fake versus real insert). Perhaps you can just do
that with careful use of a global variable?Sure, all of this is a little ugly, but we've already acknowledged that
there is some ugliness around the existing unique constraint and the
btree code that supports it (for one, the btree AM accesses the heap).
I am looking at adding unique support to hash indexes for 8.5 and
they will definitely need to visit the heap.
Regards,
Ken
Show quoted text
I propose trying to improve performance and scalability for large
numbers of deferred checks in a separate patch.Would it be possible to just check how long the list of potential
conflicts is growing, and if it gets to big, just replace them all with
a "bulk check" event?Regards,
Jeff Davis
Jeff Davis wrote:
The only problem there is telling the btree AM whether or not to do the
insert or not (i.e. fake versus real insert). Perhaps you can just do
that with careful use of a global variable?Sure, all of this is a little ugly, but we've already acknowledged that
there is some ugliness around the existing unique constraint and the
btree code that supports it (for one, the btree AM accesses the heap).
My 2c on this issue: if this is ugly (and it is) and needs revisiting to
extend it, please by all means let's make it not ugly instead of moving
the ugliness around. I didn't read the original proposal in detail so
IMBFOS, but it doesn't seem like using our existing deferred constraints
to handle uniqueness checks unuglifies this code enough ... For example
I think we'd like to support stuff like "UPDATE ... SET a = -a" where
the table is large.
--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.
On Tue, 2009-07-14 at 13:29 -0500, Kenneth Marshall wrote:
I am looking at adding unique support to hash indexes for 8.5 and
they will definitely need to visit the heap.
Have you seen this patch?
http://archives.postgresql.org/message-id/1246840119.19547.126.camel@jdavis
This patch will support unique constraints for hash indexes as well.
There may still be a use-case for specialized hash index unique
constraints, similar to btree, but please follow the work to make sure
that no work is wasted.
Also, I don't see a problem with using the same hacks in the hash index
code as is used in the btree index code. If you see a better way, or if
you think index AM changes would be useful to you as well, you should
probably open that discussion.
I was trying to provide an alternative to an index AM API change,
because I thought there might be some resistance to that. However, if
there are multiple index AMs that can make use of it, there is a
stronger case for an API change.
Regards,
Jeff Davis
On Tue, 2009-07-14 at 15:00 -0400, Alvaro Herrera wrote:
My 2c on this issue: if this is ugly (and it is) and needs revisiting to
extend it, please by all means let's make it not ugly instead of moving
the ugliness around. I didn't read the original proposal in detail so
IMBFOS, but it doesn't seem like using our existing deferred constraints
to handle uniqueness checks unuglifies this code enough ... For example
I think we'd like to support stuff like "UPDATE ... SET a = -a" where
the table is large.
I don't entirely understand what you're suggesting.
1. Are you saying that an AM API change is the best route? If so, we
should probably start a discussion along those lines. Heikki is already
changing the API for index-only scans, and Dean's API change proposal
may be useful for Kenneth's unique hash indexes. You might as well all
attack the current API in unison ;)
2. Even if we allow some kind of bulk constraint check to optimize the
"set a = a + 1" case, we should still allow some much cheaper mechanism
to defer retail constraint violations. For that, why not make use of the
existing constraint trigger mechanism?
Regards,
Jeff Davis
2009/7/14 Alvaro Herrera <alvherre@commandprompt.com>:
Jeff Davis wrote:
The only problem there is telling the btree AM whether or not to do the
insert or not (i.e. fake versus real insert). Perhaps you can just do
that with careful use of a global variable?Sure, all of this is a little ugly, but we've already acknowledged that
there is some ugliness around the existing unique constraint and the
btree code that supports it (for one, the btree AM accesses the heap).
Well the ugliness referred to here (btree accessing the heap) seems
like a necessary evil. I don't think I want to add to it by
introducing global variables.
My 2c on this issue: if this is ugly (and it is) and needs revisiting to
extend it, please by all means let's make it not ugly instead of moving
the ugliness around. I didn't read the original proposal in detail so
IMBFOS, but it doesn't seem like using our existing deferred constraints
to handle uniqueness checks unuglifies this code enough ... For example
I think we'd like to support stuff like "UPDATE ... SET a = -a" where
the table is large.
This patch works OK for around 1M rows. 10M is a real stretch (for me
it took around 1.7GB of backend memory). Any larger than that is not
going to be feasible. There is a separate TODO item to tackle this
scalability limit for deferred triggers, and I'd like to tackle that
in a separate patch. I think more discussion needs to be had on ways
to fix this (and hopefully unuglify that code in the process).
ITSM that it is not simply a matter of spooling the current queues to
disk. There is code in there which scans whole queues shuffling things
around. So perhaps a queue per trigger would help optimise this,
allowing us to move a whole queue cheaply, or drop it in favour of a
bulk check. I've not thought it through much more than that so far.
- Dean
Jeff Davis wrote:
1. Are you saying that an AM API change is the best route? If so, we
should probably start a discussion along those lines. Heikki is already
changing the API for index-only scans, and Dean's API change proposal
may be useful for Kenneth's unique hash indexes. You might as well all
attack the current API in unison ;)
Yeah, I don't think there's any point in keeping the API stable just for
the sake of keeping it stable. I mean surely we don't want to break it
for no reason, but if we can clean up the uniqueness check situation
somehow by breaking the API, for all means let's explore that ... (I
don't think anybody likes the way btree currently abuses the heap API;
it's just that it's so damn fast to do it that way. Certainly we don't
want to make it slower!)
2. Even if we allow some kind of bulk constraint check to optimize the
"set a = a + 1" case, we should still allow some much cheaper mechanism
to defer retail constraint violations. For that, why not make use of the
existing constraint trigger mechanism?
Sure, perhaps you're right, which is why I added the disclaimer that I
hadn't actually read the patch in any detail ...
--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
On Tue, 2009-07-14 at 20:32 +0100, Dean Rasheed wrote:
Well the ugliness referred to here (btree accessing the heap) seems
like a necessary evil. I don't think I want to add to it by
introducing global variables.
Ok, try to coordinate with Kenneth to make sure that the API change
satisfies deferrable uniques for both btree and hash indexes. I don't
have a strong opinion one way or another about the API change.
Regards,
Jeff Davis
On Tue, Jul 14, 2009 at 12:13:33PM -0700, Jeff Davis wrote:
On Tue, 2009-07-14 at 13:29 -0500, Kenneth Marshall wrote:
I am looking at adding unique support to hash indexes for 8.5 and
they will definitely need to visit the heap.Have you seen this patch?
http://archives.postgresql.org/message-id/1246840119.19547.126.camel@jdavis
This patch will support unique constraints for hash indexes as well.
There may still be a use-case for specialized hash index unique
constraints, similar to btree, but please follow the work to make sure
that no work is wasted.Also, I don't see a problem with using the same hacks in the hash index
code as is used in the btree index code. If you see a better way, or if
you think index AM changes would be useful to you as well, you should
probably open that discussion.I was trying to provide an alternative to an index AM API change,
because I thought there might be some resistance to that. However, if
there are multiple index AMs that can make use of it, there is a
stronger case for an API change.Regards,
Jeff Davis
I will take a look at that patch. My thought was to use the same
process as the btree support for unique indexes since it has been
well tested and optimized.
Thanks,
Ken
Review feedback:
1. Compiler warning:
index.c: In function ‘index_create’:
index.c:784: warning: implicit declaration of function ‘SystemFuncName’
2. I know that the GIN, GiST, and Hash AMs don't use the
uniqueness_check argument at all -- in fact it is #ifdef'ed out.
However, for the sake of documentation it would be good to change those
unused arguments (in, e.g., gininsert()) to be IndexUniqueCheck enums
rather than bools.
3. The unique constraint no longer waits for concurrent transactions to
finish if the unique constraint is deferrable anyway (and it's not time
to actually check the constraint yet). That makes sense, because the
whole point is to defer the constraint. However, that means there are a
few degenerate situations that were OK before, but can now get us into
trouble.
For instance, if you have a big delete and a concurrent big insert, the
current code will just block at the first conflicting tuple and wait for
the delete to finish. With deferrable constraints, it would save all of
those tuples up as potential conflicts, using a lot of memory, when it
is not strictly necessary because the tuples will be gone anyway. I'm
not particularly worried about this situation -- because I think it's a
reasonable thing to expect when using deferred constraints -- but I'd
like to bring it up.
4. Requiring DEFERRABLE after UNIQUE in order to get commands like
"UPDATE ... SET i = i + 1" to work isn't following the spec. I'm not
sure what we should do here, because the 8.4 behavior is not following
the spec at all, but people may still want it.
5. In the docs, 50.2: "This is the only situation ...": it's a little
unclear what "this" is.
6. Missing support for CREATE INDEX CONCURRENTLY is unfortunate. What
would be involved in adding support?
7. It looks like deferrable unique indexes can only be created by adding
a constraint, not as part of the CREATE INDEX syntax. One consequence is
that CIC can't be supported (right?). If you don't plan to support CIC,
then maybe that's OK.
8. Code like the following:
is_vacuum ? UNIQUE_CHECK_NO :
deferUniqueCheck ? UNIQUE_CHECK_PARTIAL :
relationDescs[i]->rd_index->indisunique ?
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
Is a little difficult to read, at least for me. It's a style thing, so
you don't have to agree with me about this.
9. Passing problemIndexList to AfterTriggerSaveEvent seems a little
awkward. I don't see an obvious way to make it cleaner, but I thought
it's worth mentioning.
10. You're overloading tgconstrrelid to hold the constraint's index's
oid, when normally it holds the referenced table. You should probably
document this a little better, because I don't think that field is used
to hold an index oid anywhere else.
The rest of the patch seems fairly complete. Tests, documentation, psql,
and pg_dump support look good. And the patch works, of course. Code and
comments look good to me as well.
I like the patch. It solves a problem, brings us closer to the SQL
standard, and the approach seems reasonable to me.
Regards,
Jeff Davis
Thanks for the thorough review. I attach an new version of the patch,
updated to HEAD, and with the index AM change discussed.
2009/7/18 Jeff Davis <pgsql@j-davis.com>:
Review feedback:
1. Compiler warning:
index.c: In function ‘index_create’:
index.c:784: warning: implicit declaration of function ‘SystemFuncName’2. I know that the GIN, GiST, and Hash AMs don't use the
uniqueness_check argument at all -- in fact it is #ifdef'ed out.
However, for the sake of documentation it would be good to change those
unused arguments (in, e.g., gininsert()) to be IndexUniqueCheck enums
rather than bools.
Fixed.
3. The unique constraint no longer waits for concurrent transactions to
finish if the unique constraint is deferrable anyway (and it's not time
to actually check the constraint yet). That makes sense, because the
whole point is to defer the constraint. However, that means there are a
few degenerate situations that were OK before, but can now get us into
trouble.For instance, if you have a big delete and a concurrent big insert, the
current code will just block at the first conflicting tuple and wait for
the delete to finish. With deferrable constraints, it would save all of
those tuples up as potential conflicts, using a lot of memory, when it
is not strictly necessary because the tuples will be gone anyway. I'm
not particularly worried about this situation -- because I think it's a
reasonable thing to expect when using deferred constraints -- but I'd
like to bring it up.4. Requiring DEFERRABLE after UNIQUE in order to get commands like
"UPDATE ... SET i = i + 1" to work isn't following the spec. I'm not
sure what we should do here, because the 8.4 behavior is not following
the spec at all, but people may still want it.
So basically NOT DEFERRABLE should behave the same as
DEFERRABLE-always-IMMEDIATE? I guess that you'd want something that
blocks at the first conflict from another transaction but otherwise
queues up potential conflicts to be checked at the end. It seems
plausible to build that on top of what I've done so far, but I'm not
brave enough to try in this patch (which is after all about implementing
deferrable).
5. In the docs, 50.2: "This is the only situation ...": it's a little
unclear what "this" is.6. Missing support for CREATE INDEX CONCURRENTLY is unfortunate. What
would be involved in adding support?
Not too hard in the btree code, just a minor reshuffle. It seems to
have no noticeable performance hit, so I've done it there, but...
7. It looks like deferrable unique indexes can only be created by adding
a constraint, not as part of the CREATE INDEX syntax. One consequence is
that CIC can't be supported (right?). If you don't plan to support CIC,
then maybe that's OK.
that's right, the current constraint syntax doesn't allow the constraint
index to be built concurrently. Perhaps in the future...
8. Code like the following:
is_vacuum ? UNIQUE_CHECK_NO :
deferUniqueCheck ? UNIQUE_CHECK_PARTIAL :
relationDescs[i]->rd_index->indisunique ?
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);Is a little difficult to read, at least for me. It's a style thing, so
you don't have to agree with me about this.
Yeah, I agree with you on that, so I've tidied it up a bit.
9. Passing problemIndexList to AfterTriggerSaveEvent seems a little
awkward. I don't see an obvious way to make it cleaner, but I thought
it's worth mentioning.10. You're overloading tgconstrrelid to hold the constraint's index's
oid, when normally it holds the referenced table. You should probably
document this a little better, because I don't think that field is used
to hold an index oid anywhere else.
Done. Thanks for the feedback.
- Dean
Show quoted text
The rest of the patch seems fairly complete. Tests, documentation, psql,
and pg_dump support look good. And the patch works, of course. Code and
comments look good to me as well.I like the patch. It solves a problem, brings us closer to the SQL
standard, and the approach seems reasonable to me.Regards,
Jeff Davis
Attachments:
deferrable_unique.patchapplication/octet-stream; name=deferrable_unique.patchDownload
*** ./doc/src/sgml/catalogs.sgml.orig 2009-07-19 14:22:11.000000000 +0100
--- ./doc/src/sgml/catalogs.sgml 2009-07-19 14:25:04.000000000 +0100
***************
*** 4507,4513 ****
<entry><structfield>tgconstrrelid</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
! <entry>The table referenced by a referential integrity constraint</entry>
</row>
<row>
--- 4507,4514 ----
<entry><structfield>tgconstrrelid</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
! <entry>The table referenced by a referential integrity constraint,
! or the index enforcing a deferrable unique constraint.</entry>
</row>
<row>
*** ./doc/src/sgml/indexam.sgml.orig 2009-07-11 22:37:18.000000000 +0100
--- ./doc/src/sgml/indexam.sgml 2009-07-19 15:12:50.000000000 +0100
***************
*** 172,191 ****
bool *isnull,
ItemPointer heap_tid,
Relation heapRelation,
! bool check_uniqueness);
</programlisting>
Insert a new tuple into an existing index. The <literal>values</> and
<literal>isnull</> arrays give the key values to be indexed, and
<literal>heap_tid</> is the TID to be indexed.
If the access method supports unique indexes (its
<structname>pg_am</>.<structfield>amcanunique</> flag is true) then
! <literal>check_uniqueness</> might be true, in which case the access method
! must verify that there is no conflicting row; this is the only situation in
! which the access method normally needs the <literal>heapRelation</>
! parameter. See <xref linkend="index-unique-checks"> for details.
! The result is TRUE if an index entry was inserted, FALSE if not. (A FALSE
! result does not denote an error condition, but is used for cases such
! as an index method refusing to index a NULL.)
</para>
<para>
--- 172,199 ----
bool *isnull,
ItemPointer heap_tid,
Relation heapRelation,
! IndexUniqueCheck uniqueness_check,
! bool *is_unique);
</programlisting>
Insert a new tuple into an existing index. The <literal>values</> and
<literal>isnull</> arrays give the key values to be indexed, and
<literal>heap_tid</> is the TID to be indexed.
If the access method supports unique indexes (its
<structname>pg_am</>.<structfield>amcanunique</> flag is true) then
! <literal>uniqueness_check</> indicates the type of uniqueness check to
! perform to verify that there is no conflicting row. The type of uniqueness
! check varies depending on whether the uniqueness constraint is deferrable;
! see <xref linkend="index-unique-checks"> for more details. Normally the
! access method only needs the <literal>heapRelation</> parameter to
! perform uniqueness checking. If uniqueness is violated, the index
! method should not insert the index entry, except in the case of a partial
! uniqueness check. The index method should set <literal>is_unique</> to TRUE
! if the new entry is definitely unique, and FALSE if it might be non-unique.
! It should not raise an error for non-unique insert attempts.
! The method return value should be set to TRUE if an index entry was
! inserted and FALSE if not. (A FALSE result does not necessarily denote an
! error condition - it is also used for cases such as an index method
! refusing to index a NULL.)
</para>
<para>
***************
*** 706,715 ****
</para>
<para>
! Furthermore, immediately before raising a uniqueness violation
according to the above rules, the access method must recheck the
liveness of the row being inserted. If it is committed dead then
! no error should be raised. (This case cannot occur during the
ordinary scenario of inserting a row that's just been created by
the current transaction. It can happen during
<command>CREATE UNIQUE INDEX CONCURRENTLY</>, however.)
--- 714,723 ----
</para>
<para>
! Furthermore, immediately before reporting a uniqueness violation
according to the above rules, the access method must recheck the
liveness of the row being inserted. If it is committed dead then
! no violation should be reported. (This case cannot occur during the
ordinary scenario of inserting a row that's just been created by
the current transaction. It can happen during
<command>CREATE UNIQUE INDEX CONCURRENTLY</>, however.)
***************
*** 728,737 ****
</para>
<para>
! The main limitation of this scheme is that it has no convenient way
! to support deferred uniqueness checks.
! </para>
</sect1>
<sect1 id="index-cost-estimation">
--- 736,803 ----
</para>
<para>
! If the unique constraint is deferrable, then the final uniqueness check
! needs to be done later - either at the end of the statement, or when the
! transaction is committed, or as part of a <command>SET CONSTRAINTS</>
! operation. This is handled by passing one of the following enumeration
! values to the index insert access method:
!
! <programlisting>
! typedef enum IndexUniqueCheck
! {
! UNIQUE_CHECK_NO,
! UNIQUE_CHECK_YES,
! UNIQUE_CHECK_PARTIAL,
! UNIQUE_CHECK_EXISTING
! } IndexUniqueCheck;
! </programlisting>
!
! <itemizedlist>
! <listitem>
! <para>
! <literal>UNQIUE_CHECK_NO</> indicates that no uniqueness checking
! need be done (this is not a unique index).
! </para>
! </listitem>
! <listitem>
! <para>
! <literal>UNIQUE_CHECK_YES</> indicates that this is a non-deferrable
! unique index, and the uniqueness check must be done immediately, as
! described above.
! </para>
! </listitem>
! <listitem>
! <para>
! <literal>UNIQUE_CHECK_PARTIAL</> indicates that the uniqueness
! constraint is deferrable. <productname>PostgreSQL</productname>
! will use this mode to insert each row's index entry; the access
! method must allow duplicate entries into the index, and report any
! potential duplicates by setting <literal>is_unique</> to false. All
! such rows will then be re-checked later when the deferred constraint
! check is triggered.
! </para>
+ <para>
+ The access method must identify any rows which might violate the
+ uniqueness constraint, but it is not an error for it to report false
+ positives. This allows the check to be done without waiting for other
+ transactions to finish; conflicts reported here are not treated as
+ errors and will be re-checked later, by which time they may no longer
+ be conflicts.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>UNIQUE_CHECK_EXISTING</> indicates that this is a deferred
+ check of a row which was reported as a potential uniqueness violation.
+ This is only used when a deferred check is triggered (after the index
+ entry has already been inserted). The access method is expected to
+ check the existing entry for uniqueness, without re-inserting it - this
+ is a read-only operation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
</sect1>
<sect1 id="index-cost-estimation">
*** ./doc/src/sgml/ref/create_table.sgml.orig 2009-07-11 22:21:03.000000000 +0100
--- ./doc/src/sgml/ref/create_table.sgml 2009-07-11 22:23:01.000000000 +0100
***************
*** 535,541 ****
deferrable can be postponed until the end of the transaction
(using the <xref linkend="sql-set-constraints" endterm="sql-set-constraints-title"> command).
<literal>NOT DEFERRABLE</literal> is the default. Only foreign
! key constraints currently accept this clause. All other
constraint types are not deferrable.
</para>
</listitem>
--- 535,541 ----
deferrable can be postponed until the end of the transaction
(using the <xref linkend="sql-set-constraints" endterm="sql-set-constraints-title"> command).
<literal>NOT DEFERRABLE</literal> is the default. Only foreign
! key and unique constraints currently accept this clause. All other
constraint types are not deferrable.
</para>
</listitem>
*** ./doc/src/sgml/ref/set_constraints.sgml.orig 2009-07-11 22:24:31.000000000 +0100
--- ./doc/src/sgml/ref/set_constraints.sgml 2009-07-11 22:27:37.000000000 +0100
***************
*** 66,73 ****
</para>
<para>
! Currently, only foreign key constraints are affected by this
! setting. Check and unique constraints are always effectively
not deferrable. Triggers that are declared as <quote>constraint
triggers</> are also affected.
</para>
--- 66,73 ----
</para>
<para>
! Currently, only foreign key and unique constraints are affected by this
! setting. Check constraints are always effectively
not deferrable. Triggers that are declared as <quote>constraint
triggers</> are also affected.
</para>
***************
*** 92,98 ****
This command complies with the behavior defined in the SQL
standard, except for the limitation that, in
<productname>PostgreSQL</productname>, it only applies to
! foreign-key constraints.
</para>
</refsect1>
--- 92,98 ----
This command complies with the behavior defined in the SQL
standard, except for the limitation that, in
<productname>PostgreSQL</productname>, it only applies to
! foreign-key and unique constraints.
</para>
</refsect1>
*** ./src/backend/access/gin/gininsert.c.orig 2009-07-19 10:34:09.000000000 +0100
--- ./src/backend/access/gin/gininsert.c 2009-07-19 10:40:12.000000000 +0100
***************
*** 415,421 ****
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! bool checkUnique = PG_GETARG_BOOL(5);
#endif
GinState ginstate;
MemoryContext oldCtx;
--- 415,422 ----
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! bool *is_unique = (bool *) PG_GETARG_POINTER(6);
#endif
GinState ginstate;
MemoryContext oldCtx;
*** ./src/backend/access/gist/gist.c.orig 2009-07-19 10:42:20.000000000 +0100
--- ./src/backend/access/gist/gist.c 2009-07-19 10:42:58.000000000 +0100
***************
*** 225,231 ****
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! bool checkUnique = PG_GETARG_BOOL(5);
#endif
IndexTuple itup;
GISTSTATE giststate;
--- 225,232 ----
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! bool *is_unique = (bool *) PG_GETARG_POINTER(6);
#endif
IndexTuple itup;
GISTSTATE giststate;
*** ./src/backend/access/hash/hash.c.orig 2009-07-19 10:39:43.000000000 +0100
--- ./src/backend/access/hash/hash.c 2009-07-19 10:41:29.000000000 +0100
***************
*** 165,171 ****
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! bool checkUnique = PG_GETARG_BOOL(5);
#endif
IndexTuple itup;
--- 165,172 ----
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! bool *is_unique = (bool *) PG_GETARG_POINTER(6);
#endif
IndexTuple itup;
*** ./src/backend/access/heap/tuptoaster.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/heap/tuptoaster.c 2009-07-18 12:02:58.000000000 +0100
***************
*** 1225,1231 ****
*/
index_insert(toastidx, t_values, t_isnull,
&(toasttup->t_self),
! toastrel, toastidx->rd_index->indisunique);
/*
* Free memory
--- 1225,1234 ----
*/
index_insert(toastidx, t_values, t_isnull,
&(toasttup->t_self),
! toastrel,
! toastidx->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
! NULL);
/*
* Free memory
*** ./src/backend/access/index/indexam.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/index/indexam.c 2009-07-18 12:37:35.000000000 +0100
***************
*** 185,193 ****
bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! bool check_uniqueness)
{
FmgrInfo *procedure;
RELATION_CHECKS;
GET_REL_PROCEDURE(aminsert);
--- 185,195 ----
bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! IndexUniqueCheck uniqueness_check,
! bool *is_unique)
{
FmgrInfo *procedure;
+ bool unique_result, result;
RELATION_CHECKS;
GET_REL_PROCEDURE(aminsert);
***************
*** 195,207 ****
/*
* have the am's insert proc do all the work.
*/
! return DatumGetBool(FunctionCall6(procedure,
! PointerGetDatum(indexRelation),
! PointerGetDatum(values),
! PointerGetDatum(isnull),
! PointerGetDatum(heap_t_ctid),
! PointerGetDatum(heapRelation),
! BoolGetDatum(check_uniqueness)));
}
/*
--- 197,229 ----
/*
* have the am's insert proc do all the work.
*/
! result = DatumGetBool(FunctionCall7(procedure,
! PointerGetDatum(indexRelation),
! PointerGetDatum(values),
! PointerGetDatum(isnull),
! PointerGetDatum(heap_t_ctid),
! PointerGetDatum(heapRelation),
! Int32GetDatum((int32) uniqueness_check),
! PointerGetDatum(&unique_result)));
!
! /*
! * check the uniqueness result if necessary. We deliberately
! * allow non-unique entries into the index for a partial check,
! * which is used to support a deferrable unique constraint.
! */
! if ( (uniqueness_check == UNIQUE_CHECK_YES ||
! uniqueness_check == UNIQUE_CHECK_EXISTING) && !unique_result )
! {
! ereport(ERROR,
! (errcode(ERRCODE_UNIQUE_VIOLATION),
! errmsg("duplicate key value violates unique constraint \"%s\"",
! RelationGetRelationName(indexRelation))));
! }
!
! if (is_unique)
! *is_unique = unique_result;
!
! return result;
}
/*
*** ./src/backend/access/nbtree/nbtinsert.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/nbtree/nbtinsert.c 2009-07-19 14:39:19.000000000 +0100
***************
*** 50,56 ****
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
Relation heapRel, Buffer buf, OffsetNumber ioffset,
! ScanKey itup_scankey);
static void _bt_findinsertloc(Relation rel,
Buffer *bufptr,
OffsetNumber *offsetptr,
--- 50,57 ----
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
Relation heapRel, Buffer buf, OffsetNumber ioffset,
! ScanKey itup_scankey, IndexUniqueCheck unique_check,
! bool *is_unique);
static void _bt_findinsertloc(Relation rel,
Buffer *bufptr,
OffsetNumber *offsetptr,
***************
*** 85,95 ****
*
* This routine is called by the public interface routines, btbuild
* and btinsert. By here, itup is filled in, including the TID.
*/
! void
_bt_doinsert(Relation rel, IndexTuple itup,
! bool index_is_unique, Relation heapRel)
{
int natts = rel->rd_rel->relnatts;
ScanKey itup_scankey;
BTStack stack;
--- 86,107 ----
*
* This routine is called by the public interface routines, btbuild
* and btinsert. By here, itup is filled in, including the TID.
+ *
+ * If unique_check is UNIQUE_CHECK_NO or UNIQUE_CHECK_PARTIAL, this
+ * will allow duplicates and set is_unique to false if the entry is
+ * potentially non-unique.
+ *
+ * Otherwise (UNIQUE_CHECK_YES or UNIQUE_CHECK_EXISTING) it will not
+ * allow duplicates. The attempt to insert a duplicate will not raise
+ * an error, but is_unique will be set the false. The caller is
+ * expected to check this.
*/
! bool
_bt_doinsert(Relation rel, IndexTuple itup,
! IndexUniqueCheck unique_check, Relation heapRel,
! bool *is_unique)
{
+ bool do_insert = unique_check != UNIQUE_CHECK_EXISTING;
int natts = rel->rd_rel->relnatts;
ScanKey itup_scankey;
BTStack stack;
***************
*** 134,165 ****
*
* If we must wait for another xact, we release the lock while waiting,
* and then must start over completely.
*/
! if (index_is_unique)
{
TransactionId xwait;
offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
! xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey);
! if (TransactionIdIsValid(xwait))
{
! /* Have to wait for the other guy ... */
! _bt_relbuf(rel, buf);
! XactLockTableWait(xwait);
! /* start over... */
! _bt_freestack(stack);
! goto top;
}
}
! /* do the insertion */
! _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
! _bt_insertonpg(rel, buf, stack, itup, offset, false);
/* be tidy */
_bt_freestack(stack);
_bt_freeskey(itup_scankey);
}
/*
--- 146,205 ----
*
* If we must wait for another xact, we release the lock while waiting,
* and then must start over completely.
+ *
+ * For a partial uniqueness check, 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 (unique_check != 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,
! unique_check, is_unique);
! if (!*is_unique)
{
! if (TransactionIdIsValid(xwait))
! {
! /* Have to wait for the other guy ... */
! _bt_relbuf(rel, buf);
! XactLockTableWait(xwait);
! /* start over... */
! _bt_freestack(stack);
! goto top;
! }
!
! /*
! * If we only did a partial uniqueness check, then this is
! * only a potential conflict; let the entry in. Otherwise
! * it is a real conflict, so don't add it to the index.
! */
! if (unique_check != UNIQUE_CHECK_PARTIAL)
! do_insert = false;
}
}
+ else
+ *is_unique = false;
! if (do_insert)
! {
! /* 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 do_insert;
}
/*
***************
*** 169,182 ****
* also point to end-of-page, which means that the first tuple to check
* is the first tuple on the next page.
*
! * 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().
*/
static TransactionId
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
! Buffer buf, OffsetNumber offset, ScanKey itup_scankey)
{
TupleDesc itupdesc = RelationGetDescr(rel);
int natts = rel->rd_rel->relnatts;
SnapshotData SnapshotDirty;
--- 209,230 ----
* also point to end-of-page, which means that the first tuple to check
* is the first tuple on the next page.
*
! * This only sets is_unique to true if the entry is definitely unique.
! * Otherwise the entry is a definite or potential conflict, and the
! * return value is the xact ID we have to wait on to find out for sure.
! *
! * The return value will be InvalidTransactionId if the entry is
! * definitely unique or non-unique, or if we are only doing a partial
! * check. For a partial unique check, _bt_doinsert() will not block,
! * but just go ahead with the insert. The proper check will be done later
! * (at the end of the statement or transaction).
*/
static TransactionId
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
! Buffer buf, OffsetNumber offset, ScanKey itup_scankey,
! IndexUniqueCheck unique_check, bool *is_unique)
{
+ TransactionId xwait = InvalidTransactionId;
TupleDesc itupdesc = RelationGetDescr(rel);
int natts = rel->rd_rel->relnatts;
SnapshotData SnapshotDirty;
***************
*** 184,189 ****
--- 232,238 ----
Page page;
BTPageOpaque opaque;
Buffer nbuf = InvalidBuffer;
+ bool found = false;
InitDirtySnapshot(SnapshotDirty);
***************
*** 191,196 ****
--- 240,248 ----
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
maxoff = PageGetMaxOffsetNumber(page);
+ /* Assume unique unless we find a duplicate */
+ *is_unique = true;
+
/*
* Scan over all equal tuples, looking for live conflicts.
*/
***************
*** 247,263 ****
*/
if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
{
! /* it is a duplicate */
! TransactionId xwait =
! (TransactionIdIsValid(SnapshotDirty.xmin)) ?
! SnapshotDirty.xmin : SnapshotDirty.xmax;
/*
! * If this tuple is being updated by other transaction
! * then we have to wait for its commit/abort.
*/
! if (TransactionIdIsValid(xwait))
{
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
/* Tell _bt_doinsert to wait... */
--- 299,353 ----
*/
if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
{
! /*
! * We have 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. This is used for deferrable
! * unique constraints.
! */
! if (unique_check == UNIQUE_CHECK_PARTIAL)
! {
! *is_unique = false;
! if (nbuf != InvalidBuffer)
! _bt_relbuf(rel, nbuf);
! return InvalidTransactionId;
! }
/*
! * Otherwise we need to do a proper check to see if
! * it really is a duplicate. First check if it is
! * being updated by another transaction, in which case
! * we may have to wait for it's commit/abort.
*/
! if (TransactionIdIsValid(SnapshotDirty.xmin) ||
! TransactionIdIsValid(SnapshotDirty.xmax))
{
+ xwait = TransactionIdIsValid(SnapshotDirty.xmin) ?
+ SnapshotDirty.xmin : SnapshotDirty.xmax;
+
+ /*
+ * If this is a deferred uniqueness check, of a
+ * tuple that we have already inserted, then there
+ * may be other conflicting tuples which we can
+ * see without waiting, so reset the snapshot and
+ * keep looking, but remember that we may need to
+ * wait on this one after all.
+ */
+ if (unique_check == UNIQUE_CHECK_EXISTING)
+ {
+ SnapshotDirty.xmin = InvalidTransactionId;
+ SnapshotDirty.xmax = InvalidTransactionId;
+
+ goto next_tuple;
+ }
+
+ /*
+ * Otherwise return the other transaction ID
+ * that we have to wait for.
+ */
+ *is_unique = false;
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
/* Tell _bt_doinsert to wait... */
***************
*** 265,271 ****
}
/*
! * Otherwise we have a definite conflict. But before
* complaining, look to see if the tuple we want to insert
* is itself now committed dead --- if so, don't complain.
* This is a waste of time in normal scenarios but we must
--- 355,361 ----
}
/*
! * This appears to be a definite conflict. But before
* complaining, look to see if the tuple we want to insert
* is itself now committed dead --- if so, don't complain.
* This is a waste of time in normal scenarios but we must
***************
*** 289,304 ****
else
{
/*
! * It's been deleted, so no error, and no need to
! * continue searching
*/
break;
}
! ereport(ERROR,
! (errcode(ERRCODE_UNIQUE_VIOLATION),
! errmsg("duplicate key value violates unique constraint \"%s\"",
! RelationGetRelationName(rel))));
}
else if (all_dead)
{
--- 379,415 ----
else
{
/*
! * It's been deleted, so no error. If this is a
! * deferred check, there may be other duplicates which
! * are live.
*/
+ if (unique_check == UNIQUE_CHECK_EXISTING)
+ goto next_tuple;
+
+ /* Otherwise, no need to continue searching */
break;
}
! /*
! * We have a definite match. If this is a deferred check,
! * of a tuple that we have already inserted, the first
! * match is not a conflict, so look for another. This
! * is used for the final check of a deferrable uniquenes
! * constraint.
! */
! if (unique_check == UNIQUE_CHECK_EXISTING && !found)
! {
! found = true;
! goto next_tuple;
! }
!
! /*
! * This is a definite conflict.
! */
! *is_unique = false;
! if (nbuf != InvalidBuffer)
! _bt_relbuf(rel, nbuf);
! return InvalidTransactionId;
}
else if (all_dead)
{
***************
*** 318,323 ****
--- 429,435 ----
}
}
+ next_tuple:
/*
* Advance to next tuple to continue checking.
*/
***************
*** 352,357 ****
--- 464,481 ----
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
+ /*
+ * If we reach here, then we didn't find a duplicate. However, in a
+ * deferred uniqueness check, we may have found 1 visible tuple plus
+ * another being updated in another transaction, in which case we need
+ * to wait for it's commit/abort.
+ */
+ if (found && TransactionIdIsValid(xwait))
+ {
+ *is_unique = false;
+ return xwait;
+ }
+
return InvalidTransactionId;
}
*** ./src/backend/access/nbtree/nbtree.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/nbtree/nbtree.c 2009-07-18 11:29:54.000000000 +0100
***************
*** 212,234 ****
Datum
btinsert(PG_FUNCTION_ARGS)
{
! Relation rel = (Relation) PG_GETARG_POINTER(0);
! Datum *values = (Datum *) PG_GETARG_POINTER(1);
! 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);
! 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);
pfree(itup);
! PG_RETURN_BOOL(true);
}
/*
--- 212,236 ----
Datum
btinsert(PG_FUNCTION_ARGS)
{
! Relation rel = (Relation) PG_GETARG_POINTER(0);
! Datum *values = (Datum *) PG_GETARG_POINTER(1);
! bool *isnull = (bool *) PG_GETARG_POINTER(2);
! ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
! Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! bool *is_unique = (bool *) PG_GETARG_POINTER(6);
! IndexTuple itup;
! bool ok;
/* generate an index tuple */
itup = index_form_tuple(RelationGetDescr(rel), values, isnull);
itup->t_tid = *ht_ctid;
! ok = _bt_doinsert(rel, itup, checkUnique, heapRel, is_unique);
pfree(itup);
! PG_RETURN_BOOL(ok);
}
/*
*** ./src/backend/bootstrap/bootparse.y.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/bootstrap/bootparse.y 2009-07-04 16:54:25.000000000 +0100
***************
*** 266,272 ****
NULL,
$10,
NULL, NIL,
! false, false, false,
false, false, true, false, false);
do_end();
}
--- 266,272 ----
NULL,
$10,
NULL, NIL,
! false, false, false, false, false,
false, false, true, false, false);
do_end();
}
***************
*** 284,290 ****
NULL,
$11,
NULL, NIL,
! true, false, false,
false, false, true, false, false);
do_end();
}
--- 284,290 ----
NULL,
$11,
NULL, NIL,
! true, false, false, false, false,
false, false, true, false, false);
do_end();
}
*** ./src/backend/catalog/index.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/index.c 2009-07-19 10:30:37.000000000 +0100
***************
*** 40,53 ****
--- 40,58 ----
#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/gramparse.h"
+ #include "parser/parser.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
***************
*** 726,733 ****
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
! false, /* isDeferrable */
! false, /* isDeferred */
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
--- 731,738 ----
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
! indexInfo->ii_Deferrable,
! indexInfo->ii_InitDeferred,
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
***************
*** 753,758 ****
--- 758,808 ----
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+
+ /*
+ * If the constraint is deferrable, define the deferred uniqueness
+ * checking triggers. The index will temporarily allow duplicates and
+ * then these triggers will re-check for duplicates at the end of the
+ * statement or transaction. These triggers are only queued if
+ * potentially non-unique entries are detected during index insertion.
+ */
+ if (indexInfo->ii_Deferrable)
+ {
+ char *nsname = get_namespace_name(namespaceId);
+ RangeVar *heapRel = makeRangeVar(nsname, pstrdup(RelationGetRelationName(heapRelation)), -1);
+ RangeVar *indexRel = makeRangeVar(nsname, pstrdup(indexRelationName), -1);
+ CreateTrigStmt *trigger;
+
+ /*
+ * Advance the command counter so that we can see the
+ * newly-entered catalog tuples for the index.
+ */
+ CommandCounterIncrement();
+
+ trigger = makeNode(CreateTrigStmt);
+ trigger->trigname = pstrdup(indexRelationName);
+ trigger->relation = heapRel;
+ trigger->funcname = SystemFuncName("unique_key_check_ins");
+ trigger->args = NIL;
+ trigger->before = false;
+ trigger->row = true;
+ trigger->events = TRIGGER_TYPE_INSERT;
+ trigger->isconstraint = true;
+ trigger->deferrable = true;
+ trigger->initdeferred = indexInfo->ii_InitDeferred;
+ trigger->constrrel = indexRel;
+
+ (void) CreateTrigger(trigger, conOid, false);
+
+ CommandCounterIncrement();
+
+ trigger->funcname = SystemFuncName("unique_key_check_upd");
+ trigger->events = TRIGGER_TYPE_UPDATE;
+
+ (void) CreateTrigger(trigger, conOid, false);
+
+ CommandCounterIncrement();
+ }
}
else
{
***************
*** 1026,1031 ****
--- 1076,1105 ----
ii->ii_Concurrent = false;
ii->ii_BrokenHotChain = false;
+ /* fetch the details of the index constraint if any */
+ ii->ii_ConstraintId = get_index_constraint(RelationGetRelid(index));
+ if (OidIsValid(ii->ii_ConstraintId))
+ {
+ HeapTuple ht_constr = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(ii->ii_ConstraintId),
+ 0, 0, 0);
+ Form_pg_constraint conrec;
+
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u", ii->ii_ConstraintId);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ ii->ii_Deferrable = conrec->condeferrable;
+ ii->ii_InitDeferred = conrec->condeferred;
+
+ ReleaseSysCache(ht_constr);
+ }
+ else
+ {
+ ii->ii_Deferrable = false;
+ ii->ii_InitDeferred = false;
+ }
+
return ii;
}
***************
*** 2190,2196 ****
isnull,
&rootTuple,
heapRelation,
! indexInfo->ii_Unique);
state->tups_inserted += 1;
}
--- 2264,2272 ----
isnull,
&rootTuple,
heapRelation,
! indexInfo->ii_Unique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
! NULL);
state->tups_inserted += 1;
}
*** ./src/backend/catalog/indexing.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/indexing.c 2009-07-18 12:01:58.000000000 +0100
***************
*** 134,140 ****
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
! relationDescs[i]->rd_index->indisunique);
}
ExecDropSingleTupleTableSlot(slot);
--- 134,142 ----
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
! relationDescs[i]->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
! NULL);
}
ExecDropSingleTupleTableSlot(slot);
*** ./src/backend/catalog/sql_features.txt.orig 2009-07-11 23:37:21.000000000 +0100
--- ./src/backend/catalog/sql_features.txt 2009-07-11 23:38:32.000000000 +0100
***************
*** 289,295 ****
F696 Additional translation documentation NO
F701 Referential update actions YES
F711 ALTER domain YES
! F721 Deferrable constraints NO foreign keys only
F731 INSERT column privileges YES
F741 Referential MATCH types NO no partial match yet
F751 View CHECK enhancements NO
--- 289,295 ----
F696 Additional translation documentation NO
F701 Referential update actions YES
F711 ALTER domain YES
! 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
*** ./src/backend/catalog/toasting.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/toasting.c 2009-07-04 16:56:10.000000000 +0100
***************
*** 241,246 ****
--- 241,249 ----
indexInfo->ii_ReadyForInserts = true;
indexInfo->ii_Concurrent = false;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_ConstraintId = InvalidOid;
+ indexInfo->ii_Deferrable = false;
+ indexInfo->ii_InitDeferred = false;
classObjectId[0] = OID_BTREE_OPS_OID;
classObjectId[1] = INT4_BTREE_OPS_OID;
*** ./src/backend/commands/copy.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/copy.c 2009-07-04 18:15:59.000000000 +0100
***************
*** 2119,2124 ****
--- 2119,2126 ----
if (!skip_tuple)
{
+ List *problemIndexList = NIL;
+
/* Place tuple in tuple slot */
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
***************
*** 2130,2139 ****
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
! ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;
--- 2132,2141 ----
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple, problemIndexList);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;
*** ./src/backend/commands/indexcmds.c.orig 2009-07-19 09:02:44.000000000 +0100
--- ./src/backend/commands/indexcmds.c 2009-07-19 08:54:46.000000000 +0100
***************
*** 87,92 ****
--- 87,94 ----
* '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.
+ * 'constrDeferrable': constraint is DEFERRABLE (UNIQUE constraint).
+ * 'constrInitDeferred': constraint is INITIALLY DEFERRED (UNIQUE constraint).
* '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.)
***************
*** 107,112 ****
--- 109,116 ----
bool unique,
bool primary,
bool isconstraint,
+ bool constrDeferrable,
+ bool constrInitDeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
***************
*** 419,424 ****
--- 423,431 ----
indexInfo->ii_ReadyForInserts = !concurrent;
indexInfo->ii_Concurrent = concurrent;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_ConstraintId = InvalidOid; /* index_create() may create this */
+ indexInfo->ii_Deferrable = constrDeferrable;
+ indexInfo->ii_InitDeferred = constrInitDeferred;
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
*** ./src/backend/commands/tablecmds.c.orig 2009-07-19 09:10:16.000000000 +0100
--- ./src/backend/commands/tablecmds.c 2009-07-19 09:14:23.000000000 +0100
***************
*** 4370,4375 ****
--- 4370,4377 ----
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->constrDeferrable,
+ stmt->constrInitDeferred,
true, /* is_alter_table */
check_rights,
skip_build,
*** ./src/backend/commands/trigger.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/trigger.c 2009-07-05 22:55:24.000000000 +0100
***************
*** 63,69 ****
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
/*
--- 63,70 ----
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *problemIndexList);
/*
***************
*** 211,224 ****
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 (OidIsValid(constraintOid))
{
snprintf(constrtrigname, sizeof(constrtrigname),
! "RI_ConstraintTrigger_%u", trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
--- 212,258 ----
trigoid = GetNewOid(tgrel);
/*
! * If trigger is for a constraint, the passed-in name is the constraint
! * name; save that and build a unique trigger name based on the type of
! * the constraint to avoid collisions with user-selected trigger names.
*/
if (OidIsValid(constraintOid))
{
+ HeapTuple ht_constr = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(constraintOid),
+ 0, 0, 0);
+ Form_pg_constraint conrec;
+ char *prefix;
+
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u", constraintOid);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ /*
+ * Prefix for trigger name, based on constraint type.
+ */
+ switch (conrec->contype)
+ {
+ case CONSTRAINT_CHECK:
+ prefix = "Check_ConstraintTrigger";
+ break;
+ case CONSTRAINT_FOREIGN:
+ prefix = "RI_ConstraintTrigger";
+ break;
+ case CONSTRAINT_PRIMARY:
+ prefix = "PK_ConstraintTrigger";
+ break;
+ case CONSTRAINT_UNIQUE:
+ prefix = "Unique_ConstraintTrigger";
+ break;
+ default:
+ prefix = "ConstraintTrigger";
+ break;
+ }
+ ReleaseSysCache(ht_constr);
+
snprintf(constrtrigname, sizeof(constrtrigname),
! "%s_%u", prefix, trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
***************
*** 1639,1645 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL);
}
HeapTuple
--- 1673,1679 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL);
}
HeapTuple
***************
*** 1695,1707 ****
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
! HeapTuple trigtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple);
}
void
--- 1729,1741 ----
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
! HeapTuple trigtuple, List *problemIndexList)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, problemIndexList);
}
void
***************
*** 1770,1776 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL);
}
bool
--- 1804,1810 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL);
}
bool
***************
*** 1847,1853 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL);
heap_freetuple(trigtuple);
}
}
--- 1881,1887 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL);
heap_freetuple(trigtuple);
}
}
***************
*** 1918,1924 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL);
}
HeapTuple
--- 1952,1958 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL);
}
HeapTuple
***************
*** 1988,1994 ****
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
! ItemPointer tupleid, HeapTuple newtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 2022,2029 ----
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
! ItemPointer tupleid, HeapTuple newtuple,
! List *problemIndexList)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
***************
*** 1998,2004 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple);
heap_freetuple(trigtuple);
}
}
--- 2033,2039 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, problemIndexList);
heap_freetuple(trigtuple);
}
}
***************
*** 2069,2075 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL);
}
--- 2104,2110 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL);
}
***************
*** 3790,3796 ****
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
! HeapTuple oldtup, HeapTuple newtup)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 3825,3832 ----
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
! HeapTuple oldtup, HeapTuple newtup,
! List *problemIndexList)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
***************
*** 3951,3956 ****
--- 3987,4004 ----
}
/*
+ * If the trigger is a deferred unique constraint check trigger,
+ * only execute it if the unique constraint was potentially
+ * violated, which we know from index insertion time.
+ */
+ if (trigger->tgfoid == F_UNIQUE_KEY_CHECK_INS ||
+ trigger->tgfoid == F_UNIQUE_KEY_CHECK_UPD)
+ {
+ if (!list_member_oid(problemIndexList, trigger->tgconstrrelid))
+ continue; /* Uniqueness definitely not violated */
+ }
+
+ /*
* Fill in event structure and add it to the current query's queue.
*/
new_shared.ats_event =
*** ./src/backend/executor/execMain.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/executor/execMain.c 2009-07-04 17:45:41.000000000 +0100
***************
*** 1753,1758 ****
--- 1753,1759 ----
ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
Oid newId;
+ List *problemIndexList = NIL;
/*
* get the heap tuple out of the tuple table slot, making sure we have a
***************
*** 1834,1843 ****
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
! ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
--- 1835,1845 ----
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self),
! estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple, problemIndexList);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
***************
*** 1999,2004 ****
--- 2001,2007 ----
HTSU_Result result;
ItemPointerData update_ctid;
TransactionId update_xmax;
+ List *problemIndexList = NIL;
/*
* abort the operation if not running transactions
***************
*** 2132,2141 ****
* 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);
/* AFTER ROW UPDATE Triggers */
! ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
--- 2135,2145 ----
* If it's a HOT update, we mustn't insert new index entries.
*/
if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self),
! estate, false);
/* AFTER ROW UPDATE Triggers */
! ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple, problemIndexList);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
*** ./src/backend/executor/execUtils.c.orig 2009-07-19 09:28:05.000000000 +0100
--- ./src/backend/executor/execUtils.c 2009-07-19 13:27:48.000000000 +0100
***************
*** 1036,1044 ****
* 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
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
--- 1036,1048 ----
* 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?
+ *
+ * This returns a list of OIDs for any unique indexes
+ * whose constraint check was deferred and which had
+ * potential (unconfirmed) conflicts.
* ----------------------------------------------------------------
*/
! List *
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
***************
*** 1053,1058 ****
--- 1057,1063 ----
ExprContext *econtext;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
+ List *result = NIL;
/*
* Get information from the result relation info structure.
***************
*** 1077,1083 ****
*/
for (i = 0; i < numIndices; i++)
{
! IndexInfo *indexInfo;
if (relationDescs[i] == NULL)
continue;
--- 1082,1090 ----
*/
for (i = 0; i < numIndices; i++)
{
! IndexInfo *indexInfo;
! IndexUniqueCheck uniqueCheck;
! bool isUnique;
if (relationDescs[i] == NULL)
continue;
***************
*** 1125,1143 ****
* 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.
*/
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);
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
}
/*
--- 1132,1175 ----
* 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.
+ *
+ * If there is a deferrable unique constraint associated with the
+ * index, then we don't enforce uniqueness here, we just do a partial
+ * check and record if the tuple was potentially non-unique.
*/
+ if (is_vacuum || !relationDescs[i]->rd_index->indisunique)
+ uniqueCheck = UNIQUE_CHECK_NO;
+ else if (indexInfo->ii_Deferrable)
+ uniqueCheck = UNIQUE_CHECK_PARTIAL;
+ else
+ uniqueCheck = UNIQUE_CHECK_YES;
+
index_insert(relationDescs[i], /* index relation */
! values, /* array of index Datums */
! isnull, /* null flags */
! tupleid, /* tid of heap tuple */
! heapRelation, /* heap relation */
! uniqueCheck, /* type of uniqueness check to do */
! &isUnique); /* result of uniqueness check */
!
! if (uniqueCheck == 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,
! relationDescs[i]->rd_index->indexrelid);
! }
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
+
+ return result;
}
/*
*** ./src/backend/nodes/copyfuncs.c.orig 2009-07-19 09:41:01.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c 2009-07-19 09:41:00.000000000 +0100
***************
*** 2091,2096 ****
--- 2091,2098 ----
COPY_NODE_FIELD(keys);
COPY_NODE_FIELD(options);
COPY_STRING_FIELD(indexspace);
+ COPY_SCALAR_FIELD(deferrable);
+ COPY_SCALAR_FIELD(initdeferred);
return newnode;
}
***************
*** 2509,2514 ****
--- 2511,2518 ----
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
+ COPY_SCALAR_FIELD(constrDeferrable);
+ COPY_SCALAR_FIELD(constrInitDeferred);
COPY_SCALAR_FIELD(concurrent);
return newnode;
*** ./src/backend/nodes/equalfuncs.c.orig 2009-07-19 09:43:39.000000000 +0100
--- ./src/backend/nodes/equalfuncs.c 2009-07-19 09:43:38.000000000 +0100
***************
*** 1159,1164 ****
--- 1159,1166 ----
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
+ COMPARE_SCALAR_FIELD(constrDeferrable);
+ COMPARE_SCALAR_FIELD(constrInitDeferred);
COMPARE_SCALAR_FIELD(concurrent);
return true;
***************
*** 2068,2073 ****
--- 2070,2077 ----
COMPARE_NODE_FIELD(keys);
COMPARE_NODE_FIELD(options);
COMPARE_STRING_FIELD(indexspace);
+ COMPARE_SCALAR_FIELD(deferrable);
+ COMPARE_SCALAR_FIELD(initdeferred);
return true;
}
*** ./src/backend/nodes/outfuncs.c.orig 2009-07-19 09:45:02.000000000 +0100
--- ./src/backend/nodes/outfuncs.c 2009-07-19 09:45:01.000000000 +0100
***************
*** 1734,1739 ****
--- 1734,1741 ----
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
+ WRITE_BOOL_FIELD(constrDeferrable);
+ WRITE_BOOL_FIELD(constrInitDeferred);
WRITE_BOOL_FIELD(concurrent);
}
***************
*** 2292,2297 ****
--- 2294,2301 ----
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
+ WRITE_BOOL_FIELD(deferrable);
+ WRITE_BOOL_FIELD(initdeferred);
break;
case CONSTR_CHECK:
*** ./src/backend/parser/gram.y.orig 2009-07-19 09:48:12.000000000 +0100
--- ./src/backend/parser/gram.y 2009-07-19 09:48:10.000000000 +0100
***************
*** 2190,2195 ****
--- 2190,2197 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| NULL_P
***************
*** 2201,2206 ****
--- 2203,2210 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE opt_definition OptConsTableSpace
***************
*** 2213,2218 ****
--- 2217,2224 ----
n->keys = NULL;
n->options = $2;
n->indexspace = $3;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| PRIMARY KEY opt_definition OptConsTableSpace
***************
*** 2225,2230 ****
--- 2231,2238 ----
n->keys = NULL;
n->options = $3;
n->indexspace = $4;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| CHECK '(' a_expr ')'
***************
*** 2236,2241 ****
--- 2244,2251 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| DEFAULT b_expr
***************
*** 2247,2252 ****
--- 2257,2264 ----
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
***************
*** 2376,2384 ****
--- 2388,2399 ----
n->raw_expr = $3;
n->cooked_expr = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
+ ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_UNIQUE;
***************
*** 2388,2393 ****
--- 2403,2410 ----
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
***************
*** 2400,2405 ****
--- 2417,2424 ----
n->keys = $4;
n->options = $6;
n->indexspace = $7;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
*** ./src/backend/parser/parse_utilcmd.c.orig 2009-07-19 09:53:57.000000000 +0100
--- ./src/backend/parser/parse_utilcmd.c 2009-07-19 09:53:56.000000000 +0100
***************
*** 34,39 ****
--- 34,40 ----
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_opclass.h"
+ #include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
***************
*** 809,815 ****
else if (!index->unique)
index->isconstraint = false;
else
! index->isconstraint = OidIsValid(get_index_constraint(source_relid));
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
--- 810,843 ----
else if (!index->unique)
index->isconstraint = false;
else
! {
! /*
! * The index is marked UNIQUE, so there may be an associated unique
! * constraint. If there is, we need to fetch its DEFERRABLE and
! * INITIALLY DEFERRED attributes.
! */
! Oid constraintId = get_index_constraint(source_relid);
!
! if (OidIsValid(constraintId))
! {
! HeapTuple ht_constr = SearchSysCache(CONSTROID,
! ObjectIdGetDatum(constraintId),
! 0, 0, 0);
! Form_pg_constraint conrec;
!
! if (!HeapTupleIsValid(ht_constr))
! elog(ERROR, "cache lookup failed for constraint %u", constraintId);
! conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
!
! index->isconstraint = true;
! index->constrDeferrable = conrec->condeferrable;
! index->constrInitDeferred = conrec->condeferred;
!
! ReleaseSysCache(ht_constr);
! }
! else
! index->isconstraint = false;
! }
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
***************
*** 1092,1097 ****
--- 1120,1127 ----
*/
}
index->isconstraint = true;
+ index->constrDeferrable = constraint->deferrable;
+ index->constrInitDeferred = constraint->initdeferred;
if (constraint->name != NULL)
index->idxname = pstrdup(constraint->name);
***************
*** 1853,1861 ****
* 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.
*/
static void
transformConstraintAttrs(List *constraintList)
{
--- 1883,1898 ----
* to attach constraint attributes to their primary constraint nodes
* and detect inconsistent/misplaced constraint attributes.
*
! * NOTE: currently, attributes are only supported for FOREIGN KEY and
! * UNIQUE primary constraints, but someday they ought to be supported
! * for other constraints.
*/
+ #define SUPPORTS_ATTRS(node) \
+ ((node) != NULL && \
+ (IsA((node), FkConstraint) || \
+ (IsA((node), Constraint) && \
+ ((Constraint *)(node))->contype == CONSTR_UNIQUE)))
+
static void
transformConstraintAttrs(List *constraintList)
{
***************
*** 1882,1889 ****
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
--- 1919,1925 ----
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
! if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
***************
*** 1892,1902 ****
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! ((FkConstraint *) lastprimarynode)->deferrable = true;
break;
case CONSTR_ATTR_NOT_DEFERRABLE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
--- 1928,1941 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! if (IsA(lastprimarynode, FkConstraint))
! ((FkConstraint *) lastprimarynode)->deferrable = true;
! else
! ((Constraint *) lastprimarynode)->deferrable = true;
break;
+
case CONSTR_ATTR_NOT_DEFERRABLE:
! if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
***************
*** 1905,1920 ****
(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")));
break;
case CONSTR_ATTR_DEFERRED:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
--- 1944,1971 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! 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 (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
***************
*** 1923,1943 ****
(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")));
break;
case CONSTR_ATTR_IMMEDIATE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
--- 1974,2009 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
/*
* If only INITIALLY DEFERRED appears, assume 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 (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
***************
*** 1946,1953 ****
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
! ((FkConstraint *) lastprimarynode)->initdeferred = false;
break;
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
--- 2012,2023 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
! if (IsA(lastprimarynode, FkConstraint))
! ((FkConstraint *) lastprimarynode)->initdeferred = false;
! else
! ((Constraint *) lastprimarynode)->initdeferred = false;
break;
+
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
*** ./src/backend/tcop/utility.c.orig 2009-07-19 09:57:54.000000000 +0100
--- ./src/backend/tcop/utility.c 2009-07-19 09:57:53.000000000 +0100
***************
*** 795,800 ****
--- 795,802 ----
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->constrDeferrable,
+ stmt->constrInitDeferred,
false, /* is_alter_table */
true, /* check_rights */
false, /* skip_build */
*** ./src/backend/utils/adt/ri_triggers.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/utils/adt/ri_triggers.c 2009-07-18 12:09:03.000000000 +0100
***************
*** 30,40 ****
--- 30,43 ----
#include "postgres.h"
+ #include "access/genam.h"
#include "access/xact.h"
+ #include "catalog/index.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
+ #include "executor/executor.h"
#include "executor/spi.h"
#include "parser/parse_coerce.h"
#include "parser/parse_relation.h"
***************
*** 4020,4022 ****
--- 4023,4174 ----
return RI_TRIGGER_NONE;
}
+
+ /* -------------------------------------------------------------------------
+ *
+ * Triggers to check deferred UNIQUE constraints.
+ *
+ * These probably belong in a new source file, since they actually have
+ * little in common with the referential integrity triggers.
+ *
+ * -------------------------------------------------------------------------
+ */
+
+ /* ----------
+ * unique_key_check -
+ *
+ * Deferred check of a uniqueness constraint (for both INSERT and UPDATE).
+ * ----------
+ */
+ static Datum
+ unique_key_check(PG_FUNCTION_ARGS)
+ {
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+ HeapTuple new_row;
+ Buffer new_row_buf;
+ Relation indexRel;
+ IndexInfo *indexInfo;
+ EState *estate;
+ ExprContext *econtext;
+ TupleTableSlot *slot;
+ Datum values[INDEX_MAX_KEYS];
+ bool isnull[INDEX_MAX_KEYS];
+
+ /*
+ * Get the new data that was inserted/updated.
+ */
+ if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ {
+ new_row = trigdata->tg_newtuple;
+ new_row_buf = trigdata->tg_newtuplebuf;
+ }
+ else
+ {
+ new_row = trigdata->tg_trigtuple;
+ new_row_buf = trigdata->tg_trigtuplebuf;
+ }
+
+ Assert(new_row_buf != InvalidBuffer);
+
+ /*
+ * Open the unique index, acquiring a RowExclusiveLock, as if we were
+ * going to update it. This does not actually protect us against
+ * concurrent insertions - that is done by the index itself which
+ * acquires a write lock on the target page during the uniqueness check.
+ */
+ indexRel = index_open(trigdata->tg_trigger->tgconstrrelid,
+ RowExclusiveLock);
+ indexInfo = BuildIndexInfo(indexRel);
+
+ /*
+ * Typically the index won't have expressions, but if it does we need
+ * an EState to evaluate them. Also need a slot for the tuple we are
+ * going to test.
+ */
+ if (indexInfo->ii_Expressions != NIL)
+ {
+ estate = CreateExecutorState();
+ econtext = GetPerTupleExprContext(estate);
+ }
+ else
+ {
+ estate = NULL;
+ econtext = NULL;
+ }
+
+ slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation));
+
+ if (econtext != NULL)
+ {
+ econtext->ecxt_scantuple = slot;
+ MemoryContextReset(econtext->ecxt_per_tuple_memory);
+ }
+
+ ExecStoreTuple(new_row, slot, InvalidBuffer, false);
+
+ /*
+ * Form the index values and is null flags for the index entry that
+ * we need to check.
+ */
+ 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, NULL);
+
+ /*
+ * If that worked, then this index entry is unique, and we are done.
+ */
+ ExecDropSingleTupleTableSlot(slot);
+
+ index_close(indexRel, RowExclusiveLock);
+
+ if (estate != NULL)
+ FreeExecutorState(estate);
+
+ return PointerGetDatum(NULL);
+ }
+
+
+ /* ----------
+ * unique_key_check_ins -
+ *
+ * Check uniqueness constraint at insert event.
+ *
+ * This applies only to UNIQUE constraints marked DEFERRABLE.
+ * ----------
+ */
+ Datum
+ unique_key_check_ins(PG_FUNCTION_ARGS)
+ {
+ /*
+ * Check that this is a valid trigger call on the right time and event.
+ */
+ ri_CheckTrigger(fcinfo, "unique_key_check_ins", RI_TRIGTYPE_INSERT);
+
+ return unique_key_check(fcinfo);
+ }
+
+
+ /* ----------
+ * unique_key_check_upd -
+ *
+ * Check uniquesness constraint at update event.
+ *
+ * This applies only to UNIQUE constraints marked DEFERRABLE.
+ * ----------
+ */
+ Datum
+ unique_key_check_upd(PG_FUNCTION_ARGS)
+ {
+ /*
+ * Check that this is a valid trigger call on the right time and event.
+ */
+ ri_CheckTrigger(fcinfo, "unique_key_check_upd", RI_TRIGTYPE_UPDATE);
+
+ return unique_key_check(fcinfo);
+ }
*** ./src/backend/utils/adt/ruleutils.c.orig 2009-07-19 10:00:24.000000000 +0100
--- ./src/backend/utils/adt/ruleutils.c 2009-07-19 10:00:23.000000000 +0100
***************
*** 1096,1101 ****
--- 1096,1106 ----
quote_identifier(get_tablespace_name(tblspc)));
}
+ if (conForm->condeferrable)
+ appendStringInfo(&buf, " DEFERRABLE");
+ if (conForm->condeferred)
+ appendStringInfo(&buf, " INITIALLY DEFERRED");
+
break;
}
case CONSTRAINT_CHECK:
*** ./src/bin/pg_dump/pg_dump.c.orig 2009-07-09 10:15:59.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.c 2009-07-09 12:31:14.000000000 +0100
***************
*** 3610,3616 ****
i_contableoid,
i_conoid,
i_tablespace,
! i_options;
int ntups;
for (i = 0; i < numTables; i++)
--- 3610,3618 ----
i_contableoid,
i_conoid,
i_tablespace,
! i_options,
! i_condeferrable,
! i_coninitdeferred;
int ntups;
for (i = 0; i < numTables; i++)
***************
*** 3651,3657 ****
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "array_to_string(t.reloptions, ', ') AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--- 3653,3661 ----
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "array_to_string(t.reloptions, ', ') AS options, "
! "c.condeferrable AS condeferrable, "
! "c.condeferred AS coninitdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
***************
*** 3677,3683 ****
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "null AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--- 3681,3689 ----
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
***************
*** 3703,3709 ****
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--- 3709,3717 ----
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
***************
*** 3731,3737 ****
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
--- 3739,3747 ----
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
***************
*** 3754,3760 ****
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
--- 3764,3772 ----
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
***************
*** 3780,3785 ****
--- 3792,3799 ----
i_conoid = PQfnumber(res, "conoid");
i_tablespace = PQfnumber(res, "tablespace");
i_options = PQfnumber(res, "options");
+ i_condeferrable = PQfnumber(res, "condeferrable");
+ i_coninitdeferred = PQfnumber(res, "coninitdeferred");
indxinfo = (IndxInfo *) malloc(ntups * sizeof(IndxInfo));
constrinfo = (ConstraintInfo *) malloc(ntups * sizeof(ConstraintInfo));
***************
*** 3838,3843 ****
--- 3852,3859 ----
constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
+ constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
+ constrinfo[j].coninitdeferred = *(PQgetvalue(res, j, i_coninitdeferred)) == 't';
indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
***************
*** 3942,3947 ****
--- 3958,3965 ----
constrinfo[j].conindex = 0;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
+ constrinfo[j].condeferrable = false; /* Part of condef */
+ constrinfo[j].coninitdeferred = false; /* Part of condef */
}
PQclear(res);
***************
*** 4026,4031 ****
--- 4044,4051 ----
constrinfo[i].conindex = 0;
constrinfo[i].conislocal = true;
constrinfo[i].separate = false;
+ constrinfo[i].condeferrable = false;
+ constrinfo[i].coninitdeferred = false;
/*
* Make the domain depend on the constraint, ensuring it won't be
***************
*** 5025,5030 ****
--- 5045,5052 ----
constrs[j].conindex = 0;
constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
constrs[j].separate = false;
+ constrs[j].condeferrable = false;
+ constrs[j].coninitdeferred = false;
constrs[j].dobj.dump = tbinfo->dobj.dump;
***************
*** 10293,10298 ****
--- 10315,10327 ----
if (indxinfo->options && strlen(indxinfo->options) > 0)
appendPQExpBuffer(q, " WITH (%s)", indxinfo->options);
+ if (coninfo->condeferrable)
+ {
+ appendPQExpBuffer(q, " DEFERRABLE");
+ if (coninfo->coninitdeferred)
+ appendPQExpBuffer(q, " INITIALLY DEFERRED");
+ }
+
appendPQExpBuffer(q, ";\n");
/* If the index is clustered, we need to record that. */
*** ./src/bin/pg_dump/pg_dump.h.orig 2009-07-09 10:15:54.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.h 2009-07-09 10:21:12.000000000 +0100
***************
*** 344,349 ****
--- 344,351 ----
DumpId conindex; /* identifies associated index if any */
bool conislocal; /* TRUE if constraint has local definition */
bool separate; /* TRUE if must dump as separate item */
+ bool condeferrable; /* TRUE if constraint is DEFERRABLE */
+ bool coninitdeferred; /* TRUE if constraint is INITIALLY DEFERRED */
} ConstraintInfo;
typedef struct _procLangInfo
*** ./src/bin/psql/describe.c.orig 2009-07-11 12:59:37.000000000 +0100
--- ./src/bin/psql/describe.c 2009-07-11 13:07:18.000000000 +0100
***************
*** 1331,1336 ****
--- 1331,1347 ----
appendPQExpBuffer(&buf, "i.indisvalid, ");
else
appendPQExpBuffer(&buf, "true as indisvalid, ");
+ if (pset.sversion >= 80500)
+ appendPQExpBuffer(&buf,
+ "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE d.objid = i.indexrelid "
+ "AND con.oid = d.refobjid AND con.condeferrable) "
+ "as condeferrable, EXISTS (SELECT 1 FROM "
+ "pg_catalog.pg_depend d, pg_catalog.pg_constraint con "
+ "WHERE d.objid = i.indexrelid AND con.oid = d.refobjid "
+ "AND con.condeferred) as condeferred, ");
+ else
+ appendPQExpBuffer(&buf, "false as condeferrable, false as condeferred, ");
appendPQExpBuffer(&buf, "a.amname, c2.relname,\n"
" 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"
***************
*** 1352,1360 ****
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);
if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, "));
--- 1363,1373 ----
char *indisprimary = PQgetvalue(result, 0, 1);
char *indisclustered = PQgetvalue(result, 0, 2);
char *indisvalid = PQgetvalue(result, 0, 3);
! 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, "));
***************
*** 1377,1382 ****
--- 1390,1401 ----
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);
***************
*** 1438,1443 ****
--- 1457,1473 ----
else
appendPQExpBuffer(&buf, "true as indisvalid, ");
appendPQExpBuffer(&buf, "pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)");
+ if (pset.sversion >= 80500)
+ appendPQExpBuffer(&buf,
+ ", EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE d.objid = i.indexrelid "
+ "AND con.oid = d.refobjid AND con.condeferrable) "
+ "as condeferrable, EXISTS (SELECT 1 FROM "
+ "pg_catalog.pg_depend d, pg_catalog.pg_constraint con "
+ "WHERE d.objid = i.indexrelid AND con.oid = d.refobjid "
+ "AND con.condeferred) as condeferred");
+ else
+ appendPQExpBuffer(&buf, ", false as condeferrable, false as condeferred");
if (pset.sversion >= 80000)
appendPQExpBuffer(&buf, ", c2.reltablespace");
appendPQExpBuffer(&buf,
***************
*** 1484,1495 ****
if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
appendPQExpBuffer(&buf, " INVALID");
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)),
false);
}
}
--- 1514,1531 ----
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, 8)),
false);
}
}
*** ./src/include/access/genam.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/access/genam.h 2009-07-18 11:19:44.000000000 +0100
***************
*** 85,90 ****
--- 85,118 ----
typedef struct IndexScanDescData *IndexScanDesc;
typedef struct SysScanDescData *SysScanDesc;
+ /*
+ * Enumeration specifying the type of uniqueness check to perform for
+ * index_insert().
+ *
+ * For deferrable unique constraints, UNIQUE_CHECK_PARTIAL is used at
+ * insertion time. This tests if the tuple is unique but doesn't error,
+ * block or prevent the insertion if the tuple appears not to be unique.
+ * Since it isn't blocking, it can't know for sure if the tuple is not
+ * unique (the other transaction might be rolled back). The return value
+ * of index_insert() in this case is true if the tuple is definitely
+ * unique and false if it is possibly non-unique.
+ *
+ * UNIQUE_CHECK_EXISTING is for deferred unqiue constraint checks. The
+ * tuple is already in the index when the check is performed, so it is
+ * not inserted again. This just checks if the tuple in the index is
+ * unique.
+ */
+ typedef enum IndexUniqueCheck
+ {
+ UNIQUE_CHECK_NO, /* Don't do any uniqueness checking */
+ UNIQUE_CHECK_YES, /* Enforce uniqueness at insertion time */
+ /* (possibly blocking on another xact) */
+ UNIQUE_CHECK_PARTIAL, /* Test uniqueness, but don't block and */
+ /* no error if potentially not unique */
+ UNIQUE_CHECK_EXISTING /* Don't do the insert, just check if */
+ /* the existing tuple is unique */
+ } IndexUniqueCheck;
+
/*
* generalized index_ interface routines (in indexam.c)
***************
*** 103,109 ****
Datum *values, bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! bool check_uniqueness);
extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
--- 131,138 ----
Datum *values, bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! IndexUniqueCheck uniqueness,
! bool *is_unique);
extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
*** ./src/include/access/nbtree.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/access/nbtree.h 2009-07-18 12:34:19.000000000 +0100
***************
*** 517,524 ****
/*
* prototypes for functions in nbtinsert.c
*/
! extern void _bt_doinsert(Relation rel, IndexTuple itup,
! bool index_is_unique, 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);
--- 517,524 ----
/*
* prototypes for functions in nbtinsert.c
*/
! extern bool _bt_doinsert(Relation rel, IndexTuple itup,
! IndexUniqueCheck unique_check, Relation heapRel, bool *is_unique);
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);
*** ./src/include/catalog/pg_proc.h.orig 2009-07-11 13:12:26.000000000 +0100
--- ./src/include/catalog/pg_proc.h 2009-07-18 12:48:21.000000000 +0100
***************
*** 663,669 ****
DESCR("btree(internal)");
DATA(insert OID = 636 ( btgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ btgetbitmap _null_ _null_ _null_ ));
DESCR("btree(internal)");
! DATA(insert OID = 331 ( btinsert PGNSP PGUID 12 1 0 0 f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ btinsert _null_ _null_ _null_ ));
DESCR("btree(internal)");
DATA(insert OID = 333 ( btbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ btbeginscan _null_ _null_ _null_ ));
DESCR("btree(internal)");
--- 663,669 ----
DESCR("btree(internal)");
DATA(insert OID = 636 ( btgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ btgetbitmap _null_ _null_ _null_ ));
DESCR("btree(internal)");
! DATA(insert OID = 331 ( btinsert PGNSP PGUID 12 1 0 0 f f f t f v 7 0 16 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ btinsert _null_ _null_ _null_ ));
DESCR("btree(internal)");
DATA(insert OID = 333 ( btbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ btbeginscan _null_ _null_ _null_ ));
DESCR("btree(internal)");
***************
*** 782,788 ****
DESCR("hash(internal)");
DATA(insert OID = 637 ( hashgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ hashgetbitmap _null_ _null_ _null_ ));
DESCR("hash(internal)");
! DATA(insert OID = 441 ( hashinsert PGNSP PGUID 12 1 0 0 f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ hashinsert _null_ _null_ _null_ ));
DESCR("hash(internal)");
DATA(insert OID = 443 ( hashbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ hashbeginscan _null_ _null_ _null_ ));
DESCR("hash(internal)");
--- 782,788 ----
DESCR("hash(internal)");
DATA(insert OID = 637 ( hashgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ hashgetbitmap _null_ _null_ _null_ ));
DESCR("hash(internal)");
! DATA(insert OID = 441 ( hashinsert PGNSP PGUID 12 1 0 0 f f f t f v 7 0 16 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ hashinsert _null_ _null_ _null_ ));
DESCR("hash(internal)");
DATA(insert OID = 443 ( hashbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ hashbeginscan _null_ _null_ _null_ ));
DESCR("hash(internal)");
***************
*** 1067,1073 ****
DESCR("gist(internal)");
DATA(insert OID = 638 ( gistgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ gistgetbitmap _null_ _null_ _null_ ));
DESCR("gist(internal)");
! DATA(insert OID = 775 ( gistinsert PGNSP PGUID 12 1 0 0 f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gistinsert _null_ _null_ _null_ ));
DESCR("gist(internal)");
DATA(insert OID = 777 ( gistbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gistbeginscan _null_ _null_ _null_ ));
DESCR("gist(internal)");
--- 1067,1073 ----
DESCR("gist(internal)");
DATA(insert OID = 638 ( gistgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ gistgetbitmap _null_ _null_ _null_ ));
DESCR("gist(internal)");
! DATA(insert OID = 775 ( gistinsert PGNSP PGUID 12 1 0 0 f f f t f v 7 0 16 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gistinsert _null_ _null_ _null_ ));
DESCR("gist(internal)");
DATA(insert OID = 777 ( gistbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gistbeginscan _null_ _null_ _null_ ));
DESCR("gist(internal)");
***************
*** 2349,2354 ****
--- 2349,2360 ----
DATA(insert OID = 1655 ( RI_FKey_noaction_upd PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ RI_FKey_noaction_upd _null_ _null_ _null_ ));
DESCR("referential integrity ON UPDATE NO ACTION");
+ /* Deferrable unique constraint triggers */
+ DATA(insert OID = 1029 ( unique_key_check_ins PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_check_ins _null_ _null_ _null_ ));
+ DESCR("deferred check UNIQUE ... DEFERRABLE");
+ DATA(insert OID = 1030 ( unique_key_check_upd PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_check_upd _null_ _null_ _null_ ));
+ DESCR("deferred check UNIQUE ... DEFERRABLE");
+
DATA(insert OID = 1666 ( varbiteq PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "1562 1562" _null_ _null_ _null_ _null_ biteq _null_ _null_ _null_ ));
DESCR("equal");
DATA(insert OID = 1667 ( varbitne PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "1562 1562" _null_ _null_ _null_ _null_ bitne _null_ _null_ _null_ ));
***************
*** 4187,4193 ****
/* GIN */
DATA(insert OID = 2731 ( gingetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ gingetbitmap _null_ _null_ _null_ ));
DESCR("gin(internal)");
! DATA(insert OID = 2732 ( gininsert PGNSP PGUID 12 1 0 0 f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gininsert _null_ _null_ _null_ ));
DESCR("gin(internal)");
DATA(insert OID = 2733 ( ginbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ ginbeginscan _null_ _null_ _null_ ));
DESCR("gin(internal)");
--- 4193,4199 ----
/* GIN */
DATA(insert OID = 2731 ( gingetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ gingetbitmap _null_ _null_ _null_ ));
DESCR("gin(internal)");
! DATA(insert OID = 2732 ( gininsert PGNSP PGUID 12 1 0 0 f f f t f v 7 0 16 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gininsert _null_ _null_ _null_ ));
DESCR("gin(internal)");
DATA(insert OID = 2733 ( ginbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ ginbeginscan _null_ _null_ _null_ ));
DESCR("gin(internal)");
*** ./src/include/commands/defrem.h.orig 2009-07-19 10:03:56.000000000 +0100
--- ./src/include/commands/defrem.h 2009-07-19 10:03:56.000000000 +0100
***************
*** 29,34 ****
--- 29,36 ----
bool unique,
bool primary,
bool isconstraint,
+ bool constrDeferrable,
+ bool constrInitDeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
*** ./src/include/commands/trigger.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/commands/trigger.h 2009-07-04 16:46:36.000000000 +0100
***************
*** 131,137 ****
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
! HeapTuple trigtuple);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
--- 131,138 ----
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
! HeapTuple trigtuple,
! List *problemIndexList);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
***************
*** 153,159 ****
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
! HeapTuple newtuple);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
--- 154,161 ----
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
! HeapTuple newtuple,
! List *problemIndexList);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
*** ./src/include/executor/executor.h.orig 2009-07-19 10:05:15.000000000 +0100
--- ./src/include/executor/executor.h 2009-07-19 10:05:15.000000000 +0100
***************
*** 301,307 ****
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern void ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum);
extern void RegisterExprContextCallback(ExprContext *econtext,
--- 301,307 ----
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum);
extern void RegisterExprContextCallback(ExprContext *econtext,
*** ./src/include/nodes/execnodes.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/nodes/execnodes.h 2009-07-04 16:34:51.000000000 +0100
***************
*** 44,49 ****
--- 44,52 ----
* ReadyForInserts is it valid for inserts?
* Concurrent are we doing a concurrent index build?
* BrokenHotChain did we detect any broken HOT chains?
+ * ConstraintId OID of constraint associated with this index
+ * Deferrable is the constraint DEFERRABLE?
+ * InitDeferred is the constraint INITIALLY DEFERRED?
*
* ii_Concurrent and ii_BrokenHotChain are used only during index build;
* they're conventionally set to false otherwise.
***************
*** 62,67 ****
--- 65,73 ----
bool ii_ReadyForInserts;
bool ii_Concurrent;
bool ii_BrokenHotChain;
+ Oid ii_ConstraintId;
+ bool ii_Deferrable;
+ bool ii_InitDeferred;
} IndexInfo;
/* ----------------
*** ./src/include/nodes/parsenodes.h.orig 2009-07-19 10:07:08.000000000 +0100
--- ./src/include/nodes/parsenodes.h 2009-07-19 10:07:07.000000000 +0100
***************
*** 1354,1360 ****
* 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).
* ----------
*/
--- 1354,1360 ----
* 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
! * Constraint and FkConstraint nodes.
* ----------
*/
***************
*** 1384,1389 ****
--- 1384,1391 ----
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;
/* ----------
***************
*** 1863,1868 ****
--- 1865,1872 ----
bool unique; /* is index unique? */
bool primary; /* is index on primary key? */
bool isconstraint; /* is it from a CONSTRAINT clause? */
+ bool constrDeferrable; /* is the constraint DEFERRABLE? */
+ bool constrInitDeferred; /* is the constraint INITIALLY DEFERRED? */
bool concurrent; /* should this be a concurrent index build? */
} IndexStmt;
*** ./src/include/utils/builtins.h.orig 2009-07-19 10:09:52.000000000 +0100
--- ./src/include/utils/builtins.h 2009-07-19 10:09:51.000000000 +0100
***************
*** 932,937 ****
--- 932,939 ----
extern Datum RI_FKey_setnull_upd(PG_FUNCTION_ARGS);
extern Datum RI_FKey_setdefault_del(PG_FUNCTION_ARGS);
extern Datum RI_FKey_setdefault_upd(PG_FUNCTION_ARGS);
+ extern Datum unique_key_check_ins(PG_FUNCTION_ARGS);
+ extern Datum unique_key_check_upd(PG_FUNCTION_ARGS);
/* trigfuncs.c */
extern Datum suppress_redundant_updates_trigger(PG_FUNCTION_ARGS);
*** ./src/test/regress/input/constraints.source.orig 2009-07-11 18:17:41.000000000 +0100
--- ./src/test/regress/input/constraints.source 2009-07-11 18:59:44.000000000 +0100
***************
*** 259,261 ****
--- 259,345 ----
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');
+
+ -- default is immediate so this should fail
+ UPDATE UNIQUE_TBL SET i = 1 WHERE i = 0;
+
+ -- check is done at end of statement, so this should succeed
+ UPDATE UNIQUE_TBL SET i = i+1;
+
+ -- 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 '' AS five, * FROM UNIQUE_TBL;
+
+ -- 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 '' AS five, * 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 which 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
+
+ DROP TABLE UNIQUE_TBL;
+
*** ./src/test/regress/output/constraints.source.orig 2009-07-11 19:07:40.000000000 +0100
--- ./src/test/regress/output/constraints.source 2009-07-11 19:08:46.000000000 +0100
***************
*** 375,377 ****
--- 375,457 ----
(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');
+ -- default is immediate so this should fail
+ UPDATE UNIQUE_TBL SET i = 1 WHERE i = 0;
+ ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+ -- check is done at end of statement, so this should succeed
+ UPDATE UNIQUE_TBL SET i = i+1;
+ -- 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 '' AS five, * FROM UNIQUE_TBL;
+ five | i | t
+ ------+---+-------
+ | 1 | one
+ | 2 | two
+ | 4 | four
+ | 5 | five
+ | 3 | three
+ (5 rows)
+
+ -- 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 '' AS five, * FROM UNIQUE_TBL;
+ five | 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 which 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"
+ DROP TABLE UNIQUE_TBL;
Dean Rasheed wrote:
Thanks for the thorough review. I attach an new version of the patch,
updated to HEAD, and with the index AM change discussed.
Wow, this is a large patch.
I didn't do a thorough review, but some quickies I noticed:
* Please move the code that says that it should be in a new file to a
new file.
* Please don't mimic the silliness of RI_FKey_check of an unused return
value. Just make it return void, and have the caller use the proper
PG_RETURN_FOO macro. (Unless there's a specific reason to not do
things that way)
* I'm not sure about the changes in trigger.h and related elsewhere.
Seems related to the new list in AfterTriggerSaveEvent, which is
used in ways that seem to conflict with its header comment ... I
wonder if we should be doing that in the executor itself instead.
In any case it's inconsistent that the list is added to
ExecARInsertTriggers but not to ExecARUpdateTrigger et al ...
--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.
2009/7/20 Alvaro Herrera <alvherre@commandprompt.com>:
Dean Rasheed wrote:
Thanks for the thorough review. I attach an new version of the patch,
updated to HEAD, and with the index AM change discussed.Wow, this is a large patch.
I didn't do a thorough review, but some quickies I noticed:
* Please move the code that says that it should be in a new file to a
new file.
Ah yes, I forgot about that. Will do.
utils/adt/constraints.c ?
I'm thinking other deferred constraints (eg. CHECK) might get added to
it one day.
* Please don't mimic the silliness of RI_FKey_check of an unused return
value. Just make it return void
OK.
and have the caller use the proper
PG_RETURN_FOO macro. (Unless there's a specific reason to not do
things that way)
It looks like I can't use PG_RETURN_NULL() because that will break
the trigger calling code, but I could use PG_RETURN_VOID(). But is that
the recommended style? Section 35.4 of the docs suggests using
PointerGetDatum(NULL).
* I'm not sure about the changes in trigger.h and related elsewhere.
Seems related to the new list in AfterTriggerSaveEvent, which is
used in ways that seem to conflict with its header comment ... I
wonder if we should be doing that in the executor itself instead.
Yes I suppose the comment is a bit misleading. I put the check there
because that's where the similar RI checks are, which already conflict
with the header comment. The simplest solution is to just update the
comment.
This seemed like the least intrusive way to do this. It is also called from
copy.c which duplicates some of the executor code.
In any case it's inconsistent that the list is added to
ExecARInsertTriggers but not to ExecARUpdateTrigger et al ...
Yes it is added to ExecARUpdateTriggers().
- Dean
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
* Please move the code that says that it should be in a new file to a
�new file.
Ah yes, I forgot about that. Will do.
utils/adt/constraints.c ?
Please, no. Constraints are not a datatype. Do not copy the brain-dead
decision that put ri_triggers there.
regards, tom lane
On Mon, Jul 20, 2009 at 10:21:53AM -0400, Tom Lane wrote:
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
* Please move the code that says that it should be in a new file to a
�new file.Ah yes, I forgot about that. Will do.
utils/adt/constraints.c ?
Please, no. Constraints are not a datatype. Do not copy the brain-dead
decision that put ri_triggers there.
Does that mean ri_triggers should come out of there?
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
2009/7/20 David Fetter <david@fetter.org>:
On Mon, Jul 20, 2009 at 10:21:53AM -0400, Tom Lane wrote:
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
* Please move the code that says that it should be in a new file to a
new file.Ah yes, I forgot about that. Will do.
utils/adt/constraints.c ?
Please, no. Constraints are not a datatype. Do not copy the brain-dead
decision that put ri_triggers there.Does that mean ri_triggers should come out of there?
The same argument could be applied to ruleutils.c, trigfuncs.c and perhaps
one or two others.
And if not there, where then?
David Fetter <david@fetter.org> writes:
Please, no. Constraints are not a datatype. Do not copy the brain-dead
decision that put ri_triggers there.
Does that mean ri_triggers should come out of there?
Not as long as we're on CVS --- it isn't worth the loss of history.
I might think about it when/if we move to git.
regards, tom lane
On Mon, Jul 20, 2009 at 01:00:12PM -0400, Tom Lane wrote:
David Fetter <david@fetter.org> writes:
Please, no. Constraints are not a datatype. Do not copy the brain-dead
decision that put ri_triggers there.Does that mean ri_triggers should come out of there?
Not as long as we're on CVS --- it isn't worth the loss of history.
I might think about it when/if we move to git.
As far as you're concerned, what's blocking that?
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
David Fetter <david@fetter.org> writes:
On Mon, Jul 20, 2009 at 01:00:12PM -0400, Tom Lane wrote:
I might think about it when/if we move to git.
As far as you're concerned, what's blocking that?
Lack of committer familiarity with git, lack of a bulletproof migration
process, uncertainty about preferred multi-branch patching techniques,
uncertainty about buildfarm integration, etc etc. Do I really need to
repeat the previous discussions to you? AFAIR none of the open
questions have gotten resolved with any clarity.
regards, tom lane
[changing subject, as we seem to have done]
Tom Lane wrote:
David Fetter <david@fetter.org> writes:
On Mon, Jul 20, 2009 at 01:00:12PM -0400, Tom Lane wrote:
I might think about it when/if we move to git.
As far as you're concerned, what's blocking that?
Lack of committer familiarity with git, lack of a bulletproof migration
process, uncertainty about preferred multi-branch patching techniques,
uncertainty about buildfarm integration, etc etc. Do I really need to
repeat the previous discussions to you? AFAIR none of the open
questions have gotten resolved with any clarity.
I have been derailed slightly by pressure of other work from making the
buildfarm client git-capable. But it is still quite high on my TODO list.
I'm not sure where we got to with doing some surgery on the CVS repo so
that we can replicate all the tags and branches properly. Has someone
fully identified what needs to be fixed so we can have all the tags?
cheers
andrew
2009/7/20 Alvaro Herrera <alvherre@commandprompt.com>:
Seems related to the new list in AfterTriggerSaveEvent, which is
used in ways that seem to conflict with its header comment ...
Reading the comment for that function, I think it is quite misleading
- mainly because the meaning of the word "event" mutates half-way
through the function. A better description might be:
/* ----------
* AfterTriggerSaveEvent()
*
* Called by ExecA[RS]...Triggers() to queue up the triggers that should
* be fired for an event.
*
* 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.
* ----------
*/
- Dean
On Monday 20 July 2009 23:17:30 Andrew Dunstan wrote:
I'm not sure where we got to with doing some surgery on the CVS repo so
that we can replicate all the tags and branches properly. Has someone
fully identified what needs to be fixed so we can have all the tags?
I think this depends on which tool we would end up using to do the final
conversion. Each tool has its own quirks.
Peter Eisentraut wrote:
On Monday 20 July 2009 23:17:30 Andrew Dunstan wrote:
I'm not sure where we got to with doing some surgery on the CVS repo so
that we can replicate all the tags and branches properly. Has someone
fully identified what needs to be fixed so we can have all the tags?I think this depends on which tool we would end up using to do the final
conversion. Each tool has its own quirks.
Didn't someone report not long ago that there were files missing in a
checkout of some not-so-old tag that made it impossible to compile such
a tree?
--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.
On Mon, 2009-07-20 at 17:24 +0100, Dean Rasheed wrote:
The same argument could be applied to ruleutils.c, trigfuncs.c and perhaps
one or two others.And if not there, where then?
I'm moving the patch status back to "waiting on author" until Alvaro's
concerns are addressed. I don't have an opinion about what should happen
to the location of the file, but some kind of decision should be made.
Regards,
Jeff Davis
2009/7/21 Jeff Davis <pgsql@j-davis.com>:
On Mon, 2009-07-20 at 17:24 +0100, Dean Rasheed wrote:
The same argument could be applied to ruleutils.c, trigfuncs.c and perhaps
one or two others.And if not there, where then?
I'm moving the patch status back to "waiting on author" until Alvaro's
concerns are addressed. I don't have an opinion about what should happen
to the location of the file, but some kind of decision should be made.Regards,
Jeff Davis
OK, I'll try to post an updated patch soon.
I'm not seeing an obvious alternative location to utils/adt. Any advice would
be appreciated.
- Dean
On Tue, 2009-07-21 at 22:01 +0100, Dean Rasheed wrote:
OK, I'll try to post an updated patch soon.
I'm not seeing an obvious alternative location to utils/adt. Any advice would
be appreciated.
My first reaction is utils/misc.
Regards,
Jeff Davis
Jeff Davis <pgsql@j-davis.com> writes:
On Tue, 2009-07-21 at 22:01 +0100, Dean Rasheed wrote:
I'm not seeing an obvious alternative location to utils/adt. Any advice would
be appreciated.
You could make a fair case for either backend/catalog or
backend/commands. Maybe the latter since that's where the core
trigger support is.
My first reaction is utils/misc.
I always hated that directory. *Double* failure to classify.
regards, tom lane
2009/7/21 Tom Lane <tgl@sss.pgh.pa.us>:
Jeff Davis <pgsql@j-davis.com> writes:
On Tue, 2009-07-21 at 22:01 +0100, Dean Rasheed wrote:
I'm not seeing an obvious alternative location to utils/adt. Any advice would
be appreciated.You could make a fair case for either backend/catalog or
backend/commands. Maybe the latter since that's where the core
trigger support is.My first reaction is utils/misc.
I always hated that directory. *Double* failure to classify.
regards, tom lane
OK, here's an updated patch.
- Dean
Attachments:
deferrable_unique.patchtext/x-patch; charset=US-ASCII; name=deferrable_unique.patchDownload
*** ./doc/src/sgml/catalogs.sgml.orig 2009-07-19 14:22:11.000000000 +0100
--- ./doc/src/sgml/catalogs.sgml 2009-07-19 14:25:04.000000000 +0100
***************
*** 4507,4513 ****
<entry><structfield>tgconstrrelid</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
! <entry>The table referenced by a referential integrity constraint</entry>
</row>
<row>
--- 4507,4514 ----
<entry><structfield>tgconstrrelid</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
! <entry>The table referenced by a referential integrity constraint,
! or the index enforcing a deferrable unique constraint.</entry>
</row>
<row>
*** ./doc/src/sgml/indexam.sgml.orig 2009-07-11 22:37:18.000000000 +0100
--- ./doc/src/sgml/indexam.sgml 2009-07-19 15:12:50.000000000 +0100
***************
*** 172,191 ****
bool *isnull,
ItemPointer heap_tid,
Relation heapRelation,
! bool check_uniqueness);
</programlisting>
Insert a new tuple into an existing index. The <literal>values</> and
<literal>isnull</> arrays give the key values to be indexed, and
<literal>heap_tid</> is the TID to be indexed.
If the access method supports unique indexes (its
<structname>pg_am</>.<structfield>amcanunique</> flag is true) then
! <literal>check_uniqueness</> might be true, in which case the access method
! must verify that there is no conflicting row; this is the only situation in
! which the access method normally needs the <literal>heapRelation</>
! parameter. See <xref linkend="index-unique-checks"> for details.
! The result is TRUE if an index entry was inserted, FALSE if not. (A FALSE
! result does not denote an error condition, but is used for cases such
! as an index method refusing to index a NULL.)
</para>
<para>
--- 172,199 ----
bool *isnull,
ItemPointer heap_tid,
Relation heapRelation,
! IndexUniqueCheck uniqueness_check,
! bool *is_unique);
</programlisting>
Insert a new tuple into an existing index. The <literal>values</> and
<literal>isnull</> arrays give the key values to be indexed, and
<literal>heap_tid</> is the TID to be indexed.
If the access method supports unique indexes (its
<structname>pg_am</>.<structfield>amcanunique</> flag is true) then
! <literal>uniqueness_check</> indicates the type of uniqueness check to
! perform to verify that there is no conflicting row. The type of uniqueness
! check varies depending on whether the uniqueness constraint is deferrable;
! see <xref linkend="index-unique-checks"> for more details. Normally the
! access method only needs the <literal>heapRelation</> parameter to
! perform uniqueness checking. If uniqueness is violated, the index
! method should not insert the index entry, except in the case of a partial
! uniqueness check. The index method should set <literal>is_unique</> to TRUE
! if the new entry is definitely unique, and FALSE if it might be non-unique.
! It should not raise an error for non-unique insert attempts.
! The method return value should be set to TRUE if an index entry was
! inserted and FALSE if not. (A FALSE result does not necessarily denote an
! error condition - it is also used for cases such as an index method
! refusing to index a NULL.)
</para>
<para>
***************
*** 706,715 ****
</para>
<para>
! Furthermore, immediately before raising a uniqueness violation
according to the above rules, the access method must recheck the
liveness of the row being inserted. If it is committed dead then
! no error should be raised. (This case cannot occur during the
ordinary scenario of inserting a row that's just been created by
the current transaction. It can happen during
<command>CREATE UNIQUE INDEX CONCURRENTLY</>, however.)
--- 714,723 ----
</para>
<para>
! Furthermore, immediately before reporting a uniqueness violation
according to the above rules, the access method must recheck the
liveness of the row being inserted. If it is committed dead then
! no violation should be reported. (This case cannot occur during the
ordinary scenario of inserting a row that's just been created by
the current transaction. It can happen during
<command>CREATE UNIQUE INDEX CONCURRENTLY</>, however.)
***************
*** 728,737 ****
</para>
<para>
! The main limitation of this scheme is that it has no convenient way
! to support deferred uniqueness checks.
! </para>
</sect1>
<sect1 id="index-cost-estimation">
--- 736,803 ----
</para>
<para>
! If the unique constraint is deferrable, then the final uniqueness check
! needs to be done later - either at the end of the statement, or when the
! transaction is committed, or as part of a <command>SET CONSTRAINTS</>
! operation. This is handled by passing one of the following enumeration
! values to the index insert access method:
!
! <programlisting>
! typedef enum IndexUniqueCheck
! {
! UNIQUE_CHECK_NO,
! UNIQUE_CHECK_YES,
! UNIQUE_CHECK_PARTIAL,
! UNIQUE_CHECK_EXISTING
! } IndexUniqueCheck;
! </programlisting>
!
! <itemizedlist>
! <listitem>
! <para>
! <literal>UNQIUE_CHECK_NO</> indicates that no uniqueness checking
! need be done (this is not a unique index).
! </para>
! </listitem>
! <listitem>
! <para>
! <literal>UNIQUE_CHECK_YES</> indicates that this is a non-deferrable
! unique index, and the uniqueness check must be done immediately, as
! described above.
! </para>
! </listitem>
! <listitem>
! <para>
! <literal>UNIQUE_CHECK_PARTIAL</> indicates that the uniqueness
! constraint is deferrable. <productname>PostgreSQL</productname>
! will use this mode to insert each row's index entry; the access
! method must allow duplicate entries into the index, and report any
! potential duplicates by setting <literal>is_unique</> to false. All
! such rows will then be re-checked later when the deferred constraint
! check is triggered.
! </para>
+ <para>
+ The access method must identify any rows which might violate the
+ uniqueness constraint, but it is not an error for it to report false
+ positives. This allows the check to be done without waiting for other
+ transactions to finish; conflicts reported here are not treated as
+ errors and will be re-checked later, by which time they may no longer
+ be conflicts.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>UNIQUE_CHECK_EXISTING</> indicates that this is a deferred
+ check of a row which was reported as a potential uniqueness violation.
+ This is only used when a deferred check is triggered (after the index
+ entry has already been inserted). The access method is expected to
+ check the existing entry for uniqueness, without re-inserting it - this
+ is a read-only operation.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
</sect1>
<sect1 id="index-cost-estimation">
*** ./doc/src/sgml/ref/create_table.sgml.orig 2009-07-11 22:21:03.000000000 +0100
--- ./doc/src/sgml/ref/create_table.sgml 2009-07-11 22:23:01.000000000 +0100
***************
*** 535,541 ****
deferrable can be postponed until the end of the transaction
(using the <xref linkend="sql-set-constraints" endterm="sql-set-constraints-title"> command).
<literal>NOT DEFERRABLE</literal> is the default. Only foreign
! key constraints currently accept this clause. All other
constraint types are not deferrable.
</para>
</listitem>
--- 535,541 ----
deferrable can be postponed until the end of the transaction
(using the <xref linkend="sql-set-constraints" endterm="sql-set-constraints-title"> command).
<literal>NOT DEFERRABLE</literal> is the default. Only foreign
! key and unique constraints currently accept this clause. All other
constraint types are not deferrable.
</para>
</listitem>
*** ./doc/src/sgml/ref/set_constraints.sgml.orig 2009-07-11 22:24:31.000000000 +0100
--- ./doc/src/sgml/ref/set_constraints.sgml 2009-07-11 22:27:37.000000000 +0100
***************
*** 66,73 ****
</para>
<para>
! Currently, only foreign key constraints are affected by this
! setting. Check and unique constraints are always effectively
not deferrable. Triggers that are declared as <quote>constraint
triggers</> are also affected.
</para>
--- 66,73 ----
</para>
<para>
! Currently, only foreign key and unique constraints are affected by this
! setting. Check constraints are always effectively
not deferrable. Triggers that are declared as <quote>constraint
triggers</> are also affected.
</para>
***************
*** 92,98 ****
This command complies with the behavior defined in the SQL
standard, except for the limitation that, in
<productname>PostgreSQL</productname>, it only applies to
! foreign-key constraints.
</para>
</refsect1>
--- 92,98 ----
This command complies with the behavior defined in the SQL
standard, except for the limitation that, in
<productname>PostgreSQL</productname>, it only applies to
! foreign-key and unique constraints.
</para>
</refsect1>
*** ./src/backend/access/gin/gininsert.c.orig 2009-07-19 10:34:09.000000000 +0100
--- ./src/backend/access/gin/gininsert.c 2009-07-19 10:40:12.000000000 +0100
***************
*** 415,421 ****
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! bool checkUnique = PG_GETARG_BOOL(5);
#endif
GinState ginstate;
MemoryContext oldCtx;
--- 415,422 ----
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! bool *is_unique = (bool *) PG_GETARG_POINTER(6);
#endif
GinState ginstate;
MemoryContext oldCtx;
*** ./src/backend/access/gist/gist.c.orig 2009-07-19 10:42:20.000000000 +0100
--- ./src/backend/access/gist/gist.c 2009-07-19 10:42:58.000000000 +0100
***************
*** 225,231 ****
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! bool checkUnique = PG_GETARG_BOOL(5);
#endif
IndexTuple itup;
GISTSTATE giststate;
--- 225,232 ----
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! bool *is_unique = (bool *) PG_GETARG_POINTER(6);
#endif
IndexTuple itup;
GISTSTATE giststate;
*** ./src/backend/access/hash/hash.c.orig 2009-07-19 10:39:43.000000000 +0100
--- ./src/backend/access/hash/hash.c 2009-07-19 10:41:29.000000000 +0100
***************
*** 165,171 ****
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! bool checkUnique = PG_GETARG_BOOL(5);
#endif
IndexTuple itup;
--- 165,172 ----
#ifdef NOT_USED
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! bool *is_unique = (bool *) PG_GETARG_POINTER(6);
#endif
IndexTuple itup;
*** ./src/backend/access/heap/tuptoaster.c.orig 2009-07-22 11:31:43.000000000 +0100
--- ./src/backend/access/heap/tuptoaster.c 2009-07-22 11:31:42.000000000 +0100
***************
*** 1229,1235 ****
*/
index_insert(toastidx, t_values, t_isnull,
&(toasttup->t_self),
! toastrel, toastidx->rd_index->indisunique);
/*
* Free memory
--- 1229,1238 ----
*/
index_insert(toastidx, t_values, t_isnull,
&(toasttup->t_self),
! toastrel,
! toastidx->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
! NULL);
/*
* Free memory
*** ./src/backend/access/index/indexam.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/index/indexam.c 2009-07-18 12:37:35.000000000 +0100
***************
*** 185,193 ****
bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! bool check_uniqueness)
{
FmgrInfo *procedure;
RELATION_CHECKS;
GET_REL_PROCEDURE(aminsert);
--- 185,195 ----
bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! IndexUniqueCheck uniqueness_check,
! bool *is_unique)
{
FmgrInfo *procedure;
+ bool unique_result, result;
RELATION_CHECKS;
GET_REL_PROCEDURE(aminsert);
***************
*** 195,207 ****
/*
* have the am's insert proc do all the work.
*/
! return DatumGetBool(FunctionCall6(procedure,
! PointerGetDatum(indexRelation),
! PointerGetDatum(values),
! PointerGetDatum(isnull),
! PointerGetDatum(heap_t_ctid),
! PointerGetDatum(heapRelation),
! BoolGetDatum(check_uniqueness)));
}
/*
--- 197,229 ----
/*
* have the am's insert proc do all the work.
*/
! result = DatumGetBool(FunctionCall7(procedure,
! PointerGetDatum(indexRelation),
! PointerGetDatum(values),
! PointerGetDatum(isnull),
! PointerGetDatum(heap_t_ctid),
! PointerGetDatum(heapRelation),
! Int32GetDatum((int32) uniqueness_check),
! PointerGetDatum(&unique_result)));
!
! /*
! * check the uniqueness result if necessary. We deliberately
! * allow non-unique entries into the index for a partial check,
! * which is used to support a deferrable unique constraint.
! */
! if ( (uniqueness_check == UNIQUE_CHECK_YES ||
! uniqueness_check == UNIQUE_CHECK_EXISTING) && !unique_result )
! {
! ereport(ERROR,
! (errcode(ERRCODE_UNIQUE_VIOLATION),
! errmsg("duplicate key value violates unique constraint \"%s\"",
! RelationGetRelationName(indexRelation))));
! }
!
! if (is_unique)
! *is_unique = unique_result;
!
! return result;
}
/*
*** ./src/backend/access/nbtree/nbtinsert.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/nbtree/nbtinsert.c 2009-07-19 14:39:19.000000000 +0100
***************
*** 50,56 ****
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
Relation heapRel, Buffer buf, OffsetNumber ioffset,
! ScanKey itup_scankey);
static void _bt_findinsertloc(Relation rel,
Buffer *bufptr,
OffsetNumber *offsetptr,
--- 50,57 ----
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
Relation heapRel, Buffer buf, OffsetNumber ioffset,
! ScanKey itup_scankey, IndexUniqueCheck unique_check,
! bool *is_unique);
static void _bt_findinsertloc(Relation rel,
Buffer *bufptr,
OffsetNumber *offsetptr,
***************
*** 85,95 ****
*
* This routine is called by the public interface routines, btbuild
* and btinsert. By here, itup is filled in, including the TID.
*/
! void
_bt_doinsert(Relation rel, IndexTuple itup,
! bool index_is_unique, Relation heapRel)
{
int natts = rel->rd_rel->relnatts;
ScanKey itup_scankey;
BTStack stack;
--- 86,107 ----
*
* This routine is called by the public interface routines, btbuild
* and btinsert. By here, itup is filled in, including the TID.
+ *
+ * If unique_check is UNIQUE_CHECK_NO or UNIQUE_CHECK_PARTIAL, this
+ * will allow duplicates and set is_unique to false if the entry is
+ * potentially non-unique.
+ *
+ * Otherwise (UNIQUE_CHECK_YES or UNIQUE_CHECK_EXISTING) it will not
+ * allow duplicates. The attempt to insert a duplicate will not raise
+ * an error, but is_unique will be set the false. The caller is
+ * expected to check this.
*/
! bool
_bt_doinsert(Relation rel, IndexTuple itup,
! IndexUniqueCheck unique_check, Relation heapRel,
! bool *is_unique)
{
+ bool do_insert = unique_check != UNIQUE_CHECK_EXISTING;
int natts = rel->rd_rel->relnatts;
ScanKey itup_scankey;
BTStack stack;
***************
*** 134,165 ****
*
* If we must wait for another xact, we release the lock while waiting,
* and then must start over completely.
*/
! if (index_is_unique)
{
TransactionId xwait;
offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
! xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey);
! if (TransactionIdIsValid(xwait))
{
! /* Have to wait for the other guy ... */
! _bt_relbuf(rel, buf);
! XactLockTableWait(xwait);
! /* start over... */
! _bt_freestack(stack);
! goto top;
}
}
! /* do the insertion */
! _bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
! _bt_insertonpg(rel, buf, stack, itup, offset, false);
/* be tidy */
_bt_freestack(stack);
_bt_freeskey(itup_scankey);
}
/*
--- 146,205 ----
*
* If we must wait for another xact, we release the lock while waiting,
* and then must start over completely.
+ *
+ * For a partial uniqueness check, 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 (unique_check != 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,
! unique_check, is_unique);
! if (!*is_unique)
{
! if (TransactionIdIsValid(xwait))
! {
! /* Have to wait for the other guy ... */
! _bt_relbuf(rel, buf);
! XactLockTableWait(xwait);
! /* start over... */
! _bt_freestack(stack);
! goto top;
! }
!
! /*
! * If we only did a partial uniqueness check, then this is
! * only a potential conflict; let the entry in. Otherwise
! * it is a real conflict, so don't add it to the index.
! */
! if (unique_check != UNIQUE_CHECK_PARTIAL)
! do_insert = false;
}
}
+ else
+ *is_unique = false;
! if (do_insert)
! {
! /* 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 do_insert;
}
/*
***************
*** 169,182 ****
* also point to end-of-page, which means that the first tuple to check
* is the first tuple on the next page.
*
! * 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().
*/
static TransactionId
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
! Buffer buf, OffsetNumber offset, ScanKey itup_scankey)
{
TupleDesc itupdesc = RelationGetDescr(rel);
int natts = rel->rd_rel->relnatts;
SnapshotData SnapshotDirty;
--- 209,230 ----
* also point to end-of-page, which means that the first tuple to check
* is the first tuple on the next page.
*
! * This only sets is_unique to true if the entry is definitely unique.
! * Otherwise the entry is a definite or potential conflict, and the
! * return value is the xact ID we have to wait on to find out for sure.
! *
! * The return value will be InvalidTransactionId if the entry is
! * definitely unique or non-unique, or if we are only doing a partial
! * check. For a partial unique check, _bt_doinsert() will not block,
! * but just go ahead with the insert. The proper check will be done later
! * (at the end of the statement or transaction).
*/
static TransactionId
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
! Buffer buf, OffsetNumber offset, ScanKey itup_scankey,
! IndexUniqueCheck unique_check, bool *is_unique)
{
+ TransactionId xwait = InvalidTransactionId;
TupleDesc itupdesc = RelationGetDescr(rel);
int natts = rel->rd_rel->relnatts;
SnapshotData SnapshotDirty;
***************
*** 184,189 ****
--- 232,238 ----
Page page;
BTPageOpaque opaque;
Buffer nbuf = InvalidBuffer;
+ bool found = false;
InitDirtySnapshot(SnapshotDirty);
***************
*** 191,196 ****
--- 240,248 ----
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
maxoff = PageGetMaxOffsetNumber(page);
+ /* Assume unique unless we find a duplicate */
+ *is_unique = true;
+
/*
* Scan over all equal tuples, looking for live conflicts.
*/
***************
*** 247,263 ****
*/
if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
{
! /* it is a duplicate */
! TransactionId xwait =
! (TransactionIdIsValid(SnapshotDirty.xmin)) ?
! SnapshotDirty.xmin : SnapshotDirty.xmax;
/*
! * If this tuple is being updated by other transaction
! * then we have to wait for its commit/abort.
*/
! if (TransactionIdIsValid(xwait))
{
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
/* Tell _bt_doinsert to wait... */
--- 299,353 ----
*/
if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
{
! /*
! * We have 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. This is used for deferrable
! * unique constraints.
! */
! if (unique_check == UNIQUE_CHECK_PARTIAL)
! {
! *is_unique = false;
! if (nbuf != InvalidBuffer)
! _bt_relbuf(rel, nbuf);
! return InvalidTransactionId;
! }
/*
! * Otherwise we need to do a proper check to see if
! * it really is a duplicate. First check if it is
! * being updated by another transaction, in which case
! * we may have to wait for it's commit/abort.
*/
! if (TransactionIdIsValid(SnapshotDirty.xmin) ||
! TransactionIdIsValid(SnapshotDirty.xmax))
{
+ xwait = TransactionIdIsValid(SnapshotDirty.xmin) ?
+ SnapshotDirty.xmin : SnapshotDirty.xmax;
+
+ /*
+ * If this is a deferred uniqueness check, of a
+ * tuple that we have already inserted, then there
+ * may be other conflicting tuples which we can
+ * see without waiting, so reset the snapshot and
+ * keep looking, but remember that we may need to
+ * wait on this one after all.
+ */
+ if (unique_check == UNIQUE_CHECK_EXISTING)
+ {
+ SnapshotDirty.xmin = InvalidTransactionId;
+ SnapshotDirty.xmax = InvalidTransactionId;
+
+ goto next_tuple;
+ }
+
+ /*
+ * Otherwise return the other transaction ID
+ * that we have to wait for.
+ */
+ *is_unique = false;
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
/* Tell _bt_doinsert to wait... */
***************
*** 265,271 ****
}
/*
! * Otherwise we have a definite conflict. But before
* complaining, look to see if the tuple we want to insert
* is itself now committed dead --- if so, don't complain.
* This is a waste of time in normal scenarios but we must
--- 355,361 ----
}
/*
! * This appears to be a definite conflict. But before
* complaining, look to see if the tuple we want to insert
* is itself now committed dead --- if so, don't complain.
* This is a waste of time in normal scenarios but we must
***************
*** 289,304 ****
else
{
/*
! * It's been deleted, so no error, and no need to
! * continue searching
*/
break;
}
! ereport(ERROR,
! (errcode(ERRCODE_UNIQUE_VIOLATION),
! errmsg("duplicate key value violates unique constraint \"%s\"",
! RelationGetRelationName(rel))));
}
else if (all_dead)
{
--- 379,415 ----
else
{
/*
! * It's been deleted, so no error. If this is a
! * deferred check, there may be other duplicates which
! * are live.
*/
+ if (unique_check == UNIQUE_CHECK_EXISTING)
+ goto next_tuple;
+
+ /* Otherwise, no need to continue searching */
break;
}
! /*
! * We have a definite match. If this is a deferred check,
! * of a tuple that we have already inserted, the first
! * match is not a conflict, so look for another. This
! * is used for the final check of a deferrable uniquenes
! * constraint.
! */
! if (unique_check == UNIQUE_CHECK_EXISTING && !found)
! {
! found = true;
! goto next_tuple;
! }
!
! /*
! * This is a definite conflict.
! */
! *is_unique = false;
! if (nbuf != InvalidBuffer)
! _bt_relbuf(rel, nbuf);
! return InvalidTransactionId;
}
else if (all_dead)
{
***************
*** 318,323 ****
--- 429,435 ----
}
}
+ next_tuple:
/*
* Advance to next tuple to continue checking.
*/
***************
*** 352,357 ****
--- 464,481 ----
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
+ /*
+ * If we reach here, then we didn't find a duplicate. However, in a
+ * deferred uniqueness check, we may have found 1 visible tuple plus
+ * another being updated in another transaction, in which case we need
+ * to wait for it's commit/abort.
+ */
+ if (found && TransactionIdIsValid(xwait))
+ {
+ *is_unique = false;
+ return xwait;
+ }
+
return InvalidTransactionId;
}
*** ./src/backend/access/nbtree/nbtree.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/access/nbtree/nbtree.c 2009-07-18 11:29:54.000000000 +0100
***************
*** 212,234 ****
Datum
btinsert(PG_FUNCTION_ARGS)
{
! Relation rel = (Relation) PG_GETARG_POINTER(0);
! Datum *values = (Datum *) PG_GETARG_POINTER(1);
! 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);
! 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);
pfree(itup);
! PG_RETURN_BOOL(true);
}
/*
--- 212,236 ----
Datum
btinsert(PG_FUNCTION_ARGS)
{
! Relation rel = (Relation) PG_GETARG_POINTER(0);
! Datum *values = (Datum *) PG_GETARG_POINTER(1);
! bool *isnull = (bool *) PG_GETARG_POINTER(2);
! ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
! Relation heapRel = (Relation) PG_GETARG_POINTER(4);
! IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
! bool *is_unique = (bool *) PG_GETARG_POINTER(6);
! IndexTuple itup;
! bool ok;
/* generate an index tuple */
itup = index_form_tuple(RelationGetDescr(rel), values, isnull);
itup->t_tid = *ht_ctid;
! ok = _bt_doinsert(rel, itup, checkUnique, heapRel, is_unique);
pfree(itup);
! PG_RETURN_BOOL(ok);
}
/*
*** ./src/backend/bootstrap/bootparse.y.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/bootstrap/bootparse.y 2009-07-04 16:54:25.000000000 +0100
***************
*** 266,272 ****
NULL,
$10,
NULL, NIL,
! false, false, false,
false, false, true, false, false);
do_end();
}
--- 266,272 ----
NULL,
$10,
NULL, NIL,
! false, false, false, false, false,
false, false, true, false, false);
do_end();
}
***************
*** 284,290 ****
NULL,
$11,
NULL, NIL,
! true, false, false,
false, false, true, false, false);
do_end();
}
--- 284,290 ----
NULL,
$11,
NULL, NIL,
! true, false, false, false, false,
false, false, true, false, false);
do_end();
}
*** ./src/backend/catalog/index.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/index.c 2009-07-19 10:30:37.000000000 +0100
***************
*** 40,53 ****
--- 40,58 ----
#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/gramparse.h"
+ #include "parser/parser.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
***************
*** 726,733 ****
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
! false, /* isDeferrable */
! false, /* isDeferred */
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
--- 731,738 ----
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
! indexInfo->ii_Deferrable,
! indexInfo->ii_InitDeferred,
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
***************
*** 753,758 ****
--- 758,808 ----
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+
+ /*
+ * If the constraint is deferrable, define the deferred uniqueness
+ * checking triggers. The index will temporarily allow duplicates and
+ * then these triggers will re-check for duplicates at the end of the
+ * statement or transaction. These triggers are only queued if
+ * potentially non-unique entries are detected during index insertion.
+ */
+ if (indexInfo->ii_Deferrable)
+ {
+ char *nsname = get_namespace_name(namespaceId);
+ RangeVar *heapRel = makeRangeVar(nsname, pstrdup(RelationGetRelationName(heapRelation)), -1);
+ RangeVar *indexRel = makeRangeVar(nsname, pstrdup(indexRelationName), -1);
+ CreateTrigStmt *trigger;
+
+ /*
+ * Advance the command counter so that we can see the
+ * newly-entered catalog tuples for the index.
+ */
+ CommandCounterIncrement();
+
+ trigger = makeNode(CreateTrigStmt);
+ trigger->trigname = pstrdup(indexRelationName);
+ trigger->relation = heapRel;
+ trigger->funcname = SystemFuncName("unique_key_check_ins");
+ trigger->args = NIL;
+ trigger->before = false;
+ trigger->row = true;
+ trigger->events = TRIGGER_TYPE_INSERT;
+ trigger->isconstraint = true;
+ trigger->deferrable = true;
+ trigger->initdeferred = indexInfo->ii_InitDeferred;
+ trigger->constrrel = indexRel;
+
+ (void) CreateTrigger(trigger, conOid, false);
+
+ CommandCounterIncrement();
+
+ trigger->funcname = SystemFuncName("unique_key_check_upd");
+ trigger->events = TRIGGER_TYPE_UPDATE;
+
+ (void) CreateTrigger(trigger, conOid, false);
+
+ CommandCounterIncrement();
+ }
}
else
{
***************
*** 1026,1031 ****
--- 1076,1105 ----
ii->ii_Concurrent = false;
ii->ii_BrokenHotChain = false;
+ /* fetch the details of the index constraint if any */
+ ii->ii_ConstraintId = get_index_constraint(RelationGetRelid(index));
+ if (OidIsValid(ii->ii_ConstraintId))
+ {
+ HeapTuple ht_constr = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(ii->ii_ConstraintId),
+ 0, 0, 0);
+ Form_pg_constraint conrec;
+
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u", ii->ii_ConstraintId);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ ii->ii_Deferrable = conrec->condeferrable;
+ ii->ii_InitDeferred = conrec->condeferred;
+
+ ReleaseSysCache(ht_constr);
+ }
+ else
+ {
+ ii->ii_Deferrable = false;
+ ii->ii_InitDeferred = false;
+ }
+
return ii;
}
***************
*** 2190,2196 ****
isnull,
&rootTuple,
heapRelation,
! indexInfo->ii_Unique);
state->tups_inserted += 1;
}
--- 2264,2272 ----
isnull,
&rootTuple,
heapRelation,
! indexInfo->ii_Unique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
! NULL);
state->tups_inserted += 1;
}
*** ./src/backend/catalog/indexing.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/indexing.c 2009-07-18 12:01:58.000000000 +0100
***************
*** 134,140 ****
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
! relationDescs[i]->rd_index->indisunique);
}
ExecDropSingleTupleTableSlot(slot);
--- 134,142 ----
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
! relationDescs[i]->rd_index->indisunique ?
! UNIQUE_CHECK_YES : UNIQUE_CHECK_NO,
! NULL);
}
ExecDropSingleTupleTableSlot(slot);
*** ./src/backend/catalog/sql_features.txt.orig 2009-07-11 23:37:21.000000000 +0100
--- ./src/backend/catalog/sql_features.txt 2009-07-11 23:38:32.000000000 +0100
***************
*** 289,295 ****
F696 Additional translation documentation NO
F701 Referential update actions YES
F711 ALTER domain YES
! F721 Deferrable constraints NO foreign keys only
F731 INSERT column privileges YES
F741 Referential MATCH types NO no partial match yet
F751 View CHECK enhancements NO
--- 289,295 ----
F696 Additional translation documentation NO
F701 Referential update actions YES
F711 ALTER domain YES
! 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
*** ./src/backend/catalog/toasting.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/catalog/toasting.c 2009-07-04 16:56:10.000000000 +0100
***************
*** 241,246 ****
--- 241,249 ----
indexInfo->ii_ReadyForInserts = true;
indexInfo->ii_Concurrent = false;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_ConstraintId = InvalidOid;
+ indexInfo->ii_Deferrable = false;
+ indexInfo->ii_InitDeferred = false;
classObjectId[0] = OID_BTREE_OPS_OID;
classObjectId[1] = INT4_BTREE_OPS_OID;
*** ./src/backend/commands/copy.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/copy.c 2009-07-04 18:15:59.000000000 +0100
***************
*** 2119,2124 ****
--- 2119,2126 ----
if (!skip_tuple)
{
+ List *problemIndexList = NIL;
+
/* Place tuple in tuple slot */
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
***************
*** 2130,2139 ****
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
! ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;
--- 2132,2141 ----
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple, problemIndexList);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;
*** ./src/backend/commands/indexcmds.c.orig 2009-07-19 09:02:44.000000000 +0100
--- ./src/backend/commands/indexcmds.c 2009-07-19 08:54:46.000000000 +0100
***************
*** 87,92 ****
--- 87,94 ----
* '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.
+ * 'constrDeferrable': constraint is DEFERRABLE (UNIQUE constraint).
+ * 'constrInitDeferred': constraint is INITIALLY DEFERRED (UNIQUE constraint).
* '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.)
***************
*** 107,112 ****
--- 109,116 ----
bool unique,
bool primary,
bool isconstraint,
+ bool constrDeferrable,
+ bool constrInitDeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
***************
*** 419,424 ****
--- 423,431 ----
indexInfo->ii_ReadyForInserts = !concurrent;
indexInfo->ii_Concurrent = concurrent;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_ConstraintId = InvalidOid; /* index_create() may create this */
+ indexInfo->ii_Deferrable = constrDeferrable;
+ indexInfo->ii_InitDeferred = constrInitDeferred;
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
*** ./src/backend/commands/Makefile.orig 2009-07-22 10:31:21.000000000 +0100
--- ./src/backend/commands/Makefile 2009-07-22 10:33:15.000000000 +0100
***************
*** 13,19 ****
include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.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 \
--- 13,19 ----
include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.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 \
*** ./src/backend/commands/tablecmds.c.orig 2009-07-22 11:33:37.000000000 +0100
--- ./src/backend/commands/tablecmds.c 2009-07-22 11:33:36.000000000 +0100
***************
*** 4388,4393 ****
--- 4388,4395 ----
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->constrDeferrable,
+ stmt->constrInitDeferred,
true, /* is_alter_table */
check_rights,
skip_build,
*** ./src/backend/commands/trigger.c.orig 2009-07-04 15:44:36.000000000 +0100
--- ./src/backend/commands/trigger.c 2009-07-22 11:26:45.000000000 +0100
***************
*** 63,69 ****
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
/*
--- 63,70 ----
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
! List *problemIndexList);
/*
***************
*** 211,224 ****
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 (OidIsValid(constraintOid))
{
snprintf(constrtrigname, sizeof(constrtrigname),
! "RI_ConstraintTrigger_%u", trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
--- 212,258 ----
trigoid = GetNewOid(tgrel);
/*
! * If trigger is for a constraint, the passed-in name is the constraint
! * name; save that and build a unique trigger name based on the type of
! * the constraint to avoid collisions with user-selected trigger names.
*/
if (OidIsValid(constraintOid))
{
+ HeapTuple ht_constr = SearchSysCache(CONSTROID,
+ ObjectIdGetDatum(constraintOid),
+ 0, 0, 0);
+ Form_pg_constraint conrec;
+ char *prefix;
+
+ if (!HeapTupleIsValid(ht_constr))
+ elog(ERROR, "cache lookup failed for constraint %u", constraintOid);
+ conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
+
+ /*
+ * Prefix for trigger name, based on constraint type.
+ */
+ switch (conrec->contype)
+ {
+ case CONSTRAINT_CHECK:
+ prefix = "Check_ConstraintTrigger";
+ break;
+ case CONSTRAINT_FOREIGN:
+ prefix = "RI_ConstraintTrigger";
+ break;
+ case CONSTRAINT_PRIMARY:
+ prefix = "PK_ConstraintTrigger";
+ break;
+ case CONSTRAINT_UNIQUE:
+ prefix = "Unique_ConstraintTrigger";
+ break;
+ default:
+ prefix = "ConstraintTrigger";
+ break;
+ }
+ ReleaseSysCache(ht_constr);
+
snprintf(constrtrigname, sizeof(constrtrigname),
! "%s_%u", prefix, trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
***************
*** 1639,1645 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL);
}
HeapTuple
--- 1673,1679 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! false, NULL, NULL, NIL);
}
HeapTuple
***************
*** 1695,1707 ****
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
! HeapTuple trigtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple);
}
void
--- 1729,1741 ----
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
! HeapTuple trigtuple, List *problemIndexList)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! true, NULL, trigtuple, problemIndexList);
}
void
***************
*** 1770,1776 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL);
}
bool
--- 1804,1810 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! false, NULL, NULL, NIL);
}
bool
***************
*** 1847,1853 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL);
heap_freetuple(trigtuple);
}
}
--- 1881,1887 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! true, trigtuple, NULL, NIL);
heap_freetuple(trigtuple);
}
}
***************
*** 1918,1924 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL);
}
HeapTuple
--- 1952,1958 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! false, NULL, NULL, NIL);
}
HeapTuple
***************
*** 1988,1994 ****
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
! ItemPointer tupleid, HeapTuple newtuple)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 2022,2029 ----
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
! ItemPointer tupleid, HeapTuple newtuple,
! List *problemIndexList)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
***************
*** 1998,2004 ****
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple);
heap_freetuple(trigtuple);
}
}
--- 2033,2039 ----
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! true, trigtuple, newtuple, problemIndexList);
heap_freetuple(trigtuple);
}
}
***************
*** 2069,2075 ****
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL);
}
--- 2104,2110 ----
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
! false, NULL, NULL, NIL);
}
***************
*** 3782,3796 ****
/* ----------
* AfterTriggerSaveEvent()
*
! * Called by ExecA[RS]...Triggers() to add the event to the queue.
*
! * NOTE: should be called only if we've determined that an event must
! * be added to the queue.
* ----------
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
! HeapTuple oldtup, HeapTuple newtup)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
--- 3817,3834 ----
/* ----------
* AfterTriggerSaveEvent()
*
! * Called by ExecA[RS]...Triggers() to queue up the triggers that should
! * be fired for an event.
*
! * 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,
! List *problemIndexList)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
***************
*** 3951,3956 ****
--- 3989,4006 ----
}
/*
+ * If the trigger is a deferred unique constraint check trigger,
+ * only execute it if the unique constraint was potentially
+ * violated, which we know from index insertion time.
+ */
+ if (trigger->tgfoid == F_UNIQUE_KEY_CHECK_INS ||
+ trigger->tgfoid == F_UNIQUE_KEY_CHECK_UPD)
+ {
+ if (!list_member_oid(problemIndexList, trigger->tgconstrrelid))
+ continue; /* Uniqueness definitely not violated */
+ }
+
+ /*
* Fill in event structure and add it to the current query's queue.
*/
new_shared.ats_event =
*** ./src/backend/executor/execMain.c.orig 2009-07-04 15:43:47.000000000 +0100
--- ./src/backend/executor/execMain.c 2009-07-04 17:45:41.000000000 +0100
***************
*** 1753,1758 ****
--- 1753,1759 ----
ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
Oid newId;
+ List *problemIndexList = NIL;
/*
* get the heap tuple out of the tuple table slot, making sure we have a
***************
*** 1834,1843 ****
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
! ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
--- 1835,1845 ----
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self),
! estate, false);
/* AFTER ROW INSERT Triggers */
! ExecARInsertTriggers(estate, resultRelInfo, tuple, problemIndexList);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
***************
*** 1999,2004 ****
--- 2001,2007 ----
HTSU_Result result;
ItemPointerData update_ctid;
TransactionId update_xmax;
+ List *problemIndexList = NIL;
/*
* abort the operation if not running transactions
***************
*** 2132,2141 ****
* 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);
/* AFTER ROW UPDATE Triggers */
! ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
--- 2135,2145 ----
* If it's a HOT update, we mustn't insert new index entries.
*/
if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
! problemIndexList = ExecInsertIndexTuples(slot, &(tuple->t_self),
! estate, false);
/* AFTER ROW UPDATE Triggers */
! ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple, problemIndexList);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
*** ./src/backend/executor/execUtils.c.orig 2009-07-19 09:28:05.000000000 +0100
--- ./src/backend/executor/execUtils.c 2009-07-19 13:27:48.000000000 +0100
***************
*** 1036,1044 ****
* 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
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
--- 1036,1048 ----
* 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?
+ *
+ * This returns a list of OIDs for any unique indexes
+ * whose constraint check was deferred and which had
+ * potential (unconfirmed) conflicts.
* ----------------------------------------------------------------
*/
! List *
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
***************
*** 1053,1058 ****
--- 1057,1063 ----
ExprContext *econtext;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
+ List *result = NIL;
/*
* Get information from the result relation info structure.
***************
*** 1077,1083 ****
*/
for (i = 0; i < numIndices; i++)
{
! IndexInfo *indexInfo;
if (relationDescs[i] == NULL)
continue;
--- 1082,1090 ----
*/
for (i = 0; i < numIndices; i++)
{
! IndexInfo *indexInfo;
! IndexUniqueCheck uniqueCheck;
! bool isUnique;
if (relationDescs[i] == NULL)
continue;
***************
*** 1125,1143 ****
* 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.
*/
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);
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
}
/*
--- 1132,1175 ----
* 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.
+ *
+ * If there is a deferrable unique constraint associated with the
+ * index, then we don't enforce uniqueness here, we just do a partial
+ * check and record if the tuple was potentially non-unique.
*/
+ if (is_vacuum || !relationDescs[i]->rd_index->indisunique)
+ uniqueCheck = UNIQUE_CHECK_NO;
+ else if (indexInfo->ii_Deferrable)
+ uniqueCheck = UNIQUE_CHECK_PARTIAL;
+ else
+ uniqueCheck = UNIQUE_CHECK_YES;
+
index_insert(relationDescs[i], /* index relation */
! values, /* array of index Datums */
! isnull, /* null flags */
! tupleid, /* tid of heap tuple */
! heapRelation, /* heap relation */
! uniqueCheck, /* type of uniqueness check to do */
! &isUnique); /* result of uniqueness check */
!
! if (uniqueCheck == 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,
! relationDescs[i]->rd_index->indexrelid);
! }
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
+
+ return result;
}
/*
*** ./src/backend/nodes/copyfuncs.c.orig 2009-07-22 11:35:30.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c 2009-07-22 11:35:29.000000000 +0100
***************
*** 2091,2096 ****
--- 2091,2098 ----
COPY_NODE_FIELD(keys);
COPY_NODE_FIELD(options);
COPY_STRING_FIELD(indexspace);
+ COPY_SCALAR_FIELD(deferrable);
+ COPY_SCALAR_FIELD(initdeferred);
return newnode;
}
***************
*** 2510,2515 ****
--- 2512,2519 ----
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
+ COPY_SCALAR_FIELD(constrDeferrable);
+ COPY_SCALAR_FIELD(constrInitDeferred);
COPY_SCALAR_FIELD(concurrent);
return newnode;
*** ./src/backend/nodes/equalfuncs.c.orig 2009-07-22 11:36:42.000000000 +0100
--- ./src/backend/nodes/equalfuncs.c 2009-07-22 11:36:40.000000000 +0100
***************
*** 1160,1165 ****
--- 1160,1167 ----
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
+ COMPARE_SCALAR_FIELD(constrDeferrable);
+ COMPARE_SCALAR_FIELD(constrInitDeferred);
COMPARE_SCALAR_FIELD(concurrent);
return true;
***************
*** 2069,2074 ****
--- 2071,2078 ----
COMPARE_NODE_FIELD(keys);
COMPARE_NODE_FIELD(options);
COMPARE_STRING_FIELD(indexspace);
+ COMPARE_SCALAR_FIELD(deferrable);
+ COMPARE_SCALAR_FIELD(initdeferred);
return true;
}
*** ./src/backend/nodes/outfuncs.c.orig 2009-07-19 09:45:02.000000000 +0100
--- ./src/backend/nodes/outfuncs.c 2009-07-19 09:45:01.000000000 +0100
***************
*** 1734,1739 ****
--- 1734,1741 ----
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
+ WRITE_BOOL_FIELD(constrDeferrable);
+ WRITE_BOOL_FIELD(constrInitDeferred);
WRITE_BOOL_FIELD(concurrent);
}
***************
*** 2292,2297 ****
--- 2294,2301 ----
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
+ WRITE_BOOL_FIELD(deferrable);
+ WRITE_BOOL_FIELD(initdeferred);
break;
case CONSTR_CHECK:
*** ./src/backend/parser/gram.y.orig 2009-07-22 11:38:06.000000000 +0100
--- ./src/backend/parser/gram.y 2009-07-22 11:38:05.000000000 +0100
***************
*** 2212,2217 ****
--- 2212,2219 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| NULL_P
***************
*** 2223,2228 ****
--- 2225,2232 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE opt_definition OptConsTableSpace
***************
*** 2235,2240 ****
--- 2239,2246 ----
n->keys = NULL;
n->options = $2;
n->indexspace = $3;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| PRIMARY KEY opt_definition OptConsTableSpace
***************
*** 2247,2252 ****
--- 2253,2260 ----
n->keys = NULL;
n->options = $3;
n->indexspace = $4;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| CHECK '(' a_expr ')'
***************
*** 2258,2263 ****
--- 2266,2273 ----
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| DEFAULT b_expr
***************
*** 2269,2274 ****
--- 2279,2286 ----
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
***************
*** 2398,2406 ****
--- 2410,2421 ----
n->raw_expr = $3;
n->cooked_expr = NULL;
n->indexspace = NULL;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
+ ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_UNIQUE;
***************
*** 2410,2415 ****
--- 2425,2432 ----
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
***************
*** 2422,2427 ****
--- 2439,2446 ----
n->keys = $4;
n->options = $6;
n->indexspace = $7;
+ n->deferrable = FALSE;
+ n->initdeferred = FALSE;
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
*** ./src/backend/parser/parse_utilcmd.c.orig 2009-07-19 09:53:57.000000000 +0100
--- ./src/backend/parser/parse_utilcmd.c 2009-07-19 09:53:56.000000000 +0100
***************
*** 34,39 ****
--- 34,40 ----
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_opclass.h"
+ #include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablecmds.h"
***************
*** 809,815 ****
else if (!index->unique)
index->isconstraint = false;
else
! index->isconstraint = OidIsValid(get_index_constraint(source_relid));
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
--- 810,843 ----
else if (!index->unique)
index->isconstraint = false;
else
! {
! /*
! * The index is marked UNIQUE, so there may be an associated unique
! * constraint. If there is, we need to fetch its DEFERRABLE and
! * INITIALLY DEFERRED attributes.
! */
! Oid constraintId = get_index_constraint(source_relid);
!
! if (OidIsValid(constraintId))
! {
! HeapTuple ht_constr = SearchSysCache(CONSTROID,
! ObjectIdGetDatum(constraintId),
! 0, 0, 0);
! Form_pg_constraint conrec;
!
! if (!HeapTupleIsValid(ht_constr))
! elog(ERROR, "cache lookup failed for constraint %u", constraintId);
! conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
!
! index->isconstraint = true;
! index->constrDeferrable = conrec->condeferrable;
! index->constrInitDeferred = conrec->condeferred;
!
! ReleaseSysCache(ht_constr);
! }
! else
! index->isconstraint = false;
! }
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
***************
*** 1092,1097 ****
--- 1120,1127 ----
*/
}
index->isconstraint = true;
+ index->constrDeferrable = constraint->deferrable;
+ index->constrInitDeferred = constraint->initdeferred;
if (constraint->name != NULL)
index->idxname = pstrdup(constraint->name);
***************
*** 1853,1861 ****
* 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.
*/
static void
transformConstraintAttrs(List *constraintList)
{
--- 1883,1898 ----
* to attach constraint attributes to their primary constraint nodes
* and detect inconsistent/misplaced constraint attributes.
*
! * NOTE: currently, attributes are only supported for FOREIGN KEY and
! * UNIQUE primary constraints, but someday they ought to be supported
! * for other constraints.
*/
+ #define SUPPORTS_ATTRS(node) \
+ ((node) != NULL && \
+ (IsA((node), FkConstraint) || \
+ (IsA((node), Constraint) && \
+ ((Constraint *)(node))->contype == CONSTR_UNIQUE)))
+
static void
transformConstraintAttrs(List *constraintList)
{
***************
*** 1882,1889 ****
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
--- 1919,1925 ----
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
! if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
***************
*** 1892,1902 ****
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! ((FkConstraint *) lastprimarynode)->deferrable = true;
break;
case CONSTR_ATTR_NOT_DEFERRABLE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
--- 1928,1941 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! if (IsA(lastprimarynode, FkConstraint))
! ((FkConstraint *) lastprimarynode)->deferrable = true;
! else
! ((Constraint *) lastprimarynode)->deferrable = true;
break;
+
case CONSTR_ATTR_NOT_DEFERRABLE:
! if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
***************
*** 1905,1920 ****
(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")));
break;
case CONSTR_ATTR_DEFERRED:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
--- 1944,1971 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
! 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 (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
***************
*** 1923,1943 ****
(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")));
break;
case CONSTR_ATTR_IMMEDIATE:
! if (lastprimarynode == NULL ||
! !IsA(lastprimarynode, FkConstraint))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
--- 1974,2009 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
/*
* If only INITIALLY DEFERRED appears, assume 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 (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
***************
*** 1946,1953 ****
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
! ((FkConstraint *) lastprimarynode)->initdeferred = false;
break;
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
--- 2012,2023 ----
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
! if (IsA(lastprimarynode, FkConstraint))
! ((FkConstraint *) lastprimarynode)->initdeferred = false;
! else
! ((Constraint *) lastprimarynode)->initdeferred = false;
break;
+
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;
*** ./src/backend/tcop/utility.c.orig 2009-07-19 09:57:54.000000000 +0100
--- ./src/backend/tcop/utility.c 2009-07-19 09:57:53.000000000 +0100
***************
*** 795,800 ****
--- 795,802 ----
stmt->unique,
stmt->primary,
stmt->isconstraint,
+ stmt->constrDeferrable,
+ stmt->constrInitDeferred,
false, /* is_alter_table */
true, /* check_rights */
false, /* skip_build */
*** ./src/backend/utils/adt/ruleutils.c.orig 2009-07-19 10:00:24.000000000 +0100
--- ./src/backend/utils/adt/ruleutils.c 2009-07-19 10:00:23.000000000 +0100
***************
*** 1096,1101 ****
--- 1096,1106 ----
quote_identifier(get_tablespace_name(tblspc)));
}
+ if (conForm->condeferrable)
+ appendStringInfo(&buf, " DEFERRABLE");
+ if (conForm->condeferred)
+ appendStringInfo(&buf, " INITIALLY DEFERRED");
+
break;
}
case CONSTRAINT_CHECK:
*** ./src/bin/pg_dump/pg_dump.c.orig 2009-07-22 11:39:55.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.c 2009-07-22 11:39:53.000000000 +0100
***************
*** 3657,3663 ****
i_contableoid,
i_conoid,
i_tablespace,
! i_options;
int ntups;
for (i = 0; i < numTables; i++)
--- 3657,3665 ----
i_contableoid,
i_conoid,
i_tablespace,
! i_options,
! i_condeferrable,
! i_coninitdeferred;
int ntups;
for (i = 0; i < numTables; i++)
***************
*** 3698,3704 ****
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "array_to_string(t.reloptions, ', ') AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--- 3700,3708 ----
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "array_to_string(t.reloptions, ', ') AS options, "
! "c.condeferrable AS condeferrable, "
! "c.condeferred AS coninitdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
***************
*** 3724,3730 ****
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "null AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--- 3728,3736 ----
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
***************
*** 3750,3756 ****
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
--- 3756,3764 ----
"c.tableoid AS contableoid, "
"c.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_catalog.pg_index i "
"JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
"LEFT JOIN pg_catalog.pg_depend d "
***************
*** 3778,3784 ****
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
--- 3786,3794 ----
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
***************
*** 3801,3807 ****
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
--- 3811,3819 ----
"0::oid AS contableoid, "
"t.oid AS conoid, "
"NULL AS tablespace, "
! "null AS options, "
! "false AS condeferrable, "
! "false AS coninitdeferred "
"FROM pg_index i, pg_class t "
"WHERE t.oid = i.indexrelid "
"AND i.indrelid = '%u'::oid "
***************
*** 3827,3832 ****
--- 3839,3846 ----
i_conoid = PQfnumber(res, "conoid");
i_tablespace = PQfnumber(res, "tablespace");
i_options = PQfnumber(res, "options");
+ i_condeferrable = PQfnumber(res, "condeferrable");
+ i_coninitdeferred = PQfnumber(res, "coninitdeferred");
indxinfo = (IndxInfo *) malloc(ntups * sizeof(IndxInfo));
constrinfo = (ConstraintInfo *) malloc(ntups * sizeof(ConstraintInfo));
***************
*** 3885,3890 ****
--- 3899,3906 ----
constrinfo[j].conindex = indxinfo[j].dobj.dumpId;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
+ constrinfo[j].condeferrable = *(PQgetvalue(res, j, i_condeferrable)) == 't';
+ constrinfo[j].coninitdeferred = *(PQgetvalue(res, j, i_coninitdeferred)) == 't';
indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
***************
*** 3989,3994 ****
--- 4005,4012 ----
constrinfo[j].conindex = 0;
constrinfo[j].conislocal = true;
constrinfo[j].separate = true;
+ constrinfo[j].condeferrable = false; /* Part of condef */
+ constrinfo[j].coninitdeferred = false; /* Part of condef */
}
PQclear(res);
***************
*** 4073,4078 ****
--- 4091,4098 ----
constrinfo[i].conindex = 0;
constrinfo[i].conislocal = true;
constrinfo[i].separate = false;
+ constrinfo[i].condeferrable = false;
+ constrinfo[i].coninitdeferred = false;
/*
* Make the domain depend on the constraint, ensuring it won't be
***************
*** 5072,5077 ****
--- 5092,5099 ----
constrs[j].conindex = 0;
constrs[j].conislocal = (PQgetvalue(res, j, 4)[0] == 't');
constrs[j].separate = false;
+ constrs[j].condeferrable = false;
+ constrs[j].coninitdeferred = false;
constrs[j].dobj.dump = tbinfo->dobj.dump;
***************
*** 10340,10345 ****
--- 10362,10374 ----
if (indxinfo->options && strlen(indxinfo->options) > 0)
appendPQExpBuffer(q, " WITH (%s)", indxinfo->options);
+ if (coninfo->condeferrable)
+ {
+ appendPQExpBuffer(q, " DEFERRABLE");
+ if (coninfo->coninitdeferred)
+ appendPQExpBuffer(q, " INITIALLY DEFERRED");
+ }
+
appendPQExpBuffer(q, ";\n");
/* If the index is clustered, we need to record that. */
*** ./src/bin/pg_dump/pg_dump.h.orig 2009-07-09 10:15:54.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.h 2009-07-09 10:21:12.000000000 +0100
***************
*** 344,349 ****
--- 344,351 ----
DumpId conindex; /* identifies associated index if any */
bool conislocal; /* TRUE if constraint has local definition */
bool separate; /* TRUE if must dump as separate item */
+ bool condeferrable; /* TRUE if constraint is DEFERRABLE */
+ bool coninitdeferred; /* TRUE if constraint is INITIALLY DEFERRED */
} ConstraintInfo;
typedef struct _procLangInfo
*** ./src/bin/psql/describe.c.orig 2009-07-22 11:41:17.000000000 +0100
--- ./src/bin/psql/describe.c 2009-07-22 11:41:16.000000000 +0100
***************
*** 1324,1329 ****
--- 1324,1340 ----
appendPQExpBuffer(&buf, "i.indisvalid, ");
else
appendPQExpBuffer(&buf, "true as indisvalid, ");
+ if (pset.sversion >= 80500)
+ appendPQExpBuffer(&buf,
+ "EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE d.objid = i.indexrelid "
+ "AND con.oid = d.refobjid AND con.condeferrable) "
+ "as condeferrable, EXISTS (SELECT 1 FROM "
+ "pg_catalog.pg_depend d, pg_catalog.pg_constraint con "
+ "WHERE d.objid = i.indexrelid AND con.oid = d.refobjid "
+ "AND con.condeferred) as condeferred, ");
+ else
+ appendPQExpBuffer(&buf, "false as condeferrable, false as condeferred, ");
appendPQExpBuffer(&buf, "a.amname, c2.relname,\n"
" 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"
***************
*** 1345,1353 ****
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);
if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, "));
--- 1356,1366 ----
char *indisprimary = PQgetvalue(result, 0, 1);
char *indisclustered = PQgetvalue(result, 0, 2);
char *indisvalid = PQgetvalue(result, 0, 3);
! 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, "));
***************
*** 1370,1375 ****
--- 1383,1394 ----
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);
***************
*** 1431,1436 ****
--- 1450,1466 ----
else
appendPQExpBuffer(&buf, "true as indisvalid, ");
appendPQExpBuffer(&buf, "pg_catalog.pg_get_indexdef(i.indexrelid, 0, true)");
+ if (pset.sversion >= 80500)
+ appendPQExpBuffer(&buf,
+ ", EXISTS (SELECT 1 FROM pg_catalog.pg_depend d, "
+ "pg_catalog.pg_constraint con WHERE d.objid = i.indexrelid "
+ "AND con.oid = d.refobjid AND con.condeferrable) "
+ "as condeferrable, EXISTS (SELECT 1 FROM "
+ "pg_catalog.pg_depend d, pg_catalog.pg_constraint con "
+ "WHERE d.objid = i.indexrelid AND con.oid = d.refobjid "
+ "AND con.condeferred) as condeferred");
+ else
+ appendPQExpBuffer(&buf, ", false as condeferrable, false as condeferred");
if (pset.sversion >= 80000)
appendPQExpBuffer(&buf, ", c2.reltablespace");
appendPQExpBuffer(&buf,
***************
*** 1477,1488 ****
if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
appendPQExpBuffer(&buf, " INVALID");
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)),
false);
}
}
--- 1507,1524 ----
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, 8)),
false);
}
}
*** ./src/include/access/genam.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/access/genam.h 2009-07-18 11:19:44.000000000 +0100
***************
*** 85,90 ****
--- 85,118 ----
typedef struct IndexScanDescData *IndexScanDesc;
typedef struct SysScanDescData *SysScanDesc;
+ /*
+ * Enumeration specifying the type of uniqueness check to perform for
+ * index_insert().
+ *
+ * For deferrable unique constraints, UNIQUE_CHECK_PARTIAL is used at
+ * insertion time. This tests if the tuple is unique but doesn't error,
+ * block or prevent the insertion if the tuple appears not to be unique.
+ * Since it isn't blocking, it can't know for sure if the tuple is not
+ * unique (the other transaction might be rolled back). The return value
+ * of index_insert() in this case is true if the tuple is definitely
+ * unique and false if it is possibly non-unique.
+ *
+ * UNIQUE_CHECK_EXISTING is for deferred unqiue constraint checks. The
+ * tuple is already in the index when the check is performed, so it is
+ * not inserted again. This just checks if the tuple in the index is
+ * unique.
+ */
+ typedef enum IndexUniqueCheck
+ {
+ UNIQUE_CHECK_NO, /* Don't do any uniqueness checking */
+ UNIQUE_CHECK_YES, /* Enforce uniqueness at insertion time */
+ /* (possibly blocking on another xact) */
+ UNIQUE_CHECK_PARTIAL, /* Test uniqueness, but don't block and */
+ /* no error if potentially not unique */
+ UNIQUE_CHECK_EXISTING /* Don't do the insert, just check if */
+ /* the existing tuple is unique */
+ } IndexUniqueCheck;
+
/*
* generalized index_ interface routines (in indexam.c)
***************
*** 103,109 ****
Datum *values, bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! bool check_uniqueness);
extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
--- 131,138 ----
Datum *values, bool *isnull,
ItemPointer heap_t_ctid,
Relation heapRelation,
! IndexUniqueCheck uniqueness,
! bool *is_unique);
extern IndexScanDesc index_beginscan(Relation heapRelation,
Relation indexRelation,
*** ./src/include/access/nbtree.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/access/nbtree.h 2009-07-18 12:34:19.000000000 +0100
***************
*** 517,524 ****
/*
* prototypes for functions in nbtinsert.c
*/
! extern void _bt_doinsert(Relation rel, IndexTuple itup,
! bool index_is_unique, 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);
--- 517,524 ----
/*
* prototypes for functions in nbtinsert.c
*/
! extern bool _bt_doinsert(Relation rel, IndexTuple itup,
! IndexUniqueCheck unique_check, Relation heapRel, bool *is_unique);
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);
*** ./src/include/catalog/pg_proc.h.orig 2009-07-11 13:12:26.000000000 +0100
--- ./src/include/catalog/pg_proc.h 2009-07-18 12:48:21.000000000 +0100
***************
*** 663,669 ****
DESCR("btree(internal)");
DATA(insert OID = 636 ( btgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ btgetbitmap _null_ _null_ _null_ ));
DESCR("btree(internal)");
! DATA(insert OID = 331 ( btinsert PGNSP PGUID 12 1 0 0 f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ btinsert _null_ _null_ _null_ ));
DESCR("btree(internal)");
DATA(insert OID = 333 ( btbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ btbeginscan _null_ _null_ _null_ ));
DESCR("btree(internal)");
--- 663,669 ----
DESCR("btree(internal)");
DATA(insert OID = 636 ( btgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ btgetbitmap _null_ _null_ _null_ ));
DESCR("btree(internal)");
! DATA(insert OID = 331 ( btinsert PGNSP PGUID 12 1 0 0 f f f t f v 7 0 16 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ btinsert _null_ _null_ _null_ ));
DESCR("btree(internal)");
DATA(insert OID = 333 ( btbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ btbeginscan _null_ _null_ _null_ ));
DESCR("btree(internal)");
***************
*** 782,788 ****
DESCR("hash(internal)");
DATA(insert OID = 637 ( hashgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ hashgetbitmap _null_ _null_ _null_ ));
DESCR("hash(internal)");
! DATA(insert OID = 441 ( hashinsert PGNSP PGUID 12 1 0 0 f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ hashinsert _null_ _null_ _null_ ));
DESCR("hash(internal)");
DATA(insert OID = 443 ( hashbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ hashbeginscan _null_ _null_ _null_ ));
DESCR("hash(internal)");
--- 782,788 ----
DESCR("hash(internal)");
DATA(insert OID = 637 ( hashgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ hashgetbitmap _null_ _null_ _null_ ));
DESCR("hash(internal)");
! DATA(insert OID = 441 ( hashinsert PGNSP PGUID 12 1 0 0 f f f t f v 7 0 16 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ hashinsert _null_ _null_ _null_ ));
DESCR("hash(internal)");
DATA(insert OID = 443 ( hashbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ hashbeginscan _null_ _null_ _null_ ));
DESCR("hash(internal)");
***************
*** 1067,1073 ****
DESCR("gist(internal)");
DATA(insert OID = 638 ( gistgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ gistgetbitmap _null_ _null_ _null_ ));
DESCR("gist(internal)");
! DATA(insert OID = 775 ( gistinsert PGNSP PGUID 12 1 0 0 f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gistinsert _null_ _null_ _null_ ));
DESCR("gist(internal)");
DATA(insert OID = 777 ( gistbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gistbeginscan _null_ _null_ _null_ ));
DESCR("gist(internal)");
--- 1067,1073 ----
DESCR("gist(internal)");
DATA(insert OID = 638 ( gistgetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ gistgetbitmap _null_ _null_ _null_ ));
DESCR("gist(internal)");
! DATA(insert OID = 775 ( gistinsert PGNSP PGUID 12 1 0 0 f f f t f v 7 0 16 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gistinsert _null_ _null_ _null_ ));
DESCR("gist(internal)");
DATA(insert OID = 777 ( gistbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ gistbeginscan _null_ _null_ _null_ ));
DESCR("gist(internal)");
***************
*** 2349,2354 ****
--- 2349,2360 ----
DATA(insert OID = 1655 ( RI_FKey_noaction_upd PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ RI_FKey_noaction_upd _null_ _null_ _null_ ));
DESCR("referential integrity ON UPDATE NO ACTION");
+ /* Deferrable unique constraint triggers */
+ DATA(insert OID = 1029 ( unique_key_check_ins PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_check_ins _null_ _null_ _null_ ));
+ DESCR("deferred check UNIQUE ... DEFERRABLE");
+ DATA(insert OID = 1030 ( unique_key_check_upd PGNSP PGUID 12 1 0 0 f f f t f v 0 0 2279 "" _null_ _null_ _null_ _null_ unique_key_check_upd _null_ _null_ _null_ ));
+ DESCR("deferred check UNIQUE ... DEFERRABLE");
+
DATA(insert OID = 1666 ( varbiteq PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "1562 1562" _null_ _null_ _null_ _null_ biteq _null_ _null_ _null_ ));
DESCR("equal");
DATA(insert OID = 1667 ( varbitne PGNSP PGUID 12 1 0 0 f f f t f i 2 0 16 "1562 1562" _null_ _null_ _null_ _null_ bitne _null_ _null_ _null_ ));
***************
*** 4187,4193 ****
/* GIN */
DATA(insert OID = 2731 ( gingetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ gingetbitmap _null_ _null_ _null_ ));
DESCR("gin(internal)");
! DATA(insert OID = 2732 ( gininsert PGNSP PGUID 12 1 0 0 f f f t f v 6 0 16 "2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gininsert _null_ _null_ _null_ ));
DESCR("gin(internal)");
DATA(insert OID = 2733 ( ginbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ ginbeginscan _null_ _null_ _null_ ));
DESCR("gin(internal)");
--- 4193,4199 ----
/* GIN */
DATA(insert OID = 2731 ( gingetbitmap PGNSP PGUID 12 1 0 0 f f f t f v 2 0 20 "2281 2281" _null_ _null_ _null_ _null_ gingetbitmap _null_ _null_ _null_ ));
DESCR("gin(internal)");
! DATA(insert OID = 2732 ( gininsert PGNSP PGUID 12 1 0 0 f f f t f v 7 0 16 "2281 2281 2281 2281 2281 2281 2281" _null_ _null_ _null_ _null_ gininsert _null_ _null_ _null_ ));
DESCR("gin(internal)");
DATA(insert OID = 2733 ( ginbeginscan PGNSP PGUID 12 1 0 0 f f f t f v 3 0 2281 "2281 2281 2281" _null_ _null_ _null_ _null_ ginbeginscan _null_ _null_ _null_ ));
DESCR("gin(internal)");
*** ./src/include/commands/defrem.h.orig 2009-07-19 10:03:56.000000000 +0100
--- ./src/include/commands/defrem.h 2009-07-19 10:03:56.000000000 +0100
***************
*** 29,34 ****
--- 29,36 ----
bool unique,
bool primary,
bool isconstraint,
+ bool constrDeferrable,
+ bool constrInitDeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
*** ./src/include/commands/trigger.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/commands/trigger.h 2009-07-04 16:46:36.000000000 +0100
***************
*** 131,137 ****
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
! HeapTuple trigtuple);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
--- 131,138 ----
HeapTuple trigtuple);
extern void ExecARInsertTriggers(EState *estate,
ResultRelInfo *relinfo,
! HeapTuple trigtuple,
! List *problemIndexList);
extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate,
***************
*** 153,159 ****
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
! HeapTuple newtuple);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
--- 154,161 ----
extern void ExecARUpdateTriggers(EState *estate,
ResultRelInfo *relinfo,
ItemPointer tupleid,
! HeapTuple newtuple,
! List *problemIndexList);
extern void ExecBSTruncateTriggers(EState *estate,
ResultRelInfo *relinfo);
extern void ExecASTruncateTriggers(EState *estate,
*** ./src/include/executor/executor.h.orig 2009-07-19 10:05:15.000000000 +0100
--- ./src/include/executor/executor.h 2009-07-19 10:05:15.000000000 +0100
***************
*** 301,307 ****
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern void ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum);
extern void RegisterExprContextCallback(ExprContext *econtext,
--- 301,307 ----
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
! extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum);
extern void RegisterExprContextCallback(ExprContext *econtext,
*** ./src/include/nodes/execnodes.h.orig 2009-07-04 15:42:49.000000000 +0100
--- ./src/include/nodes/execnodes.h 2009-07-04 16:34:51.000000000 +0100
***************
*** 44,49 ****
--- 44,52 ----
* ReadyForInserts is it valid for inserts?
* Concurrent are we doing a concurrent index build?
* BrokenHotChain did we detect any broken HOT chains?
+ * ConstraintId OID of constraint associated with this index
+ * Deferrable is the constraint DEFERRABLE?
+ * InitDeferred is the constraint INITIALLY DEFERRED?
*
* ii_Concurrent and ii_BrokenHotChain are used only during index build;
* they're conventionally set to false otherwise.
***************
*** 62,67 ****
--- 65,73 ----
bool ii_ReadyForInserts;
bool ii_Concurrent;
bool ii_BrokenHotChain;
+ Oid ii_ConstraintId;
+ bool ii_Deferrable;
+ bool ii_InitDeferred;
} IndexInfo;
/* ----------------
*** ./src/include/nodes/parsenodes.h.orig 2009-07-22 11:43:07.000000000 +0100
--- ./src/include/nodes/parsenodes.h 2009-07-22 11:43:06.000000000 +0100
***************
*** 1355,1361 ****
* 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).
* ----------
*/
--- 1355,1361 ----
* 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
! * Constraint and FkConstraint nodes.
* ----------
*/
***************
*** 1385,1390 ****
--- 1385,1392 ----
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;
/* ----------
***************
*** 1864,1869 ****
--- 1866,1873 ----
bool unique; /* is index unique? */
bool primary; /* is index on primary key? */
bool isconstraint; /* is it from a CONSTRAINT clause? */
+ bool constrDeferrable; /* is the constraint DEFERRABLE? */
+ bool constrInitDeferred; /* is the constraint INITIALLY DEFERRED? */
bool concurrent; /* should this be a concurrent index build? */
} IndexStmt;
*** ./src/include/utils/builtins.h.orig 2009-07-19 10:09:52.000000000 +0100
--- ./src/include/utils/builtins.h 2009-07-22 10:28:49.000000000 +0100
***************
*** 1029,1034 ****
--- 1029,1038 ----
/* access/transam/twophase.c */
extern Datum pg_prepared_xact(PG_FUNCTION_ARGS);
+ /* commands/constraint.c */
+ extern Datum unique_key_check_ins(PG_FUNCTION_ARGS);
+ extern Datum unique_key_check_upd(PG_FUNCTION_ARGS);
+
/* commands/prepare.c */
extern Datum pg_prepared_statement(PG_FUNCTION_ARGS);
*** ./src/test/regress/input/constraints.source.orig 2009-07-11 18:17:41.000000000 +0100
--- ./src/test/regress/input/constraints.source 2009-07-11 18:59:44.000000000 +0100
***************
*** 259,261 ****
--- 259,345 ----
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');
+
+ -- default is immediate so this should fail
+ UPDATE UNIQUE_TBL SET i = 1 WHERE i = 0;
+
+ -- check is done at end of statement, so this should succeed
+ UPDATE UNIQUE_TBL SET i = i+1;
+
+ -- 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 '' AS five, * FROM UNIQUE_TBL;
+
+ -- 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 '' AS five, * 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 which 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
+
+ DROP TABLE UNIQUE_TBL;
+
*** ./src/test/regress/output/constraints.source.orig 2009-07-11 19:07:40.000000000 +0100
--- ./src/test/regress/output/constraints.source 2009-07-11 19:08:46.000000000 +0100
***************
*** 375,377 ****
--- 375,457 ----
(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');
+ -- default is immediate so this should fail
+ UPDATE UNIQUE_TBL SET i = 1 WHERE i = 0;
+ ERROR: duplicate key value violates unique constraint "unique_tbl_i_key"
+ -- check is done at end of statement, so this should succeed
+ UPDATE UNIQUE_TBL SET i = i+1;
+ -- 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 '' AS five, * FROM UNIQUE_TBL;
+ five | i | t
+ ------+---+-------
+ | 1 | one
+ | 2 | two
+ | 4 | four
+ | 5 | five
+ | 3 | three
+ (5 rows)
+
+ -- 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 '' AS five, * FROM UNIQUE_TBL;
+ five | 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 which 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"
+ DROP TABLE UNIQUE_TBL;
2009/7/22 Dean Rasheed <dean.a.rasheed@googlemail.com>:
OK, here's an updated patch.
In case it's not obvious, I've opted for backend/commands for the new file.
- Dean
On Wed, 2009-07-22 at 12:25 +0100, Dean Rasheed wrote:
OK, here's an updated patch.
One thing that Alvaro mentioned that you didn't do yet is use the macro
to return from the function (either PG_RETURN_VOID() or
PG_RETURN_NULL()).
You seem to be following the document here:
http://www.postgresql.org/docs/8.4/static/trigger-example.html
So I'm not going to hold you up on this issue. It's passed my review,
and I'm marking it as such on the commitfest page.
Thanks!
Regards,
Jeff Davis
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
[ latest deferrable-unique patch ]
I'm starting to look at this now. I haven't read the patch itself yet,
but I went through the discussion thread. I'm not sure whether we have
actually decided that we want to commit this, as opposed to treating it
as an investigation exercise.
The main thing that is bothering me is something Dean pointed out at
the very beginning: the patch will not scale well for large numbers of
conflicts. The reason this bothers me is that from what I recall of
past complaints about our lack of deferred unique checks, the *primary*
use case is things like "update foo set id = id + 1". So I'm afraid
that this might be a toy implementation that's not useful in practice.
The three likely responses to this objection seem to be
1. "You're right, we should reject the patch until that's fixed."
2. "You're wrong, the patch is perfectly useful as-is."
3. "You're right, but we should commit anyway because it will be fixed
later."
I don't think I'm going to believe #3 though, because there's no
concrete design for a fix on the table, much less a commitment to
implement it.
Comments?
regards, tom lane
On Mon, 2009-07-27 at 13:14 -0400, Tom Lane wrote:
The main thing that is bothering me is something Dean pointed out at
the very beginning: the patch will not scale well for large numbers of
conflicts.
The way I see it, there are two strategies:
(a) build up a list as you go, and check it later
(b) do a check of the full table at once
Is there another reasonable option?
The patch seems like a reasonable implementation of (a), so what it's
missing is the ability to fall back to (b) when the list gets too large
(compared with available memory or relative to the table size).
Are you suggesting that we wait until (b) is implemented, or do you
envision something else entirely?
Regards,
Jeff Davis
2009/7/27 Jeff Davis <pgsql@j-davis.com>:
On Mon, 2009-07-27 at 13:14 -0400, Tom Lane wrote:
The main thing that is bothering me is something Dean pointed out at
the very beginning: the patch will not scale well for large numbers of
conflicts.
I'd pefer to take option #3, and I want to try to tackle the scaling
issue next. I didn't do it as part of this patch because it has
already grown very big, and I think the scaling problem is largely an
orthogonal issue which affects all AR triggers.
The way I see it, there are two strategies:
(a) build up a list as you go, and check it later
(b) do a check of the full table at onceIs there another reasonable option?
IMO (a) is the only way to go, since you don't know until the end of
an update command what proportion of rows were potential conflicts,
and so whether or not to do a row-by-row or a wholesale check.
I think that this will be almost entirely a patch to trigger.c, for
which there is already a separate TODO item.
Actually I see 2 parts to this:
(1). make trigger queue scalable with easy mechanism to switchover to
wholesale check
(2). implement wholesale check for UNIQUE
but (1) seems like to lion's share of the work.
(and then maybe (3). wholesale check for RI constraints)
- Dean
Show quoted text
The patch seems like a reasonable implementation of (a), so what it's
missing is the ability to fall back to (b) when the list gets too large
(compared with available memory or relative to the table size).Are you suggesting that we wait until (b) is implemented, or do you
envision something else entirely?Regards,
Jeff Davis
Jeff Davis <pgsql@j-davis.com> writes:
The way I see it, there are two strategies:
(a) build up a list as you go, and check it later
(b) do a check of the full table at once
The patch seems like a reasonable implementation of (a), so what it's
missing is the ability to fall back to (b) when the list gets too large
(compared with available memory or relative to the table size).
Are you suggesting that we wait until (b) is implemented, or do you
envision something else entirely?
What's mainly bothering me is the fear that real use cases are so
heavily skewed towards (b) that we shouldn't bother implementing (a)
at all. In that case we'd end up throwing away a lot of this patch,
or else carrying a lot of not-very-useful code.
I don't have a lot of concrete evidence about this, but as I said
most of the complaints we've heard are suggesting bulk updates, eg
http://archives.postgresql.org/pgsql-bugs/2003-05/msg00052.php
http://archives.postgresql.org/pgsql-bugs/2004-09/msg00248.php
http://archives.postgresql.org/pgsql-bugs/2007-07/msg00070.php
There are some suggesting the other, eg,
http://archives.postgresql.org/pgsql-bugs/2008-01/msg00172.php
but they're clearly in the minority.
It's possible that the first group are just using simple updates
to illustrate the bug, rather than as examples of what they
really want to do, but I'm unconvinced of that.
Anyway, I'd feel a lot better about it if there were a near-term
plan to do something about (b).
regards, tom lane
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
Actually I see 2 parts to this:
(1). make trigger queue scalable with easy mechanism to switchover to
wholesale check
(2). implement wholesale check for UNIQUE
but (1) seems like to lion's share of the work.
(and then maybe (3). wholesale check for RI constraints)
We already have those wholesale checks. It might take a bit of
refactoring to make them conveniently callable in this context,
but there shouldn't be a lot of new code there.
I think that "scalable" is sort of missing the mark as far as the needed
queue behavior goes. "Lossy" might be a better adjective. The main
reason nobody has implemented spill-to-disk for the existing trigger
queue is that by the time you've filled memory with the queue,
performance already sucks, because it's gonna take forever to fire all
those trigger events. Being able to make the queue even bigger isn't
going to make it suck less, quite the opposite. You need some way to
slide into bulk instead of retail checking strategies, just like join
strategies vary depending on the number of rows involved.
(In fact, in a real sense these ARE join problems ... maybe we should
stop thinking of them as fire-a-bunch-of-triggers and instead think of
executing a single check query with appropriate WHERE clause ...)
regards, tom lane
2009/7/27 Tom Lane <tgl@sss.pgh.pa.us>:
Jeff Davis <pgsql@j-davis.com> writes:
The way I see it, there are two strategies:
(a) build up a list as you go, and check it later
(b) do a check of the full table at onceThe patch seems like a reasonable implementation of (a), so what it's
missing is the ability to fall back to (b) when the list gets too large
(compared with available memory or relative to the table size).Are you suggesting that we wait until (b) is implemented, or do you
envision something else entirely?What's mainly bothering me is the fear that real use cases are so
heavily skewed towards (b) that we shouldn't bother implementing (a)
at all. In that case we'd end up throwing away a lot of this patch,
or else carrying a lot of not-very-useful code.
I have a definite use for (a), which is why I started this. I typically
write web apps on top of PG using an ORM, and I've had to write
some fairly intricate code to save things in the right order to avoid
violating my own unique constraints.
I agree that (b) is important too, but typically when I do this kind of
bulk update, I drop and re-add the indexes.
More importantly, if you implement (b) first, how is it ever going
to help with (a)? I thought it best to do (a) first and then think about
how to switchover to (b).
- Dean
Show quoted text
I don't have a lot of concrete evidence about this, but as I said
most of the complaints we've heard are suggesting bulk updates, eg
http://archives.postgresql.org/pgsql-bugs/2003-05/msg00052.php
http://archives.postgresql.org/pgsql-bugs/2004-09/msg00248.php
http://archives.postgresql.org/pgsql-bugs/2007-07/msg00070.phpThere are some suggesting the other, eg,
http://archives.postgresql.org/pgsql-bugs/2008-01/msg00172.php
but they're clearly in the minority.It's possible that the first group are just using simple updates
to illustrate the bug, rather than as examples of what they
really want to do, but I'm unconvinced of that.Anyway, I'd feel a lot better about it if there were a near-term
plan to do something about (b).regards, tom lane
2009/7/27 Tom Lane <tgl@sss.pgh.pa.us>:
(In fact, in a real sense these ARE join problems ... maybe we should
stop thinking of them as fire-a-bunch-of-triggers and instead think of
executing a single check query with appropriate WHERE clause ...)
Hmm. Presumably that is the same WHERE clause as the UPDATE.
But it has to execute after the update. How does it avoid re-executing
functions, re-incrementing sequences, etc... ?
- Dean
[ still poking around in this patch ]
Jeff Davis <pgsql@j-davis.com> writes:
10. You're overloading tgconstrrelid to hold the constraint's index's
oid, when normally it holds the referenced table. You should probably
document this a little better, because I don't think that field is used
to hold an index oid anywhere else.
Having documented this kluge doesn't make it any more acceptable. It
risks breaking any number of things that expect that field to reference
a table, not an index.
There seem to be two reasonable alternatives here:
* Add another column to pg_trigger (and hence the Trigger data
structure) to carry the index OID.
* Store the index OID as a trigger argument (which means converting it
to text form, and then back again for each use).
The former approach would be a lot easier if anyone is trying to query
pg_trigger for the triggers associated with an index, but OTOH I'm not
sure anyone would really need to do that. The latter approach minimizes
catalog changes at the cost of a bit of runtime inefficiency; but
considering everything else that goes on to fire a deferred trigger,
worrying about a strtoul call is probably silly.
If we did add another column to pg_trigger, I'd be a bit tempted to add
one to pg_constraint too. tgconstrrelid for RI triggers is a mirror of
a pg_constraint column, and it seems like the index data perhaps should
be as well. (Indeed, one of the thing that got me annoyed about this
kluge in the first place was that it broke that relationship without
changing the documentation.)
Comments, opinions?
regards, tom lane
On Mon, 2009-07-27 at 16:33 -0400, Tom Lane wrote:
If we did add another column to pg_trigger, I'd be a bit tempted to add
one to pg_constraint too. tgconstrrelid for RI triggers is a mirror of
a pg_constraint column, and it seems like the index data perhaps should
be as well. (Indeed, one of the thing that got me annoyed about this
kluge in the first place was that it broke that relationship without
changing the documentation.)
That would work great for me, as I was planning to add such a column
anyway for my "generalized index constraints" patch.
Regards,
Jeff Davis
2009/7/27 Jeff Davis <pgsql@j-davis.com>:
On Mon, 2009-07-27 at 16:33 -0400, Tom Lane wrote:
If we did add another column to pg_trigger, I'd be a bit tempted to add
one to pg_constraint too. tgconstrrelid for RI triggers is a mirror of
a pg_constraint column, and it seems like the index data perhaps should
be as well. (Indeed, one of the thing that got me annoyed about this
kluge in the first place was that it broke that relationship without
changing the documentation.)That would work great for me, as I was planning to add such a column
anyway for my "generalized index constraints" patch.
Yes that seems like the most sensible option to me.
- Dean
On Mon, Jul 27, 2009 at 7:51 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:
(In fact, in a real sense these ARE join problems ... maybe we should
stop thinking of them as fire-a-bunch-of-triggers and instead think of
executing a single check query with appropriate WHERE clause ...)
A while back I suggested keeping the deferred trigger list in
tuplestore format and executing the trigger check as a query between
the materialized tuplestore of and the tuples on disk.
I love the idea of doing a full SQL query but the problem is that
there's no particular reason to assume that a deferred trigger list
large enough to warrant a wholesale check is actually a significant
percentage of the table. It might only take a few hundred or few
thousand checks to warrant a bitmap index scan instead of repeated
index probes but a plain SQL query with no reference back to the
deferred list would have to check all the millions of rows in the
table for no purpose.
For foreign keys I was picturing some way to issue an SQL statement
like "SELECT from tabletocheck where ctid in (<magic parameter>) and
not exists (select 1 from referenced_table where pk =
tabletocheck.fk)" and then somehow pass the list of ctids from the
deferred list.
Greg Stark <gsstark@mit.edu> writes:
For foreign keys I was picturing some way to issue an SQL statement
like "SELECT from tabletocheck where ctid in (<magic parameter>) and
not exists (select 1 from referenced_table where pk =
tabletocheck.fk)" and then somehow pass the list of ctids from the
deferred list.
I have no problem with having some "magic" in there --- FK checks
already have to do some things that aren't expressible in standard SQL,
because of snapshotting issues. However, the above still presumes that
we can afford to store all the CTIDs involved. Which is more or less
exactly what the trigger event queue is doing now. We need a different
view about that bit, I think.
Perhaps we could remember the CTIDs the transaction has touched in a
bitmap (which could become lossy under memory pressure)? On a lossy
page you'd need to check all the tuples to see which ones have xmins
matching your transaction; but that's not too terrible and at least
you're not visiting pages you don't need to.
regards, tom lane
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
2009/7/27 Jeff Davis <pgsql@j-davis.com>:
On Mon, 2009-07-27 at 16:33 -0400, Tom Lane wrote:
If we did add another column to pg_trigger, I'd be a bit tempted to add
one to pg_constraint too.That would work great for me, as I was planning to add such a column
anyway for my "generalized index constraints" patch.
Yes that seems like the most sensible option to me.
Okay. I will go off and do that, then come back to Dean's patch.
Proposed names:
pg_trigger.tgconstrindid analogous to tgconstrrelid
pg_constraint.conuindid analogous to confrelid
where the latter will be populated for any unique or pkey constraint.
The former will be always 0 for the moment, but we'll start filling
it in with Dean's patch.
(thinks...) Actually, u for unique might be a poor choice if Jeff's
patch goes in and starts using it for things that aren't exactly
unique indexes. Should it be just conindid?
regards, tom lane
On Mon, 2009-07-27 at 19:12 -0400, Tom Lane wrote:
(thinks...) Actually, u for unique might be a poor choice if Jeff's
patch goes in and starts using it for things that aren't exactly
unique indexes. Should it be just conindid?
My thoughts exactly.
Regards,
Jeff Davis
On Tue, Jul 28, 2009 at 12:04 AM, Tom Lane<tgl@sss.pgh.pa.us> wrote:
Greg Stark <gsstark@mit.edu> writes:
For foreign keys I was picturing some way to issue an SQL statement
like "SELECT from tabletocheck where ctid in (<magic parameter>) and
not exists (select 1 from referenced_table where pk =
tabletocheck.fk)" and then somehow pass the list of ctids from the
deferred list.I have no problem with having some "magic" in there --- FK checks
already have to do some things that aren't expressible in standard SQL,
because of snapshotting issues. However, the above still presumes that
we can afford to store all the CTIDs involved. Which is more or less
exactly what the trigger event queue is doing now. We need a different
view about that bit, I think.
It wasn't clear in the SQL example but I described storing them in a
tuplestore. The tuplestore would get spilled to disk automatically but
the SQL query could (semi)join against it using whatever form of join
is most efficient.
Now that I look at that query though it's pretty clear that we don't
actually have a good join type to handle this. We would need some kind
of merge-join which knew that ctids from a sequential scan were in
order (and could ensure that they were in fact in order).
There might be a better way to write the query above in a way that
didn't need anything special like that. The need to check that the
inserted tuple is still live is a big part of the headache. If we
could check for violations first and then go back and check any
violations if there are any to see if they come from live tuples that
would save a lot of work.
Jeff Davis <pgsql@j-davis.com> writes:
On Mon, 2009-07-27 at 19:12 -0400, Tom Lane wrote:
(thinks...) Actually, u for unique might be a poor choice if Jeff's
patch goes in and starts using it for things that aren't exactly
unique indexes. Should it be just conindid?
My thoughts exactly.
On looking closer, it appears we should populate this column for FKEY
constraints too --- for example this would greatly simplify some
of the information_schema views (cf _pg_underlying_index).
Now those references will also point at unique indexes, but still this
seems like another reason to use a relatively generic column name.
conindid it is.
regards, tom lane
... btw, where in the SQL spec do you read that PRIMARY KEY constraints
can't be deferred? I don't see that.
regards, tom lane
... speaking of adding catalog columns, I just discovered that the patch
adds searches of pg_depend and pg_constraint to BuildIndexInfo. This
seems utterly unacceptable on two grounds:
* It's sheer luck that it gets through bootstrap without crashing.
Those aren't part of the core set of catalogs that we expect to be
accessed by primitive catalog searches. I wouldn't be too surprised
if it gets the wrong answer, and the only reason there's not a visible
bug is none of the core catalogs have deferrable indexes (so there's
no pg_depend entry to be found).
* It increases the runtime of BuildIndexInfo by what seems likely to
be orders of magnitude (note that get_index_constraint is not a
syscacheable operation). Did anyone do any performance checks on
this patch, like seeing if pgbench got slower?
I think we had better add the deferrability state to pg_index
to avoid this.
I tried to see if we could dispense with storing the flags in IndexInfo
at all, so as not to have to do that. It looks to me like the only
place where it's really needed is in ExecInsertIndexTuples:
if (is_vacuum || !relationDescs[i]->rd_index->indisunique)
uniqueCheck = UNIQUE_CHECK_NO;
==> else if (indexInfo->ii_Deferrable)
uniqueCheck = UNIQUE_CHECK_PARTIAL;
else
uniqueCheck = UNIQUE_CHECK_YES;
Since this code has its hands on the pg_index row already, it definitely
doesn't need a copy in IndexInfo if the state is in pg_index. However,
I also notice that it doesn't particularly care about the deferrability
state in the sense that the triggers use (ie, whether to check at end of
statement or end of transaction). What we want to know here is whether
to force an old-school immediate uniqueness check in the index AM. So
I'm thinking that we only need one bool added to pg_index, not two.
And I'm further thinking about intentionally calling it something other
than "deferred", to emphasize that the semantics aren't quite like
regular constraint deferral. Maybe invert the sense and call it
"immediate". Comments?
regards, tom lane
On Tue, 2009-07-28 at 13:41 -0400, Tom Lane wrote:
* It's sheer luck that it gets through bootstrap without crashing.
Those aren't part of the core set of catalogs that we expect to be
accessed by primitive catalog searches. I wouldn't be too surprised
if it gets the wrong answer, and the only reason there's not a visible
bug is none of the core catalogs have deferrable indexes (so there's
no pg_depend entry to be found).* It increases the runtime of BuildIndexInfo by what seems likely to
be orders of magnitude (note that get_index_constraint is not a
syscacheable operation). Did anyone do any performance checks on
this patch, like seeing if pgbench got slower?
Not I. I didn't notice anything that made me think it would slow down in
the default case, but I'll be more careful in the future.
This also impacts my patch. After moving the constraint information from
pg_index (where my patch currently has it) to pg_constraint, I will also
need access to the index and the constraint at the same time. Perhaps
this can all be handled more like CHECK constraints, storing this
information in the ResultRelInfo?
I think we had better add the deferrability state to pg_index
to avoid this.
This might make it difficult to allow multiple constraints to use the
same index. We might have to make some kind of rules about when two
constraints can share an index, and when they can't.
I also notice that it doesn't particularly care about the deferrability
state in the sense that the triggers use (ie, whether to check at end of
statement or end of transaction). What we want to know here is whether
to force an old-school immediate uniqueness check in the index AM.
[...]
And I'm further thinking about intentionally calling it something other
than "deferred", to emphasize that the semantics aren't quite like
regular constraint deferral. Maybe invert the sense and call it
"immediate". Comments?
I'm trying to figure out how this fits with the generalized index
constraints idea. We may want the generalized index constraints to have
the same "immediate" behavior, but that doesn't have much to do with the
index.
Regards,
Jeff Davis
Jeff Davis <pgsql@j-davis.com> writes:
On Tue, 2009-07-28 at 13:41 -0400, Tom Lane wrote:
I think we had better add the deferrability state to pg_index
to avoid this.
This might make it difficult to allow multiple constraints to use the
same index.
Huh? That hardly seems possible anyway, if some of them want deferred
checks and others do not.
I'm trying to figure out how this fits with the generalized index
constraints idea. We may want the generalized index constraints to have
the same "immediate" behavior, but that doesn't have much to do with the
index.
Sure it does. Whether the check is immediate must be considered a
property of the index itself. Any checking you do later could be
per-constraint, but the index is either going to fail at insert or not.
regards, tom lane
On Tue, 2009-07-28 at 15:15 -0400, Tom Lane wrote:
This might make it difficult to allow multiple constraints to use the
same index.Huh? That hardly seems possible anyway, if some of them want deferred
checks and others do not.
I don't see why it's completely impossible. You could have:
* non-overlapping, deferred
* "not completely contained in", fail-early behavior
Probably not worth supporting, though.
Sure it does. Whether the check is immediate must be considered a
property of the index itself. Any checking you do later could be
per-constraint, but the index is either going to fail at insert or not.
My point is that the "immediate" behavior does not require the index
itself to fail early. My original patch for generalized index
constraints has the same behavior as UNIQUE currently does (including
the fail early behavior), but can be used over indexes that know nothing
about UNIQUE (list GiST).
Regards,
Jeff Davis
Jeff Davis <pgsql@j-davis.com> writes:
On Tue, 2009-07-28 at 15:15 -0400, Tom Lane wrote:
Sure it does. Whether the check is immediate must be considered a
property of the index itself. Any checking you do later could be
per-constraint, but the index is either going to fail at insert or not.
My point is that the "immediate" behavior does not require the index
itself to fail early. My original patch for generalized index
constraints has the same behavior as UNIQUE currently does (including
the fail early behavior), but can be used over indexes that know nothing
about UNIQUE (list GiST).
Fail-early still sounds like a property of the index. Whether the
property is implemented inside or outside the index AM isn't very
relevant. Partial and functional index support are outside the AM, for
example, but we have no problem representing those features in pg_index.
In any case, this can be redesigned as needed when and if your other
patch gets to the point of being ready for consideration.
regards, tom lane
Another thought on the index AM API issues: after poking through the
code I realized that there is *nobody* paying any attention to the
existing bool result of aminsert() (ie, did we insert anything or not).
So I think that instead of adding a bool* parameter, we should repurpose
the function result, along the lines of this spec:
<para>
The method's boolean result value is significant only when
<literal>checkUnique</> is <literal>UNIQUE_CHECK_PARTIAL</>.
In this case a TRUE result means the new entry is known unique, whereas
FALSE means it might be non-unique (and a deferred uniqueness check must
be scheduled). For other cases a constant FALSE result is recommended.
</para>
<para>
For non-unique indexes, it is not required that <function>aminsert</>
do anything; it might for instance refuse to index NULLs.
</para>
The bool* parameter is fairly ugly in a couple of ways: it's not clear
when it's okay to pass a NULL pointer, and the compiler doesn't give
you a lot of help in being sure you've set the result in all code paths.
So I'd rather not use one if we don't have to.
regards, tom lane
2009/7/28 Tom Lane <tgl@sss.pgh.pa.us>:
... speaking of adding catalog columns, I just discovered that the patch
adds searches of pg_depend and pg_constraint to BuildIndexInfo. This
seems utterly unacceptable on two grounds:* It's sheer luck that it gets through bootstrap without crashing.
Those aren't part of the core set of catalogs that we expect to be
accessed by primitive catalog searches. I wouldn't be too surprised
if it gets the wrong answer, and the only reason there's not a visible
bug is none of the core catalogs have deferrable indexes (so there's
no pg_depend entry to be found).* It increases the runtime of BuildIndexInfo by what seems likely to
be orders of magnitude (note that get_index_constraint is not a
syscacheable operation). Did anyone do any performance checks on
this patch, like seeing if pgbench got slower?I think we had better add the deferrability state to pg_index
to avoid this.I tried to see if we could dispense with storing the flags in IndexInfo
at all, so as not to have to do that. It looks to me like the only
place where it's really needed is in ExecInsertIndexTuples:if (is_vacuum || !relationDescs[i]->rd_index->indisunique)
uniqueCheck = UNIQUE_CHECK_NO;
==> else if (indexInfo->ii_Deferrable)
uniqueCheck = UNIQUE_CHECK_PARTIAL;
else
uniqueCheck = UNIQUE_CHECK_YES;Since this code has its hands on the pg_index row already, it definitely
doesn't need a copy in IndexInfo if the state is in pg_index. However,
I also notice that it doesn't particularly care about the deferrability
state in the sense that the triggers use (ie, whether to check at end of
statement or end of transaction). What we want to know here is whether
to force an old-school immediate uniqueness check in the index AM. So
I'm thinking that we only need one bool added to pg_index, not two.And I'm further thinking about intentionally calling it something other
than "deferred", to emphasize that the semantics aren't quite like
regular constraint deferral. Maybe invert the sense and call it
"immediate". Comments?regards, tom lane
Yes that makes sense. Sorry I didn't spot this - it was a performance
regression, which I should have spotted with pgbench.
- Dean
2009/7/29 Tom Lane <tgl@sss.pgh.pa.us>:
Another thought on the index AM API issues: after poking through the
code I realized that there is *nobody* paying any attention to the
existing bool result of aminsert() (ie, did we insert anything or not).
So I think that instead of adding a bool* parameter, we should repurpose
the function result, along the lines of this spec:<para>
The method's boolean result value is significant only when
<literal>checkUnique</> is <literal>UNIQUE_CHECK_PARTIAL</>.
In this case a TRUE result means the new entry is known unique, whereas
FALSE means it might be non-unique (and a deferred uniqueness check must
be scheduled). For other cases a constant FALSE result is recommended.
</para>
And you'll be moving the ereport() back into the btree code? Makes
sense, provided that nothing is ever going to care whether the index
actually inserted an entry. I can see arguments for making the
recommended return value for "other cases" either TRUE or FALSE, but I
guess it doesn't matter since nothing is going to check it.
<para>
For non-unique indexes, it is not required that <function>aminsert</>
do anything; it might for instance refuse to index NULLs.
</para>
Doesn't this comment apply equally to unique indexes?
- Dean
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
2009/7/29 Tom Lane <tgl@sss.pgh.pa.us>:
� For non-unique indexes, it is not required that <function>aminsert</>
� do anything; it might for instance refuse to index NULLs.
Doesn't this comment apply equally to unique indexes?
Hmm, I was thinking that a unique-capable index would have to index all
tuples. But I guess if it's restricted to one index column (like hash)
it could omit nulls and still enforce uniqueness correctly. I'll change
that comment.
regards, tom lane
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
[ deferrable unique constraints patch ]
Applied after rather extensive editorialization. Aside from the points
we already discussed, I redid the logic in _bt_check_unique ... it
didn't look right to me, and I also felt that we need a cross-check
to verify that the original tuple's index entry gets found in the
UNIQUE_CHECK_EXISTING search.
I'm going to go add the point about better support of bulk updates
to TODO.
regards, tom lane
Tom Lane wrote:
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
[ deferrable unique constraints patch ]
Applied after rather extensive editorialization.
Kudos!!
--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.
On Wed, Jul 29, 2009 at 5:00 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
[ deferrable unique constraints patch ]
Applied after rather extensive editorialization. Aside from the points
Wow, cool.
...Robert
2009/7/29 Tom Lane <tgl@sss.pgh.pa.us>:
Dean Rasheed <dean.a.rasheed@googlemail.com> writes:
[ deferrable unique constraints patch ]
Applied after rather extensive editorialization.
Excellent! Thanks for all your work sorting out my rookie mistakes. I
haven't had a chance to go through all your "editorializations" in
detail yet, but I'm sure that I'll learn a lot from them, so thanks
again. And thanks also to everyone else who reviewed this and provided
helpful feedback and advice.
Cheers!
- Dean