From e7a3b3c963cf0ce92b97cd61dc435560820ef311 Mon Sep 17 00:00:00 2001 From: jian he Date: Wed, 7 Jan 2026 11:45:37 +0800 Subject: [PATCH v3 1/1] ALTER COLUMN SET EXPRESSION [ VIRTUAL | STORED ] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ALTER COLUMN SET EXPRESSION with the VIRTUAL or STORED keyword updates the generation expression and the column’s persistence simultaneously. For converting a stored generated column to a virtual one: previously, this required dropping the column and re-adding it as a virtual generated column, which would break all the existing dependencies. With this patch, the conversion can be done directly, making the process more flexible and preserving dependencies. For converting a virtual generated column to a stored one: for the same reasons as above. This will also materializes the values of the virtual generated column. context: https://git.postgresql.org/cgit/postgresql.git/commit/?id=83ea6c54025bea67bcd4949a6d58d3fc11c3e21b discussion: https://postgr.es/m/CACJufxGXLU400QBBgdoboaza0xK58rQqsCAPrxbAMUmo0f8xCQ@mail.gmail.com --- doc/src/sgml/ref/alter_table.sgml | 22 ++- src/backend/access/heap/heapam_handler.c | 12 ++ src/backend/commands/tablecmds.c | 142 +++++++++++++++++- src/backend/parser/gram.y | 28 ++++ src/include/nodes/parsenodes.h | 13 ++ .../test_ddl_deparse/expected/alter_table.out | 6 + .../test_ddl_deparse/sql/alter_table.sql | 2 + .../test_ddl_deparse/test_ddl_deparse.c | 14 +- src/test/regress/expected/fast_default.out | 14 ++ .../regress/expected/generated_stored.out | 140 ++++++++++++++++- .../regress/expected/generated_virtual.out | 121 +++++++++++++++ src/test/regress/expected/publication.out | 4 + src/test/regress/sql/fast_default.sql | 12 ++ src/test/regress/sql/generated_stored.sql | 68 +++++++++ src/test/regress/sql/generated_virtual.sql | 43 ++++++ src/test/regress/sql/publication.sql | 2 + src/tools/pgindent/typedefs.list | 1 + 17 files changed, 632 insertions(+), 12 deletions(-) diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 1bd479c917a..cfb3a114fa0 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -52,7 +52,7 @@ ALTER TABLE [ IF EXISTS ] name ALTER [ COLUMN ] column_name SET DEFAULT expression ALTER [ COLUMN ] column_name DROP DEFAULT ALTER [ COLUMN ] column_name { SET | DROP } NOT NULL - ALTER [ COLUMN ] column_name SET EXPRESSION AS ( expression ) + ALTER [ COLUMN ] column_name SET EXPRESSION AS ( expression ) [ STORED | VIRTUAL ] ALTER [ COLUMN ] column_name DROP EXPRESSION [ IF EXISTS ] ALTER [ COLUMN ] column_name ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ] ALTER [ COLUMN ] column_name { SET GENERATED { ALWAYS | BY DEFAULT } | SET sequence_option | RESTART [ [ WITH ] restart ] } [...] @@ -272,16 +272,26 @@ WITH ( MODULUS numeric_literal, REM - SET EXPRESSION AS + SET EXPRESSION AS ( expression ) [ STORED | VIRTUAL ] - This form replaces the expression of a generated column. Existing data - in a stored generated column is rewritten and all the future changes - will apply the new generation expression. + This form replaces the expression of a generated column and may optionally + change its storage persistence type. + + + If the column is stored generated column or STORED is + specified, existing data is rewritten and all the future changes will apply + the new generation expression. + If VIRTUAL is specified, existing data won’t be rewritten, + and pg_attribute.attgenerated + is set to v. + If STORED is specified, + pg_attribute.attgenerated + set to s. - When this form is used on a stored generated column, its statistics + When this form is used on a stored generated column or STORED is specified, its statistics are removed, so running ANALYZE on the table afterwards is recommended. diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 09a456e9966..2004f2c3b29 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -2375,6 +2375,10 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate, * currently only known to happen as an after-effect of ALTER TABLE * SET WITHOUT OIDS. * + * 3. ALTER TABLE SET EXPRESSION VIRTUAL will converts the stored generated column + * into virtual; in that case, the corresponding tuple value must + * be set to NULL. + * * So, we must reconstruct the tuple from component Datums. */ static void @@ -2394,6 +2398,14 @@ reform_and_rewrite_tuple(HeapTuple tuple, { if (TupleDescCompactAttr(newTupDesc, i)->attisdropped) isnull[i] = true; + if (TupleDescCompactAttr(newTupDesc, i)->attgenerated) + { + if (TupleDescAttr(newTupDesc, i)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) + { + values[i] = (Datum) 0; + isnull[i] = true; + } + } } copiedTuple = heap_form_tuple(newTupDesc, values, isnull); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f976c0e5c7e..b40019188a5 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -523,6 +523,7 @@ 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 void ATPrepSetExpression(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 ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newValue, LOCKMODE lockmode); @@ -5032,6 +5033,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); + ATPrepSetExpression(rel, cmd, recurse, recursing, lockmode); pass = AT_PASS_SET_EXPRESSION; break; case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */ @@ -8641,8 +8643,9 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName, Expr *defval; NewColumnValue *newval; RawColumnDefault *rawEnt; + Node *raw_default; - tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName); + tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), @@ -8669,7 +8672,8 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName, * TODO: This could be done, just need to recheck any constraints * afterwards. */ - if (attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && + if (!IsA(newExpr, GenerationExpr) && + attgenerated == ATTRIBUTE_GENERATED_VIRTUAL && rel->rd_att->constr && rel->rd_att->constr->num_check > 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -8697,7 +8701,80 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName, rewrite = (attgenerated == ATTRIBUTE_GENERATED_STORED); - ReleaseSysCache(tuple); + /* + * For ALTER TABLE ALTER COLUMN SET EXPRESSION STORED/VIRTUAL, newExpr is + * a GenerationExpr node. For ALTER TABLE ALTER COLUMN SET EXPRESSION + * without STORED/VIRTUAL, newExpr is a non-GenerationExpr node; see + * gram.y. + */ + if (!IsA(newExpr, GenerationExpr)) + raw_default = newExpr; + else + { + GenerationExpr *g = castNode(GenerationExpr, newExpr); + + raw_default = g->raw_expr; + + if (attgenerated == g->generated_kind && + g->generated_kind == ATTRIBUTE_GENERATED_VIRTUAL && + rel->rd_att->constr && rel->rd_att->constr->num_check > 0) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ALTER TABLE / SET EXPRESSION VIRTUAL is not supported for virtual generated columns in tables with check constraints"), + errdetail("Column \"%s\" of relation \"%s\" is a virtual generated column.", + colName, RelationGetRelationName(rel))); + + if (attgenerated != g->generated_kind) + { + Relation pg_attribute = table_open(AttributeRelationId, + RowExclusiveLock); + + attgenerated = g->generated_kind; + attTup->attgenerated = g->generated_kind; + + if (g->generated_kind == ATTRIBUTE_GENERATED_STORED) + rewrite = true; + else + { + char *errdetail_msg = NULL; + + RememberAllDependentForRebuilding(tab, AT_SetExpression, rel, attnum, colName); + + /* + * Virtual generated columns do not currently support indexes, + * statistics, user-defined types, or publications. + */ + if (tab->changedIndexOids != NIL) + errdetail_msg = _("Indexes on virtual generated columns are not supported."); + else if (tab->changedStatisticsOids != NIL) + errdetail_msg = _("Statistics creation on virtual generated columns is not supported."); + else if (attTup->atttypid >= FirstUnpinnedObjectId) + errdetail_msg = _("Virtual generated columns that make use of user-defined types are not yet supported."); + else if (GetRelationPublications(RelationGetRelid(rel)) != NIL) + errdetail_msg = _("Publication on virtual generated columns are not supported."); + + if (errdetail_msg != NULL) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot changed generated column (%s) from %s to %s", colName, "STORED", "VIRTUAL"), + errdetail_internal("%s", errdetail_msg)); + + /* + * change GENERATED COLUMN from stored to virtual do not need + * table rewrite + */ + rewrite = false; + } + + CatalogTupleUpdate(pg_attribute, &tuple->t_self, tuple); + + InvokeObjectPostAlterHook(RelationRelationId, + RelationGetRelid(rel), + attnum); + table_close(pg_attribute, RowExclusiveLock); + } + } + heap_freetuple(tuple); if (rewrite) { @@ -8743,7 +8820,7 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName, /* Prepare to store the new expression, in the catalogs */ rawEnt = palloc_object(RawColumnDefault); rawEnt->attnum = attnum; - rawEnt->raw_default = newExpr; + rawEnt->raw_default = raw_default; rawEnt->generated = attgenerated; /* Store the generated expression */ @@ -8824,6 +8901,63 @@ ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recurs } } +/* + * ALTER TABLE ALTER COLUMN SET EXPRESSION [STORED | VIRTUAL] + * + * This needs to recurse into all child tables; otherwise, the parent and child + * may end up with different storage types for the generated column. + */ +static void +ATPrepSetExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode) +{ + GenerationExpr *genexpr = NULL; + + if (IsA(cmd->def, GenerationExpr)) + genexpr = castNode(GenerationExpr, cmd->def); + + /* + * Reject ONLY if there are child tables. + */ + if (!recurse && !recursing && + genexpr != NULL && + find_inheritance_children(RelationGetRelid(rel), lockmode)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s must be applied to child tables too", + (genexpr->generated_kind == ATTRIBUTE_GENERATED_STORED) ? + "ALTER TABLE ... SET EXPRESSION STORED" : + "ALTER TABLE ... SET EXPRESSION VIRTUAL"), + errhint("Do not specify the ONLY keyword.")); + + /* + * Cannot change generation expression kind from inherited columns. + */ + if (!recursing && genexpr != NULL) + { + Form_pg_attribute attTup; + + HeapTuple tuple = SearchSysCacheAttName(RelationGetRelid(rel), cmd->name); + + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + cmd->name, RelationGetRelationName(rel))); + + attTup = (Form_pg_attribute) GETSTRUCT(tuple); + + if (attTup->attinhcount > 0) + ereport(ERROR, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot use %s change generation expression from inherited column", + (genexpr->generated_kind == ATTRIBUTE_GENERATED_STORED) ? + "ALTER TABLE ... SET EXPRESSION STORED" : + "ALTER TABLE ... SET EXPRESSION VIRTUAL")); + + ReleaseSysCache(tuple); + } +} + /* * Return the address of the affected column. */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 713ee5c10a2..883dccd8e3b 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2576,6 +2576,34 @@ alter_table_cmd: n->def = $8; $$ = (Node *) n; } + /* ALTER TABLE ALTER [COLUMN] SET EXPRESSION AS VIRTUAL */ + | ALTER opt_column ColId SET EXPRESSION AS '(' a_expr ')' VIRTUAL + { + AlterTableCmd *n = makeNode(AlterTableCmd); + GenerationExpr *g = makeNode(GenerationExpr); + + g->raw_expr = $8; + g->generated_kind = ATTRIBUTE_GENERATED_VIRTUAL; + + n->subtype = AT_SetExpression; + n->name = $3; + n->def = (Node *) g; + $$ = (Node *) n; + } + /* ALTER TABLE ALTER [COLUMN] SET EXPRESSION AS STORED */ + | ALTER opt_column ColId SET EXPRESSION AS '(' a_expr ')' STORED + { + AlterTableCmd *n = makeNode(AlterTableCmd); + GenerationExpr *g = makeNode(GenerationExpr); + + g->raw_expr = $8; + g->generated_kind = ATTRIBUTE_GENERATED_STORED; + + n->subtype = AT_SetExpression; + n->name = $3; + n->def = (Node *) g; + $$ = (Node *) n; + } /* ALTER TABLE ALTER [COLUMN] DROP EXPRESSION */ | ALTER opt_column ColId DROP EXPRESSION { diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index aac4bfc70d9..8a73b76a7ca 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2906,6 +2906,19 @@ typedef struct Constraint ParseLoc location; /* token location, or -1 if unknown */ } Constraint; +/* ---------------------- + * ALTER COLUMN SET EXPRESSION STORED + * ALTER COLUMN SET EXPRESSION VIRTUAL + * ---------------------- + */ +typedef struct GenerationExpr +{ + NodeTag type; + Node *raw_expr; /* generation expression as a untransformed + * a_expr node */ + char generated_kind; /* STORED or VIRTUAL */ +} GenerationExpr; + /* ---------------------- * Create/Drop Table Space Statements * ---------------------- diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index 3a2f576f3b6..11d000f2f4f 100644 --- a/src/test/modules/test_ddl_deparse/expected/alter_table.out +++ b/src/test/modules/test_ddl_deparse/expected/alter_table.out @@ -135,6 +135,12 @@ NOTICE: DDL test: type simple, tag CREATE TABLE ALTER TABLE tbl ALTER COLUMN a SET EXPRESSION AS (b::int * 3); NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type SET EXPRESSION desc column a of table tbl +ALTER TABLE tbl ALTER COLUMN a SET EXPRESSION AS (b::int * 2) VIRTUAL; +NOTICE: DDL test: type alter table, tag ALTER TABLE +NOTICE: subcommand: type SET EXPRESSION VIRTUAL desc column a of table tbl +ALTER TABLE tbl ALTER COLUMN a SET EXPRESSION AS (b::int * 2) STORED; +NOTICE: DDL test: type alter table, tag ALTER TABLE +NOTICE: subcommand: type SET EXPRESSION STORED desc column a of table tbl ALTER TABLE tbl ALTER COLUMN a DROP EXPRESSION; NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type DROP EXPRESSION desc column a of table tbl diff --git a/src/test/modules/test_ddl_deparse/sql/alter_table.sql b/src/test/modules/test_ddl_deparse/sql/alter_table.sql index 0980097048e..6e65605cf8a 100644 --- a/src/test/modules/test_ddl_deparse/sql/alter_table.sql +++ b/src/test/modules/test_ddl_deparse/sql/alter_table.sql @@ -75,6 +75,8 @@ CREATE TABLE tbl ( ); ALTER TABLE tbl ALTER COLUMN a SET EXPRESSION AS (b::int * 3); +ALTER TABLE tbl ALTER COLUMN a SET EXPRESSION AS (b::int * 2) VIRTUAL; +ALTER TABLE tbl ALTER COLUMN a SET EXPRESSION AS (b::int * 2) STORED; ALTER TABLE tbl ALTER COLUMN a DROP EXPRESSION; ALTER TABLE tbl ALTER COLUMN b SET COMPRESSION pglz; diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c index 380b3e754b7..b9b62341ce5 100644 --- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -129,8 +129,20 @@ get_altertable_subcmdinfo(PG_FUNCTION_ARGS) strtype = "SET NOT NULL"; break; case AT_SetExpression: - strtype = "SET EXPRESSION"; + { + if (IsA(subcmd->def, GenerationExpr)) + { + GenerationExpr *genexpr = castNode(GenerationExpr, subcmd->def); + + if (genexpr->generated_kind == ATTRIBUTE_GENERATED_STORED) + strtype = "SET EXPRESSION STORED"; + else + strtype = "SET EXPRESSION VIRTUAL"; + } + else + strtype = "SET EXPRESSION"; break; + } case AT_DropExpression: strtype = "DROP EXPRESSION"; break; diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out index ccbcdf8403f..4fc5986f9c1 100644 --- a/src/test/regress/expected/fast_default.out +++ b/src/test/regress/expected/fast_default.out @@ -70,6 +70,20 @@ 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 +-- Changing a generated column from virtual to stored does not require a table +-- rewrite. +ALTER TABLE has_volatile ADD col8 int GENERATED ALWAYS AS (55) VIRTUAL; +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55) STORED; +NOTICE: rewriting table has_volatile for reason 2 +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55) STORED; +NOTICE: rewriting table has_volatile for reason 2 +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55); +NOTICE: rewriting table has_volatile for reason 2 +-- Changing a generated column from stored to virtual does not require a table +-- rewrite. +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55) VIRTUAL; +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55) VIRTUAL; +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55); -- 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_stored.out b/src/test/regress/expected/generated_stored.out index 8b7a71d8f0c..d8a507668ac 100644 --- a/src/test/regress/expected/generated_stored.out +++ b/src/test/regress/expected/generated_stored.out @@ -2,6 +2,12 @@ CREATE SCHEMA generated_stored_tests; GRANT USAGE ON SCHEMA generated_stored_tests TO PUBLIC; SET search_path = generated_stored_tests; +PREPARE get_generated_info(regclass[], text[]) AS +SELECT attrelid::regclass as table, attname, attnum, attgenerated, pg_get_expr(pd.adbin, pa.attrelid) +FROM pg_attribute pa JOIN pg_attrdef pd +ON pd.adrelid = pa.attrelid +WHERE pa.attrelid = ANY($1) AND pa.attname =ANY($2) AND pa.attnum > 0 AND atthasdef +ORDER BY attrelid::regclass::text COLLATE "C", attnum; CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED); CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_schema = 'generated_stored_tests' ORDER BY 1, 2; @@ -559,6 +565,14 @@ SELECT * FROM gtest_varlena ORDER BY a; (2 rows) DROP TABLE gtest_varlena; +-- varlena types change from stored to virtual +CREATE TABLE gtest_varlena ( + a int, + b text STORAGE EXTERNAL GENERATED ALWAYS AS (repeat('g', a)) STORED +) WITH (autovacuum_enabled = OFF); +INSERT INTO gtest_varlena VALUES(2001), (1); +ALTER TABLE gtest_varlena ALTER COLUMN b SET EXPRESSION AS (repeat('g', a)) VIRTUAL; +VACUUM (FULL) gtest_varlena; --should ok -- composite types CREATE TYPE double_int as (a int, b int); CREATE TABLE gtest4 ( @@ -573,6 +587,9 @@ SELECT * FROM gtest4; 6 | (12,18) (2 rows) +ALTER TABLE gtest4 ALTER COLUMN b SET EXPRESSION AS ((a * 2, a * 3)) VIRTUAL; --error +ERROR: cannot changed generated column (b) from STORED to VIRTUAL +DETAIL: Virtual generated columns that make use of user-defined types are not yet supported. DROP TABLE gtest4; DROP TYPE double_int; -- using tableoid is allowed @@ -666,6 +683,10 @@ ERROR: check constraint "gtest20a_b_check" of relation "gtest20a" is violated b ALTER TABLE gtest20a ADD COLUMN c float8 DEFAULT random() CHECK (b < 50); -- fails on existing row ERROR: check constraint "gtest20a_b_check" of relation "gtest20a" is violated by some row ALTER TABLE gtest20a ADD COLUMN c float8 DEFAULT random() CHECK (b < 61); -- ok +ALTER TABLE gtest20a ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; -- violates constraint +ERROR: check constraint "gtest20a_b_check" of relation "gtest20a" is violated by some row +ALTER TABLE gtest20a ALTER COLUMN b SET EXPRESSION AS (a * 3) STORED; -- violates constraint +ERROR: check constraint "gtest20a_b_check" of relation "gtest20a" is violated by some row CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); INSERT INTO gtest20b (a) VALUES (10); INSERT INTO gtest20b (a) VALUES (30); @@ -833,6 +854,9 @@ ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 5); -- error ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey" DETAIL: Key (b)=(5) is not present in table "gtest23a". ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 1); -- ok +--virutal generated column does not support foreign key +ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 11) VIRTUAL; --error +ERROR: foreign key constraints on virtual generated columns are not supported DROP TABLE gtest23b; DROP TABLE gtest23a; CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y)); @@ -858,6 +882,10 @@ CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) STORE INSERT INTO gtest24nn (a) VALUES (4); -- ok INSERT INTO gtest24nn (a) VALUES (NULL); -- error ERROR: value for domain gtestdomainnn violates check constraint "gtestdomainnn_check" +--error, virutal generated column does not support domains +ALTER TABLE gtest24nn ALTER COLUMN b SET EXPRESSION AS (a * 2) VIRTUAL; +ERROR: cannot changed generated column (b) from STORED to VIRTUAL +DETAIL: Virtual generated columns that make use of user-defined types are not yet supported. -- typed tables (currently not supported) CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); @@ -980,7 +1008,17 @@ SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; -- alter only parent's and one child's generation expression ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4) STORED; --error +ERROR: ALTER TABLE ... SET EXPRESSION STORED must be applied to child tables too +HINT: Do not specify the ONLY keyword. +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4) VIRTUAL; --error +ERROR: ALTER TABLE ... SET EXPRESSION VIRTUAL must be applied to child tables too +HINT: Do not specify the ONLY keyword. ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10) VIRTUAL; --error +ERROR: cannot use ALTER TABLE ... SET EXPRESSION VIRTUAL change generation expression from inherited column +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10) STORED; --error +ERROR: cannot use ALTER TABLE ... SET EXPRESSION STORED change generation expression from inherited column \d gtest_parent Partitioned table "generated_stored_tests.gtest_parent" Column | Type | Collation | Nullable | Default @@ -1065,6 +1103,26 @@ Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016') f3 | bigint | | | generated always as (f2 * 2) stored Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016') +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 11) VIRTUAL; +EXECUTE get_generated_info('{gtest_parent, gtest_child, gtest_child2, gtest_child3}', '{f3}'); + table | attname | attnum | attgenerated | pg_get_expr +--------------+---------+--------+--------------+------------- + gtest_child | f3 | 3 | v | (f2 * 11) + gtest_child2 | f3 | 3 | v | (f2 * 11) + gtest_child3 | f3 | 3 | v | (f2 * 11) + gtest_parent | f3 | 3 | v | (f2 * 11) +(4 rows) + +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2) STORED; +EXECUTE get_generated_info('{gtest_parent, gtest_child, gtest_child2, gtest_child3}', '{f3}'); + table | attname | attnum | attgenerated | pg_get_expr +--------------+---------+--------+--------------+------------- + gtest_child | f3 | 3 | s | (f2 * 2) + gtest_child2 | f3 | 3 | s | (f2 * 2) + gtest_child3 | f3 | 3 | s | (f2 * 2) + gtest_parent | f3 | 3 | s | (f2 * 2) +(4 rows) + SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; tableoid | f1 | f2 | f3 --------------+------------+----+---- @@ -1141,6 +1199,10 @@ SELECT * FROM gtest25 ORDER BY a; Indexes: "gtest25_pkey" PRIMARY KEY, btree (a) +--error: adding a column and setting its generation expression cannot be done in a single statement. +ALTER TABLE gtest25 ADD COLUMN d1 int GENERATED ALWAYS AS (a * 2) VIRTUAL, + ALTER COLUMN d1 SET EXPRESSION AS (a * 3) STORED; +ERROR: column "d1" of relation "gtest25" does not exist -- ALTER TABLE ... ALTER COLUMN CREATE TABLE gtest27 ( a int, @@ -1279,6 +1341,46 @@ ALTER TABLE gtest29 DROP COLUMN a; -- should not drop b --------+---------+-----------+----------+--------- b | integer | | | +-- ALTER COLUMN ... SET EXPRESSION VIRTUAL +-- ALTER COLUMN ... SET EXPRESSION STORED +DROP TABLE gtest29; +CREATE TABLE gtest29 (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest29 (a) VALUES (3), (4); +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; +EXECUTE get_generated_info('{gtest29}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +---------+---------+--------+--------------+------------- + gtest29 | b | 2 | v | (a * 3) +(1 row) + +SELECT * FROM gtest29; + a | b +---+---- + 3 | 9 + 4 | 12 +(2 rows) + +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 4); +EXECUTE get_generated_info('{gtest29}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +---------+---------+--------+--------------+------------- + gtest29 | b | 2 | v | (a * 4) +(1 row) + +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 5) STORED; +SELECT * FROM gtest29; + a | b +---+---- + 3 | 15 + 4 | 20 +(2 rows) + +EXECUTE get_generated_info('{gtest29}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +---------+---------+--------+--------------+------------- + gtest29 | b | 2 | s | (a * 5) +(1 row) + -- with inheritance CREATE TABLE gtest30 ( a int, @@ -1329,6 +1431,27 @@ Inherits: gtest30 ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error ERROR: cannot drop generation expression from inherited column +ALTER TABLE gtest30_1 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; -- error +ERROR: cannot use ALTER TABLE ... SET EXPRESSION STORED change generation expression from inherited column +ALTER TABLE gtest30_1 ALTER COLUMN b SET EXPRESSION AS (a * 2) VIRTUAL; -- error +ERROR: cannot use ALTER TABLE ... SET EXPRESSION VIRTUAL change generation expression from inherited column +ALTER TABLE gtest30 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; +EXECUTE get_generated_info('{gtest30, gtest30_1}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +-----------+---------+--------+--------------+------------- + gtest30 | b | 2 | s | (a * 2) + gtest30_1 | b | 2 | s | (a * 2) +(2 rows) + +ALTER TABLE gtest30 ALTER COLUMN b SET EXPRESSION AS (a * 2) VIRTUAL; +EXECUTE get_generated_info('{gtest30, gtest30_1}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +-----------+---------+--------+--------------+------------- + gtest30 | b | 2 | v | (a * 2) + gtest30_1 | b | 2 | v | (a * 2) +(2 rows) + +DEALLOCATE get_generated_info; -- 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); @@ -1338,15 +1461,30 @@ ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL); ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1'); ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1') STORED; +ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1') VIRTUAL; ALTER TABLE gtest31_2 DROP CONSTRAINT cc; CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2; ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2'); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2') STORED; ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2') VIRTUAL; DROP STATISTICS gtest31_2_stat; CREATE INDEX gtest31_2_y_idx ON gtest31_2(((y).b)); ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello3'); -ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type DROP TABLE gtest31_1, gtest31_2; +CREATE TABLE gtest31_1 (a int, b int GENERATED ALWAYS AS (a) STORED); +CREATE STATISTICS gtest31_1_stat ON (b is not null) FROM gtest31_1; +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS (a) VIRTUAL; --error +ERROR: cannot changed generated column (b) from STORED to VIRTUAL +DETAIL: Statistics creation on virtual generated columns is not supported. +DROP STATISTICS gtest31_1_stat; +CREATE INDEX ON gtest31_1(b); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS (a) VIRTUAL; --error +ERROR: cannot changed generated column (b) from STORED to VIRTUAL +DETAIL: Indexes on virtual generated columns are not supported. +DROP TABLE gtest31_1; -- Check it for a partitioned table, too CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text) PARTITION BY LIST (a); CREATE TABLE gtest31_2 (x int, y gtest31_1); diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index 249e68be654..68ffc938665 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -2,6 +2,12 @@ CREATE SCHEMA generated_virtual_tests; GRANT USAGE ON SCHEMA generated_virtual_tests TO PUBLIC; SET search_path = generated_virtual_tests; +PREPARE get_generated_info(regclass[], text[]) AS +SELECT attrelid::regclass as table, attname, attnum, attgenerated, pg_get_expr(pd.adbin, pa.attrelid) +FROM pg_attribute pa JOIN pg_attrdef pd +ON pd.adrelid = pa.attrelid +WHERE pa.attrelid = ANY($1) AND pa.attname =ANY($2) AND pa.attnum > 0 AND atthasdef +ORDER BY attrelid::regclass::text COLLATE "C", attnum; CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) VIRTUAL); CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_schema = 'generated_virtual_tests' ORDER BY 1, 2; @@ -645,6 +651,23 @@ DETAIL: Column "b" of relation "gtest20" is a virtual generated column. ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3); -- ok (currently not supported) ERROR: ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables with check constraints DETAIL: Column "b" of relation "gtest20" is a virtual generated column. +ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; -- ok (currently not supported) +ERROR: ALTER TABLE / SET EXPRESSION VIRTUAL is not supported for virtual generated columns in tables with check constraints +DETAIL: Column "b" of relation "gtest20" is a virtual generated column. +ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 5) STORED; --error, violates constraint +ERROR: check constraint "gtest20_b_check" of relation "gtest20" is violated by some row +ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3) STORED; --ok +\d gtest20 + Table "generated_virtual_tests.gtest20" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | not null | + b | integer | | | generated always as (a * 3) stored +Indexes: + "gtest20_pkey" PRIMARY KEY, btree (a) +Check constraints: + "gtest20_b_check" CHECK (b < 50) + CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); INSERT INTO gtest20a (a) VALUES (10); INSERT INTO gtest20a (a) VALUES (30); @@ -943,6 +966,17 @@ SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; -- alter only parent's and one child's generation expression ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); +-- ALTER COLUMN SET EXPRESSION VIRTUAL/STORED must apply to all inherited tables +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4) STORED; --error +ERROR: ALTER TABLE ... SET EXPRESSION STORED must be applied to child tables too +HINT: Do not specify the ONLY keyword. +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4) VIRTUAL; --error +ERROR: ALTER TABLE ... SET EXPRESSION VIRTUAL must be applied to child tables too +HINT: Do not specify the ONLY keyword. +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10) VIRTUAL; --error +ERROR: cannot use ALTER TABLE ... SET EXPRESSION VIRTUAL change generation expression from inherited column +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10) STORED; --error +ERROR: cannot use ALTER TABLE ... SET EXPRESSION STORED change generation expression from inherited column \d gtest_parent Partitioned table "generated_virtual_tests.gtest_parent" Column | Type | Collation | Nullable | Default @@ -1027,6 +1061,26 @@ Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016') f3 | bigint | | | generated always as (f2 * 2) Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016') +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 11) STORED; +EXECUTE get_generated_info('{gtest_parent, gtest_child, gtest_child2, gtest_child3}', '{f3}'); + table | attname | attnum | attgenerated | pg_get_expr +--------------+---------+--------+--------------+------------- + gtest_child | f3 | 3 | s | (f2 * 11) + gtest_child2 | f3 | 3 | s | (f2 * 11) + gtest_child3 | f3 | 3 | s | (f2 * 11) + gtest_parent | f3 | 3 | s | (f2 * 11) +(4 rows) + +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2) VIRTUAL; +EXECUTE get_generated_info('{gtest_parent, gtest_child, gtest_child2, gtest_child3}', '{f3}'); + table | attname | attnum | attgenerated | pg_get_expr +--------------+---------+--------+--------------+------------- + gtest_child | f3 | 3 | v | (f2 * 2) + gtest_child2 | f3 | 3 | v | (f2 * 2) + gtest_child3 | f3 | 3 | v | (f2 * 2) + gtest_parent | f3 | 3 | v | (f2 * 2) +(4 rows) + SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; tableoid | f1 | f2 | f3 --------------+------------+----+---- @@ -1103,6 +1157,10 @@ SELECT * FROM gtest25 ORDER BY a; Indexes: "gtest25_pkey" PRIMARY KEY, btree (a) +--error, can not add and set generation expression in one statement +ALTER TABLE gtest25 ADD COLUMN d1 int GENERATED ALWAYS AS (a * 2) VIRTUAL, + ALTER COLUMN d1 SET EXPRESSION AS (a * 3) STORED; +ERROR: column "d1" of relation "gtest25" does not exist -- ALTER TABLE ... ALTER COLUMN CREATE TABLE gtest27 ( a int, @@ -1244,6 +1302,45 @@ SELECT * FROM gtest29; a | integer | | | b | integer | | | generated always as (a * 3) +-- ALTER COLUMN ... SET EXPRESSION VIRTUAL +-- ALTER COLUMN ... SET EXPRESSION STORED +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; +EXECUTE get_generated_info('{gtest29}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +---------+---------+--------+--------------+------------- + gtest29 | b | 2 | s | (a * 2) +(1 row) + +SELECT * FROM gtest29; + a | b +---+---- + 3 | 6 + 4 | 8 + 5 | 10 +(3 rows) + +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 4); +EXECUTE get_generated_info('{gtest29}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +---------+---------+--------+--------------+------------- + gtest29 | b | 2 | s | (a * 4) +(1 row) + +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; +EXECUTE get_generated_info('{gtest29}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +---------+---------+--------+--------------+------------- + gtest29 | b | 2 | v | (a * 3) +(1 row) + +SELECT * FROM gtest29; + a | b +---+---- + 3 | 9 + 4 | 12 + 5 | 15 +(3 rows) + -- check that dependencies between columns have also been removed --ALTER TABLE gtest29 DROP COLUMN a; -- should not drop b --\d gtest29 @@ -1299,6 +1396,27 @@ Inherits: gtest30 ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error ERROR: cannot drop generation expression from inherited column +ALTER TABLE gtest30_1 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; -- error +ERROR: cannot use ALTER TABLE ... SET EXPRESSION STORED change generation expression from inherited column +ALTER TABLE gtest30_1 ALTER COLUMN b SET EXPRESSION AS (a * 2) VIRTUAL; -- error +ERROR: cannot use ALTER TABLE ... SET EXPRESSION VIRTUAL change generation expression from inherited column +ALTER TABLE gtest30 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; +EXECUTE get_generated_info('{gtest30, gtest30_1}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +-----------+---------+--------+--------------+------------- + gtest30 | b | 2 | s | (a * 2) + gtest30_1 | b | 2 | s | (a * 2) +(2 rows) + +ALTER TABLE gtest30 ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; +EXECUTE get_generated_info('{gtest30, gtest30_1}', '{b}'); + table | attname | attnum | attgenerated | pg_get_expr +-----------+---------+--------+--------------+------------- + gtest30 | b | 2 | v | (a * 3) + gtest30_1 | b | 2 | v | (a * 3) +(2 rows) + +DEALLOCATE get_generated_info; -- 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); @@ -1307,6 +1425,9 @@ ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row -- bug #18970 ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL); ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1'); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1') VIRTUAL; +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1') STORED; +ERROR: cannot alter table "gtest31_1" because column "gtest31_2.y" uses its row type ALTER TABLE gtest31_2 DROP CONSTRAINT cc; CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2; ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2'); diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out index 7fb49aaf29b..2e7e2bc7af2 100644 --- a/src/test/regress/expected/publication.out +++ b/src/test/regress/expected/publication.out @@ -876,6 +876,10 @@ DETAIL: Column list used by the publication does not cover the replica identity ALTER PUBLICATION testpub_fortable DROP TABLE testpub_tbl5; -- ok: stored generated column "d" can be in the list too ALTER PUBLICATION testpub_fortable ADD TABLE testpub_tbl5 (a, d); +--error +ALTER TABLE testpub_tbl5 ALTER COLUMN d SET EXPRESSION AS (a + length(b)) VIRTUAL; +ERROR: cannot changed generated column (d) from STORED to VIRTUAL +DETAIL: Publication on virtual generated columns are not supported. ALTER PUBLICATION testpub_fortable DROP TABLE testpub_tbl5; -- error: virtual generated column "e" can't be in list ALTER PUBLICATION testpub_fortable ADD TABLE testpub_tbl5 (a, e); diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql index 068dd0bc8aa..6e215550434 100644 --- a/src/test/regress/sql/fast_default.sql +++ b/src/test/regress/sql/fast_default.sql @@ -77,6 +77,18 @@ 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; +-- Changing a generated column from virtual to stored does not require a table +-- rewrite. +ALTER TABLE has_volatile ADD col8 int GENERATED ALWAYS AS (55) VIRTUAL; +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55) STORED; +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55) STORED; +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55); + +-- Changing a generated column from stored to virtual does not require a table +-- rewrite. +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55) VIRTUAL; +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55) VIRTUAL; +ALTER TABLE has_volatile ALTER COLUMN col8 SET EXPRESSION AS (55); -- Test a large sample of different datatypes diff --git a/src/test/regress/sql/generated_stored.sql b/src/test/regress/sql/generated_stored.sql index 2001a47bcc6..dfbea1a66b7 100644 --- a/src/test/regress/sql/generated_stored.sql +++ b/src/test/regress/sql/generated_stored.sql @@ -5,6 +5,13 @@ CREATE SCHEMA generated_stored_tests; GRANT USAGE ON SCHEMA generated_stored_tests TO PUBLIC; SET search_path = generated_stored_tests; +PREPARE get_generated_info(regclass[], text[]) AS +SELECT attrelid::regclass as table, attname, attnum, attgenerated, pg_get_expr(pd.adbin, pa.attrelid) +FROM pg_attribute pa JOIN pg_attrdef pd +ON pd.adrelid = pa.attrelid +WHERE pa.attrelid = ANY($1) AND pa.attname =ANY($2) AND pa.attnum > 0 AND atthasdef +ORDER BY attrelid::regclass::text COLLATE "C", attnum; + CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED); CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); @@ -251,6 +258,15 @@ INSERT INTO gtest_varlena (a) VALUES(NULL); SELECT * FROM gtest_varlena ORDER BY a; DROP TABLE gtest_varlena; +-- varlena types change from stored to virtual +CREATE TABLE gtest_varlena ( + a int, + b text STORAGE EXTERNAL GENERATED ALWAYS AS (repeat('g', a)) STORED +) WITH (autovacuum_enabled = OFF); +INSERT INTO gtest_varlena VALUES(2001), (1); +ALTER TABLE gtest_varlena ALTER COLUMN b SET EXPRESSION AS (repeat('g', a)) VIRTUAL; +VACUUM (FULL) gtest_varlena; --should ok + -- composite types CREATE TYPE double_int as (a int, b int); CREATE TABLE gtest4 ( @@ -259,6 +275,7 @@ CREATE TABLE gtest4 ( ); INSERT INTO gtest4 VALUES (1), (6); SELECT * FROM gtest4; +ALTER TABLE gtest4 ALTER COLUMN b SET EXPRESSION AS ((a * 2, a * 3)) VIRTUAL; --error DROP TABLE gtest4; DROP TYPE double_int; @@ -326,6 +343,8 @@ ALTER TABLE gtest20a ADD CHECK (b < 50); -- fails on existing row -- table rewrite cases ALTER TABLE gtest20a ADD COLUMN c float8 DEFAULT random() CHECK (b < 50); -- fails on existing row ALTER TABLE gtest20a ADD COLUMN c float8 DEFAULT random() CHECK (b < 61); -- ok +ALTER TABLE gtest20a ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; -- violates constraint +ALTER TABLE gtest20a ALTER COLUMN b SET EXPRESSION AS (a * 3) STORED; -- violates constraint CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); INSERT INTO gtest20b (a) VALUES (10); @@ -403,6 +422,8 @@ INSERT INTO gtest23b VALUES (5); -- error ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 5); -- error ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 1); -- ok +--virutal generated column does not support foreign key +ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 11) VIRTUAL; --error DROP TABLE gtest23b; DROP TABLE gtest23a; @@ -428,6 +449,9 @@ CREATE TABLE gtest24nn (a int, b gtestdomainnn GENERATED ALWAYS AS (a * 2) STORE INSERT INTO gtest24nn (a) VALUES (4); -- ok INSERT INTO gtest24nn (a) VALUES (NULL); -- error +--error, virutal generated column does not support domains +ALTER TABLE gtest24nn ALTER COLUMN b SET EXPRESSION AS (a * 2) VIRTUAL; + -- typed tables (currently not supported) CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); @@ -486,7 +510,11 @@ SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; -- alter only parent's and one child's generation expression ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4) STORED; --error +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4) VIRTUAL; --error ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10) VIRTUAL; --error +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10) STORED; --error \d gtest_parent \d gtest_child \d gtest_child2 @@ -499,6 +527,10 @@ ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2); \d gtest_child \d gtest_child2 \d gtest_child3 +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 11) VIRTUAL; +EXECUTE get_generated_info('{gtest_parent, gtest_child, gtest_child2, gtest_child3}', '{f3}'); +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2) STORED; +EXECUTE get_generated_info('{gtest_parent, gtest_child, gtest_child2, gtest_child3}', '{f3}'); SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; -- we leave these tables around for purposes of testing dump/reload/upgrade @@ -523,6 +555,9 @@ ALTER TABLE gtest25 ALTER COLUMN d SET DATA TYPE float8, ADD COLUMN y float8 GENERATED ALWAYS AS (d * 4) STORED; SELECT * FROM gtest25 ORDER BY a; \d gtest25 +--error: adding a column and setting its generation expression cannot be done in a single statement. +ALTER TABLE gtest25 ADD COLUMN d1 int GENERATED ALWAYS AS (a * 2) VIRTUAL, + ALTER COLUMN d1 SET EXPRESSION AS (a * 3) STORED; -- ALTER TABLE ... ALTER COLUMN CREATE TABLE gtest27 ( @@ -578,6 +613,20 @@ SELECT * FROM gtest29; ALTER TABLE gtest29 DROP COLUMN a; -- should not drop b \d gtest29 +-- ALTER COLUMN ... SET EXPRESSION VIRTUAL +-- ALTER COLUMN ... SET EXPRESSION STORED +DROP TABLE gtest29; +CREATE TABLE gtest29 (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +INSERT INTO gtest29 (a) VALUES (3), (4); +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; +EXECUTE get_generated_info('{gtest29}', '{b}'); +SELECT * FROM gtest29; +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 4); +EXECUTE get_generated_info('{gtest29}', '{b}'); +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 5) STORED; +SELECT * FROM gtest29; +EXECUTE get_generated_info('{gtest29}', '{b}'); + -- with inheritance CREATE TABLE gtest30 ( a int, @@ -597,6 +646,13 @@ 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 +ALTER TABLE gtest30_1 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; -- error +ALTER TABLE gtest30_1 ALTER COLUMN b SET EXPRESSION AS (a * 2) VIRTUAL; -- error +ALTER TABLE gtest30 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; +EXECUTE get_generated_info('{gtest30, gtest30_1}', '{b}'); +ALTER TABLE gtest30 ALTER COLUMN b SET EXPRESSION AS (a * 2) VIRTUAL; +EXECUTE get_generated_info('{gtest30, gtest30_1}', '{b}'); +DEALLOCATE get_generated_info; -- composite type dependencies CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text); @@ -606,10 +662,14 @@ ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails -- bug #18970: these cases are unsupported, but make sure they fail cleanly ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL); ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1'); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1') STORED; +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1') VIRTUAL; ALTER TABLE gtest31_2 DROP CONSTRAINT cc; CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2; ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2'); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2') STORED; +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello2') VIRTUAL; DROP STATISTICS gtest31_2_stat; CREATE INDEX gtest31_2_y_idx ON gtest31_2(((y).b)); @@ -617,6 +677,14 @@ ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello3'); DROP TABLE gtest31_1, gtest31_2; +CREATE TABLE gtest31_1 (a int, b int GENERATED ALWAYS AS (a) STORED); +CREATE STATISTICS gtest31_1_stat ON (b is not null) FROM gtest31_1; +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS (a) VIRTUAL; --error +DROP STATISTICS gtest31_1_stat; +CREATE INDEX ON gtest31_1(b); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS (a) VIRTUAL; --error +DROP TABLE gtest31_1; + -- Check it for a partitioned table, too CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') STORED, c text) PARTITION BY LIST (a); CREATE TABLE gtest31_2 (x int, y gtest31_1); diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index 81152b39a79..1782558fd7f 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -5,6 +5,13 @@ CREATE SCHEMA generated_virtual_tests; GRANT USAGE ON SCHEMA generated_virtual_tests TO PUBLIC; SET search_path = generated_virtual_tests; +PREPARE get_generated_info(regclass[], text[]) AS +SELECT attrelid::regclass as table, attname, attnum, attgenerated, pg_get_expr(pd.adbin, pa.attrelid) +FROM pg_attribute pa JOIN pg_attrdef pd +ON pd.adrelid = pa.attrelid +WHERE pa.attrelid = ANY($1) AND pa.attname =ANY($2) AND pa.attnum > 0 AND atthasdef +ORDER BY attrelid::regclass::text COLLATE "C", attnum; + CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) VIRTUAL); CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); @@ -319,6 +326,10 @@ INSERT INTO gtest20 (a) VALUES (30); -- violates constraint ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 100); -- violates constraint (currently not supported) ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3); -- ok (currently not supported) +ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; -- ok (currently not supported) +ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 5) STORED; --error, violates constraint +ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3) STORED; --ok +\d gtest20 CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); INSERT INTO gtest20a (a) VALUES (10); @@ -530,6 +541,11 @@ SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; -- alter only parent's and one child's generation expression ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); +-- ALTER COLUMN SET EXPRESSION VIRTUAL/STORED must apply to all inherited tables +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4) STORED; --error +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4) VIRTUAL; --error +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10) VIRTUAL; --error +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10) STORED; --error \d gtest_parent \d gtest_child \d gtest_child2 @@ -542,6 +558,10 @@ ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2); \d gtest_child \d gtest_child2 \d gtest_child3 +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 11) STORED; +EXECUTE get_generated_info('{gtest_parent, gtest_child, gtest_child2, gtest_child3}', '{f3}'); +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2) VIRTUAL; +EXECUTE get_generated_info('{gtest_parent, gtest_child, gtest_child2, gtest_child3}', '{f3}'); SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; -- we leave these tables around for purposes of testing dump/reload/upgrade @@ -566,6 +586,9 @@ ALTER TABLE gtest25 ALTER COLUMN d SET DATA TYPE float8, ADD COLUMN y float8 GENERATED ALWAYS AS (d * 4) VIRTUAL; SELECT * FROM gtest25 ORDER BY a; \d gtest25 +--error, can not add and set generation expression in one statement +ALTER TABLE gtest25 ADD COLUMN d1 int GENERATED ALWAYS AS (a * 2) VIRTUAL, + ALTER COLUMN d1 SET EXPRESSION AS (a * 3) STORED; -- ALTER TABLE ... ALTER COLUMN CREATE TABLE gtest27 ( @@ -625,6 +648,17 @@ INSERT INTO gtest29 (a, b) VALUES (6, 66); SELECT * FROM gtest29; \d gtest29 +-- ALTER COLUMN ... SET EXPRESSION VIRTUAL +-- ALTER COLUMN ... SET EXPRESSION STORED +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; +EXECUTE get_generated_info('{gtest29}', '{b}'); +SELECT * FROM gtest29; +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 4); +EXECUTE get_generated_info('{gtest29}', '{b}'); +ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; +EXECUTE get_generated_info('{gtest29}', '{b}'); +SELECT * FROM gtest29; + -- check that dependencies between columns have also been removed --ALTER TABLE gtest29 DROP COLUMN a; -- should not drop b --\d gtest29 @@ -648,6 +682,13 @@ 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 +ALTER TABLE gtest30_1 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; -- error +ALTER TABLE gtest30_1 ALTER COLUMN b SET EXPRESSION AS (a * 2) VIRTUAL; -- error +ALTER TABLE gtest30 ALTER COLUMN b SET EXPRESSION AS (a * 2) STORED; +EXECUTE get_generated_info('{gtest30, gtest30_1}', '{b}'); +ALTER TABLE gtest30 ALTER COLUMN b SET EXPRESSION AS (a * 3) VIRTUAL; +EXECUTE get_generated_info('{gtest30, gtest30_1}', '{b}'); +DEALLOCATE get_generated_info; -- composite type dependencies CREATE TABLE gtest31_1 (a int, b text GENERATED ALWAYS AS ('hello') VIRTUAL, c text); @@ -657,6 +698,8 @@ ALTER TABLE gtest31_1 ALTER COLUMN b TYPE varchar; -- fails -- bug #18970 ALTER TABLE gtest31_2 ADD CONSTRAINT cc CHECK ((y).b IS NOT NULL); ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1'); +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1') VIRTUAL; +ALTER TABLE gtest31_1 ALTER COLUMN b SET EXPRESSION AS ('hello1') STORED; ALTER TABLE gtest31_2 DROP CONSTRAINT cc; CREATE STATISTICS gtest31_2_stat ON ((y).b is not null) FROM gtest31_2; diff --git a/src/test/regress/sql/publication.sql b/src/test/regress/sql/publication.sql index 85b00bd67c8..0ef62d4ad66 100644 --- a/src/test/regress/sql/publication.sql +++ b/src/test/regress/sql/publication.sql @@ -547,6 +547,8 @@ ALTER PUBLICATION testpub_fortable DROP TABLE testpub_tbl5; -- ok: stored generated column "d" can be in the list too ALTER PUBLICATION testpub_fortable ADD TABLE testpub_tbl5 (a, d); +--error +ALTER TABLE testpub_tbl5 ALTER COLUMN d SET EXPRESSION AS (a + length(b)) VIRTUAL; ALTER PUBLICATION testpub_fortable DROP TABLE testpub_tbl5; -- error: virtual generated column "e" can't be in list ALTER PUBLICATION testpub_fortable ADD TABLE testpub_tbl5 (a, e); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index b9e671fcda8..16348426f5e 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -1049,6 +1049,7 @@ Gene GeneratePruningStepsContext GenerationBlock GenerationContext +GenerationExpr GenerationPointer GenericCosts GenericXLogPageData -- 2.34.1