v2-0002-Allow-to-change-generated-column-expression.patch
application/octet-stream
Filename: v2-0002-Allow-to-change-generated-column-expression.patch
Type: application/octet-stream
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0002
Subject: Allow to change generated column expression
| File | + | − |
|---|---|---|
| doc/src/sgml/ref/alter_table.sgml | 10 | 4 |
| src/backend/commands/tablecmds.c | 68 | 23 |
| src/backend/parser/gram.y | 10 | 0 |
| src/bin/psql/tab-complete.c | 1 | 1 |
| src/include/nodes/parsenodes.h | 1 | 1 |
| src/test/regress/expected/generated.out | 144 | 23 |
| src/test/regress/sql/generated.sql | 31 | 5 |
From 0fafb9405fca021f4cfdc286fb2e79ee3fcb63d3 Mon Sep 17 00:00:00 2001
From: Amul Sul <amul.sul@enterprisedb.com>
Date: Mon, 28 Aug 2023 15:23:06 +0530
Subject: [PATCH v2 2/2] Allow to change generated column expression
---
doc/src/sgml/ref/alter_table.sgml | 14 +-
src/backend/commands/tablecmds.c | 91 +++++++++----
src/backend/parser/gram.y | 10 ++
src/bin/psql/tab-complete.c | 2 +-
src/include/nodes/parsenodes.h | 2 +-
src/test/regress/expected/generated.out | 167 ++++++++++++++++++++----
src/test/regress/sql/generated.sql | 36 ++++-
7 files changed, 265 insertions(+), 57 deletions(-)
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2c4138e4e9f..84bf8fa6ef3 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -46,6 +46,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> { SET | DROP } NOT NULL
+ ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET EXPRESSION <replaceable class="parameter">expression</replaceable> STORED
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP EXPRESSION [ IF EXISTS ]
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ]
ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> { SET GENERATED { ALWAYS | BY DEFAULT } | SET <replaceable>sequence_option</replaceable> | RESTART [ [ WITH ] <replaceable class="parameter">restart</replaceable> ] } [...]
@@ -256,13 +257,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
</listitem>
</varlistentry>
- <varlistentry id="sql-altertable-desc-drop-expression">
+ <varlistentry id="sql-altertable-desc-set-drop-expression">
<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.
+ The <literal>SET</literal> form replaces stored generated value for a
+ column. Existing data in the columns is rewritten and all the future
+ changes will apply the new generation expression.
+ </para>
+ <para>
+ The <literal>DROP</literal> 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>
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bfaa3d2e95f..0f76fd639c3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -457,7 +457,8 @@ static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
static void ATPrepColumnExpression(Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing, LOCKMODE lockmode);
-static ObjectAddress ATExecColumnExpression(Relation rel, const char *colName,
+static ObjectAddress ATExecColumnExpression(AlteredTableInfo *tab, Relation rel,
+ const char *colName, Node *newDefault,
bool missing_ok, LOCKMODE lockmode);
static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum,
Node *newValue, LOCKMODE lockmode);
@@ -4793,7 +4794,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
pass = AT_PASS_COL_ATTRS;
break;
- case AT_ColumnExpression: /* ALTER COLUMN DROP EXPRESSION */
+ case AT_ColumnExpression: /* ALTER COLUMN SET/DROP EXPRESSION */
ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
ATPrepColumnExpression(rel, cmd, recurse, recursing, lockmode);
@@ -5179,7 +5180,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
address = ATExecSetAttNotNull(wqueue, rel, cmd->name, lockmode);
break;
case AT_ColumnExpression:
- address = ATExecColumnExpression(rel, cmd->name, cmd->missing_ok, lockmode);
+ address = ATExecColumnExpression(tab, rel, cmd->name, cmd->def,
+ cmd->missing_ok, lockmode);
break;
case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
@@ -8252,16 +8254,22 @@ static void
ATPrepColumnExpression(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.
+ * Only SET EXPRESSION would be having new expression for the replacement.
*/
- if (!recurse &&
+ bool isdrop = (cmd->def == NULL);
+
+ /*
+ * Reject ALTER TABLE ONLY ... DROP EXPRESSION 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.
+ */
+ if (!recurse && isdrop &&
find_inheritance_children(RelationGetRelid(rel), lockmode))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -8284,7 +8292,7 @@ ATPrepColumnExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recu
attTup = (Form_pg_attribute) GETSTRUCT(tuple);
- if (attTup->attinhcount > 0)
+ if (attTup->attinhcount > 0 && isdrop)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot drop generation expression from inherited column")));
@@ -8295,7 +8303,8 @@ ATPrepColumnExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recu
* Return the address of the affected column.
*/
static ObjectAddress
-ATExecColumnExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode)
+ATExecColumnExpression(AlteredTableInfo *tab, Relation rel, const char *colName,
+ Node *newDefault, bool missing_ok, LOCKMODE lockmode)
{
HeapTuple tuple;
Form_pg_attribute attTup;
@@ -8339,16 +8348,21 @@ ATExecColumnExpression(Relation rel, const char *colName, bool missing_ok, LOCKM
}
}
- /*
- * Mark the column as no longer generated. (The atthasdef flag needs to
- * get cleared too, but RemoveAttrDefault will handle that.)
- */
- attTup->attgenerated = '\0';
- CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
+ /* DROP EXPRESSION */
+ if (newDefault == NULL)
+ {
+ /*
+ * Mark the column as no longer generated. (The atthasdef flag needs
+ * to get cleared too, but RemoveAttrDefault will handle that.)
+ */
+ attTup->attgenerated = '\0';
+ CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
+
+ InvokeObjectPostAlterHook(RelationRelationId,
+ RelationGetRelid(rel),
+ attnum);
+ }
- InvokeObjectPostAlterHook(RelationRelationId,
- RelationGetRelid(rel),
- attnum);
heap_freetuple(tuple);
table_close(attrelation, RowExclusiveLock);
@@ -8375,6 +8389,37 @@ ATExecColumnExpression(Relation rel, const char *colName, bool missing_ok, LOCKM
RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT,
false, false);
+ /* SET EXPRESSION */
+ if (newDefault)
+ {
+ Expr *defval;
+ NewColumnValue *newval;
+ RawColumnDefault *rawEnt;
+
+ /* Prepare to store the EXPRESSION, in the catalogs */
+ rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
+ rawEnt->attnum = attnum;
+ rawEnt->raw_default = newDefault;
+ rawEnt->missingMode = false;
+ rawEnt->generated = ATTRIBUTE_GENERATED_STORED;
+
+ /* Store the EXPRESSION */
+ AddRelationNewConstraints(rel, list_make1(rawEnt), NIL,
+ false, true, false, NULL);
+ CommandCounterIncrement();
+
+ /* Prepare for table rewrite */
+ defval = (Expr *) build_column_default(rel, attnum);
+
+ newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
+ newval->attnum = attnum;
+ newval->expr = expression_planner(defval);
+ newval->is_generated = true;
+
+ tab->newvals = lappend(tab->newvals, newval);
+ tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+ }
+
ObjectAddressSubSet(address, RelationRelationId,
RelationGetRelid(rel), attnum);
return address;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7afb562cdac..16a66487314 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2404,6 +2404,16 @@ alter_table_cmd:
n->name = $3;
$$ = (Node *) n;
}
+ /* ALTER TABLE <name> ALTER [COLUMN] <colname> SET EXPRESSION <expr> STORED */
+ | ALTER opt_column ColId SET EXPRESSION '(' a_expr ')' STORED
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+
+ n->subtype = AT_ColumnExpression;
+ n->name = $3;
+ n->def = $7;
+ $$ = (Node *) n;
+ }
/* ALTER TABLE <name> ALTER [COLUMN] <colname> DROP EXPRESSION */
| ALTER opt_column ColId DROP EXPRESSION
{
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 779fdc90cbc..8043907904e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2483,7 +2483,7 @@ psql_completion(const char *text, int start, int end)
/* ALTER TABLE ALTER [COLUMN] <foo> SET */
else if (Matches("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
Matches("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
- COMPLETE_WITH("(", "COMPRESSION", "DEFAULT", "GENERATED", "NOT NULL", "STATISTICS", "STORAGE",
+ COMPLETE_WITH("(", "COMPRESSION", "DEFAULT", "EXPRESSION", "GENERATED", "NOT NULL", "STATISTICS", "STORAGE",
/* a subset of ALTER SEQUENCE options */
"INCREMENT", "MINVALUE", "MAXVALUE", "START", "NO", "CACHE", "CYCLE");
/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 29ad91739c4..987137de03d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2216,7 +2216,7 @@ typedef enum AlterTableType
AT_DropNotNull, /* alter column drop not null */
AT_SetNotNull, /* alter column set not null */
AT_SetAttNotNull, /* set attnotnull w/o a constraint */
- AT_ColumnExpression, /* alter column drop expression */
+ AT_ColumnExpression, /* alter column set/drop expression */
AT_SetStatistics, /* alter column set statistics */
AT_SetOptions, /* alter column set ( options ) */
AT_ResetOptions, /* alter column reset ( options ) */
diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out
index dc97ed3fe01..521b5fa1e2e 100644
--- a/src/test/regress/expected/generated.out
+++ b/src/test/regress/expected/generated.out
@@ -782,30 +782,119 @@ Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016')
Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016')
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1);
-SELECT * FROM gtest_parent;
- f1 | f2 | f3
-------------+----+----
- 07-15-2016 | 1 | 2
-(1 row)
+INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 2);
+INSERT INTO gtest_parent (f1, f2) VALUES ('2016-08-15', 3);
+SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1;
+ tableoid | f1 | f2 | f3
+--------------+------------+----+----
+ gtest_child | 07-15-2016 | 1 | 2
+ gtest_child | 07-15-2016 | 2 | 4
+ gtest_child2 | 08-15-2016 | 3 | 66
+(3 rows)
-SELECT * FROM gtest_child;
- f1 | f2 | f3
-------------+----+----
- 07-15-2016 | 1 | 2
-(1 row)
+UPDATE gtest_parent SET f1 = f1 + 60 WHERE f2 = 1;
+SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1;
+ tableoid | f1 | f2 | f3
+--------------+------------+----+----
+ gtest_child | 07-15-2016 | 2 | 4
+ gtest_child2 | 08-15-2016 | 3 | 66
+ gtest_child3 | 09-13-2016 | 1 | 33
+(3 rows)
-UPDATE gtest_parent SET f1 = f1 + 60;
-SELECT * FROM gtest_parent;
- f1 | f2 | f3
-------------+----+----
- 09-13-2016 | 1 | 33
-(1 row)
+-- alter only parent's and one child's generated expression
+ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION (f2 * 4) STORED;
+ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION (f2 * 10) STORED;
+\d gtest_parent
+ Partitioned table "public.gtest_parent"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+-------------------------------------
+ f1 | date | | not null |
+ f2 | bigint | | |
+ f3 | bigint | | | generated always as (f2 * 4) stored
+Partition key: RANGE (f1)
+Number of partitions: 3 (Use \d+ to list them.)
-SELECT * FROM gtest_child3;
- f1 | f2 | f3
-------------+----+----
- 09-13-2016 | 1 | 33
-(1 row)
+\d gtest_child
+ Table "public.gtest_child"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+--------------------------------------
+ f1 | date | | not null |
+ f2 | bigint | | |
+ f3 | bigint | | | generated always as (f2 * 10) stored
+Partition of: gtest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016')
+
+\d gtest_child2
+ Table "public.gtest_child2"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+--------------------------------------
+ f1 | date | | not null |
+ f2 | bigint | | |
+ f3 | bigint | | | generated always as (f2 * 22) stored
+Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016')
+
+\d gtest_child3
+ Table "public.gtest_child3"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+--------------------------------------
+ f1 | date | | not null |
+ f2 | bigint | | |
+ f3 | bigint | | | generated always as (f2 * 33) stored
+Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016')
+
+SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1;
+ tableoid | f1 | f2 | f3
+--------------+------------+----+----
+ gtest_child | 07-15-2016 | 2 | 20
+ gtest_child2 | 08-15-2016 | 3 | 66
+ gtest_child3 | 09-13-2016 | 1 | 33
+(3 rows)
+
+-- alter generated expression of a parent and all it's child altogether
+ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION (f2 * 2) STORED;
+\d gtest_parent
+ Partitioned table "public.gtest_parent"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+-------------------------------------
+ f1 | date | | not null |
+ f2 | bigint | | |
+ f3 | bigint | | | generated always as (f2 * 2) stored
+Partition key: RANGE (f1)
+Number of partitions: 3 (Use \d+ to list them.)
+
+\d gtest_child
+ Table "public.gtest_child"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+-------------------------------------
+ f1 | date | | not null |
+ f2 | bigint | | |
+ f3 | bigint | | | generated always as (f2 * 2) stored
+Partition of: gtest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016')
+
+\d gtest_child2
+ Table "public.gtest_child2"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+-------------------------------------
+ f1 | date | | not null |
+ f2 | bigint | | |
+ f3 | bigint | | | generated always as (f2 * 2) stored
+Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016')
+
+\d gtest_child3
+ Table "public.gtest_child3"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+-------------------------------------
+ f1 | date | | not null |
+ f2 | bigint | | |
+ f3 | bigint | | | generated always as (f2 * 2) stored
+Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016')
+
+SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1;
+ tableoid | f1 | f2 | f3
+--------------+------------+----+----
+ gtest_child | 07-15-2016 | 2 | 4
+ gtest_child2 | 08-15-2016 | 3 | 6
+ gtest_child3 | 09-13-2016 | 1 | 2
+(3 rows)
-- we leave these tables around for purposes of testing dump/reload/upgrade
-- generated columns in partition key (not allowed)
@@ -932,18 +1021,50 @@ CREATE TABLE gtest29 (
b int GENERATED ALWAYS AS (a * 2) STORED
);
INSERT INTO gtest29 (a) VALUES (3), (4);
+SELECT * FROM gtest29;
+ a | b
+---+---
+ 3 | 6
+ 4 | 8
+(2 rows)
+
+\d gtest29
+ Table "public.gtest29"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 2) stored
+
+ALTER TABLE gtest29 ALTER COLUMN a SET EXPRESSION (a * 3) STORED; -- error
+ERROR: column "a" of relation "gtest29" is not a stored generated column
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION; -- error
ERROR: column "a" of relation "gtest29" is not a stored generated column
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice
NOTICE: column "a" of relation "gtest29" is not a stored generated column, skipping
+-- Change the expression
+ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION (a * 3) STORED;
+SELECT * FROM gtest29;
+ a | b
+---+----
+ 3 | 9
+ 4 | 12
+(2 rows)
+
+\d gtest29
+ Table "public.gtest29"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+------------------------------------
+ a | integer | | |
+ b | integer | | | generated always as (a * 3) stored
+
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;
a | b
---+----
- 3 | 6
- 4 | 8
+ 3 | 9
+ 4 | 12
5 |
6 | 66
(4 rows)
diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql
index 8ddecf0cc38..f7af399b8cc 100644
--- a/src/test/regress/sql/generated.sql
+++ b/src/test/regress/sql/generated.sql
@@ -411,11 +411,28 @@ ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09
\d gtest_child2
\d gtest_child3
INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1);
-SELECT * FROM gtest_parent;
-SELECT * FROM gtest_child;
-UPDATE gtest_parent SET f1 = f1 + 60;
-SELECT * FROM gtest_parent;
-SELECT * FROM gtest_child3;
+INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 2);
+INSERT INTO gtest_parent (f1, f2) VALUES ('2016-08-15', 3);
+SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1;
+UPDATE gtest_parent SET f1 = f1 + 60 WHERE f2 = 1;
+SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1;
+
+-- alter only parent's and one child's generated expression
+ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION (f2 * 4) STORED;
+ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION (f2 * 10) STORED;
+\d gtest_parent
+\d gtest_child
+\d gtest_child2
+\d gtest_child3
+SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1;
+
+-- alter generated expression of a parent and all it's child altogether
+ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION (f2 * 2) STORED;
+\d gtest_parent
+\d gtest_child
+\d gtest_child2
+\d gtest_child3
+SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1;
-- we leave these tables around for purposes of testing dump/reload/upgrade
-- generated columns in partition key (not allowed)
@@ -470,8 +487,17 @@ CREATE TABLE gtest29 (
b int GENERATED ALWAYS AS (a * 2) STORED
);
INSERT INTO gtest29 (a) VALUES (3), (4);
+SELECT * FROM gtest29;
+\d gtest29
+ALTER TABLE gtest29 ALTER COLUMN a SET EXPRESSION (a * 3) STORED; -- error
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION; -- error
ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice
+
+-- Change the expression
+ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION (a * 3) STORED;
+SELECT * FROM gtest29;
+\d gtest29
+
ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION;
INSERT INTO gtest29 (a) VALUES (5);
INSERT INTO gtest29 (a, b) VALUES (6, 66);
--
2.18.0