1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Fix ALTER COLUMN ... DROP EXPRESSION with subpartitions

Per commit 8bf6ec3ba3, 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
8bf6ec3ba3 explains.

Author: Alberto Piai <alberto.piai@gmail.com>
Backpatch-through: 14
Discussion: https://postgr.es/m/DHMT78XOD8BK.341V3H87KZ7NO@gmail.com
This commit is contained in:
Álvaro Herrera
2026-08-04 09:06:46 +02:00
parent fec40878c8
commit cad17745ed
3 changed files with 70 additions and 10 deletions

View File

@@ -7854,17 +7854,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 attlocal 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")));

View File

@@ -960,6 +960,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 "public.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 "public.gtest_leaf"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
a | integer | | |
b | integer | | |
c | integer | | |
Partition of: gtest_node FOR VALUES IN (1)
Partitioned table "public.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 "public.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;
-- triggers
CREATE TABLE gtest26 (
a int PRIMARY KEY,

View File

@@ -480,6 +480,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;
-- triggers
CREATE TABLE gtest26 (