diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index c4c09e53bb1..4c3f7722468 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -540,23 +540,31 @@ vac_estimate_reltuples(Relation relation, bool is_analyze, * * We violate transaction semantics here by overwriting the rel's * existing pg_class tuple with the new values. This is reasonably - * safe since the new values are correct whether or not this transaction - * commits. The reason for this is that if we updated these tuples in - * the usual way, vacuuming pg_class itself wouldn't work very well --- - * by the time we got done with a vacuum cycle, most of the tuples in - * pg_class would've been obsoleted. Of course, this only works for - * fixed-size never-null columns, but these are. - * - * Note another assumption: that two VACUUMs/ANALYZEs on a table can't - * run in parallel, nor can VACUUM/ANALYZE run in parallel with a - * schema alteration such as adding an index, rule, or trigger. Otherwise - * our updates of relhasindex etc might overwrite uncommitted updates. + * safe as long as we're sure that the new values are correct whether or + * not this transaction commits. The reason for doing this is that if + * we updated these tuples in the usual way, vacuuming pg_class itself + * wouldn't work very well --- by the time we got done with a vacuum + * cycle, most of the tuples in pg_class would've been obsoleted. Of + * course, this only works for fixed-size not-null columns, but these are. * * Another reason for doing it this way is that when we are in a lazy - * VACUUM and have PROC_IN_VACUUM set, we mustn't do any updates --- - * somebody vacuuming pg_class might think they could delete a tuple + * VACUUM and have PROC_IN_VACUUM set, we mustn't do any regular updates. + * Somebody vacuuming pg_class might think they could delete a tuple * marked with xmin = our xid. * + * In addition to fundamentally nontransactional statistics such as + * relpages and relallvisible, we try to maintain certain lazily-updated + * DDL flags such as relhasindex, by clearing them if no longer correct. + * It's safe to do this in VACUUM, which can't run in parallel with + * CREATE INDEX/RULE/TRIGGER and can't be part of a transaction block. + * However, it's *not* safe to do it in an ANALYZE that's within a + * transaction block, because for example the current transaction might + * have dropped the last index; then we'd think relhasindex should be + * cleared, but if the transaction later rolls back this would be wrong. + * So we refrain from updating the DDL flags if we're inside a + * transaction block. This is OK since postponing the flag maintenance + * is always allowable. + * * This routine is shared by VACUUM and ANALYZE. */ void @@ -579,7 +587,7 @@ vac_update_relstats(Relation relation, relid); pgcform = (Form_pg_class) GETSTRUCT(ctup); - /* Apply required updates, if any, to copied tuple */ + /* Apply statistical updates, if any, to copied tuple */ dirty = false; if (pgcform->relpages != (int32) num_pages) @@ -592,41 +600,50 @@ vac_update_relstats(Relation relation, pgcform->reltuples = (float4) num_tuples; dirty = true; } - if (pgcform->relhasindex != hasindex) - { - pgcform->relhasindex = hasindex; - dirty = true; - } - /* - * If we have discovered that there are no indexes, then there's no - * primary key either, nor any exclusion constraints. This could be done - * more thoroughly... - */ - if (!hasindex) + /* Apply DDL updates, but not inside a transaction block (see above) */ + + if (!IsTransactionBlock()) { - if (pgcform->relhaspkey) + /* + * If we didn't find any indexes, reset relhasindex. + */ + if (pgcform->relhasindex && !hasindex) { - pgcform->relhaspkey = false; + pgcform->relhasindex = false; dirty = true; } - if (pgcform->relhasexclusion && pgcform->relkind != RELKIND_INDEX) + + /* + * If we have discovered that there are no indexes, then there's no + * primary key either, nor any exclusion constraints. This could be + * done more thoroughly... + */ + if (!hasindex) { - pgcform->relhasexclusion = false; + if (pgcform->relhaspkey) + { + pgcform->relhaspkey = false; + dirty = true; + } + if (pgcform->relhasexclusion && pgcform->relkind != RELKIND_INDEX) + { + pgcform->relhasexclusion = false; + dirty = true; + } + } + + /* We also clear relhasrules and relhastriggers if needed */ + if (pgcform->relhasrules && relation->rd_rules == NULL) + { + pgcform->relhasrules = false; + dirty = true; + } + if (pgcform->relhastriggers && relation->trigdesc == NULL) + { + pgcform->relhastriggers = false; dirty = true; } - } - - /* We also clear relhasrules and relhastriggers if needed */ - if (pgcform->relhasrules && relation->rd_rules == NULL) - { - pgcform->relhasrules = false; - dirty = true; - } - if (pgcform->relhastriggers && relation->trigdesc == NULL) - { - pgcform->relhastriggers = false; - dirty = true; } /* diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 0da5ec58b56..e793a293b2f 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -1544,6 +1544,24 @@ select non_strict(NULL); (1 row) +-- check for rollback of ANALYZE corrupting table property flags (bug #11638) +CREATE TABLE check_fk_presence_1 (id int PRIMARY KEY, t text); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "check_fk_presence_1_pkey" for table "check_fk_presence_1" +CREATE TABLE check_fk_presence_2 (id int REFERENCES check_fk_presence_1, t text); +BEGIN; +ALTER TABLE check_fk_presence_2 DROP CONSTRAINT check_fk_presence_2_id_fkey; +ANALYZE check_fk_presence_2; +ROLLBACK; +\d check_fk_presence_2 +Table "public.check_fk_presence_2" + Column | Type | Modifiers +--------+---------+----------- + id | integer | + t | text | +Foreign-key constraints: + "check_fk_presence_2_id_fkey" FOREIGN KEY (id) REFERENCES check_fk_presence_1(id) + +DROP TABLE check_fk_presence_1, check_fk_presence_2; -- -- alter object set schema -- diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 0a41fd85dc6..9cb69bb0264 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1124,6 +1124,16 @@ select non_strict(NULL); alter function non_strict(text) returns null on null input; select non_strict(NULL); +-- check for rollback of ANALYZE corrupting table property flags (bug #11638) +CREATE TABLE check_fk_presence_1 (id int PRIMARY KEY, t text); +CREATE TABLE check_fk_presence_2 (id int REFERENCES check_fk_presence_1, t text); +BEGIN; +ALTER TABLE check_fk_presence_2 DROP CONSTRAINT check_fk_presence_2_id_fkey; +ANALYZE check_fk_presence_2; +ROLLBACK; +\d check_fk_presence_2 +DROP TABLE check_fk_presence_1, check_fk_presence_2; + -- -- alter object set schema --