From bc84ba6c8ad89721807b701de51a0f12a1d99706 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 16:00:27 +0200
Subject: [PATCH v1 3/3] Skip index rewriting for PK-associated indexes when
 possible on ALTER TABLE ALTER COLUMN TYPE for partitioned tables

This is a follow-up to the same fix for non-constraint indexes. Just as in there, the parent constraint is dropped
first, and cascades to the child constraints, so that the associated indexes cannot be recreated properly.

We remove the condition that preented child constraints from being re-added. However, this causes the constraints
to be added twice (once when the parent is recreated, and once when the constraints themselves are recreated). We
need to make sure that this happens in the right order, so we first create the child constraints (reusing the index
if possible), and then the parent constraint, which should automatically reuse all child constraints and indexes.
---
 src/backend/commands/tablecmds.c          | 31 ++++++++++++++++++-----
 src/test/regress/expected/alter_table.out | 20 +++++++--------
 2 files changed, 35 insertions(+), 16 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 9bd2936476f..568eda6600c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15782,10 +15782,12 @@ ATPostAlterTypeCleanup(List **wqueue, ObjectAddresses *objects, AlteredTableInfo
 		 * If the constraint is inherited (only), we don't want to inject a
 		 * new definition here; it'll get recreated when
 		 * ATAddCheckNNConstraint recurses from adding the parent table's
-		 * constraint.  But we had to carry the info this far so that we can
-		 * drop the constraint below.
+		 * constraint. But we had to carry the info this far so that we can
+		 * drop the constraint below. The exception to this are index-backed
+		 * constraints: we need to process the child constraints explicitly so
+		 * we save the necessary information to avoid re-indexing.
 		 */
-		if (!conislocal)
+		if (!conislocal && con->conindid == InvalidOid)
 			continue;
 
 		/*
@@ -16017,9 +16019,12 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 				{
 					IndexStmt  *indstmt;
 					Oid			indoid;
+					char		relkind;
+					AlterTablePass indexCreationPass;
 
 					indstmt = castNode(IndexStmt, cmd->def);
 					indoid = get_constraint_index(oldId);
+					relkind = get_rel_relkind(indoid);
 
 					if (!rewrite)
 						TryReuseIndex(indoid, indstmt);
@@ -16029,12 +16034,26 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 					indstmt->reset_default_tblspc = true;
 
 					cmd->subtype = AT_ReAddIndex;
-					tab->subcmds[AT_PASS_OLD_INDEX] =
-						lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
+
+					/*
+					 * We process constraints backed by a partitioned index at
+					 * a later stage. Otherwise, we might create the child 
+					 * indexes twice, which will error out. We mimic here
+					 * the behavior for partitioned indexes, although in here
+					 * the logic relies on parent constraints automatically 
+					 * reusing existing child constraints.
+					 */
+					if (relkind == RELKIND_PARTITIONED_INDEX)
+						indexCreationPass = AT_PASS_OLD_PARTITIONED_INDEX;
+					else
+						indexCreationPass = AT_PASS_OLD_INDEX;
+
+					tab->subcmds[indexCreationPass] =
+						lappend(tab->subcmds[indexCreationPass], cmd);
 
 					/* recreate any comment on the constraint */
 					RebuildConstraintComment(tab,
-											 AT_PASS_OLD_INDEX,
+											 indexCreationPass,
 											 oldId,
 											 rel,
 											 NIL,
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 40beb57c08f..37b599329ef 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2304,23 +2304,23 @@ select relname,
   from pg_class c left join old_oids using (relname)
   where relname like 'at_partitioned%'
   order by relname;
-           relname            | orig_oid | storage |     desc     
-------------------------------+----------+---------+--------------
+           relname            | orig_oid | storage |     desc      
+------------------------------+----------+---------+---------------
  at_partitioned               | t        | none    | 
  at_partitioned_0             | t        | own     | 
- at_partitioned_0_id_name_key | f        | own     | 
+ at_partitioned_0_id_name_key | f        | orig    | child 0 index
  at_partitioned_1             | t        | own     | 
- at_partitioned_1_id_name_key | f        | own     | 
+ at_partitioned_1_id_name_key | f        | orig    | child 1 index
  at_partitioned_id_name_key   | f        | none    | parent index
 (6 rows)
 
 select conname, obj_description(oid, 'pg_constraint') as desc
   from pg_constraint where conname like 'at_partitioned%'
   order by conname;
-           conname            |       desc        
-------------------------------+-------------------
- at_partitioned_0_id_name_key | 
- at_partitioned_1_id_name_key | 
+           conname            |        desc        
+------------------------------+--------------------
+ at_partitioned_0_id_name_key | child 0 constraint
+ at_partitioned_1_id_name_key | child 1 constraint
  at_partitioned_id_name_key   | parent constraint
 (3 rows)
 
@@ -2358,10 +2358,10 @@ select relname,
  at_idx_part_code_idx    | f        | none
  at_idx_part_p1          | t        | own
  at_idx_part_p1_code_idx | f        | orig
- at_idx_part_p1_pkey     | f        | own
+ at_idx_part_p1_pkey     | f        | orig
  at_idx_part_p2          | t        | own
  at_idx_part_p2_code_idx | f        | orig
- at_idx_part_p2_pkey     | f        | own
+ at_idx_part_p2_pkey     | f        | orig
  at_idx_part_pkey        | f        | none
 (9 rows)
 
-- 
2.52.0

