mirror of
https://github.com/postgres/postgres.git
synced 2025-06-23 14:01:44 +03:00
Make ALTER TABLE revalidate uniqueness and exclusion constraints.
Failure to do so can lead to constraint violations. This was broken by
commit 1ddc2703a9
on 2010-02-07, so
back-patch to 9.0.
Noah Misch. Regression test by me.
This commit is contained in:
@ -2529,26 +2529,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
|
|||||||
* reindex_relation - This routine is used to recreate all indexes
|
* reindex_relation - This routine is used to recreate all indexes
|
||||||
* of a relation (and optionally its toast relation too, if any).
|
* of a relation (and optionally its toast relation too, if any).
|
||||||
*
|
*
|
||||||
* If heap_rebuilt is true, then the relation was just completely rebuilt by
|
* "flags" can include REINDEX_SUPPRESS_INDEX_USE and REINDEX_CHECK_CONSTRAINTS.
|
||||||
* an operation such as VACUUM FULL or CLUSTER, and therefore its indexes are
|
|
||||||
* inconsistent with it. This makes things tricky if the relation is a system
|
|
||||||
* catalog that we might consult during the reindexing. To deal with that
|
|
||||||
* case, we mark all of the indexes as pending rebuild so that they won't be
|
|
||||||
* trusted until rebuilt. The caller is required to call us *without* having
|
|
||||||
* made the rebuilt versions visible by doing CommandCounterIncrement; we'll
|
|
||||||
* do CCI after having collected the index list. (This way we can still use
|
|
||||||
* catalog indexes while collecting the list.)
|
|
||||||
*
|
*
|
||||||
* We also skip rechecking uniqueness/exclusion constraint properties if
|
* If flags has REINDEX_SUPPRESS_INDEX_USE, the relation was just completely
|
||||||
* heap_rebuilt is true. This avoids likely deadlock conditions when doing
|
* rebuilt by an operation such as VACUUM FULL or CLUSTER, and therefore its
|
||||||
* VACUUM FULL or CLUSTER on system catalogs. REINDEX should be used to
|
* indexes are inconsistent with it. This makes things tricky if the relation
|
||||||
* rebuild an index if constraint inconsistency is suspected.
|
* is a system catalog that we might consult during the reindexing. To deal
|
||||||
|
* with that case, we mark all of the indexes as pending rebuild so that they
|
||||||
|
* won't be trusted until rebuilt. The caller is required to call us *without*
|
||||||
|
* having made the rebuilt versions visible by doing CommandCounterIncrement;
|
||||||
|
* we'll do CCI after having collected the index list. (This way we can still
|
||||||
|
* use catalog indexes while collecting the list.)
|
||||||
|
*
|
||||||
|
* To avoid deadlocks, VACUUM FULL or CLUSTER on a system catalog must omit the
|
||||||
|
* REINDEX_CHECK_CONSTRAINTS flag. REINDEX should be used to rebuild an index
|
||||||
|
* if constraint inconsistency is suspected. For optimal performance, other
|
||||||
|
* callers should include the flag only after transforming the data in a manner
|
||||||
|
* that risks a change in constraint validity.
|
||||||
*
|
*
|
||||||
* Returns true if any indexes were rebuilt. Note that a
|
* Returns true if any indexes were rebuilt. Note that a
|
||||||
* CommandCounterIncrement will occur after each index rebuild.
|
* CommandCounterIncrement will occur after each index rebuild.
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt)
|
reindex_relation(Oid relid, bool toast_too, int flags)
|
||||||
{
|
{
|
||||||
Relation rel;
|
Relation rel;
|
||||||
Oid toast_relid;
|
Oid toast_relid;
|
||||||
@ -2604,7 +2607,7 @@ reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt)
|
|||||||
List *doneIndexes;
|
List *doneIndexes;
|
||||||
ListCell *indexId;
|
ListCell *indexId;
|
||||||
|
|
||||||
if (heap_rebuilt)
|
if (flags & REINDEX_SUPPRESS_INDEX_USE)
|
||||||
{
|
{
|
||||||
/* Suppress use of all the indexes until they are rebuilt */
|
/* Suppress use of all the indexes until they are rebuilt */
|
||||||
SetReindexPending(indexIds);
|
SetReindexPending(indexIds);
|
||||||
@ -2625,11 +2628,11 @@ reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt)
|
|||||||
if (is_pg_class)
|
if (is_pg_class)
|
||||||
RelationSetIndexList(rel, doneIndexes, InvalidOid);
|
RelationSetIndexList(rel, doneIndexes, InvalidOid);
|
||||||
|
|
||||||
reindex_index(indexOid, heap_rebuilt);
|
reindex_index(indexOid, !(flags & REINDEX_CHECK_CONSTRAINTS));
|
||||||
|
|
||||||
CommandCounterIncrement();
|
CommandCounterIncrement();
|
||||||
|
|
||||||
if (heap_rebuilt)
|
if (flags & REINDEX_SUPPRESS_INDEX_USE)
|
||||||
RemoveReindexPending(indexOid);
|
RemoveReindexPending(indexOid);
|
||||||
|
|
||||||
if (is_pg_class)
|
if (is_pg_class)
|
||||||
@ -2657,10 +2660,12 @@ reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* If the relation has a secondary toast rel, reindex that too while we
|
* If the relation has a secondary toast rel, reindex that too while we
|
||||||
* still hold the lock on the master table.
|
* still hold the lock on the master table. There's never a reason to
|
||||||
|
* reindex the toast table right after rebuilding the heap.
|
||||||
*/
|
*/
|
||||||
|
Assert(!(toast_too && (flags & REINDEX_SUPPRESS_INDEX_USE)));
|
||||||
if (toast_too && OidIsValid(toast_relid))
|
if (toast_too && OidIsValid(toast_relid))
|
||||||
result |= reindex_relation(toast_relid, false, false);
|
result |= reindex_relation(toast_relid, false, flags);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -565,7 +565,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid,
|
|||||||
* rebuild the target's indexes and throw away the transient table.
|
* rebuild the target's indexes and throw away the transient table.
|
||||||
*/
|
*/
|
||||||
finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
|
finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
|
||||||
swap_toast_by_content, frozenXid);
|
swap_toast_by_content, false, frozenXid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1362,10 +1362,12 @@ void
|
|||||||
finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
|
finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
|
||||||
bool is_system_catalog,
|
bool is_system_catalog,
|
||||||
bool swap_toast_by_content,
|
bool swap_toast_by_content,
|
||||||
|
bool check_constraints,
|
||||||
TransactionId frozenXid)
|
TransactionId frozenXid)
|
||||||
{
|
{
|
||||||
ObjectAddress object;
|
ObjectAddress object;
|
||||||
Oid mapped_tables[4];
|
Oid mapped_tables[4];
|
||||||
|
int reindex_flags;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
/* Zero out possible results from swapped_relation_files */
|
/* Zero out possible results from swapped_relation_files */
|
||||||
@ -1395,7 +1397,10 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
|
|||||||
* so no chance to reclaim disk space before commit. We do not need a
|
* so no chance to reclaim disk space before commit. We do not need a
|
||||||
* final CommandCounterIncrement() because reindex_relation does it.
|
* final CommandCounterIncrement() because reindex_relation does it.
|
||||||
*/
|
*/
|
||||||
reindex_relation(OIDOldHeap, false, true);
|
reindex_flags = REINDEX_SUPPRESS_INDEX_USE;
|
||||||
|
if (check_constraints)
|
||||||
|
reindex_flags |= REINDEX_CHECK_CONSTRAINTS;
|
||||||
|
reindex_relation(OIDOldHeap, false, reindex_flags);
|
||||||
|
|
||||||
/* Destroy new heap with old filenode */
|
/* Destroy new heap with old filenode */
|
||||||
object.classId = RelationRelationId;
|
object.classId = RelationRelationId;
|
||||||
|
@ -1629,7 +1629,7 @@ ReindexTable(RangeVar *relation)
|
|||||||
|
|
||||||
ReleaseSysCache(tuple);
|
ReleaseSysCache(tuple);
|
||||||
|
|
||||||
if (!reindex_relation(heapOid, true, false))
|
if (!reindex_relation(heapOid, true, 0))
|
||||||
ereport(NOTICE,
|
ereport(NOTICE,
|
||||||
(errmsg("table \"%s\" has no indexes",
|
(errmsg("table \"%s\" has no indexes",
|
||||||
relation->relname)));
|
relation->relname)));
|
||||||
@ -1742,7 +1742,7 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
|
|||||||
StartTransactionCommand();
|
StartTransactionCommand();
|
||||||
/* functions in indexes may want a snapshot set */
|
/* functions in indexes may want a snapshot set */
|
||||||
PushActiveSnapshot(GetTransactionSnapshot());
|
PushActiveSnapshot(GetTransactionSnapshot());
|
||||||
if (reindex_relation(relid, true, false))
|
if (reindex_relation(relid, true, 0))
|
||||||
ereport(NOTICE,
|
ereport(NOTICE,
|
||||||
(errmsg("table \"%s.%s\" was reindexed",
|
(errmsg("table \"%s.%s\" was reindexed",
|
||||||
get_namespace_name(get_rel_namespace(relid)),
|
get_namespace_name(get_rel_namespace(relid)),
|
||||||
|
@ -1076,7 +1076,7 @@ ExecuteTruncate(TruncateStmt *stmt)
|
|||||||
/*
|
/*
|
||||||
* Reconstruct the indexes to match, and we're done.
|
* Reconstruct the indexes to match, and we're done.
|
||||||
*/
|
*/
|
||||||
reindex_relation(heap_relid, true, false);
|
reindex_relation(heap_relid, true, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3236,13 +3236,14 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Swap the physical files of the old and new heaps, then rebuild
|
* Swap the physical files of the old and new heaps, then rebuild
|
||||||
* indexes and discard the new heap. We can use RecentXmin for
|
* indexes and discard the old heap. We can use RecentXmin for
|
||||||
* the table's new relfrozenxid because we rewrote all the tuples
|
* the table's new relfrozenxid because we rewrote all the tuples
|
||||||
* in ATRewriteTable, so no older Xid remains in the table. Also,
|
* in ATRewriteTable, so no older Xid remains in the table. Also,
|
||||||
* we never try to swap toast tables by content, since we have no
|
* we never try to swap toast tables by content, since we have no
|
||||||
* interest in letting this code work on system catalogs.
|
* interest in letting this code work on system catalogs.
|
||||||
*/
|
*/
|
||||||
finish_heap_swap(tab->relid, OIDNewHeap, false, false, RecentXmin);
|
finish_heap_swap(tab->relid, OIDNewHeap,
|
||||||
|
false, false, true, RecentXmin);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -71,7 +71,10 @@ extern double IndexBuildHeapScan(Relation heapRelation,
|
|||||||
extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
|
extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
|
||||||
|
|
||||||
extern void reindex_index(Oid indexId, bool skip_constraint_checks);
|
extern void reindex_index(Oid indexId, bool skip_constraint_checks);
|
||||||
extern bool reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt);
|
|
||||||
|
#define REINDEX_CHECK_CONSTRAINTS 0x1
|
||||||
|
#define REINDEX_SUPPRESS_INDEX_USE 0x2
|
||||||
|
extern bool reindex_relation(Oid relid, bool toast_too, int flags);
|
||||||
|
|
||||||
extern bool ReindexIsProcessingHeap(Oid heapOid);
|
extern bool ReindexIsProcessingHeap(Oid heapOid);
|
||||||
extern bool ReindexIsProcessingIndex(Oid indexOid);
|
extern bool ReindexIsProcessingIndex(Oid indexOid);
|
||||||
|
@ -29,6 +29,7 @@ extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace);
|
|||||||
extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
|
extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
|
||||||
bool is_system_catalog,
|
bool is_system_catalog,
|
||||||
bool swap_toast_by_content,
|
bool swap_toast_by_content,
|
||||||
|
bool check_constraints,
|
||||||
TransactionId frozenXid);
|
TransactionId frozenXid);
|
||||||
|
|
||||||
#endif /* CLUSTER_H */
|
#endif /* CLUSTER_H */
|
||||||
|
@ -413,6 +413,10 @@ insert into atacc1 (test) values (4);
|
|||||||
-- try adding a unique oid constraint
|
-- try adding a unique oid constraint
|
||||||
alter table atacc1 add constraint atacc_oid1 unique(oid);
|
alter table atacc1 add constraint atacc_oid1 unique(oid);
|
||||||
NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "atacc_oid1" for table "atacc1"
|
NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "atacc_oid1" for table "atacc1"
|
||||||
|
-- try to create duplicates via alter table using - should fail
|
||||||
|
alter table atacc1 alter column test type integer using 0;
|
||||||
|
ERROR: could not create unique index "atacc_test1"
|
||||||
|
DETAIL: Key (test)=(0) is duplicated.
|
||||||
drop table atacc1;
|
drop table atacc1;
|
||||||
-- let's do one where the unique constraint fails when added
|
-- let's do one where the unique constraint fails when added
|
||||||
create table atacc1 ( test int );
|
create table atacc1 ( test int );
|
||||||
|
@ -419,6 +419,8 @@ insert into atacc1 (test) values (2);
|
|||||||
insert into atacc1 (test) values (4);
|
insert into atacc1 (test) values (4);
|
||||||
-- try adding a unique oid constraint
|
-- try adding a unique oid constraint
|
||||||
alter table atacc1 add constraint atacc_oid1 unique(oid);
|
alter table atacc1 add constraint atacc_oid1 unique(oid);
|
||||||
|
-- try to create duplicates via alter table using - should fail
|
||||||
|
alter table atacc1 alter column test type integer using 0;
|
||||||
drop table atacc1;
|
drop table atacc1;
|
||||||
|
|
||||||
-- let's do one where the unique constraint fails when added
|
-- let's do one where the unique constraint fails when added
|
||||||
|
Reference in New Issue
Block a user