From 0663a0d2cd4af96b77805648f57791e36cd45354 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sat, 5 Sep 2026 03:15:36 +0000
Subject: [PATCH v19 1/3] Preserve index DEPENDS ON EXTENSION across ALTER
 COLUMN TYPE

A DEPENDS ON EXTENSION relationship on an index was silently lost when
ALTER TABLE ... ALTER COLUMN TYPE rebuilt the index. That dependency is
not expressible as part of a CREATE INDEX definition, so it is not
reproduced by the pg_get_indexdef_string() output used to recreate the
index.

Capture any auto-extension dependencies before dropping the index and
restore them after creating the replacement index.

Keep this patch focused on non-descendant indexes. Descendant partition
index preservation remains in the following patch.
---
 src/backend/commands/indexcmds.c          | 17 +++++++++++++++++
 src/backend/commands/tablecmds.c          |  7 +++++++
 src/include/nodes/parsenodes.h            |  1 +
 src/test/regress/expected/alter_table.out | 20 ++++++++++++++++++++
 src/test/regress/sql/alter_table.sql      | 14 ++++++++++++++
 5 files changed, 59 insertions(+)

diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 5a0312fe772..932b2febe17 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -25,6 +25,7 @@
 #include "access/tableam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/dependency.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
@@ -33,6 +34,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_database.h"
+#include "catalog/pg_extension.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
@@ -1324,6 +1326,21 @@ DefineIndex(ParseState *pstate,
 		CreateComments(indexRelationId, RelationRelationId, 0,
 					   stmt->idxcomment);
 
+	if (stmt->idxextensionOids != NIL)
+	{
+		ObjectAddress indexAddress;
+
+		ObjectAddressSet(indexAddress, RelationRelationId, indexRelationId);
+		foreach_oid(extensionOid, stmt->idxextensionOids)
+		{
+			ObjectAddress extensionAddress;
+
+			ObjectAddressSet(extensionAddress, ExtensionRelationId, extensionOid);
+			recordDependencyOn(&indexAddress, &extensionAddress,
+							   DEPENDENCY_AUTO_EXTENSION);
+		}
+	}
+
 	if (partitioned)
 	{
 		PartitionDesc partdesc;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fd144d783d9..5d9df8bf134 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -30,6 +30,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "catalog/dependency.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
@@ -16377,6 +16378,9 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, Oid ownerId,
 			stmt->reset_default_tblspc = true;
 			/* keep the index's comment */
 			stmt->idxcomment = GetComment(oldId, RelationRelationId, 0);
+			/* keep any auto-extension dependencies */
+			stmt->idxextensionOids =
+				getAutoExtensionsOfObject(RelationRelationId, oldId);
 
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = AT_ReAddIndex;
@@ -16406,6 +16410,9 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, Oid ownerId,
 					/* keep any comment on the index */
 					indstmt->idxcomment = GetComment(indoid,
 													 RelationRelationId, 0);
+					/* keep any auto-extension dependencies */
+					indstmt->idxextensionOids =
+						getAutoExtensionsOfObject(RelationRelationId, indoid);
 					indstmt->reset_default_tblspc = true;
 
 					cmd->subtype = AT_ReAddIndex;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a0ab2b885e8..bd72ffb09d2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3613,6 +3613,7 @@ typedef struct IndexStmt
 	Node	   *whereClause;	/* qualification (partial-index predicate) */
 	List	   *excludeOpNames; /* exclusion operator names, or NIL if none */
 	char	   *idxcomment;		/* comment to apply to index, or NULL */
+	List	   *idxextensionOids;	/* extensions to depend on */
 	Oid			indexOid;		/* OID of an existing index, if any */
 	RelFileNumber oldNumber;	/* relfilenumber of existing storage, if any */
 	SubTransactionId oldCreateSubid;	/* rd_createSubid of oldNumber */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index e167a41ce79..6ff3b3c3bf2 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2326,6 +2326,26 @@ select conname, obj_description(oid, 'pg_constraint') as desc
 
 -- Don't remove this DROP, it exposes bug #15672
 drop table at_partitioned;
+create table at_reb_dep (id int not null, val int not null);
+create index at_reb_dep_expr on at_reb_dep ((val + 1));
+alter table at_reb_dep add constraint at_reb_dep_key unique (id, val);
+alter index at_reb_dep_expr depends on extension plpgsql;
+alter index at_reb_dep_key depends on extension plpgsql;
+alter table at_reb_dep alter column val type bigint;
+select c.relname, d.deptype, e.extname
+  from pg_depend d join pg_class c on c.oid = d.objid
+    join pg_extension e on e.oid = d.refobjid
+  where d.classid = 'pg_class'::regclass
+    and c.relname in ('at_reb_dep_expr', 'at_reb_dep_key')
+    and d.refclassid = 'pg_extension'::regclass
+  order by c.relname;
+     relname     | deptype | extname 
+-----------------+---------+---------
+ at_reb_dep_expr | x       | plpgsql
+ at_reb_dep_key  | x       | plpgsql
+(2 rows)
+
+drop table at_reb_dep;
 -- 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 9f6c2a4bb08..6d1ba1b4273 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1530,6 +1530,20 @@ select conname, obj_description(oid, 'pg_constraint') as desc
 
 -- Don't remove this DROP, it exposes bug #15672
 drop table at_partitioned;
+create table at_reb_dep (id int not null, val int not null);
+create index at_reb_dep_expr on at_reb_dep ((val + 1));
+alter table at_reb_dep add constraint at_reb_dep_key unique (id, val);
+alter index at_reb_dep_expr depends on extension plpgsql;
+alter index at_reb_dep_key depends on extension plpgsql;
+alter table at_reb_dep alter column val type bigint;
+select c.relname, d.deptype, e.extname
+  from pg_depend d join pg_class c on c.oid = d.objid
+    join pg_extension e on e.oid = d.refobjid
+  where d.classid = 'pg_class'::regclass
+    and c.relname in ('at_reb_dep_expr', 'at_reb_dep_key')
+    and d.refclassid = 'pg_extension'::regclass
+  order by c.relname;
+drop table at_reb_dep;
 
 -- disallow recursive containment of row types
 create temp table recur1 (f1 int);
-- 
2.50.1

