Skip to content

Commit 39b5e5f

Browse files
committed
Make ALTER TABLE revalidate uniqueness and exclusion constraints.
Failure to do so can lead to constraint violations. This was broken by commit 1ddc270 on 2010-02-07, so back-patch to 9.0. Noah Misch. Regression test by me.
1 parent ba3afc8 commit 39b5e5f

File tree

8 files changed

+48
-27
lines changed

8 files changed

+48
-27
lines changed

src/backend/catalog/index.c

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,26 +2508,29 @@ reindex_index(Oid indexId, bool skip_constraint_checks)
25082508
* reindex_relation - This routine is used to recreate all indexes
25092509
* of a relation (and optionally its toast relation too, if any).
25102510
*
2511-
* If heap_rebuilt is true, then the relation was just completely rebuilt by
2512-
* an operation such as VACUUM FULL or CLUSTER, and therefore its indexes are
2513-
* inconsistent with it. This makes things tricky if the relation is a system
2514-
* catalog that we might consult during the reindexing. To deal with that
2515-
* case, we mark all of the indexes as pending rebuild so that they won't be
2516-
* trusted until rebuilt. The caller is required to call us *without* having
2517-
* made the rebuilt versions visible by doing CommandCounterIncrement; we'll
2518-
* do CCI after having collected the index list. (This way we can still use
2519-
* catalog indexes while collecting the list.)
2511+
* "flags" can include REINDEX_SUPPRESS_INDEX_USE and REINDEX_CHECK_CONSTRAINTS.
25202512
*
2521-
* We also skip rechecking uniqueness/exclusion constraint properties if
2522-
* heap_rebuilt is true. This avoids likely deadlock conditions when doing
2523-
* VACUUM FULL or CLUSTER on system catalogs. REINDEX should be used to
2524-
* rebuild an index if constraint inconsistency is suspected.
2513+
* If flags has REINDEX_SUPPRESS_INDEX_USE, the relation was just completely
2514+
* rebuilt by an operation such as VACUUM FULL or CLUSTER, and therefore its
2515+
* indexes are inconsistent with it. This makes things tricky if the relation
2516+
* is a system catalog that we might consult during the reindexing. To deal
2517+
* with that case, we mark all of the indexes as pending rebuild so that they
2518+
* won't be trusted until rebuilt. The caller is required to call us *without*
2519+
* having made the rebuilt versions visible by doing CommandCounterIncrement;
2520+
* we'll do CCI after having collected the index list. (This way we can still
2521+
* use catalog indexes while collecting the list.)
2522+
*
2523+
* To avoid deadlocks, VACUUM FULL or CLUSTER on a system catalog must omit the
2524+
* REINDEX_CHECK_CONSTRAINTS flag. REINDEX should be used to rebuild an index
2525+
* if constraint inconsistency is suspected. For optimal performance, other
2526+
* callers should include the flag only after transforming the data in a manner
2527+
* that risks a change in constraint validity.
25252528
*
25262529
* Returns true if any indexes were rebuilt. Note that a
25272530
* CommandCounterIncrement will occur after each index rebuild.
25282531
*/
25292532
bool
2530-
reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt)
2533+
reindex_relation(Oid relid, bool toast_too, int flags)
25312534
{
25322535
Relation rel;
25332536
Oid toast_relid;
@@ -2583,7 +2586,7 @@ reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt)
25832586
List *doneIndexes;
25842587
ListCell *indexId;
25852588

2586-
if (heap_rebuilt)
2589+
if (flags & REINDEX_SUPPRESS_INDEX_USE)
25872590
{
25882591
/* Suppress use of all the indexes until they are rebuilt */
25892592
SetReindexPending(indexIds);
@@ -2604,11 +2607,11 @@ reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt)
26042607
if (is_pg_class)
26052608
RelationSetIndexList(rel, doneIndexes, InvalidOid);
26062609

2607-
reindex_index(indexOid, heap_rebuilt);
2610+
reindex_index(indexOid, !(flags & REINDEX_CHECK_CONSTRAINTS));
26082611

26092612
CommandCounterIncrement();
26102613

2611-
if (heap_rebuilt)
2614+
if (flags & REINDEX_SUPPRESS_INDEX_USE)
26122615
RemoveReindexPending(indexOid);
26132616

26142617
if (is_pg_class)
@@ -2636,10 +2639,12 @@ reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt)
26362639

26372640
/*
26382641
* If the relation has a secondary toast rel, reindex that too while we
2639-
* still hold the lock on the master table.
2642+
* still hold the lock on the master table. There's never a reason to
2643+
* reindex the toast table right after rebuilding the heap.
26402644
*/
2645+
Assert(!(toast_too && (flags & REINDEX_SUPPRESS_INDEX_USE)));
26412646
if (toast_too && OidIsValid(toast_relid))
2642-
result |= reindex_relation(toast_relid, false, false);
2647+
result |= reindex_relation(toast_relid, false, flags);
26432648

26442649
return result;
26452650
}

src/backend/commands/cluster.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid,
607607
* rebuild the target's indexes and throw away the transient table.
608608
*/
609609
finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
610-
swap_toast_by_content, frozenXid);
610+
swap_toast_by_content, false, frozenXid);
611611
}
612612

613613

@@ -1326,10 +1326,12 @@ void
13261326
finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
13271327
bool is_system_catalog,
13281328
bool swap_toast_by_content,
1329+
bool check_constraints,
13291330
TransactionId frozenXid)
13301331
{
13311332
ObjectAddress object;
13321333
Oid mapped_tables[4];
1334+
int reindex_flags;
13331335
int i;
13341336

13351337
/* Zero out possible results from swapped_relation_files */
@@ -1359,7 +1361,10 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
13591361
* so no chance to reclaim disk space before commit. We do not need a
13601362
* final CommandCounterIncrement() because reindex_relation does it.
13611363
*/
1362-
reindex_relation(OIDOldHeap, false, true);
1364+
reindex_flags = REINDEX_SUPPRESS_INDEX_USE;
1365+
if (check_constraints)
1366+
reindex_flags |= REINDEX_CHECK_CONSTRAINTS;
1367+
reindex_relation(OIDOldHeap, false, reindex_flags);
13631368

13641369
/* Destroy new heap with old filenode */
13651370
object.classId = RelationRelationId;

src/backend/commands/indexcmds.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,7 +1634,7 @@ ReindexTable(RangeVar *relation)
16341634

16351635
ReleaseSysCache(tuple);
16361636

1637-
if (!reindex_relation(heapOid, true, false))
1637+
if (!reindex_relation(heapOid, true, 0))
16381638
ereport(NOTICE,
16391639
(errmsg("table \"%s\" has no indexes",
16401640
relation->relname)));
@@ -1747,7 +1747,7 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
17471747
StartTransactionCommand();
17481748
/* functions in indexes may want a snapshot set */
17491749
PushActiveSnapshot(GetTransactionSnapshot());
1750-
if (reindex_relation(relid, true, false))
1750+
if (reindex_relation(relid, true, 0))
17511751
ereport(NOTICE,
17521752
(errmsg("table \"%s.%s\" was reindexed",
17531753
get_namespace_name(get_rel_namespace(relid)),

src/backend/commands/tablecmds.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ ExecuteTruncate(TruncateStmt *stmt)
10321032
/*
10331033
* Reconstruct the indexes to match, and we're done.
10341034
*/
1035-
reindex_relation(heap_relid, true, false);
1035+
reindex_relation(heap_relid, true, 0);
10361036
}
10371037
}
10381038

@@ -2941,13 +2941,14 @@ ATRewriteTables(List **wqueue)
29412941

29422942
/*
29432943
* Swap the physical files of the old and new heaps, then rebuild
2944-
* indexes and discard the new heap. We can use RecentXmin for
2944+
* indexes and discard the old heap. We can use RecentXmin for
29452945
* the table's new relfrozenxid because we rewrote all the tuples
29462946
* in ATRewriteTable, so no older Xid remains in the table. Also,
29472947
* we never try to swap toast tables by content, since we have no
29482948
* interest in letting this code work on system catalogs.
29492949
*/
2950-
finish_heap_swap(tab->relid, OIDNewHeap, false, false, RecentXmin);
2950+
finish_heap_swap(tab->relid, OIDNewHeap,
2951+
false, false, true, RecentXmin);
29512952
}
29522953
else
29532954
{

src/include/catalog/index.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ extern double IndexBuildHeapScan(Relation heapRelation,
7171
extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
7272

7373
extern void reindex_index(Oid indexId, bool skip_constraint_checks);
74-
extern bool reindex_relation(Oid relid, bool toast_too, bool heap_rebuilt);
74+
75+
#define REINDEX_CHECK_CONSTRAINTS 0x1
76+
#define REINDEX_SUPPRESS_INDEX_USE 0x2
77+
extern bool reindex_relation(Oid relid, bool toast_too, int flags);
7578

7679
extern bool ReindexIsProcessingHeap(Oid heapOid);
7780
extern bool ReindexIsProcessingIndex(Oid indexOid);

src/include/commands/cluster.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ extern Oid make_new_heap(Oid OIDOldHeap, Oid NewTableSpace);
2828
extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
2929
bool is_system_catalog,
3030
bool swap_toast_by_content,
31+
bool check_constraints,
3132
TransactionId frozenXid);
3233

3334
#endif /* CLUSTER_H */

src/test/regress/expected/alter_table.out

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ insert into atacc1 (test) values (4);
413413
-- try adding a unique oid constraint
414414
alter table atacc1 add constraint atacc_oid1 unique(oid);
415415
NOTICE: ALTER TABLE / ADD UNIQUE will create implicit index "atacc_oid1" for table "atacc1"
416+
-- try to create duplicates via alter table using - should fail
417+
alter table atacc1 alter column test type integer using 0;
418+
ERROR: could not create unique index "atacc_test1"
419+
DETAIL: Key (test)=(0) is duplicated.
416420
drop table atacc1;
417421
-- let's do one where the unique constraint fails when added
418422
create table atacc1 ( test int );

src/test/regress/sql/alter_table.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,8 @@ insert into atacc1 (test) values (2);
419419
insert into atacc1 (test) values (4);
420420
-- try adding a unique oid constraint
421421
alter table atacc1 add constraint atacc_oid1 unique(oid);
422+
-- try to create duplicates via alter table using - should fail
423+
alter table atacc1 alter column test type integer using 0;
422424
drop table atacc1;
423425

424426
-- let's do one where the unique constraint fails when added

0 commit comments

Comments
 (0)