hi. the attached patch is to implement $subject. the generation expression will be dropped. the column value previous was NULL will be materialized based on generation expression.
It seems fairly straightforward: drop the generation expression in ATExecDropExpression, and instruct phase 3 to compute the generation expression and do the table rewrite. the doc changes: <para> - This form turns a stored generated column into a normal base column. - Existing data in the columns is retained, but future changes will no - longer apply the generation expression. - </para> - - <para> - This form is currently only supported for stored generated columns (not - virtual ones). + This form turns a generated column into a normal base column. + For stored generated column, existing data in the columns is retained; + For virtual generated column, it will compute the generation expression and + store the value in the columns. For inheritance hierarchy or partition hierarchy, + the virtual generation expression is computed based on the child's own generation expression. + The future changes will no longer apply the generation expression. </para>
From b3b23e5d7fee6143521560790bf4ad14e21e8a49 Mon Sep 17 00:00:00 2001 From: jian he <jian.universal...@gmail.com> Date: Thu, 27 Mar 2025 09:49:29 +0800 Subject: [PATCH v1 1/1] support ALTER TABLE DROP EXPRESSION for virtual generated column It seems fairly straightforward: drop the generation expression in ATExecDropExpression, and instruct phase 3 to compute the generation expression and do the table rewrite. discussion: https://postgr.es/m/ commitfest entry: --- doc/src/sgml/ref/alter_table.sgml | 14 ++-- src/backend/commands/tablecmds.c | 36 +++++---- src/test/regress/expected/fast_default.out | 3 + .../regress/expected/generated_virtual.out | 81 ++++++++++++++----- src/test/regress/sql/fast_default.sql | 2 + src/test/regress/sql/generated_virtual.sql | 26 +++++- 6 files changed, 114 insertions(+), 48 deletions(-) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 4f15b89a98f..cd68697e215 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -275,14 +275,12 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM <term><literal>DROP EXPRESSION [ IF EXISTS ]</literal></term> <listitem> <para> - This form turns a stored generated column into a normal base column. - Existing data in the columns is retained, but future changes will no - longer apply the generation expression. - </para> - - <para> - This form is currently only supported for stored generated columns (not - virtual ones). + This form turns a generated column into a normal base column. + For stored generated column, existing data in the columns is retained; + For virtual generated column, it will compute the generation expression and + store the value in the columns. For inheritance hierarchy or partition hierarchy, + the virtual generation expression is computed based on the child's own generation expression. + The future changes will no longer apply the generation expression. </para> <para> diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1202544ebd0..f1f5abf50de 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -494,7 +494,8 @@ static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName, Node *newExpr, 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 ATExecDropExpression(AlteredTableInfo *tab, Relation rel, const char *colName, + bool missing_ok, LOCKMODE lockmode); static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode); static ObjectAddress ATExecSetOptions(Relation rel, const char *colName, @@ -5381,7 +5382,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, address = ATExecSetExpression(tab, rel, cmd->name, cmd->def, lockmode); break; case AT_DropExpression: - address = ATExecDropExpression(rel, cmd->name, cmd->missing_ok, lockmode); + address = ATExecDropExpression(tab, rel, cmd->name, cmd->missing_ok, lockmode); break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode); @@ -8658,7 +8659,8 @@ ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recurs * Return the address of the affected column. */ static ObjectAddress -ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode) +ATExecDropExpression(AlteredTableInfo *tab, Relation rel, const char *colName, + bool missing_ok, LOCKMODE lockmode) { HeapTuple tuple; Form_pg_attribute attTup; @@ -8684,19 +8686,6 @@ ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMOD errmsg("cannot alter system column \"%s\"", colName))); - /* - * TODO: This could be done, but it would need a table rewrite to - * materialize the generated values. Note that for the time being, we - * still error with missing_ok, so that we don't silently leave the column - * as generated. - */ - if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns"), - errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.", - colName, RelationGetRelationName(rel)))); - if (!attTup->attgenerated) { if (!missing_ok) @@ -8715,6 +8704,21 @@ ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMOD } } + if (attTup->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) + { + NewColumnValue *newval; + Expr *defval; + + newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); + newval->attnum = attnum; + defval = (Expr *) build_generation_expression(rel, attnum); + newval->expr = defval; + newval->is_generated = true; + + tab->newvals = lappend(tab->newvals, newval); + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; + } + /* * Mark the column as no longer generated. (The atthasdef flag needs to * get cleared too, but RemoveAttrDefault will handle that.) diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out index ccbcdf8403f..7e99b822f11 100644 --- a/src/test/regress/expected/fast_default.out +++ b/src/test/regress/expected/fast_default.out @@ -70,6 +70,9 @@ NOTICE: rewriting table has_volatile for reason 4 -- stored generated columns need a rewrite ALTER TABLE has_volatile ADD col7 int GENERATED ALWAYS AS (55) stored; NOTICE: rewriting table has_volatile for reason 2 +-- drop generation expression over virtual generated colum need rewrite +ALTER TABLE has_volatile ALTER COLUMN col6 DROP EXPRESSION; +NOTICE: rewriting table has_volatile for reason 2 -- Test a large sample of different datatypes CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1); SELECT set('t'); diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index dc09c85938e..959cf4608ab 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -1133,27 +1133,24 @@ SELECT * FROM gtest29; a | integer | | | b | integer | | | generated always as (a * 3) -ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION; -- not supported -ERROR: ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns -DETAIL: Column "b" of relation "gtest29" is a virtual generated column. +ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION; INSERT INTO gtest29 (a) VALUES (5); INSERT INTO gtest29 (a, b) VALUES (6, 66); -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -SELECT * FROM gtest29; +SELECT * FROM gtest29 ORDER BY a, b; a | b ---+---- 3 | 9 4 | 12 - 5 | 15 -(3 rows) + 5 | + 6 | 66 +(4 rows) \d gtest29 - Table "generated_virtual_tests.gtest29" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+----------------------------- + Table "generated_virtual_tests.gtest29" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- a | integer | | | - b | integer | | | generated always as (a * 3) + b | integer | | | -- check that dependencies between columns have also been removed --ALTER TABLE gtest29 DROP COLUMN a; -- should not drop b @@ -1165,22 +1162,20 @@ CREATE TABLE gtest30 ( ); CREATE TABLE gtest30_1 () INHERITS (gtest30); ALTER TABLE gtest30 ALTER COLUMN b DROP EXPRESSION; -ERROR: ALTER TABLE / DROP EXPRESSION is not supported for virtual generated columns -DETAIL: Column "b" of relation "gtest30" is a virtual generated column. \d gtest30 - Table "generated_virtual_tests.gtest30" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+----------------------------- + Table "generated_virtual_tests.gtest30" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- a | integer | | | - b | integer | | | generated always as (a * 2) + b | integer | | | Number of child tables: 1 (Use \d+ to list them.) \d gtest30_1 - Table "generated_virtual_tests.gtest30_1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+----------------------------- + Table "generated_virtual_tests.gtest30_1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- a | integer | | | - b | integer | | | generated always as (a * 2) + b | integer | | | Inherits: gtest30 DROP TABLE gtest30 CASCADE; @@ -1210,6 +1205,48 @@ Inherits: gtest30 ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error ERROR: cannot drop generation expression from inherited column +-- alter table drop expression with partitioning +CREATE TABLE gtest_pp (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) VIRTUAL) PARTITION BY RANGE (f1); +CREATE TABLE gtest_ch1 PARTITION OF gtest_pp + FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- inherits gen expr +CREATE TABLE gtest_ch2 PARTITION OF gtest_pp ( + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 22) VIRTUAL -- overrides gen expr +) FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +INSERT INTO gtest_pp (f1, f2) VALUES ('2016-07-15', 1), ('2016-07-15', 2), ('2016-08-15', 3); +ALTER TABLE ONLY gtest_pp ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); +ALTER TABLE gtest_ch1 ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); +SELECT tableoid::regclass, * FROM gtest_pp ORDER BY 1, 2, 3; + tableoid | f1 | f2 | f3 +-----------+------------+----+---- + gtest_ch1 | 07-15-2016 | 1 | 4 + gtest_ch1 | 07-15-2016 | 2 | 8 + gtest_ch2 | 08-15-2016 | 3 | 12 +(3 rows) + +ALTER TABLE gtest_ch1 ALTER COLUMN f3 DROP EXPRESSION; --error +ERROR: cannot drop generation expression from inherited column +ALTER TABLE ONLY gtest_pp ALTER COLUMN f3 DROP EXPRESSION; --error +ERROR: ALTER TABLE / DROP EXPRESSION must be applied to child tables too +ALTER TABLE gtest_pp ALTER COLUMN f3 DROP EXPRESSION; --ok +SELECT tableoid::regclass, * FROM gtest_pp ORDER BY 1, 2, 3; + tableoid | f1 | f2 | f3 +-----------+------------+----+---- + gtest_ch1 | 07-15-2016 | 1 | 10 + gtest_ch1 | 07-15-2016 | 2 | 20 + gtest_ch2 | 08-15-2016 | 3 | 66 +(3 rows) + +\d gtest_pp +Partitioned table "generated_virtual_tests.gtest_pp" + Column | Type | Collation | Nullable | Default +--------+--------+-----------+----------+--------- + f1 | date | | not null | + f2 | bigint | | | + f3 | bigint | | | +Partition key: RANGE (f1) +Number of partitions: 2 (Use \d+ to list them.) + +DROP TABLE gtest_pp; -- composite type dependencies CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') VIRTUAL, c text); CREATE TABLE gtest31_2 (x int, y gtest31_1); diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql index 068dd0bc8aa..c0ef10b1ca3 100644 --- a/src/test/regress/sql/fast_default.sql +++ b/src/test/regress/sql/fast_default.sql @@ -77,6 +77,8 @@ ALTER TABLE has_volatile ALTER COLUMN col1 SET DATA TYPE float8, -- stored generated columns need a rewrite ALTER TABLE has_volatile ADD col7 int GENERATED ALWAYS AS (55) stored; +-- drop generation expression over virtual generated colum need rewrite +ALTER TABLE has_volatile ALTER COLUMN col6 DROP EXPRESSION; -- Test a large sample of different datatypes diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index dab8c92ef99..d278a868107 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -562,10 +562,10 @@ ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 3); SELECT * FROM gtest29; \d gtest29 -ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION; -- not supported +ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION; INSERT INTO gtest29 (a) VALUES (5); INSERT INTO gtest29 (a, b) VALUES (6, 66); -SELECT * FROM gtest29; +SELECT * FROM gtest29 ORDER BY a, b; \d gtest29 -- check that dependencies between columns have also been removed @@ -592,6 +592,28 @@ ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; -- error \d gtest30_1 ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error +-- alter table drop expression with partitioning +CREATE TABLE gtest_pp (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) VIRTUAL) PARTITION BY RANGE (f1); +CREATE TABLE gtest_ch1 PARTITION OF gtest_pp + FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- inherits gen expr +CREATE TABLE gtest_ch2 PARTITION OF gtest_pp ( + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 22) VIRTUAL -- overrides gen expr +) FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +INSERT INTO gtest_pp (f1, f2) VALUES ('2016-07-15', 1), ('2016-07-15', 2), ('2016-08-15', 3); + +ALTER TABLE ONLY gtest_pp ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); +ALTER TABLE gtest_ch1 ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); + +SELECT tableoid::regclass, * FROM gtest_pp ORDER BY 1, 2, 3; + +ALTER TABLE gtest_ch1 ALTER COLUMN f3 DROP EXPRESSION; --error +ALTER TABLE ONLY gtest_pp ALTER COLUMN f3 DROP EXPRESSION; --error + +ALTER TABLE gtest_pp ALTER COLUMN f3 DROP EXPRESSION; --ok +SELECT tableoid::regclass, * FROM gtest_pp ORDER BY 1, 2, 3; +\d gtest_pp +DROP TABLE gtest_pp; + -- composite type dependencies CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') VIRTUAL, c text); CREATE TABLE gtest31_2 (x int, y gtest31_1); -- 2.34.1