From 2182f2630be505385bc540a9b844034e7ad4e1c1 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Fri, 7 Aug 2026 10:07:51 +0800
Subject: [PATCH v1 1/1] Disallow ALTER TABLE SET EXPRESSION if dependent
 cannot rebuilt

ALTER TABLE ONLY ... SET EXPRESSION should fail if the generated column has
dependencies, because rebuilding dependent objects would violate the semantics
of ONLY.

Executing ALTER TABLE ... SET EXPRESSION directly on a child table should also
fail, because rebuilding dependent objects while preserving the inheritance
hierarchy would be very difficult.

commitfest: https://commitfest.postgresql.org/patch/
discussion: https://postgr.es/m/CACJufxEomSz3BwUGfk8A6MeAKj=Cki5B+gYTbJO=ACCJyytWZg@mail.gmail.com
---
 src/backend/commands/tablecmds.c              | 80 +++++++++++++++++--
 .../regress/expected/generated_stored.out     | 49 ++++++++++++
 .../regress/expected/generated_virtual.out    | 45 +++++++++++
 src/test/regress/sql/generated_stored.sql     | 40 ++++++++++
 src/test/regress/sql/generated_virtual.sql    | 38 +++++++++
 5 files changed, 246 insertions(+), 6 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2fa534413ea..5870e370ee3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -565,8 +565,8 @@ static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
 									   Node *def, LOCKMODE lockmode, bool recurse, bool recursing);
 static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode,
 										bool recurse, bool recursing);
-static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
-										 Node *newExpr, LOCKMODE lockmode);
+static ObjectAddress ATExecSetExpression(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
+										 Node *newExpr, bool recurse, bool recursing, LOCKMODE lockmode);
 static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode);
 static ObjectAddress ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
 static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum,
@@ -5125,7 +5125,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_SetExpression:	/* ALTER COLUMN SET EXPRESSION */
 			ATSimplePermissions(cmd->subtype, rel,
 								ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
-			ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
+			/* Set up recursion for phase 2; no other prep needed */
+			if (recurse)
+				cmd->recurse = true;
 			pass = AT_PASS_SET_EXPRESSION;
 			break;
 		case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
@@ -5520,7 +5522,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 									   cmd->recurse, false, lockmode);
 			break;
 		case AT_SetExpression:
-			address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode);
+			address = ATExecSetExpression(wqueue, tab, rel,
+										  cmd->name, cmd->def, cmd->recurse, false, lockmode);
 			break;
 		case AT_DropExpression:
 			address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode);
@@ -8736,8 +8739,9 @@ ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE
  * Return the address of the affected column.
  */
 static ObjectAddress
-ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
-					Node *newExpr, LOCKMODE lockmode)
+ATExecSetExpression(List **wqueue, AlteredTableInfo *tab,
+					Relation rel, const char *colName,
+					Node *newExpr, bool recurse, bool recursing, LOCKMODE lockmode)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute attTup;
@@ -8749,6 +8753,15 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
 	Expr	   *defval;
 	NewColumnValue *newval;
 	RawColumnDefault *rawEnt;
+	List	   *children = NIL;
+
+	/* Guard against stack overflow due to overly deep inheritance tree. */
+	check_stack_depth();
+
+	/* At top level, permission check was done in ATPrepCmd, else do it */
+	if (recursing)
+		ATSimplePermissions(AT_AddConstraint, rel,
+							ATT_PARTITIONED_TABLE | ATT_TABLE | ATT_FOREIGN_TABLE);
 
 	tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
 	if (!HeapTupleIsValid(tuple))
@@ -8820,6 +8833,34 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
 	 */
 	RememberWholeRowDependentForRebuilding(tab, AT_SetExpression, rel);
 
+	children = find_inheritance_children(RelationGetRelid(rel),
+										 lockmode);
+
+	if (tab->changedConstraintOids != NIL || tab->changedIndexOids != NIL)
+	{
+		/*
+		 * Cannot use ONLY to modify a generated column's expression when
+		 * there are dependencies (e.g. constraints, indexes) that need to be
+		 * rebuilt, since that rebuild must cascade to the children too.
+		 */
+		if (!recurse && children != NIL)
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("ALTER TABLE ONLY ... SET EXPRESSION is not supported for generated columns with dependent objects on a parent table"),
+					errdetail("Dependent objects, such as constraints and indexes, must be rebuilt in child tables as well, which conflicts with ONLY."));
+
+		/*
+		 * Cannot modify a partition's generated column expression directly
+		 * when it has dependencies, since the partition's inherited
+		 * constraints and indexes cannot be rebuilt independently.
+		 */
+		if (!recursing && (rel->rd_rel->relispartition || has_superclass(RelationGetRelid(rel))))
+			ereport(ERROR,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("cannot use ALTER TABLE ... SET EXPRESSION on a generated column in a child table with dependent objects"),
+					errdetail("Inherited objects, such as constraints and indexes, cannot be rebuilt independently for a child table."));
+	}
+
 	/*
 	 * Drop the dependency records of the GENERATED expression, in particular
 	 * its INTERNAL dependency on the column, which would otherwise cause
@@ -8877,6 +8918,33 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
 
 	ObjectAddressSubSet(address, RelationRelationId,
 						RelationGetRelid(rel), attnum);
+
+	/*
+	 * Recurse to propagate the constraint to children that don't have one.
+	 */
+	if (recurse)
+	{
+		foreach_oid(childoid, children)
+		{
+			AlteredTableInfo *childtab;
+
+			/* find_inheritance_children already got lock */
+			Relation	childrel = table_open(childoid, NoLock);
+
+			CheckAlterTableIsSafe(childrel);
+
+			CommandCounterIncrement();
+
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
+			ATExecSetExpression(wqueue, childtab, childrel, colName,
+								newExpr, recurse, true, lockmode);
+
+			table_close(childrel, NoLock);
+		}
+	}
+
 	return address;
 }
 
diff --git a/src/test/regress/expected/generated_stored.out b/src/test/regress/expected/generated_stored.out
index fd6caf1cf2d..de9082db5b7 100644
--- a/src/test/regress/expected/generated_stored.out
+++ b/src/test/regress/expected/generated_stored.out
@@ -1683,6 +1683,55 @@ CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED);
  c      | integer |           |          | 
  x      | integer |           |          | generated always as (b * 2) stored
 
+CREATE TABLE gtest_pk AS SELECT g::int as b FROM generate_series(1, 10) g;
+ALTER TABLE gtest_pk ADD PRIMARY KEY (b);
+CREATE TABLE gtest35(a int, b int GENERATED ALWAYS AS (a) STORED NOT NULL, CONSTRAINT cc check (b > 0)) PARTITION BY RANGE (a);
+CREATE TABLE gtest35_1 PARTITION OF gtest35 FOR VALUES FROM (1) TO (100);
+CREATE INDEX gtest35_b_idx ON gtest35(b);
+INSERT INTO gtest35 SELECT g FROM generate_series(1, 10) g;
+ALTER TABLE gtest35 ADD CONSTRAINT gtest35_fk FOREIGN KEY (b) REFERENCES gtest_pk;
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  ALTER TABLE ONLY ... SET EXPRESSION is not supported for generated columns with dependent objects on a parent table
+DETAIL:  Dependent objects, such as constraints and indexes, must be rebuilt in child tables as well, which conflicts with ONLY.
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  cannot use ALTER TABLE ... SET EXPRESSION on a generated column in a child table with dependent objects
+DETAIL:  Inherited objects, such as constraints and indexes, cannot be rebuilt independently for a child table.
+ALTER TABLE gtest35 DROP CONSTRAINT cc;
+ALTER TABLE gtest35 ALTER COLUMN b DROP NOT NULL;
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  ALTER TABLE ONLY ... SET EXPRESSION is not supported for generated columns with dependent objects on a parent table
+DETAIL:  Dependent objects, such as constraints and indexes, must be rebuilt in child tables as well, which conflicts with ONLY.
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  cannot use ALTER TABLE ... SET EXPRESSION on a generated column in a child table with dependent objects
+DETAIL:  Inherited objects, such as constraints and indexes, cannot be rebuilt independently for a child table.
+ALTER TABLE gtest35 DROP CONSTRAINT gtest35_fk;
+DROP INDEX gtest35_b_idx;
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a + 1); -- ok
+CREATE TABLE gtest36(a int, b int GENERATED ALWAYS AS (a) STORED NOT NULL, CONSTRAINT cc check (b > 0));
+CREATE TABLE gtest36_1 () INHERITS (gtest36);
+CREATE TABLE gtest36_12 () INHERITS (gtest36, gtest36_1);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+INSERT INTO gtest36 SELECT g FROM generate_series(1, 10) g;
+CREATE INDEX gtest36_b_idx ON gtest36(b);
+ALTER TABLE gtest36 ADD CONSTRAINT gtest36_fk FOREIGN KEY (b) REFERENCES gtest_pk;
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  ALTER TABLE ONLY ... SET EXPRESSION is not supported for generated columns with dependent objects on a parent table
+DETAIL:  Dependent objects, such as constraints and indexes, must be rebuilt in child tables as well, which conflicts with ONLY.
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  cannot use ALTER TABLE ... SET EXPRESSION on a generated column in a child table with dependent objects
+DETAIL:  Inherited objects, such as constraints and indexes, cannot be rebuilt independently for a child table.
+ALTER TABLE gtest36 DROP CONSTRAINT cc;
+ALTER TABLE gtest36 ALTER COLUMN b DROP NOT NULL;
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  ALTER TABLE ONLY ... SET EXPRESSION is not supported for generated columns with dependent objects on a parent table
+DETAIL:  Dependent objects, such as constraints and indexes, must be rebuilt in child tables as well, which conflicts with ONLY.
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest36 DROP CONSTRAINT gtest36_fk;
+DROP INDEX gtest36_b_idx;
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a + 1); -- ok
 -- rule actions referring to generated columns:
 -- NEW.b in a rule action should reflect the generated column's new value
 CREATE TABLE gtest_rule (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out
index 6ee029796f1..7e07612d85f 100644
--- a/src/test/regress/expected/generated_virtual.out
+++ b/src/test/regress/expected/generated_virtual.out
@@ -1856,6 +1856,51 @@ insert into gtest34p values (1, 2)
 (1 row)
 
 drop table gtest34p;
+-- CREATE TABLE gtest_pk AS SELECT g::int as b FROM generate_series(1, 10) g;
+-- ALTER TABLE gtest_pk ADD PRIMARY KEY (b);
+CREATE TABLE gtest35(a int, b int GENERATED ALWAYS AS (a) NOT NULL, CONSTRAINT cc check (b > 0)) PARTITION BY RANGE (a);
+CREATE TABLE gtest35_1 PARTITION OF gtest35 FOR VALUES FROM (1) TO (100);
+-- CREATE INDEX gtest35_b_idx ON gtest35(b);
+INSERT INTO gtest35 SELECT g FROM generate_series(1, 10) g;
+-- ALTER TABLE gtest35 ADD CONSTRAINT gtest35_fk FOREIGN KEY (b) REFERENCES gtest_pk;
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  ALTER TABLE ONLY ... SET EXPRESSION is not supported for generated columns with dependent objects on a parent table
+DETAIL:  Dependent objects, such as constraints and indexes, must be rebuilt in child tables as well, which conflicts with ONLY.
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  cannot use ALTER TABLE ... SET EXPRESSION on a generated column in a child table with dependent objects
+DETAIL:  Inherited objects, such as constraints and indexes, cannot be rebuilt independently for a child table.
+ALTER TABLE gtest35 DROP CONSTRAINT cc;
+ALTER TABLE gtest35 ALTER COLUMN b DROP NOT NULL;
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+-- ALTER TABLE gtest35 DROP CONSTRAINT gtest35_fk;
+-- DROP INDEX gtest35_b_idx;
+-- ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+-- ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a + 1); -- ok
+DROP TABLE gtest36;
+ERROR:  table "gtest36" does not exist
+CREATE TABLE gtest36(a int, b int GENERATED ALWAYS AS (a) NOT NULL, CONSTRAINT cc check (b > 0));
+CREATE TABLE gtest36_1 () INHERITS (gtest36);
+CREATE TABLE gtest36_12 () INHERITS (gtest36, gtest36_1);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging multiple inherited definitions of column "b"
+-- CREATE INDEX gtest36_b_idx ON gtest36(b);
+INSERT INTO gtest36 SELECT g FROM generate_series(1, 10) g;
+-- ALTER TABLE gtest36 ADD CONSTRAINT gtest36_fk FOREIGN KEY (b) REFERENCES gtest_pk;
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  ALTER TABLE ONLY ... SET EXPRESSION is not supported for generated columns with dependent objects on a parent table
+DETAIL:  Dependent objects, such as constraints and indexes, must be rebuilt in child tables as well, which conflicts with ONLY.
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ERROR:  cannot use ALTER TABLE ... SET EXPRESSION on a generated column in a child table with dependent objects
+DETAIL:  Inherited objects, such as constraints and indexes, cannot be rebuilt independently for a child table.
+ALTER TABLE gtest36 DROP CONSTRAINT cc;
+ALTER TABLE gtest36 ALTER COLUMN b DROP NOT NULL;
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+-- ALTER TABLE gtest36 DROP CONSTRAINT gtest36_fk;
+-- DROP INDEX gtest36_b_idx;
+-- ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+-- ALTER TABLE gtest6_1 ALTER COLUMN b SET EXPRESSION AS (a + 1); -- ok
 -- Ensure that virtual generated columns work with WHERE CURRENT OF
 create table gtest_cursor (id int primary key, a int, b int generated always as (a * 2) virtual);
 insert into gtest_cursor values (1, 10), (2, 20), (3, 30);
diff --git a/src/test/regress/sql/generated_stored.sql b/src/test/regress/sql/generated_stored.sql
index 9eecd13dd9e..07f61d3127a 100644
--- a/src/test/regress/sql/generated_stored.sql
+++ b/src/test/regress/sql/generated_stored.sql
@@ -845,6 +845,46 @@ CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED);
 
 \d gtest28*
 
+CREATE TABLE gtest_pk AS SELECT g::int as b FROM generate_series(1, 10) g;
+ALTER TABLE gtest_pk ADD PRIMARY KEY (b);
+CREATE TABLE gtest35(a int, b int GENERATED ALWAYS AS (a) STORED NOT NULL, CONSTRAINT cc check (b > 0)) PARTITION BY RANGE (a);
+CREATE TABLE gtest35_1 PARTITION OF gtest35 FOR VALUES FROM (1) TO (100);
+CREATE INDEX gtest35_b_idx ON gtest35(b);
+INSERT INTO gtest35 SELECT g FROM generate_series(1, 10) g;
+ALTER TABLE gtest35 ADD CONSTRAINT gtest35_fk FOREIGN KEY (b) REFERENCES gtest_pk;
+
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+
+ALTER TABLE gtest35 DROP CONSTRAINT cc;
+ALTER TABLE gtest35 ALTER COLUMN b DROP NOT NULL;
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+
+ALTER TABLE gtest35 DROP CONSTRAINT gtest35_fk;
+DROP INDEX gtest35_b_idx;
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a + 1); -- ok
+
+CREATE TABLE gtest36(a int, b int GENERATED ALWAYS AS (a) STORED NOT NULL, CONSTRAINT cc check (b > 0));
+CREATE TABLE gtest36_1 () INHERITS (gtest36);
+CREATE TABLE gtest36_12 () INHERITS (gtest36, gtest36_1);
+INSERT INTO gtest36 SELECT g FROM generate_series(1, 10) g;
+CREATE INDEX gtest36_b_idx ON gtest36(b);
+ALTER TABLE gtest36 ADD CONSTRAINT gtest36_fk FOREIGN KEY (b) REFERENCES gtest_pk;
+
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ALTER TABLE gtest36 DROP CONSTRAINT cc;
+ALTER TABLE gtest36 ALTER COLUMN b DROP NOT NULL;
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest36 DROP CONSTRAINT gtest36_fk;
+DROP INDEX gtest36_b_idx;
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a + 1); -- ok
+
+
 -- rule actions referring to generated columns:
 -- NEW.b in a rule action should reflect the generated column's new value
 CREATE TABLE gtest_rule (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql
index ed9d50fe784..9a987fad26e 100644
--- a/src/test/regress/sql/generated_virtual.sql
+++ b/src/test/regress/sql/generated_virtual.sql
@@ -986,6 +986,44 @@ insert into gtest34p values (1, 2)
     on conflict (id) do update set a = gtest34p.c + excluded.c returning *;
 drop table gtest34p;
 
+-- CREATE TABLE gtest_pk AS SELECT g::int as b FROM generate_series(1, 10) g;
+-- ALTER TABLE gtest_pk ADD PRIMARY KEY (b);
+CREATE TABLE gtest35(a int, b int GENERATED ALWAYS AS (a) NOT NULL, CONSTRAINT cc check (b > 0)) PARTITION BY RANGE (a);
+CREATE TABLE gtest35_1 PARTITION OF gtest35 FOR VALUES FROM (1) TO (100);
+-- CREATE INDEX gtest35_b_idx ON gtest35(b);
+INSERT INTO gtest35 SELECT g FROM generate_series(1, 10) g;
+-- ALTER TABLE gtest35 ADD CONSTRAINT gtest35_fk FOREIGN KEY (b) REFERENCES gtest_pk;
+
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+
+ALTER TABLE gtest35 DROP CONSTRAINT cc;
+ALTER TABLE gtest35 ALTER COLUMN b DROP NOT NULL;
+ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+-- ALTER TABLE gtest35 DROP CONSTRAINT gtest35_fk;
+-- DROP INDEX gtest35_b_idx;
+-- ALTER TABLE ONLY gtest35 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+-- ALTER TABLE gtest35_1 ALTER COLUMN b SET EXPRESSION AS (a + 1); -- ok
+
+DROP TABLE gtest36;
+CREATE TABLE gtest36(a int, b int GENERATED ALWAYS AS (a) NOT NULL, CONSTRAINT cc check (b > 0));
+CREATE TABLE gtest36_1 () INHERITS (gtest36);
+CREATE TABLE gtest36_12 () INHERITS (gtest36, gtest36_1);
+-- CREATE INDEX gtest36_b_idx ON gtest36(b);
+INSERT INTO gtest36 SELECT g FROM generate_series(1, 10) g;
+-- ALTER TABLE gtest36 ADD CONSTRAINT gtest36_fk FOREIGN KEY (b) REFERENCES gtest_pk;
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a); -- error
+ALTER TABLE gtest36 DROP CONSTRAINT cc;
+ALTER TABLE gtest36 ALTER COLUMN b DROP NOT NULL;
+ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+ALTER TABLE gtest36_1 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+-- ALTER TABLE gtest36 DROP CONSTRAINT gtest36_fk;
+-- DROP INDEX gtest36_b_idx;
+-- ALTER TABLE ONLY gtest36 ALTER COLUMN b SET EXPRESSION AS (a); -- ok
+-- ALTER TABLE gtest6_1 ALTER COLUMN b SET EXPRESSION AS (a + 1); -- ok
+
 -- Ensure that virtual generated columns work with WHERE CURRENT OF
 create table gtest_cursor (id int primary key, a int, b int generated always as (a * 2) virtual);
 insert into gtest_cursor values (1, 10), (2, 20), (3, 30);
-- 
2.34.1

