From bd5edd67b1e4a15d0c1ff0a58f8b89df8eded3af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=81lvaro=20Rodr=C3=ADguez?=
 <alvarorodriguez.garcia@datadoghq.com>
Date: Thu, 14 May 2026 15:27:30 +0200
Subject: [PATCH v5 1/3] Add regression test to highlight unexpected behavior
 when running 'safe' alter column types on partitioned tables

---
 src/test/regress/expected/alter_table.out | 90 +++++++++++++++++++++++
 src/test/regress/sql/alter_table.sql      | 68 +++++++++++++++++
 2 files changed, 158 insertions(+)

diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 20707c186b3..0b67306f532 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2351,6 +2351,96 @@ select conname, obj_description(oid, 'pg_constraint') as desc
 
 -- Don't remove this DROP, it exposes bug #15672
 drop table at_partitioned;
+-- Alter column type when no index rewrite is required
+-- for partitioned tables. Reindex is required although
+-- it shouldn't be.
+create table at_idx_part (id int, code varchar(10),
+                          primary key (id, code))
+  partition by range (id);
+create table at_idx_part_p1 partition of at_idx_part
+  for values from (0)   to (100);
+create table at_idx_part_p2 partition of at_idx_part
+  for values from (100) to (200);
+create index at_idx_part_code_idx on at_idx_part (code);
+insert into at_idx_part values (1, 'a'), (101, 'b');
+create temp table at_idx_old as
+  select relname, oid as oldoid, relfilenode as oldfilenode
+  from pg_class where relname like 'at_idx_part%';
+alter table at_idx_part alter column code type varchar(20);
+select relname,
+  c.oid = oldoid as same_oid,
+  case relfilenode
+    when 0 then 'none'
+    when c.oid then 'own'
+    when oldfilenode then 'orig'
+    else 'OTHER'
+    end as storage
+  from pg_class c join at_idx_old using (relname)
+  where relname like 'at_idx_part%'
+  order by relname;
+         relname         | same_oid | storage 
+-------------------------+----------+---------
+ at_idx_part             | t        | none
+ at_idx_part_code_idx    | f        | none
+ at_idx_part_p1          | t        | own
+ at_idx_part_p1_code_idx | f        | own
+ at_idx_part_p1_pkey     | f        | own
+ at_idx_part_p2          | t        | own
+ at_idx_part_p2_code_idx | f        | own
+ at_idx_part_p2_pkey     | f        | own
+ at_idx_part_pkey        | f        | none
+(9 rows)
+
+drop table at_idx_part;
+-- Same, but with two levels of partitioning.
+create table at_idx_part2 (id int, code varchar(10),
+                           primary key (id, code))
+  partition by range (id);
+create table at_idx_part2_p1 partition of at_idx_part2
+  for values from (0)   to (100) partition by range (id);
+create table at_idx_part2_p1_1 partition of at_idx_part2_p1
+  for values from (0)   to (50);
+create table at_idx_part2_p1_2 partition of at_idx_part2_p1
+  for values from (50)  to (100);
+create table at_idx_part2_p2 partition of at_idx_part2
+  for values from (100) to (200);
+create index at_idx_part2_code_idx on at_idx_part2 (code);
+insert into at_idx_part2 values (1, 'a'), (51, 'b'), (101, 'c');
+create temp table at_idx_old2 as
+  select relname, oid as oldoid, relfilenode as oldfilenode
+  from pg_class where relname like 'at_idx_part2%';
+alter table at_idx_part2 alter column code type varchar(20);
+select relname,
+  c.oid = oldoid as same_oid,
+  case relfilenode
+    when 0 then 'none'
+    when c.oid then 'own'
+    when oldfilenode then 'orig'
+    else 'OTHER'
+    end as storage
+  from pg_class c join at_idx_old2 using (relname)
+  where relname like 'at_idx_part2%'
+  order by relname;
+          relname           | same_oid | storage 
+----------------------------+----------+---------
+ at_idx_part2               | t        | none
+ at_idx_part2_code_idx      | f        | none
+ at_idx_part2_p1            | t        | none
+ at_idx_part2_p1_1          | t        | own
+ at_idx_part2_p1_1_code_idx | f        | own
+ at_idx_part2_p1_1_pkey     | f        | own
+ at_idx_part2_p1_2          | t        | own
+ at_idx_part2_p1_2_code_idx | f        | own
+ at_idx_part2_p1_2_pkey     | f        | own
+ at_idx_part2_p1_code_idx   | f        | none
+ at_idx_part2_p1_pkey       | f        | none
+ at_idx_part2_p2            | t        | own
+ at_idx_part2_p2_code_idx   | f        | own
+ at_idx_part2_p2_pkey       | f        | own
+ at_idx_part2_pkey          | f        | none
+(15 rows)
+
+drop table at_idx_part2;
 -- disallow recursive containment of row types
 create temp table recur1 (f1 int);
 alter table recur1 add column f2 recur1; -- fails
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 18f3fdac57a..5d2098befc4 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1557,6 +1557,74 @@ select conname, obj_description(oid, 'pg_constraint') as desc
 -- Don't remove this DROP, it exposes bug #15672
 drop table at_partitioned;
 
+-- Alter column type when no index rewrite is required
+-- for partitioned tables. Reindex is required although
+-- it shouldn't be.
+create table at_idx_part (id int, code varchar(10),
+                          primary key (id, code))
+  partition by range (id);
+create table at_idx_part_p1 partition of at_idx_part
+  for values from (0)   to (100);
+create table at_idx_part_p2 partition of at_idx_part
+  for values from (100) to (200);
+create index at_idx_part_code_idx on at_idx_part (code);
+insert into at_idx_part values (1, 'a'), (101, 'b');
+
+create temp table at_idx_old as
+  select relname, oid as oldoid, relfilenode as oldfilenode
+  from pg_class where relname like 'at_idx_part%';
+
+alter table at_idx_part alter column code type varchar(20);
+
+select relname,
+  c.oid = oldoid as same_oid,
+  case relfilenode
+    when 0 then 'none'
+    when c.oid then 'own'
+    when oldfilenode then 'orig'
+    else 'OTHER'
+    end as storage
+  from pg_class c join at_idx_old using (relname)
+  where relname like 'at_idx_part%'
+  order by relname;
+
+drop table at_idx_part;
+
+-- Same, but with two levels of partitioning.
+create table at_idx_part2 (id int, code varchar(10),
+                           primary key (id, code))
+  partition by range (id);
+create table at_idx_part2_p1 partition of at_idx_part2
+  for values from (0)   to (100) partition by range (id);
+create table at_idx_part2_p1_1 partition of at_idx_part2_p1
+  for values from (0)   to (50);
+create table at_idx_part2_p1_2 partition of at_idx_part2_p1
+  for values from (50)  to (100);
+create table at_idx_part2_p2 partition of at_idx_part2
+  for values from (100) to (200);
+create index at_idx_part2_code_idx on at_idx_part2 (code);
+insert into at_idx_part2 values (1, 'a'), (51, 'b'), (101, 'c');
+
+create temp table at_idx_old2 as
+  select relname, oid as oldoid, relfilenode as oldfilenode
+  from pg_class where relname like 'at_idx_part2%';
+
+alter table at_idx_part2 alter column code type varchar(20);
+
+select relname,
+  c.oid = oldoid as same_oid,
+  case relfilenode
+    when 0 then 'none'
+    when c.oid then 'own'
+    when oldfilenode then 'orig'
+    else 'OTHER'
+    end as storage
+  from pg_class c join at_idx_old2 using (relname)
+  where relname like 'at_idx_part2%'
+  order by relname;
+
+drop table at_idx_part2;
+
 -- disallow recursive containment of row types
 create temp table recur1 (f1 int);
 alter table recur1 add column f2 recur1; -- fails
-- 
2.52.0

