Commit 8ceb2456 authored by Robert Haas's avatar Robert Haas

Make ALTER TABLE revalidate uniqueness and exclusion constraints.

Failure to do so can lead to constraint violations.  This was broken by
commit 1ddc2703 on 2010-02-07, so
back-patch to 9.0.

Noah Misch.  Regression test by me.
parent 14b9f69c
...@@ -2529,26 +2529,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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, ...@@ -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 ...@@ -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, ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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, ...@@ -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); ...@@ -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); ...@@ -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); ...@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment