From 8a4f3341a865dbaf160a4ef39ab780d66dd85542 Mon Sep 17 00:00:00 2001
From: Kevin Biju <kevinkbiju@gmail.com>
Date: Tue, 13 Jan 2026 18:32:39 +0530
Subject: [PATCH v2] Allow ALTER COLUMN TYPE for publication column lists

Currently, ALTER COLUMN TYPE is blocked for any column with a
pg_publication_rel dependency, even when the column is only in a publication's
column list and not referenced in a row filter. This is because both
column lists and row filters create identical entries in pg_depend.

This patch refines the check to distinguish between the two cases.
If prqual is NULL, the dependency is from a column list only and
ALTER COLUMN TYPE is allowed. If prqual is not NULL, we parse it
and validate if the specific column is actually used in the
row filter expression.
---
 src/backend/commands/tablecmds.c          | 69 +++++++++++++++++++----
 src/test/regress/expected/publication.out | 68 ++++++++++++++++++++++
 src/test/regress/sql/publication.sql      | 24 ++++++++
 3 files changed, 149 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f976c0e5c7e..4e7b0c37d73 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -15261,19 +15261,64 @@ RememberAllDependentForRebuilding(AlteredTableInfo *tab, AlterTableType subtype,
 				break;
 
 			case PublicationRelRelationId:
+				{
+					HeapTuple pubreloidtuple;
+					Form_pg_publication_rel pubrelform;
+					Oid publicationId;
+					HeapTuple pubreltuple;
+					bool isnull;
+					Datum rfdatum;
+
+					pubreloidtuple = SearchSysCache1(PUBLICATIONREL,
+												  ObjectIdGetDatum(foundObject.objectId));
+					if (!HeapTupleIsValid(pubreloidtuple))
+						elog(ERROR, "cache lookup failed for pg_publication_rel OID %u", foundObject.objectId);
+
+					pubrelform = (Form_pg_publication_rel) GETSTRUCT(pubreloidtuple);
+					publicationId = pubrelform->prpubid;
+					ReleaseSysCache(pubreloidtuple);
+
+					pubreltuple = SearchSysCache2(PUBLICATIONRELMAP,
+							ObjectIdGetDatum(RelationGetRelid(rel)),
+							ObjectIdGetDatum(publicationId));
+					if (!HeapTupleIsValid(pubreltuple))
+						elog(ERROR, "cache lookup failed for publication ID %u", publicationId);
+
+					/* Lookup the prqual to check row filter, if it is null dependency is a column list only. */
+					rfdatum = SysCacheGetAttr(PUBLICATIONRELMAP, pubreltuple,
+											Anum_pg_publication_rel_prqual, &isnull);
+
+					if (!isnull)
+					{
+						Node *rfnode;
+						Bitmapset *rfattrs = NULL;
 
-				/*
-				 * Column reference in a PUBLICATION ... FOR TABLE ... WHERE
-				 * clause.  Same issues as above.  FIXME someday.
-				 */
-				if (subtype == AT_AlterColumnType)
-					ereport(ERROR,
-							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-							 errmsg("cannot alter type of a column used by a publication WHERE clause"),
-							 errdetail("%s depends on column \"%s\"",
-									   getObjectDescription(&foundObject, false),
-									   colName)));
-				break;
+						/*
+						 * The column dependency could be from the column list, but there's
+						 * also a row filter present. Check if this specific column is used
+						 * in the row filter expression.
+						 */
+						rfnode = (Node *) stringToNode(TextDatumGetCString(rfdatum));
+						pull_varattnos(rfnode, 1, &rfattrs);
+						if (bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, rfattrs))
+						{
+							/*
+							 * Column reference in a PUBLICATION ... FOR TABLE ... WHERE
+							 * clause.  Same issues as above.  FIXME someday.
+							 */
+							if (subtype == AT_AlterColumnType)
+								ereport(ERROR,
+										(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+										errmsg("cannot alter type of a column used by a publication WHERE clause"),
+										errdetail("%s depends on column \"%s\"",
+												getObjectDescription(&foundObject, false),
+												colName)));
+						}
+					}
+
+					ReleaseSysCache(pubreltuple);
+					break;
+				}
 
 			default:
 
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index 7fb49aaf29b..dad7200e61e 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -625,6 +625,52 @@ CREATE PUBLICATION testpub8 FOR TABLE testpub_rf_tbl7 WHERE (y > 100);
 ALTER TABLE testpub_rf_tbl7 ALTER COLUMN y SET EXPRESSION AS (x * testpub_rf_func2());
 ERROR:  ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables that are part of a publication
 DETAIL:  Column "y" of relation "testpub_rf_tbl7" is a virtual generated column.
+RESET client_min_messages;
+-- test that ALTER COLUMN ALTER [ SET DATA ] TYPE is rejected when the column is in a row filter
+CREATE TABLE testpub_rf_tbl8 (id int PRIMARY KEY, x int, y int, z int);
+CREATE PUBLICATION testpub9 FOR TABLE testpub_rf_tbl8 WHERE (x > 100 AND y < 200);
+SET client_min_messages = 'ERROR';
+-- fail
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN x TYPE bigint;
+ERROR:  cannot alter type of a column used by a publication WHERE clause
+DETAIL:  publication of table testpub_rf_tbl8 in publication testpub9 depends on column "x"
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN y TYPE bigint;
+ERROR:  cannot alter type of a column used by a publication WHERE clause
+DETAIL:  publication of table testpub_rf_tbl8 in publication testpub9 depends on column "y"
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN y TYPE bigint, ALTER COLUMN z TYPE bigint;
+ERROR:  cannot alter type of a column used by a publication WHERE clause
+DETAIL:  publication of table testpub_rf_tbl8 in publication testpub9 depends on column "y"
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN z TYPE bigint, ALTER COLUMN x TYPE bigint;
+ERROR:  cannot alter type of a column used by a publication WHERE clause
+DETAIL:  publication of table testpub_rf_tbl8 in publication testpub9 depends on column "x"
+\d testpub_rf_tbl8
+          Table "public.testpub_rf_tbl8"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ id     | integer |           | not null | 
+ x      | integer |           |          | 
+ y      | integer |           |          | 
+ z      | integer |           |          | 
+Indexes:
+    "testpub_rf_tbl8_pkey" PRIMARY KEY, btree (id)
+Publications:
+    "testpub9" WHERE ((x > 100) AND (y < 200))
+
+-- ok
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN z TYPE bigint;
+\d testpub_rf_tbl8
+          Table "public.testpub_rf_tbl8"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ id     | integer |           | not null | 
+ x      | integer |           |          | 
+ y      | integer |           |          | 
+ z      | bigint  |           |          | 
+Indexes:
+    "testpub_rf_tbl8_pkey" PRIMARY KEY, btree (id)
+Publications:
+    "testpub9" WHERE ((x > 100) AND (y < 200))
+
 RESET client_min_messages;
 DROP TABLE testpub_rf_tbl1;
 DROP TABLE testpub_rf_tbl2;
@@ -644,6 +690,8 @@ DROP OPERATOR =#>(integer, integer);
 DROP FUNCTION testpub_rf_func1(integer, integer);
 DROP FUNCTION testpub_rf_func2();
 DROP COLLATION user_collation;
+DROP PUBLICATION testpub9;
+DROP TABLE testpub_rf_tbl8;
 -- ======================================================
 -- More row filter tests for validating column references
 CREATE TABLE rf_tbl_abcd_nopk(a int, b int, c int, d int);
@@ -1108,6 +1156,26 @@ Not-null constraints:
     "testpub_tbl_both_filters_a_not_null" NOT NULL "a"
     "testpub_tbl_both_filters_c_not_null" NOT NULL "c"
 
+SET client_min_messages = 'ERROR';
+-- fail - ALTER TABLE ALTER COLUMN [ SET DATA ] TYPE affecting column in row filter and column list
+ALTER TABLE testpub_tbl_both_filters ALTER COLUMN c TYPE bigint;
+ERROR:  cannot alter type of a column used by a publication WHERE clause
+DETAIL:  publication of table testpub_tbl_both_filters in publication testpub_both_filters depends on column "c"
+-- ok - ALTER TABLE ALTER COLUMN ALTER [ SET DATA ] TYPE affecting column in column list and not in row filter
+ALTER TABLE testpub_tbl_both_filters ALTER COLUMN a TYPE text;
+\d testpub_tbl_both_filters
+      Table "public.testpub_tbl_both_filters"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | text    |           | not null | 
+ b      | integer |           |          | 
+ c      | integer |           | not null | 
+Indexes:
+    "testpub_tbl_both_filters_pkey" PRIMARY KEY, btree (a, c) REPLICA IDENTITY
+Publications:
+    "testpub_both_filters" (a, c) WHERE (c <> 1)
+
+RESET client_min_messages;
 DROP TABLE testpub_tbl_both_filters;
 DROP PUBLICATION testpub_both_filters;
 -- ======================================================
diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql
index 85b00bd67c8..2301e9c5c9f 100644
--- a/src/test/regress/sql/publication.sql
+++ b/src/test/regress/sql/publication.sql
@@ -318,6 +318,20 @@ CREATE TABLE testpub_rf_tbl7 (id int PRIMARY KEY, x int, y int GENERATED ALWAYS
 CREATE PUBLICATION testpub8 FOR TABLE testpub_rf_tbl7 WHERE (y > 100);
 ALTER TABLE testpub_rf_tbl7 ALTER COLUMN y SET EXPRESSION AS (x * testpub_rf_func2());
 RESET client_min_messages;
+-- test that ALTER COLUMN ALTER [ SET DATA ] TYPE is rejected when the column is in a row filter
+CREATE TABLE testpub_rf_tbl8 (id int PRIMARY KEY, x int, y int, z int);
+CREATE PUBLICATION testpub9 FOR TABLE testpub_rf_tbl8 WHERE (x > 100 AND y < 200);
+SET client_min_messages = 'ERROR';
+-- fail
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN x TYPE bigint;
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN y TYPE bigint;
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN y TYPE bigint, ALTER COLUMN z TYPE bigint;
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN z TYPE bigint, ALTER COLUMN x TYPE bigint;
+\d testpub_rf_tbl8
+-- ok
+ALTER TABLE testpub_rf_tbl8 ALTER COLUMN z TYPE bigint;
+\d testpub_rf_tbl8
+RESET client_min_messages;
 
 DROP TABLE testpub_rf_tbl1;
 DROP TABLE testpub_rf_tbl2;
@@ -337,6 +351,8 @@ DROP OPERATOR =#>(integer, integer);
 DROP FUNCTION testpub_rf_func1(integer, integer);
 DROP FUNCTION testpub_rf_func2();
 DROP COLLATION user_collation;
+DROP PUBLICATION testpub9;
+DROP TABLE testpub_rf_tbl8;
 
 -- ======================================================
 -- More row filter tests for validating column references
@@ -706,6 +722,14 @@ ALTER PUBLICATION testpub_both_filters ADD TABLE testpub_tbl_both_filters (a,c)
 \dRp+ testpub_both_filters
 \d+ testpub_tbl_both_filters
 
+SET client_min_messages = 'ERROR';
+-- fail - ALTER TABLE ALTER COLUMN [ SET DATA ] TYPE affecting column in row filter and column list
+ALTER TABLE testpub_tbl_both_filters ALTER COLUMN c TYPE bigint;
+-- ok - ALTER TABLE ALTER COLUMN ALTER [ SET DATA ] TYPE affecting column in column list and not in row filter
+ALTER TABLE testpub_tbl_both_filters ALTER COLUMN a TYPE text;
+\d testpub_tbl_both_filters
+RESET client_min_messages;
+
 DROP TABLE testpub_tbl_both_filters;
 DROP PUBLICATION testpub_both_filters;
 -- ======================================================
-- 
2.50.1 (Apple Git-155)

