Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c01eb61

Browse files
committed
Apply stopgap fix for bug #15672.
Fix DefineIndex so that it doesn't attempt to pass down a to-be-reused index relfilenode to a child index creation, and fix TryReuseIndex to not think that reuse is sensible for a partitioned index. In v11, this fixes a problem where ALTER TABLE on a partitioned table could assign the same relfilenode to several different child indexes, causing very nasty catalog corruption --- in fact, attempting to DROP the partitioned table then leads not only to a database crash, but to inability to restart because the same crash will recur during WAL replay. Either of these two changes would be enough to prevent the failure, but since neither action could possibly be sane, let's put in both changes for future-proofing. In HEAD, no such bug manifests, but that's just an accidental consequence of having changed the pg_class representation of partitioned indexes to have relfilenode = 0. Both of these changes still seem like smart future-proofing. This is only a stop-gap because the code for ALTER TABLE on a partitioned table with a no-op type change still leaves a great deal to be desired. As the added regression tests show, it gets things wrong for comments on child indexes/constraints, and it is regenerating child indexes it doesn't have to. However, fixing those problems will take more work which may not get back-patched into v11. We need a fix for the corruption problem now. Per bug #15672 from Jianing Yang. Patch by me, regression test cases based on work by Amit Langote, who also did a lot of the investigative work. Discussion: https://postgr.es/m/15672-b9fa7db32698269f@postgresql.org
1 parent 7fcdb5e commit c01eb61

File tree

4 files changed

+166
-3
lines changed

4 files changed

+166
-3
lines changed

src/backend/commands/indexcmds.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,18 @@ DefineIndex(Oid relationId,
11501150
bool found_whole_row;
11511151
ListCell *lc;
11521152

1153+
/*
1154+
* We can't use the same index name for the child index,
1155+
* so clear idxname to let the recursive invocation choose
1156+
* a new name. Likewise, the existing target relation
1157+
* field is wrong, and if indexOid or oldNode are set,
1158+
* they mustn't be applied to the child either.
1159+
*/
1160+
childStmt->idxname = NULL;
1161+
childStmt->relation = NULL;
1162+
childStmt->indexOid = InvalidOid;
1163+
childStmt->oldNode = InvalidOid;
1164+
11531165
/*
11541166
* Adjust any Vars (both in expressions and in the index's
11551167
* WHERE clause) to match the partition's column numbering
@@ -1181,8 +1193,6 @@ DefineIndex(Oid relationId,
11811193
if (found_whole_row)
11821194
elog(ERROR, "cannot convert whole-row table reference");
11831195

1184-
childStmt->idxname = NULL;
1185-
childStmt->relation = NULL;
11861196
DefineIndex(childRelid, childStmt,
11871197
InvalidOid, /* no predefined OID */
11881198
indexRelationId, /* this is our child */

src/backend/commands/tablecmds.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11452,7 +11452,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt)
1145211452
{
1145311453
Relation irel = index_open(oldId, NoLock);
1145411454

11455-
stmt->oldNode = irel->rd_node.relNode;
11455+
/* If it's a partitioned index, there is no storage to share. */
11456+
if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
11457+
stmt->oldNode = irel->rd_node.relNode;
1145611458
index_close(irel, NoLock);
1145711459
}
1145811460
}

src/test/regress/expected/alter_table.out

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,6 +1990,94 @@ Indexes:
19901990
"at_part_2_a_idx" btree (a)
19911991
"at_part_2_b_idx" btree (b)
19921992

1993+
drop table at_partitioned;
1994+
-- Alter column type when no table rewrite is required
1995+
-- Also check that comments are preserved
1996+
create table at_partitioned(id int, name varchar(64), unique (id, name))
1997+
partition by hash(id);
1998+
comment on constraint at_partitioned_id_name_key on at_partitioned is 'parent constraint';
1999+
comment on index at_partitioned_id_name_key is 'parent index';
2000+
create table at_partitioned_0 partition of at_partitioned
2001+
for values with (modulus 2, remainder 0);
2002+
comment on constraint at_partitioned_0_id_name_key on at_partitioned_0 is 'child 0 constraint';
2003+
comment on index at_partitioned_0_id_name_key is 'child 0 index';
2004+
create table at_partitioned_1 partition of at_partitioned
2005+
for values with (modulus 2, remainder 1);
2006+
comment on constraint at_partitioned_1_id_name_key on at_partitioned_1 is 'child 1 constraint';
2007+
comment on index at_partitioned_1_id_name_key is 'child 1 index';
2008+
insert into at_partitioned values(1, 'foo');
2009+
insert into at_partitioned values(3, 'bar');
2010+
create temp table old_oids as
2011+
select relname, oid as oldoid, relfilenode as oldfilenode
2012+
from pg_class where relname like 'at_partitioned%';
2013+
select relname,
2014+
c.oid = oldoid as orig_oid,
2015+
case relfilenode
2016+
when 0 then 'none'
2017+
when c.oid then 'own'
2018+
when oldfilenode then 'orig'
2019+
else 'OTHER'
2020+
end as storage,
2021+
obj_description(c.oid, 'pg_class') as desc
2022+
from pg_class c left join old_oids using (relname)
2023+
where relname like 'at_partitioned%'
2024+
order by relname;
2025+
relname | orig_oid | storage | desc
2026+
------------------------------+----------+---------+---------------
2027+
at_partitioned | t | none |
2028+
at_partitioned_0 | t | own |
2029+
at_partitioned_0_id_name_key | t | own | child 0 index
2030+
at_partitioned_1 | t | own |
2031+
at_partitioned_1_id_name_key | t | own | child 1 index
2032+
at_partitioned_id_name_key | t | none | parent index
2033+
(6 rows)
2034+
2035+
select conname, obj_description(oid, 'pg_constraint') as desc
2036+
from pg_constraint where conname like 'at_partitioned%'
2037+
order by conname;
2038+
conname | desc
2039+
------------------------------+--------------------
2040+
at_partitioned_0_id_name_key | child 0 constraint
2041+
at_partitioned_1_id_name_key | child 1 constraint
2042+
at_partitioned_id_name_key | parent constraint
2043+
(3 rows)
2044+
2045+
alter table at_partitioned alter column name type varchar(127);
2046+
-- Note: these tests currently show the wrong behavior for comments :-(
2047+
select relname,
2048+
c.oid = oldoid as orig_oid,
2049+
case relfilenode
2050+
when 0 then 'none'
2051+
when c.oid then 'own'
2052+
when oldfilenode then 'orig'
2053+
else 'OTHER'
2054+
end as storage,
2055+
obj_description(c.oid, 'pg_class') as desc
2056+
from pg_class c left join old_oids using (relname)
2057+
where relname like 'at_partitioned%'
2058+
order by relname;
2059+
relname | orig_oid | storage | desc
2060+
------------------------------+----------+---------+--------------
2061+
at_partitioned | t | none |
2062+
at_partitioned_0 | t | own |
2063+
at_partitioned_0_id_name_key | f | own | parent index
2064+
at_partitioned_1 | t | own |
2065+
at_partitioned_1_id_name_key | f | own | parent index
2066+
at_partitioned_id_name_key | f | none | parent index
2067+
(6 rows)
2068+
2069+
select conname, obj_description(oid, 'pg_constraint') as desc
2070+
from pg_constraint where conname like 'at_partitioned%'
2071+
order by conname;
2072+
conname | desc
2073+
------------------------------+-------------------
2074+
at_partitioned_0_id_name_key |
2075+
at_partitioned_1_id_name_key |
2076+
at_partitioned_id_name_key | parent constraint
2077+
(3 rows)
2078+
2079+
-- Don't remove this DROP, it exposes bug #15672
2080+
drop table at_partitioned;
19932081
-- disallow recursive containment of row types
19942082
create temp table recur1 (f1 int);
19952083
alter table recur1 add column f2 recur1; -- fails

src/test/regress/sql/alter_table.sql

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,69 @@ alter table at_partitioned attach partition at_part_2 for values from (1000) to
13531353
alter table at_partitioned alter column b type numeric using b::numeric;
13541354
\d at_part_1
13551355
\d at_part_2
1356+
drop table at_partitioned;
1357+
1358+
-- Alter column type when no table rewrite is required
1359+
-- Also check that comments are preserved
1360+
create table at_partitioned(id int, name varchar(64), unique (id, name))
1361+
partition by hash(id);
1362+
comment on constraint at_partitioned_id_name_key on at_partitioned is 'parent constraint';
1363+
comment on index at_partitioned_id_name_key is 'parent index';
1364+
create table at_partitioned_0 partition of at_partitioned
1365+
for values with (modulus 2, remainder 0);
1366+
comment on constraint at_partitioned_0_id_name_key on at_partitioned_0 is 'child 0 constraint';
1367+
comment on index at_partitioned_0_id_name_key is 'child 0 index';
1368+
create table at_partitioned_1 partition of at_partitioned
1369+
for values with (modulus 2, remainder 1);
1370+
comment on constraint at_partitioned_1_id_name_key on at_partitioned_1 is 'child 1 constraint';
1371+
comment on index at_partitioned_1_id_name_key is 'child 1 index';
1372+
insert into at_partitioned values(1, 'foo');
1373+
insert into at_partitioned values(3, 'bar');
1374+
1375+
create temp table old_oids as
1376+
select relname, oid as oldoid, relfilenode as oldfilenode
1377+
from pg_class where relname like 'at_partitioned%';
1378+
1379+
select relname,
1380+
c.oid = oldoid as orig_oid,
1381+
case relfilenode
1382+
when 0 then 'none'
1383+
when c.oid then 'own'
1384+
when oldfilenode then 'orig'
1385+
else 'OTHER'
1386+
end as storage,
1387+
obj_description(c.oid, 'pg_class') as desc
1388+
from pg_class c left join old_oids using (relname)
1389+
where relname like 'at_partitioned%'
1390+
order by relname;
1391+
1392+
select conname, obj_description(oid, 'pg_constraint') as desc
1393+
from pg_constraint where conname like 'at_partitioned%'
1394+
order by conname;
1395+
1396+
alter table at_partitioned alter column name type varchar(127);
1397+
1398+
-- Note: these tests currently show the wrong behavior for comments :-(
1399+
1400+
select relname,
1401+
c.oid = oldoid as orig_oid,
1402+
case relfilenode
1403+
when 0 then 'none'
1404+
when c.oid then 'own'
1405+
when oldfilenode then 'orig'
1406+
else 'OTHER'
1407+
end as storage,
1408+
obj_description(c.oid, 'pg_class') as desc
1409+
from pg_class c left join old_oids using (relname)
1410+
where relname like 'at_partitioned%'
1411+
order by relname;
1412+
1413+
select conname, obj_description(oid, 'pg_constraint') as desc
1414+
from pg_constraint where conname like 'at_partitioned%'
1415+
order by conname;
1416+
1417+
-- Don't remove this DROP, it exposes bug #15672
1418+
drop table at_partitioned;
13561419

13571420
-- disallow recursive containment of row types
13581421
create temp table recur1 (f1 int);

0 commit comments

Comments
 (0)