Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wA2la-0020m9-25 for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Apr 2026 09:30:38 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wA2lZ-00GHjE-0d for pgsql-hackers@arkaria.postgresql.org; Tue, 07 Apr 2026 09:30:37 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wA2lY-00GHj6-2Z for pgsql-hackers@lists.postgresql.org; Tue, 07 Apr 2026 09:30:37 +0000 Received: from mail-pj1-x1030.google.com ([2607:f8b0:4864:20::1030]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wA2lX-000000010XH-0TI7 for pgsql-hackers@lists.postgresql.org; Tue, 07 Apr 2026 09:30:36 +0000 Received: by mail-pj1-x1030.google.com with SMTP id 98e67ed59e1d1-35d95017a68so2673745a91.3 for ; Tue, 07 Apr 2026 02:30:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775554234; x=1776159034; darn=lists.postgresql.org; h=content-transfer-encoding:content-disposition:mime-version :mail-followup-to:message-id:subject:to:from:date:from:to:cc:subject :date:message-id:reply-to; bh=dN2zfQoDGQ4tfaNNMR3XmiUWs/Q5F1144M1FFmzIAe0=; b=E2CY6oyq/B3ruaeIWXhhIS+4PQJZoiCtOZgO2WL3JKNqikC2pxgD16aP4GoKaPHjtm 164pj4W4x40nCQD1+IgPiK58TwzwzTvj7pPGNG5iCyT0BX0jIfRO83HPqB4g4XlcaRrm j3SggNiq5vLxYKMD9jS2+rfMZ3LKdD65BdyDuucxys+mvH9AtkCit73qMI6NUZfmiE6u ixgjFKps3q+cnGt4ayxP0RKWqWieiI93PiYtTDdBEHV6agJRIrqCnDgyMY4huTX9zhYF xdob/akpgmwipfX34TnkmdzT7gtSDV2nmJpBnJym7iKjv01hbmYu8Sl9fa8XDh/vY1sD LVhw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775554234; x=1776159034; h=content-transfer-encoding:content-disposition:mime-version :mail-followup-to:message-id:subject:to:from:date:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=dN2zfQoDGQ4tfaNNMR3XmiUWs/Q5F1144M1FFmzIAe0=; b=f57RjltD+GOodjb2nDo9vdTbYqbCltYYeP+GasNSuhcmETUQG+uPvyvKF+0LMfM+tf iDbGlYBc6ZJQXDesuugO5Oc7YUOtqORZYSQdapOz8bZfGDchHQKUbVHIx5w7iSrCzL4h mDIsLnyKCsRsLEAjaeNpw+r4ItbfZYsVMI+8sPIJpl1oYdX/3vlCo9sNEa0Z2IDWJNq7 mN2QLhZThiz0fbMx2iLsFuCBwLslN1O8GI9c+Hq5OporcBALek6ypbBO1X/atsMWoGY2 6MtT53yamAcuFwWiqL4YQ4S0ZoJKg4QnJbdNe4aciEWwA6PpOLV09HuHd1ae8GeXBXQW t9Rw== X-Gm-Message-State: AOJu0YxKq1T5rs5QOd15YZ2coFizVxjZlF2vE0y9jssawDeYlNZy8ICy wnsiVuyRiFJ8boVm9sWFjWqxtlx8PNvf1Cpmn+xbY8dHmLYMjrsPEnaYg+EAzICM X-Gm-Gg: AeBDieszxMnGT4uczhZFQEtImvDzYClVh/mnKkfNu0vBvMzlI/X5As2WOW2Tau6mUgy Dam53GM7n6Pgb4enVJzsYFUNWUCm7SJDsae+sPHXPAy4CpF6lsFMINOv+Sd4sM+exmHrXbnes8z PfD/GZNbmXl75P6vv3sdmpmhBXlZbEk4NHk4bhKg+ptEsIYoj8bocg/xX+/R5bed8i+KCQ4xsBY dGfiSW8EZaik5FrfwGqbMiFeZv5sCqJuPOW+SC/L/mYqP4A51OygAgA2VxlGaz9eLDMiYxeW6oE +i00V7WNNu1ybwyGg75dVyqearECS4xwaLaN4PgYBrpyjU6Cyhps+1l+UlJ91qWyiXxvRMeC/ju BpsShoAVpNjmlhOJXEWb7VvsaVWQgDMCN9LomBYURHFpDCkQzmbeAk8GNnERQ0O47GGiW6jaWzB jZ5ZzmEHrEdMmvlXITPBHx2ViAGAeFWZcCvlRYsU3zKUhCVQU6Kg== X-Received: by 2002:a17:90b:3f4d:b0:35d:9482:2233 with SMTP id 98e67ed59e1d1-35de69a66e1mr15749416a91.24.1775554233695; Tue, 07 Apr 2026 02:30:33 -0700 (PDT) Received: from localhost (ppp-49-237-93-72.revip6.asianet.co.th. [49.237.93.72]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-35ddb973ffasm15312351a91.2.2026.04.07.02.30.32 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 07 Apr 2026 02:30:33 -0700 (PDT) Date: Tue, 7 Apr 2026 16:30:02 +0700 From: Alberto Piai To: pgsql-hackers@lists.postgresql.org Subject: Fix ALTER COLUMN ... DROP EXPRESSSION with subpartitions Message-ID: Mail-Followup-To: pgsql-hackers@lists.postgresql.org X-Mailer: aerc 0.21.0 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="vd5olc53tjms46zu" Content-Disposition: inline Content-Transfer-Encoding: 8bit List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --vd5olc53tjms46zu Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit While working on [0], I noticed that DROP EXPRESSION currently refuses to be applied to inheritance trees of depth > 2, e.g. when there are subpartitions. This works as expected: CREATE TABLE gtest_root (a int, b int, c int GENERATED ALWAYS AS (a + b) STORED) PARTITION BY LIST (a); CREATE TABLE gtest_leaf PARTITION OF gtest_root FOR VALUES IN (1); ALTER TABLE gtest_root ALTER COLUMN c DROP EXPRESSION; while this doesn't: 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_root ALTER COLUMN c DROP EXPRESSION; and results in ERROR: ALTER TABLE / DROP EXPRESSION must be applied to child tables too This seems like a simple oversight while trying to enforce that a GENERATED column must be such in the whole inheritance tree [1]. PFA a fix for this and a test case. I added the test case to generated_stored.sql, even though the comments at the top say it should be kept in sync with generated_virtual.sql, because DROP EXPRESSION is not supported for virtual generated columns. It seemed better to keep the test case closed to the other tests of DROP/SET EXPRESSION with partitioning, rather than putting it e.g. in alter_table.sql, but happy to move it of course. Kind regards, Alberto [0] https://postgr.es/m/abkrpUwlGngF4e-d%40phidippus.sen.work [1] See 8bf6ec3ba3a44448817af47a080587f3b71bee08 and the associated discussion at https://postgr.es/m/2793383.1672944799@sss.pgh.pa.us -- Alberto Piai Sensational AG Zürich, Switzerland --vd5olc53tjms46zu Content-Type: text/x-patch; charset=utf-8 Content-Disposition: attachment; filename="v1-0001-Fix-ALTER-COLUMN-.-DROP-EXPRESSSION-with-subparti.patch" From 73318f99618b281534ce89322371e22d81771f05 Mon Sep 17 00:00:00 2001 From: Alberto Piai Date: Sun, 5 Apr 2026 06:10:41 +0200 Subject: [PATCH v1] Fix ALTER COLUMN ... DROP EXPRESSSION with subpartitions A column can be GENERATED only if it is such in the whole inheritance tree (see 8bf6ec3ba3a44448817af47a080587f3b71bee08). 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 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. --- src/backend/commands/tablecmds.c | 6 +++- .../regress/expected/generated_stored.out | 36 +++++++++++++++++++ src/test/regress/sql/generated_stored.sql | 10 ++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 0ce2e81f9c2..d54b33bf897 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8839,8 +8839,12 @@ ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recurs * 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. + * + * We must check this only on the root node of an inheritance tree + * (recursing = false), otherwise it would be impossible to apply this + * operation recursively to trees with depth > 2. */ - if (!recurse && + if (!recursing && !recurse && find_inheritance_children(RelationGetRelid(rel), lockmode)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), diff --git a/src/test/regress/expected/generated_stored.out b/src/test/regress/expected/generated_stored.out index 43cddeac373..49ae13f9c14 100644 --- a/src/test/regress/expected/generated_stored.out +++ b/src/test/regress/expected/generated_stored.out @@ -1134,6 +1134,42 @@ SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; (3 rows) -- we leave these tables around for purposes of testing dump/reload/upgrade +-- 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_root ALTER COLUMN c DROP EXPRESSION; +\d gtest_root +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.) + +\d gtest_node +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.) + +\d gtest_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) + +DROP TABLE gtest_root; -- generated columns in partition key (not allowed) CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); ERROR: cannot use generated column in partition key diff --git a/src/test/regress/sql/generated_stored.sql b/src/test/regress/sql/generated_stored.sql index 280021d79b7..ef44666b968 100644 --- a/src/test/regress/sql/generated_stored.sql +++ b/src/test/regress/sql/generated_stored.sql @@ -541,6 +541,16 @@ ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2); SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; -- we leave these tables around for purposes of testing dump/reload/upgrade +-- 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_root ALTER COLUMN c DROP EXPRESSION; +\d gtest_root +\d gtest_node +\d gtest_leaf +DROP TABLE gtest_root; + -- generated columns in partition key (not allowed) CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3)); base-commit: c06443063f01b0996a16dea77462ac6b31eb181d -- 2.47.0 --vd5olc53tjms46zu--