1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-10 17:42:29 +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:
Tom Lane
2009-07-29 20:56:21 +00:00
parent 8504905793
commit 25d9bf2e3e
51 changed files with 1241 additions and 245 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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)));
}
/*

View File

@@ -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);

View File

@@ -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);
}
/*

View File

@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.96 2009/01/01 17:23:36 momjian Exp $
* $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.97 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -266,7 +266,7 @@ Boot_DeclareIndexStmt:
NULL,
$10,
NULL, NIL,
false, false, false,
false, false, false, false, false,
false, false, true, false, false);
do_end();
}
@@ -284,7 +284,7 @@ Boot_DeclareUniqueIndexStmt:
NULL,
$11,
NULL, NIL,
true, false, false,
true, false, false, false, false,
false, false, true, false, false);
do_end();
}

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.319 2009/07/28 02:56:29 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.320 2009/07/29 20:56:18 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -40,14 +40,18 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/var.h"
#include "parser/parser.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
@@ -87,6 +91,7 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
Oid *classOids,
int16 *coloptions,
bool primary,
bool immediate,
bool isvalid);
static void index_update_stats(Relation rel, bool hasindex, bool isprimary,
Oid reltoastidxid, double reltuples);
@@ -372,6 +377,7 @@ UpdateIndexRelation(Oid indexoid,
Oid *classOids,
int16 *coloptions,
bool primary,
bool immediate,
bool isvalid)
{
int2vector *indkey;
@@ -439,6 +445,7 @@ UpdateIndexRelation(Oid indexoid,
values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs);
values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique);
values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
values[Anum_pg_index_indimmediate - 1] = BoolGetDatum(immediate);
values[Anum_pg_index_indisclustered - 1] = BoolGetDatum(false);
values[Anum_pg_index_indisvalid - 1] = BoolGetDatum(isvalid);
values[Anum_pg_index_indcheckxmin - 1] = BoolGetDatum(false);
@@ -488,6 +495,8 @@ UpdateIndexRelation(Oid indexoid,
* reloptions: AM-specific options
* isprimary: index is a PRIMARY KEY
* isconstraint: index is owned by a PRIMARY KEY or UNIQUE constraint
* deferrable: constraint is DEFERRABLE
* initdeferred: constraint is INITIALLY DEFERRED
* allow_system_table_mods: allow table to be a system catalog
* skip_build: true to skip the index_build() step for the moment; caller
* must do it later (typically via reindex_index())
@@ -509,6 +518,8 @@ index_create(Oid heapRelationId,
Datum reloptions,
bool isprimary,
bool isconstraint,
bool deferrable,
bool initdeferred,
bool allow_system_table_mods,
bool skip_build,
bool concurrent)
@@ -679,7 +690,9 @@ index_create(Oid heapRelationId,
* ----------------
*/
UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
classObjectId, coloptions, isprimary, !concurrent);
classObjectId, coloptions, isprimary,
!deferrable,
!concurrent);
/*
* Register constraint and dependencies for the index.
@@ -726,8 +739,8 @@ index_create(Oid heapRelationId,
conOid = CreateConstraintEntry(indexRelationName,
namespaceId,
constraintType,
false, /* isDeferrable */
false, /* isDeferred */
deferrable,
initdeferred,
heapRelationId,
indexInfo->ii_KeyAttrNumbers,
indexInfo->ii_NumIndexAttrs,
@@ -753,6 +766,40 @@ index_create(Oid heapRelationId,
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
/*
* If the constraint is deferrable, create the deferred uniqueness
* checking trigger. (The trigger will be given an internal
* dependency on the constraint by CreateTrigger, so there's no
* need to do anything more here.)
*/
if (deferrable)
{
RangeVar *heapRel;
CreateTrigStmt *trigger;
heapRel = makeRangeVar(get_namespace_name(namespaceId),
pstrdup(RelationGetRelationName(heapRelation)),
-1);
trigger = makeNode(CreateTrigStmt);
trigger->trigname = pstrdup(indexRelationName);
trigger->relation = heapRel;
trigger->funcname = SystemFuncName("unique_key_recheck");
trigger->args = NIL;
trigger->before = false;
trigger->row = true;
trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
trigger->isconstraint = true;
trigger->deferrable = true;
trigger->initdeferred = initdeferred;
trigger->constrrel = NULL;
(void) CreateTrigger(trigger, conOid, indexRelationId,
isprimary ? "PK_ConstraintTrigger" :
"Unique_ConstraintTrigger",
false);
}
}
else
{
@@ -791,6 +838,10 @@ index_create(Oid heapRelationId,
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
}
/* Non-constraint indexes can't be deferrable */
Assert(!deferrable);
Assert(!initdeferred);
}
/* Store dependency on operator classes */
@@ -823,6 +874,13 @@ index_create(Oid heapRelationId,
DEPENDENCY_AUTO);
}
}
else
{
/* Bootstrap mode - assert we weren't asked for constraint support */
Assert(!isconstraint);
Assert(!deferrable);
Assert(!initdeferred);
}
/*
* Advance the command counter so that we can see the newly-entered
@@ -2190,7 +2248,8 @@ validate_index_heapscan(Relation heapRelation,
isnull,
&rootTuple,
heapRelation,
indexInfo->ii_Unique);
indexInfo->ii_Unique ?
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
state->tups_inserted += 1;
}

View File

@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.117 2009/01/01 17:23:37 momjian Exp $
* $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.118 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -134,7 +134,8 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
isnull, /* is-null flags */
&(heapTuple->t_self), /* tid of heap tuple */
heapRelation,
relationDescs[i]->rd_index->indisunique);
relationDescs[i]->rd_index->indisunique ?
UNIQUE_CHECK_YES : UNIQUE_CHECK_NO);
}
ExecDropSingleTupleTableSlot(slot);

View File

@@ -289,7 +289,7 @@ F695 Translation support NO
F696 Additional translation documentation NO
F701 Referential update actions YES
F711 ALTER domain YES
F721 Deferrable constraints NO foreign keys only
F721 Deferrable constraints NO foreign and unique keys only
F731 INSERT column privileges YES
F741 Referential MATCH types NO no partial match yet
F751 View CHECK enhancements NO

View File

@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.17 2009/06/11 20:46:11 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/toasting.c,v 1.18 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -253,7 +253,8 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
classObjectId, coloptions, (Datum) 0,
true, false, true, false, false);
true, false, false, false,
true, false, false);
/*
* Store the toast table's OID in the parent relation's pg_class row

View File

@@ -4,7 +4,7 @@
# Makefile for backend/commands
#
# IDENTIFICATION
# $PostgreSQL: pgsql/src/backend/commands/Makefile,v 1.39 2008/12/19 16:25:17 petere Exp $
# $PostgreSQL: pgsql/src/backend/commands/Makefile,v 1.40 2009/07/29 20:56:18 tgl Exp $
#
#-------------------------------------------------------------------------
@@ -13,7 +13,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
conversioncmds.o copy.o \
constraint.o conversioncmds.o copy.o \
dbcommands.o define.o discard.o explain.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \

View File

@@ -0,0 +1,166 @@
/*-------------------------------------------------------------------------
*
* constraint.c
* PostgreSQL CONSTRAINT support code.
*
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/constraint.c,v 1.1 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/index.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "utils/builtins.h"
#include "utils/tqual.h"
/*
* unique_key_recheck - trigger function to do a deferred uniqueness check.
*
* This is invoked as an AFTER ROW trigger for both INSERT and UPDATE,
* for any rows recorded as potentially violating a deferrable unique
* constraint.
*
* This may be an end-of-statement check, a commit-time check, or a
* check triggered by a SET CONSTRAINTS command.
*/
Datum
unique_key_recheck(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
const char *funcname = "unique_key_recheck";
HeapTuple new_row;
ItemPointerData tmptid;
Relation indexRel;
IndexInfo *indexInfo;
EState *estate;
ExprContext *econtext;
TupleTableSlot *slot;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
/*
* Make sure this is being called as an AFTER ROW trigger. Note:
* translatable error strings are shared with ri_triggers.c, so
* resist the temptation to fold the function name into them.
*/
if (!CALLED_AS_TRIGGER(fcinfo))
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" was not called by trigger manager",
funcname)));
if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" must be fired AFTER ROW",
funcname)));
/*
* Get the new data that was inserted/updated.
*/
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
new_row = trigdata->tg_trigtuple;
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
new_row = trigdata->tg_newtuple;
else
{
ereport(ERROR,
(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
errmsg("function \"%s\" must be fired for INSERT or UPDATE",
funcname)));
new_row = NULL; /* keep compiler quiet */
}
/*
* If the new_row is now dead (ie, inserted and then deleted within our
* transaction), we can skip the check. However, we have to be careful,
* because this trigger gets queued only in response to index insertions;
* which means it does not get queued for HOT updates. The row we are
* called for might now be dead, but have a live HOT child, in which case
* we still need to make the uniqueness check. Therefore we have to use
* heap_hot_search, not just HeapTupleSatisfiesVisibility as is done in
* the comparable test in RI_FKey_check.
*
* This might look like just an optimization, because the index AM will
* make this identical test before throwing an error. But it's actually
* needed for correctness, because the index AM will also throw an error
* if it doesn't find the index entry for the row. If the row's dead then
* it's possible the index entry has also been marked dead, and even
* removed.
*/
tmptid = new_row->t_self;
if (!heap_hot_search(&tmptid, trigdata->tg_relation, SnapshotSelf, NULL))
{
/*
* All rows in the HOT chain are dead, so skip the check.
*/
return PointerGetDatum(NULL);
}
/*
* Open the index, acquiring a RowExclusiveLock, just as if we were
* going to update it. (This protects against possible changes of the
* index schema, not against concurrent updates.)
*/
indexRel = index_open(trigdata->tg_trigger->tgconstrindid,
RowExclusiveLock);
indexInfo = BuildIndexInfo(indexRel);
/*
* The heap tuple must be put into a slot for FormIndexDatum.
*/
slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation));
ExecStoreTuple(new_row, slot, InvalidBuffer, false);
/*
* Typically the index won't have expressions, but if it does we need
* an EState to evaluate them.
*/
if (indexInfo->ii_Expressions != NIL)
{
estate = CreateExecutorState();
econtext = GetPerTupleExprContext(estate);
econtext->ecxt_scantuple = slot;
}
else
estate = NULL;
/*
* Form the index values and isnull flags for the index entry that
* we need to check.
*
* Note: if the index uses functions that are not as immutable as they
* are supposed to be, this could produce an index tuple different from
* the original. The index AM can catch such errors by verifying that
* it finds a matching index entry with the tuple's TID.
*/
FormIndexDatum(indexInfo, slot, estate, values, isnull);
/*
* Now do the uniqueness check. This is not a real insert; it is a
* check that the index entry that has already been inserted is unique.
*/
index_insert(indexRel, values, isnull, &(new_row->t_self),
trigdata->tg_relation, UNIQUE_CHECK_EXISTING);
/*
* If that worked, then this index entry is unique, and we are done.
*/
if (estate != NULL)
FreeExecutorState(estate);
ExecDropSingleTupleTableSlot(slot);
index_close(indexRel, RowExclusiveLock);
return PointerGetDatum(NULL);
}

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.315 2009/07/25 17:04:19 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.316 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2130,6 +2130,8 @@ CopyFrom(CopyState cstate)
if (!skip_tuple)
{
List *recheckIndexes = NIL;
/* Place tuple in tuple slot */
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
@@ -2141,10 +2143,12 @@ CopyFrom(CopyState cstate)
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
estate, false);
/* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, tuple);
ExecARInsertTriggers(estate, resultRelInfo, tuple,
recheckIndexes);
/*
* We count only tuples not suppressed by a BEFORE INSERT trigger;

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.186 2009/07/16 06:33:42 petere Exp $
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.187 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -87,6 +87,8 @@ static bool relationHasPrimaryKey(Relation rel);
* 'primary': mark the index as a primary key in the catalogs.
* 'isconstraint': index is for a PRIMARY KEY or UNIQUE constraint,
* so build a pg_constraint entry for it.
* 'deferrable': constraint is DEFERRABLE.
* 'initdeferred': constraint is INITIALLY DEFERRED.
* 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
* 'check_rights': check for CREATE rights in the namespace. (This should
* be true except when ALTER is deleting/recreating an index.)
@@ -107,6 +109,8 @@ DefineIndex(RangeVar *heapRelation,
bool unique,
bool primary,
bool isconstraint,
bool deferrable,
bool initdeferred,
bool is_alter_table,
bool check_rights,
bool skip_build,
@@ -447,7 +451,8 @@ DefineIndex(RangeVar *heapRelation,
indexRelationId =
index_create(relationId, indexRelationName, indexRelationId,
indexInfo, accessMethodId, tablespaceId, classObjectId,
coloptions, reloptions, primary, isconstraint,
coloptions, reloptions, primary,
isconstraint, deferrable, initdeferred,
allowSystemTableMods, skip_build, concurrent);
return; /* We're done, in the standard case */
@@ -465,7 +470,8 @@ DefineIndex(RangeVar *heapRelation,
indexRelationId =
index_create(relationId, indexRelationName, indexRelationId,
indexInfo, accessMethodId, tablespaceId, classObjectId,
coloptions, reloptions, primary, isconstraint,
coloptions, reloptions, primary,
isconstraint, deferrable, initdeferred,
allowSystemTableMods, true, concurrent);
/*

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.292 2009/07/28 02:56:30 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.293 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -4391,6 +4391,8 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
stmt->unique,
stmt->primary,
stmt->isconstraint,
stmt->deferrable,
stmt->initdeferred,
true, /* is_alter_table */
check_rights,
skip_build,
@@ -4955,6 +4957,17 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
if (indexStruct->indisprimary)
{
/*
* Refuse to use a deferrable primary key. This is per SQL spec,
* and there would be a lot of interesting semantic problems if
* we tried to allow it.
*/
if (!indexStruct->indimmediate)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("cannot use a deferrable primary key for referenced table \"%s\"",
RelationGetRelationName(pkrel))));
*indexOid = indexoid;
break;
}
@@ -5040,11 +5053,12 @@ transformFkeyCheckAttrs(Relation pkrel,
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
/*
* Must have the right number of columns; must be unique and not a
* partial index; forget it if there are any expressions, too
* Must have the right number of columns; must be unique (non
* deferrable) and not a partial index; forget it if there are any
* expressions, too
*/
if (indexStruct->indnatts == numattrs &&
indexStruct->indisunique &&
indexStruct->indisunique && indexStruct->indimmediate &&
heap_attisnull(indexTuple, Anum_pg_index_indpred) &&
heap_attisnull(indexTuple, Anum_pg_index_indexprs))
{
@@ -5243,7 +5257,8 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
fk_trigger->constrrel = fkconstraint->pktable;
fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, constraintOid, indexOid, false);
(void) CreateTrigger(fk_trigger, constraintOid, indexOid,
"RI_ConstraintTrigger", false);
/* Make changes-so-far visible */
CommandCounterIncrement();
@@ -5322,7 +5337,8 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
}
fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, constraintOid, indexOid, false);
(void) CreateTrigger(fk_trigger, constraintOid, indexOid,
"RI_ConstraintTrigger", false);
/* Make changes-so-far visible */
CommandCounterIncrement();
@@ -5373,7 +5389,8 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
}
fk_trigger->args = NIL;
(void) CreateTrigger(fk_trigger, constraintOid, indexOid, false);
(void) CreateTrigger(fk_trigger, constraintOid, indexOid,
"RI_ConstraintTrigger", false);
}
/*

View File

@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.249 2009/07/28 02:56:30 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.250 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -63,7 +63,8 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
Instrumentation *instr,
MemoryContext per_tuple_context);
static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
bool row_trigger, HeapTuple oldtup, HeapTuple newtup,
List *recheckIndexes);
/*
@@ -77,6 +78,10 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
* indexOid, if nonzero, is the OID of an index associated with the constraint.
* We do nothing with this except store it into pg_trigger.tgconstrindid.
*
* prefix is NULL for user-created triggers. For internally generated
* constraint triggers, it is a prefix string to use in building the
* trigger name. (stmt->trigname is the constraint name in such cases.)
*
* If checkPermissions is true we require ACL_TRIGGER permissions on the
* relation. If not, the caller already checked permissions. (This is
* currently redundant with constraintOid being zero, but it's clearer to
@@ -87,7 +92,7 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
*/
Oid
CreateTrigger(CreateTrigStmt *stmt,
Oid constraintOid, Oid indexOid,
Oid constraintOid, Oid indexOid, const char *prefix,
bool checkPermissions)
{
int16 tgtype;
@@ -216,20 +221,20 @@ CreateTrigger(CreateTrigStmt *stmt,
trigoid = GetNewOid(tgrel);
/*
* If trigger is for an RI constraint, the passed-in name is the
* constraint name; save that and build a unique trigger name to avoid
* collisions with user-selected trigger names.
* If trigger is for a constraint, stmt->trigname is the constraint
* name; save that and build a unique trigger name based on the supplied
* prefix, to avoid collisions with user-selected trigger names.
*/
if (OidIsValid(constraintOid))
if (prefix != NULL)
{
snprintf(constrtrigname, sizeof(constrtrigname),
"RI_ConstraintTrigger_%u", trigoid);
"%s_%u", prefix, trigoid);
trigname = constrtrigname;
constrname = stmt->trigname;
}
else if (stmt->isconstraint)
{
/* constraint trigger: trigger name is also constraint name */
/* user constraint trigger: trigger name is also constraint name */
trigname = stmt->trigname;
constrname = stmt->trigname;
}
@@ -1650,7 +1655,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
false, NULL, NULL);
false, NULL, NULL, NIL);
}
HeapTuple
@@ -1706,13 +1711,13 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
void
ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
HeapTuple trigtuple)
HeapTuple trigtuple, List *recheckIndexes)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
true, NULL, trigtuple);
true, NULL, trigtuple, recheckIndexes);
}
void
@@ -1781,7 +1786,7 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
false, NULL, NULL);
false, NULL, NULL, NIL);
}
bool
@@ -1858,7 +1863,7 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
true, trigtuple, NULL);
true, trigtuple, NULL, NIL);
heap_freetuple(trigtuple);
}
}
@@ -1929,7 +1934,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
false, NULL, NULL);
false, NULL, NULL, NIL);
}
HeapTuple
@@ -1999,7 +2004,8 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
void
ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
ItemPointer tupleid, HeapTuple newtuple)
ItemPointer tupleid, HeapTuple newtuple,
List *recheckIndexes)
{
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
@@ -2009,7 +2015,7 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
tupleid, NULL);
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
true, trigtuple, newtuple);
true, trigtuple, newtuple, recheckIndexes);
heap_freetuple(trigtuple);
}
}
@@ -2080,7 +2086,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
false, NULL, NULL);
false, NULL, NULL, NIL);
}
@@ -3793,15 +3799,18 @@ AfterTriggerPendingOnRel(Oid relid)
/* ----------
* AfterTriggerSaveEvent()
*
* Called by ExecA[RS]...Triggers() to add the event to the queue.
* Called by ExecA[RS]...Triggers() to queue up the triggers that should
* be fired for an event.
*
* NOTE: should be called only if we've determined that an event must
* be added to the queue.
* NOTE: this is called whenever there are any triggers associated with
* the event (even if they are disabled). This function decides which
* triggers actually need to be queued.
* ----------
*/
static void
AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup)
HeapTuple oldtup, HeapTuple newtup,
List *recheckIndexes)
{
Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
@@ -3961,6 +3970,17 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
}
}
/*
* If the trigger is a deferred unique constraint check trigger,
* only queue it if the unique constraint was potentially violated,
* which we know from index insertion time.
*/
if (trigger->tgfoid == F_UNIQUE_KEY_RECHECK)
{
if (!list_member_oid(recheckIndexes, trigger->tgconstrindid))
continue; /* Uniqueness definitely not violated */
}
/*
* Fill in event structure and add it to the current query's queue.
*/

View File

@@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.326 2009/06/11 20:46:11 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.327 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1753,6 +1753,7 @@ ExecInsert(TupleTableSlot *slot,
ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
/*
* get the heap tuple out of the tuple table slot, making sure we have a
@@ -1834,10 +1835,11 @@ ExecInsert(TupleTableSlot *slot,
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
estate, false);
/* AFTER ROW INSERT Triggers */
ExecARInsertTriggers(estate, resultRelInfo, tuple);
ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
@@ -1999,6 +2001,7 @@ ExecUpdate(TupleTableSlot *slot,
HTSU_Result result;
ItemPointerData update_ctid;
TransactionId update_xmax;
List *recheckIndexes = NIL;
/*
* abort the operation if not running transactions
@@ -2132,10 +2135,12 @@ lreplace:;
* If it's a HOT update, we mustn't insert new index entries.
*/
if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
estate, false);
/* AFTER ROW UPDATE Triggers */
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
recheckIndexes);
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.160 2009/07/18 19:15:41 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.161 2009/07/29 20:56:18 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1033,17 +1033,22 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
* doesn't provide the functionality needed by the
* executor.. -cim 9/27/89
*
* This returns a list of OIDs for any unique indexes
* whose constraint check was deferred and which had
* potential (unconfirmed) conflicts.
*
* CAUTION: this must not be called for a HOT update.
* We can't defend against that here for lack of info.
* Should we change the API to make it safer?
* ----------------------------------------------------------------
*/
void
List *
ExecInsertIndexTuples(TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
bool is_vacuum)
bool is_vacuum_full)
{
List *result = NIL;
ResultRelInfo *resultRelInfo;
int i;
int numIndices;
@@ -1077,9 +1082,12 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
*/
for (i = 0; i < numIndices; i++)
{
Relation indexRelation = relationDescs[i];
IndexInfo *indexInfo;
IndexUniqueCheck checkUnique;
bool isUnique;
if (relationDescs[i] == NULL)
if (indexRelation == NULL)
continue;
indexInfo = indexInfoArray[i];
@@ -1122,22 +1130,50 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
isnull);
/*
* The index AM does the rest. Note we suppress unique-index checks
* if we are being called from VACUUM, since VACUUM may need to move
* dead tuples that have the same keys as live ones.
* The index AM does the rest, including uniqueness checking.
*
* For an immediate-mode unique index, we just tell the index AM to
* throw error if not unique.
*
* For a deferrable unique index, we tell the index AM to just detect
* possible non-uniqueness, and we add the index OID to the result
* list if further checking is needed.
*
* Special hack: we suppress unique-index checks if we are being
* called from VACUUM FULL, since VACUUM FULL may need to move dead
* tuples that have the same keys as live ones.
*/
index_insert(relationDescs[i], /* index relation */
values, /* array of index Datums */
isnull, /* null flags */
tupleid, /* tid of heap tuple */
heapRelation,
relationDescs[i]->rd_index->indisunique && !is_vacuum);
if (is_vacuum_full || !indexRelation->rd_index->indisunique)
checkUnique = UNIQUE_CHECK_NO;
else if (indexRelation->rd_index->indimmediate)
checkUnique = UNIQUE_CHECK_YES;
else
checkUnique = UNIQUE_CHECK_PARTIAL;
isUnique =
index_insert(indexRelation, /* index relation */
values, /* array of index Datums */
isnull, /* null flags */
tupleid, /* tid of heap tuple */
heapRelation, /* heap relation */
checkUnique); /* type of uniqueness check to do */
if (checkUnique == UNIQUE_CHECK_PARTIAL && !isUnique)
{
/*
* The tuple potentially violates the uniqueness constraint,
* so make a note of the index so that we can re-check it later.
*/
result = lappend_oid(result, RelationGetRelid(indexRelation));
}
/*
* keep track of index inserts for debugging
*/
IncrIndexInserted();
}
return result;
}
/*

View File

@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.435 2009/07/26 23:34:17 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.436 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2091,6 +2091,8 @@ _copyConstraint(Constraint *from)
COPY_NODE_FIELD(keys);
COPY_NODE_FIELD(options);
COPY_STRING_FIELD(indexspace);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
return newnode;
}
@@ -2510,6 +2512,8 @@ _copyIndexStmt(IndexStmt *from)
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
COPY_SCALAR_FIELD(deferrable);
COPY_SCALAR_FIELD(initdeferred);
COPY_SCALAR_FIELD(concurrent);
return newnode;

View File

@@ -22,7 +22,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.358 2009/07/26 23:34:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.359 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1160,6 +1160,8 @@ _equalIndexStmt(IndexStmt *a, IndexStmt *b)
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
COMPARE_SCALAR_FIELD(deferrable);
COMPARE_SCALAR_FIELD(initdeferred);
COMPARE_SCALAR_FIELD(concurrent);
return true;
@@ -2068,6 +2070,8 @@ _equalConstraint(Constraint *a, Constraint *b)
COMPARE_NODE_FIELD(keys);
COMPARE_NODE_FIELD(options);
COMPARE_STRING_FIELD(indexspace);
COMPARE_SCALAR_FIELD(deferrable);
COMPARE_SCALAR_FIELD(initdeferred);
return true;
}

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.361 2009/07/16 06:33:42 petere Exp $
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.362 2009/07/29 20:56:19 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@@ -1734,6 +1734,8 @@ _outIndexStmt(StringInfo str, IndexStmt *node)
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
WRITE_BOOL_FIELD(deferrable);
WRITE_BOOL_FIELD(initdeferred);
WRITE_BOOL_FIELD(concurrent);
}
@@ -2285,6 +2287,8 @@ _outConstraint(StringInfo str, Constraint *node)
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
WRITE_BOOL_FIELD(deferrable);
WRITE_BOOL_FIELD(initdeferred);
break;
case CONSTR_UNIQUE:
@@ -2292,6 +2296,8 @@ _outConstraint(StringInfo str, Constraint *node)
WRITE_NODE_FIELD(keys);
WRITE_NODE_FIELD(options);
WRITE_STRING_FIELD(indexspace);
WRITE_BOOL_FIELD(deferrable);
WRITE_BOOL_FIELD(initdeferred);
break;
case CONSTR_CHECK:

View File

@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.673 2009/07/26 23:34:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.674 2009/07/29 20:56:19 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -2220,6 +2220,8 @@ ColConstraintElem:
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
n->deferrable = FALSE;
n->initdeferred = FALSE;
$$ = (Node *)n;
}
| NULL_P
@@ -2231,6 +2233,8 @@ ColConstraintElem:
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
n->deferrable = FALSE;
n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE opt_definition OptConsTableSpace
@@ -2243,6 +2247,8 @@ ColConstraintElem:
n->keys = NULL;
n->options = $2;
n->indexspace = $3;
n->deferrable = FALSE;
n->initdeferred = FALSE;
$$ = (Node *)n;
}
| PRIMARY KEY opt_definition OptConsTableSpace
@@ -2255,6 +2261,8 @@ ColConstraintElem:
n->keys = NULL;
n->options = $3;
n->indexspace = $4;
n->deferrable = FALSE;
n->initdeferred = FALSE;
$$ = (Node *)n;
}
| CHECK '(' a_expr ')'
@@ -2266,6 +2274,8 @@ ColConstraintElem:
n->cooked_expr = NULL;
n->keys = NULL;
n->indexspace = NULL;
n->deferrable = FALSE;
n->initdeferred = FALSE;
$$ = (Node *)n;
}
| DEFAULT b_expr
@@ -2277,6 +2287,8 @@ ColConstraintElem:
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,7 +2410,7 @@ TableConstraint:
;
ConstraintElem:
CHECK '(' a_expr ')'
CHECK '(' a_expr ')' ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_CHECK;
@@ -2406,9 +2418,17 @@ ConstraintElem:
n->raw_expr = $3;
n->cooked_expr = NULL;
n->indexspace = NULL;
if ($5 != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CHECK constraints cannot be deferred"),
parser_errposition(@5)));
n->deferrable = FALSE;
n->initdeferred = FALSE;
$$ = (Node *)n;
}
| UNIQUE '(' columnList ')' opt_definition OptConsTableSpace
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_UNIQUE;
@@ -2418,9 +2438,12 @@ ConstraintElem:
n->keys = $3;
n->options = $5;
n->indexspace = $6;
n->deferrable = ($7 & 1) != 0;
n->initdeferred = ($7 & 2) != 0;
$$ = (Node *)n;
}
| PRIMARY KEY '(' columnList ')' opt_definition OptConsTableSpace
ConstraintAttributeSpec
{
Constraint *n = makeNode(Constraint);
n->contype = CONSTR_PRIMARY;
@@ -2430,6 +2453,8 @@ ConstraintElem:
n->keys = $4;
n->options = $6;
n->indexspace = $7;
n->deferrable = ($8 & 1) != 0;
n->initdeferred = ($8 & 2) != 0;
$$ = (Node *)n;
}
| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name

View File

@@ -19,7 +19,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.23 2009/07/16 06:33:43 petere Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.24 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -33,6 +33,7 @@
#include "catalog/heap.h"
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
@@ -801,15 +802,38 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
/*
* If the index is marked PRIMARY, it's certainly from a constraint; else,
* if it's not marked UNIQUE, it certainly isn't; else, we have to search
* pg_depend to see if there's an associated unique constraint.
* if it's not marked UNIQUE, it certainly isn't. If it is or might be
* from a constraint, we have to fetch the constraint to check for
* deferrability attributes.
*/
if (index->primary)
index->isconstraint = true;
else if (!index->unique)
index->isconstraint = false;
if (index->primary || index->unique)
{
Oid constraintId = get_index_constraint(source_relid);
if (OidIsValid(constraintId))
{
HeapTuple ht_constr;
Form_pg_constraint conrec;
ht_constr = SearchSysCache(CONSTROID,
ObjectIdGetDatum(constraintId),
0, 0, 0);
if (!HeapTupleIsValid(ht_constr))
elog(ERROR, "cache lookup failed for constraint %u",
constraintId);
conrec = (Form_pg_constraint) GETSTRUCT(ht_constr);
index->isconstraint = true;
index->deferrable = conrec->condeferrable;
index->initdeferred = conrec->condeferred;
ReleaseSysCache(ht_constr);
}
else
index->isconstraint = false;
}
else
index->isconstraint = OidIsValid(get_index_constraint(source_relid));
index->isconstraint = false;
/* Get the index expressions, if any */
datum = SysCacheGetAttr(INDEXRELID, ht_idx,
@@ -1039,7 +1063,9 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
if (equal(index->indexParams, priorindex->indexParams) &&
equal(index->whereClause, priorindex->whereClause) &&
strcmp(index->accessMethod, priorindex->accessMethod) == 0)
strcmp(index->accessMethod, priorindex->accessMethod) == 0 &&
index->deferrable == priorindex->deferrable &&
index->initdeferred == priorindex->initdeferred)
{
priorindex->unique |= index->unique;
@@ -1092,6 +1118,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
*/
}
index->isconstraint = true;
index->deferrable = constraint->deferrable;
index->initdeferred = constraint->initdeferred;
if (constraint->name != NULL)
index->idxname = pstrdup(constraint->name);
@@ -1853,8 +1881,9 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
* to attach constraint attributes to their primary constraint nodes
* and detect inconsistent/misplaced constraint attributes.
*
* NOTE: currently, attributes are only supported for FOREIGN KEY primary
* constraints, but someday they ought to be supported for other constraints.
* NOTE: currently, attributes are only supported for FOREIGN KEY, UNIQUE,
* and PRIMARY KEY constraints, but someday they ought to be supported
* for other constraint types.
*/
static void
transformConstraintAttrs(List *constraintList)
@@ -1864,6 +1893,13 @@ transformConstraintAttrs(List *constraintList)
bool saw_initially = false;
ListCell *clist;
#define SUPPORTS_ATTRS(node) \
((node) != NULL && \
(IsA((node), FkConstraint) || \
(IsA((node), Constraint) && \
(((Constraint *) (node))->contype == CONSTR_PRIMARY || \
((Constraint *) (node))->contype == CONSTR_UNIQUE))))
foreach(clist, constraintList)
{
Node *node = lfirst(clist);
@@ -1882,8 +1918,7 @@ transformConstraintAttrs(List *constraintList)
switch (con->contype)
{
case CONSTR_ATTR_DEFERRABLE:
if (lastprimarynode == NULL ||
!IsA(lastprimarynode, FkConstraint))
if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced DEFERRABLE clause")));
@@ -1892,11 +1927,14 @@ transformConstraintAttrs(List *constraintList)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
((FkConstraint *) lastprimarynode)->deferrable = true;
if (IsA(lastprimarynode, FkConstraint))
((FkConstraint *) lastprimarynode)->deferrable = true;
else
((Constraint *) lastprimarynode)->deferrable = true;
break;
case CONSTR_ATTR_NOT_DEFERRABLE:
if (lastprimarynode == NULL ||
!IsA(lastprimarynode, FkConstraint))
if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced NOT DEFERRABLE clause")));
@@ -1905,16 +1943,28 @@ transformConstraintAttrs(List *constraintList)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed")));
saw_deferrability = true;
((FkConstraint *) lastprimarynode)->deferrable = false;
if (saw_initially &&
((FkConstraint *) lastprimarynode)->initdeferred)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
if (IsA(lastprimarynode, FkConstraint))
{
((FkConstraint *) lastprimarynode)->deferrable = false;
if (saw_initially &&
((FkConstraint *) lastprimarynode)->initdeferred)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
}
else
{
((Constraint *) lastprimarynode)->deferrable = false;
if (saw_initially &&
((Constraint *) lastprimarynode)->initdeferred)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
}
break;
case CONSTR_ATTR_DEFERRED:
if (lastprimarynode == NULL ||
!IsA(lastprimarynode, FkConstraint))
if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY DEFERRED clause")));
@@ -1923,21 +1973,36 @@ transformConstraintAttrs(List *constraintList)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
((FkConstraint *) lastprimarynode)->initdeferred = true;
/*
* If only INITIALLY DEFERRED appears, assume DEFERRABLE
*/
if (!saw_deferrability)
((FkConstraint *) lastprimarynode)->deferrable = true;
else if (!((FkConstraint *) lastprimarynode)->deferrable)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
if (IsA(lastprimarynode, FkConstraint))
{
((FkConstraint *) lastprimarynode)->initdeferred = true;
if (!saw_deferrability)
((FkConstraint *) lastprimarynode)->deferrable = true;
else if (!((FkConstraint *) lastprimarynode)->deferrable)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
}
else
{
((Constraint *) lastprimarynode)->initdeferred = true;
if (!saw_deferrability)
((Constraint *) lastprimarynode)->deferrable = true;
else if (!((Constraint *) lastprimarynode)->deferrable)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("constraint declared INITIALLY DEFERRED must be DEFERRABLE")));
}
break;
case CONSTR_ATTR_IMMEDIATE:
if (lastprimarynode == NULL ||
!IsA(lastprimarynode, FkConstraint))
if (!SUPPORTS_ATTRS(lastprimarynode))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("misplaced INITIALLY IMMEDIATE clause")));
@@ -1946,8 +2011,12 @@ transformConstraintAttrs(List *constraintList)
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed")));
saw_initially = true;
((FkConstraint *) lastprimarynode)->initdeferred = false;
if (IsA(lastprimarynode, FkConstraint))
((FkConstraint *) lastprimarynode)->initdeferred = false;
else
((Constraint *) lastprimarynode)->initdeferred = false;
break;
default:
/* Otherwise it's not an attribute */
lastprimarynode = node;

View File

@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.312 2009/07/28 02:56:30 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.313 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -795,6 +795,8 @@ ProcessUtility(Node *parsetree,
stmt->unique,
stmt->primary,
stmt->isconstraint,
stmt->deferrable,
stmt->initdeferred,
false, /* is_alter_table */
true, /* check_rights */
false, /* skip_build */
@@ -929,7 +931,7 @@ ProcessUtility(Node *parsetree,
case T_CreateTrigStmt:
CreateTrigger((CreateTrigStmt *) parsetree,
InvalidOid, InvalidOid, true);
InvalidOid, InvalidOid, NULL, true);
break;
case T_DropPropertyStmt:

View File

@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.304 2009/07/24 21:08:42 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.305 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1044,11 +1044,6 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
if (string)
appendStringInfo(&buf, " ON DELETE %s", string);
if (conForm->condeferrable)
appendStringInfo(&buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfo(&buf, " INITIALLY DEFERRED");
break;
}
case CONSTRAINT_PRIMARY:
@@ -1150,6 +1145,11 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
break;
}
if (conForm->condeferrable)
appendStringInfo(&buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfo(&buf, " INITIALLY DEFERRED");
/* Cleanup */
ReleaseSysCache(tup);

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.287 2009/06/11 14:49:05 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.288 2009/07/29 20:56:19 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2938,7 +2938,7 @@ RelationGetIndexList(Relation relation)
/* Check to see if it is a unique, non-partial btree index on OID */
if (index->indnatts == 1 &&
index->indisunique &&
index->indisunique && index->indimmediate &&
index->indkey.values[0] == ObjectIdAttributeNumber &&
index->indclass.values[0] == OID_BTREE_OPS_OID &&
heap_attisnull(htup, Anum_pg_index_indpred))