From c374f2807c236dd8fb4bee9f4ebfe4af300491f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Herrera?= Date: Tue, 4 Aug 2026 09:06:46 +0200 Subject: [PATCH] Fix ALTER COLUMN ... DROP EXPRESSION with subpartitions Per commit 8bf6ec3ba3a4, a column can be GENERATED only if it is such in the whole inheritance tree. For this reason, ATPrepDropExpression refuses to be called with ONLY on a partitioned table. To detect this, the current implementation checks whether recurse is set to false and the rel has direct children. Recursion is implemented with ATSimpleRecursion, which calls ATPrepCmd with recurse = false for every node in the tree. Inner nodes (for example a partition which itself has subpartitions) then fail the check, accidentally preventing the command from working on inheritance trees of depth > 2. This commit fixes it by also checking that we're at the top level of the recursive calls using the recursing parameter, which is always true when called through ATSimpleRecursion, always false when invoked on the root rel. Also, remove a comment claiming that DROP EXPRESSION could be implemented with some effort. It cannot, as the commit message for 8bf6ec3ba3a4 explains. Author: Alberto Piai Backpatch-through: 14 Discussion: https://postgr.es/m/DHMT78XOD8BK.341V3H87KZ7NO@gmail.com --- src/backend/commands/tablecmds.c | 15 ++---- .../regress/expected/generated_stored.out | 50 +++++++++++++++++++ src/test/regress/sql/generated_stored.sql | 15 ++++++ 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index d4eaf30815b..1ef8b355116 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8752,17 +8752,12 @@ static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode) { /* - * Reject ONLY if there are child tables. We could implement this, but it - * is a bit complicated. GENERATED clauses must be attached to the column - * definition and cannot be added later like DEFAULT, so if a child table - * has a generation expression that the parent does not have, the child - * column will necessarily be an attislocal column. So to implement ONLY - * here, we'd need extra code to update attislocal of the direct child - * tables, somewhat similar to how DROP COLUMN does it, so that the - * resulting state can be properly dumped and restored. + * Reject ONLY if there are child tables -- but only, of course, at the + * top of the tree, otherwise it'd be impossible to run this command with + * trees deeper than two levels. Caller already got lock. */ - if (!recurse && - find_inheritance_children(RelationGetRelid(rel), lockmode)) + if (!recurse && !recursing && + find_inheritance_children(RelationGetRelid(rel), NoLock)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("ALTER TABLE / DROP EXPRESSION must be applied to child tables too"))); diff --git a/src/test/regress/expected/generated_stored.out b/src/test/regress/expected/generated_stored.out index 86a04548a02..77fcd9002c6 100644 --- a/src/test/regress/expected/generated_stored.out +++ b/src/test/regress/expected/generated_stored.out @@ -1329,6 +1329,56 @@ Inherits: gtest30 ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error ERROR: cannot drop generation expression from inherited column +BEGIN; +CREATE TABLE gtest30_1_1 () INHERITS (gtest30_1); +ALTER TABLE gtest30 ALTER COLUMN b DROP EXPRESSION; +\d gtest30_1_1 + Table "generated_stored_tests.gtest30_1_1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | +Inherits: gtest30_1 + +ROLLBACK; +-- test drop expression with subpartitions +CREATE TABLE gtest_root (a int, b int, c int GENERATED ALWAYS AS (a + b) STORED) PARTITION BY LIST (a); +CREATE TABLE gtest_node PARTITION OF gtest_root FOR VALUES IN (1) PARTITION BY LIST (b); +CREATE TABLE gtest_leaf PARTITION OF gtest_node FOR VALUES IN (1); +ALTER TABLE gtest_node ALTER COLUMN c DROP EXPRESSION; -- fails +ERROR: cannot drop generation expression from inherited column +ALTER TABLE ONLY gtest_root ALTER COLUMN c DROP EXPRESSION; -- fails +ERROR: ALTER TABLE / DROP EXPRESSION must be applied to child tables too +ALTER TABLE gtest_root ALTER COLUMN c DROP EXPRESSION; +\d gtest_(root|node|leaf) + Table "generated_stored_tests.gtest_leaf" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + c | integer | | | +Partition of: gtest_node FOR VALUES IN (1) + +Partitioned table "generated_stored_tests.gtest_node" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + c | integer | | | +Partition of: gtest_root FOR VALUES IN (1) +Partition key: LIST (b) +Number of partitions: 1 (Use \d+ to list them.) + +Partitioned table "generated_stored_tests.gtest_root" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | | + b | integer | | | + c | integer | | | +Partition key: LIST (a) +Number of partitions: 1 (Use \d+ to list them.) + +DROP TABLE gtest_root; -- composite type dependencies CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text); CREATE TABLE gtest31_2 (x int, y gtest31_1); diff --git a/src/test/regress/sql/generated_stored.sql b/src/test/regress/sql/generated_stored.sql index 6abae289ffe..9ba83c6f5ab 100644 --- a/src/test/regress/sql/generated_stored.sql +++ b/src/test/regress/sql/generated_stored.sql @@ -597,6 +597,21 @@ ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; -- error \d gtest30 \d gtest30_1 ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error +BEGIN; +CREATE TABLE gtest30_1_1 () INHERITS (gtest30_1); +ALTER TABLE gtest30 ALTER COLUMN b DROP EXPRESSION; +\d gtest30_1_1 +ROLLBACK; + +-- test drop expression with subpartitions +CREATE TABLE gtest_root (a int, b int, c int GENERATED ALWAYS AS (a + b) STORED) PARTITION BY LIST (a); +CREATE TABLE gtest_node PARTITION OF gtest_root FOR VALUES IN (1) PARTITION BY LIST (b); +CREATE TABLE gtest_leaf PARTITION OF gtest_node FOR VALUES IN (1); +ALTER TABLE gtest_node ALTER COLUMN c DROP EXPRESSION; -- fails +ALTER TABLE ONLY gtest_root ALTER COLUMN c DROP EXPRESSION; -- fails +ALTER TABLE gtest_root ALTER COLUMN c DROP EXPRESSION; +\d gtest_(root|node|leaf) +DROP TABLE gtest_root; -- composite type dependencies CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text);