mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Support deferrable uniqueness constraints.
The current implementation fires an AFTER ROW trigger for each tuple that looks like it might be non-unique according to the index contents at the time of insertion. This works well as long as there aren't many conflicts, but won't scale to massive unique-key reassignments. Improving that case is a TODO item. Dean Rasheed
This commit is contained in:
@ -8,7 +8,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.22 2009/06/11 14:48:53 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/access/gin/gininsert.c,v 1.23 2009/07/29 20:56:17 tgl Exp $
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@ -415,12 +415,11 @@ gininsert(PG_FUNCTION_ARGS)
|
||||
|
||||
#ifdef NOT_USED
|
||||
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
|
||||
bool checkUnique = PG_GETARG_BOOL(5);
|
||||
IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
|
||||
#endif
|
||||
GinState ginstate;
|
||||
MemoryContext oldCtx;
|
||||
MemoryContext insertCtx;
|
||||
uint32 res = 0;
|
||||
int i;
|
||||
|
||||
insertCtx = AllocSetContextCreate(CurrentMemoryContext,
|
||||
@ -440,7 +439,7 @@ gininsert(PG_FUNCTION_ARGS)
|
||||
memset(&collector, 0, sizeof(GinTupleCollector));
|
||||
for (i = 0; i < ginstate.origTupdesc->natts; i++)
|
||||
if (!isnull[i])
|
||||
res += ginHeapTupleFastCollect(index, &ginstate, &collector,
|
||||
ginHeapTupleFastCollect(index, &ginstate, &collector,
|
||||
(OffsetNumber) (i + 1), values[i], ht_ctid);
|
||||
|
||||
ginHeapTupleFastInsert(index, &ginstate, &collector);
|
||||
@ -449,7 +448,7 @@ gininsert(PG_FUNCTION_ARGS)
|
||||
{
|
||||
for (i = 0; i < ginstate.origTupdesc->natts; i++)
|
||||
if (!isnull[i])
|
||||
res += ginHeapTupleInsert(index, &ginstate,
|
||||
ginHeapTupleInsert(index, &ginstate,
|
||||
(OffsetNumber) (i + 1), values[i], ht_ctid);
|
||||
|
||||
}
|
||||
@ -457,5 +456,5 @@ gininsert(PG_FUNCTION_ARGS)
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
MemoryContextDelete(insertCtx);
|
||||
|
||||
PG_RETURN_BOOL(res > 0);
|
||||
PG_RETURN_BOOL(false);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/access/gist/gist.c,v 1.156 2009/01/01 17:23:34 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/access/gist/gist.c,v 1.157 2009/07/29 20:56:18 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -225,7 +225,7 @@ gistinsert(PG_FUNCTION_ARGS)
|
||||
|
||||
#ifdef NOT_USED
|
||||
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
|
||||
bool checkUnique = PG_GETARG_BOOL(5);
|
||||
IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
|
||||
#endif
|
||||
IndexTuple itup;
|
||||
GISTSTATE giststate;
|
||||
@ -248,7 +248,7 @@ gistinsert(PG_FUNCTION_ARGS)
|
||||
MemoryContextSwitchTo(oldCtx);
|
||||
MemoryContextDelete(insertCtx);
|
||||
|
||||
PG_RETURN_BOOL(true);
|
||||
PG_RETURN_BOOL(false);
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.112 2009/06/11 14:48:53 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.113 2009/07/29 20:56:18 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* This file contains only the public interface routines.
|
||||
@ -165,7 +165,7 @@ hashinsert(PG_FUNCTION_ARGS)
|
||||
|
||||
#ifdef NOT_USED
|
||||
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
|
||||
bool checkUnique = PG_GETARG_BOOL(5);
|
||||
IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
|
||||
#endif
|
||||
IndexTuple itup;
|
||||
|
||||
@ -192,7 +192,7 @@ hashinsert(PG_FUNCTION_ARGS)
|
||||
|
||||
pfree(itup);
|
||||
|
||||
PG_RETURN_BOOL(true);
|
||||
PG_RETURN_BOOL(false);
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.94 2009/07/22 01:21:22 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.95 2009/07/29 20:56:18 tgl Exp $
|
||||
*
|
||||
*
|
||||
* INTERFACE ROUTINES
|
||||
@ -1229,7 +1229,9 @@ toast_save_datum(Relation rel, Datum value, int options)
|
||||
*/
|
||||
index_insert(toastidx, t_values, t_isnull,
|
||||
&(toasttup->t_self),
|
||||
toastrel, toastidx->rd_index->indisunique);
|
||||
toastrel,
|
||||
toastidx->rd_index->indisunique ?
|
||||
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
|
||||
|
||||
/*
|
||||
* Free memory
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/access/index/indexam.c,v 1.114 2009/06/11 14:48:54 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/access/index/indexam.c,v 1.115 2009/07/29 20:56:18 tgl Exp $
|
||||
*
|
||||
* INTERFACE ROUTINES
|
||||
* index_open - open an index relation by relation OID
|
||||
@ -185,7 +185,7 @@ index_insert(Relation indexRelation,
|
||||
bool *isnull,
|
||||
ItemPointer heap_t_ctid,
|
||||
Relation heapRelation,
|
||||
bool check_uniqueness)
|
||||
IndexUniqueCheck checkUnique)
|
||||
{
|
||||
FmgrInfo *procedure;
|
||||
|
||||
@ -201,7 +201,7 @@ index_insert(Relation indexRelation,
|
||||
PointerGetDatum(isnull),
|
||||
PointerGetDatum(heap_t_ctid),
|
||||
PointerGetDatum(heapRelation),
|
||||
BoolGetDatum(check_uniqueness)));
|
||||
Int32GetDatum((int32) checkUnique)));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.170 2009/06/11 14:48:54 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/access/nbtree/nbtinsert.c,v 1.171 2009/07/29 20:56:18 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -49,8 +49,9 @@ typedef struct
|
||||
static Buffer _bt_newroot(Relation rel, Buffer lbuf, Buffer rbuf);
|
||||
|
||||
static TransactionId _bt_check_unique(Relation rel, IndexTuple itup,
|
||||
Relation heapRel, Buffer buf, OffsetNumber ioffset,
|
||||
ScanKey itup_scankey);
|
||||
Relation heapRel, Buffer buf, OffsetNumber offset,
|
||||
ScanKey itup_scankey,
|
||||
IndexUniqueCheck checkUnique, bool *is_unique);
|
||||
static void _bt_findinsertloc(Relation rel,
|
||||
Buffer *bufptr,
|
||||
OffsetNumber *offsetptr,
|
||||
@ -85,11 +86,24 @@ static void _bt_vacuum_one_page(Relation rel, Buffer buffer);
|
||||
*
|
||||
* This routine is called by the public interface routines, btbuild
|
||||
* and btinsert. By here, itup is filled in, including the TID.
|
||||
*
|
||||
* If checkUnique is UNIQUE_CHECK_NO or UNIQUE_CHECK_PARTIAL, this
|
||||
* will allow duplicates. Otherwise (UNIQUE_CHECK_YES or
|
||||
* UNIQUE_CHECK_EXISTING) it will throw error for a duplicate.
|
||||
* For UNIQUE_CHECK_EXISTING we merely run the duplicate check, and
|
||||
* don't actually insert.
|
||||
*
|
||||
* The result value is only significant for UNIQUE_CHECK_PARTIAL:
|
||||
* it must be TRUE if the entry is known unique, else FALSE.
|
||||
* (In the current implementation we'll also return TRUE after a
|
||||
* successful UNIQUE_CHECK_YES or UNIQUE_CHECK_EXISTING call, but
|
||||
* that's just a coding artifact.)
|
||||
*/
|
||||
void
|
||||
bool
|
||||
_bt_doinsert(Relation rel, IndexTuple itup,
|
||||
bool index_is_unique, Relation heapRel)
|
||||
IndexUniqueCheck checkUnique, Relation heapRel)
|
||||
{
|
||||
bool is_unique = false;
|
||||
int natts = rel->rd_rel->relnatts;
|
||||
ScanKey itup_scankey;
|
||||
BTStack stack;
|
||||
@ -134,13 +148,18 @@ top:
|
||||
*
|
||||
* If we must wait for another xact, we release the lock while waiting,
|
||||
* and then must start over completely.
|
||||
*
|
||||
* For a partial uniqueness check, we don't wait for the other xact.
|
||||
* Just let the tuple in and return false for possibly non-unique,
|
||||
* or true for definitely unique.
|
||||
*/
|
||||
if (index_is_unique)
|
||||
if (checkUnique != UNIQUE_CHECK_NO)
|
||||
{
|
||||
TransactionId xwait;
|
||||
|
||||
offset = _bt_binsrch(rel, buf, natts, itup_scankey, false);
|
||||
xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey);
|
||||
xwait = _bt_check_unique(rel, itup, heapRel, buf, offset, itup_scankey,
|
||||
checkUnique, &is_unique);
|
||||
|
||||
if (TransactionIdIsValid(xwait))
|
||||
{
|
||||
@ -153,13 +172,23 @@ top:
|
||||
}
|
||||
}
|
||||
|
||||
/* do the insertion */
|
||||
_bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
|
||||
_bt_insertonpg(rel, buf, stack, itup, offset, false);
|
||||
if (checkUnique != UNIQUE_CHECK_EXISTING)
|
||||
{
|
||||
/* do the insertion */
|
||||
_bt_findinsertloc(rel, &buf, &offset, natts, itup_scankey, itup);
|
||||
_bt_insertonpg(rel, buf, stack, itup, offset, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* just release the buffer */
|
||||
_bt_relbuf(rel, buf);
|
||||
}
|
||||
|
||||
/* be tidy */
|
||||
_bt_freestack(stack);
|
||||
_bt_freeskey(itup_scankey);
|
||||
|
||||
return is_unique;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -172,10 +201,16 @@ top:
|
||||
* Returns InvalidTransactionId if there is no conflict, else an xact ID
|
||||
* we must wait for to see if it commits a conflicting tuple. If an actual
|
||||
* conflict is detected, no return --- just ereport().
|
||||
*
|
||||
* However, if checkUnique == UNIQUE_CHECK_PARTIAL, we always return
|
||||
* InvalidTransactionId because we don't want to wait. In this case we
|
||||
* set *is_unique to false if there is a potential conflict, and the
|
||||
* core code must redo the uniqueness check later.
|
||||
*/
|
||||
static TransactionId
|
||||
_bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
|
||||
Buffer buf, OffsetNumber offset, ScanKey itup_scankey)
|
||||
Buffer buf, OffsetNumber offset, ScanKey itup_scankey,
|
||||
IndexUniqueCheck checkUnique, bool *is_unique)
|
||||
{
|
||||
TupleDesc itupdesc = RelationGetDescr(rel);
|
||||
int natts = rel->rd_rel->relnatts;
|
||||
@ -184,6 +219,10 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
|
||||
Page page;
|
||||
BTPageOpaque opaque;
|
||||
Buffer nbuf = InvalidBuffer;
|
||||
bool found = false;
|
||||
|
||||
/* Assume unique until we find a duplicate */
|
||||
*is_unique = true;
|
||||
|
||||
InitDirtySnapshot(SnapshotDirty);
|
||||
|
||||
@ -240,22 +279,49 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
|
||||
curitup = (IndexTuple) PageGetItem(page, curitemid);
|
||||
htid = curitup->t_tid;
|
||||
|
||||
/*
|
||||
* If we are doing a recheck, we expect to find the tuple we
|
||||
* are rechecking. It's not a duplicate, but we have to keep
|
||||
* scanning.
|
||||
*/
|
||||
if (checkUnique == UNIQUE_CHECK_EXISTING &&
|
||||
ItemPointerCompare(&htid, &itup->t_tid) == 0)
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We check the whole HOT-chain to see if there is any tuple
|
||||
* that satisfies SnapshotDirty. This is necessary because we
|
||||
* have just a single index entry for the entire chain.
|
||||
*/
|
||||
if (heap_hot_search(&htid, heapRel, &SnapshotDirty, &all_dead))
|
||||
else if (heap_hot_search(&htid, heapRel, &SnapshotDirty,
|
||||
&all_dead))
|
||||
{
|
||||
/* it is a duplicate */
|
||||
TransactionId xwait =
|
||||
(TransactionIdIsValid(SnapshotDirty.xmin)) ?
|
||||
SnapshotDirty.xmin : SnapshotDirty.xmax;
|
||||
TransactionId xwait;
|
||||
|
||||
/*
|
||||
* It is a duplicate. If we are only doing a partial
|
||||
* check, then don't bother checking if the tuple is
|
||||
* being updated in another transaction. Just return
|
||||
* the fact that it is a potential conflict and leave
|
||||
* the full check till later.
|
||||
*/
|
||||
if (checkUnique == UNIQUE_CHECK_PARTIAL)
|
||||
{
|
||||
if (nbuf != InvalidBuffer)
|
||||
_bt_relbuf(rel, nbuf);
|
||||
*is_unique = false;
|
||||
return InvalidTransactionId;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this tuple is being updated by other transaction
|
||||
* then we have to wait for its commit/abort.
|
||||
*/
|
||||
xwait = (TransactionIdIsValid(SnapshotDirty.xmin)) ?
|
||||
SnapshotDirty.xmin : SnapshotDirty.xmax;
|
||||
|
||||
if (TransactionIdIsValid(xwait))
|
||||
{
|
||||
if (nbuf != InvalidBuffer)
|
||||
@ -295,6 +361,9 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a definite conflict.
|
||||
*/
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNIQUE_VIOLATION),
|
||||
errmsg("duplicate key value violates unique constraint \"%s\"",
|
||||
@ -349,6 +418,18 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are doing a recheck then we should have found the tuple we
|
||||
* are checking. Otherwise there's something very wrong --- probably,
|
||||
* the index is on a non-immutable expression.
|
||||
*/
|
||||
if (checkUnique == UNIQUE_CHECK_EXISTING && !found)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INTERNAL_ERROR),
|
||||
errmsg("failed to re-find tuple within index \"%s\"",
|
||||
RelationGetRelationName(rel)),
|
||||
errhint("This may be because of a non-immutable index expression.")));
|
||||
|
||||
if (nbuf != InvalidBuffer)
|
||||
_bt_relbuf(rel, nbuf);
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.171 2009/06/11 14:48:54 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.172 2009/07/29 20:56:18 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -217,18 +217,19 @@ btinsert(PG_FUNCTION_ARGS)
|
||||
bool *isnull = (bool *) PG_GETARG_POINTER(2);
|
||||
ItemPointer ht_ctid = (ItemPointer) PG_GETARG_POINTER(3);
|
||||
Relation heapRel = (Relation) PG_GETARG_POINTER(4);
|
||||
bool checkUnique = PG_GETARG_BOOL(5);
|
||||
IndexUniqueCheck checkUnique = (IndexUniqueCheck) PG_GETARG_INT32(5);
|
||||
bool result;
|
||||
IndexTuple itup;
|
||||
|
||||
/* generate an index tuple */
|
||||
itup = index_form_tuple(RelationGetDescr(rel), values, isnull);
|
||||
itup->t_tid = *ht_ctid;
|
||||
|
||||
_bt_doinsert(rel, itup, checkUnique, heapRel);
|
||||
result = _bt_doinsert(rel, itup, checkUnique, heapRel);
|
||||
|
||||
pfree(itup);
|
||||
|
||||
PG_RETURN_BOOL(true);
|
||||
PG_RETURN_BOOL(result);
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user