From 046485f55baddf964721de4a80636bad8c79d9cf Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Tue, 13 Oct 2026 23:22:52 -0400 Subject: [PATCH v1 REL_18] Require partitions to use the parent's virtual generation expression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A query on a partitioned table computes a virtual generated column with the parent's expression, but constraints on a partition are checked with the partition's own. So a NOT NULL column could read as NULL through the parent. Reject a partition whose expression differs. Suggested-by: Álvaro Herrera Discussion: https://postgr.es/m/E1xFKs5-0000000116b-2Cge@gemulon.postgresql.org --- doc/src/sgml/ddl.sgml | 4 +- doc/src/sgml/ref/alter_table.sgml | 3 + src/backend/commands/tablecmds.c | 113 +++++++++++++++++- .../regress/expected/generated_virtual.out | 109 ++++++++--------- src/test/regress/sql/generated_virtual.sql | 37 ++++-- 5 files changed, 197 insertions(+), 69 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index ddd7096f6b2..5c892706494 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -452,7 +452,9 @@ CREATE TABLE people ( If a parent column is a generated column, its child column must also be a generated column of the same kind (stored or virtual); however, - the child column can have a different generation expression. + the child column can have a different generation expression, except + that a virtual generated column of a partition must have the same + generation expression as the partitioned table. diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 31f2d91418b..d6a009a54a3 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -272,6 +272,9 @@ WITH ( MODULUS numeric_literal, REM 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. + The expression of a virtual generated column cannot be changed for a + partition alone, or with ONLY for a partitioned table + that has partitions. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 5e8e247d9fe..7143655042f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -378,6 +378,7 @@ static List *MergeCheckConstraint(List *constraints, const char *name, Node *exp static void MergeChildAttribute(List *inh_columns, int exist_attno, int newcol_attno, const ColumnDef *newdef); static ColumnDef *MergeInheritedAttribute(List *inh_columns, int exist_attno, const ColumnDef *newdef); static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition); +static void CheckPartitionVirtualGeneratedExprs(Relation partrel, Relation parentrel); static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); static void StoreCatalogInheritance(Oid relationId, List *supers, bool child_is_partition); @@ -522,6 +523,7 @@ static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool bool recurse, bool recursing); static ObjectAddress ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName, Node *newExpr, LOCKMODE lockmode); +static void ATPrepSetExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing); 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 ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, @@ -1142,6 +1144,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, errmsg("\"%s\" is not partitioned", RelationGetRelationName(parent)))); + CheckPartitionVirtualGeneratedExprs(rel, parent); + /* * The partition constraint of the default partition depends on the * partition bounds of every other partition. It is possible that @@ -3059,7 +3063,8 @@ MergeAttributes(List *columns, const List *supers, char relpersistence, * * Same rules as above: generated-ness has to match the * parent, but the contents of the generation expression - * can be different. + * can be different, except for virtual generated columns + * (see CheckPartitionVirtualGeneratedExprs). */ if (coldef->generated) { @@ -5036,6 +5041,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_SetExpression: /* ALTER COLUMN SET EXPRESSION */ ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE); + ATPrepSetExpression(rel, cmd, recurse, recursing); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); pass = AT_PASS_SET_EXPRESSION; break; @@ -8774,6 +8780,45 @@ ATExecSetExpression(AlteredTableInfo *tab, Relation rel, const char *colName, return address; } +/* + * ALTER TABLE ALTER COLUMN SET EXPRESSION + * + * The expression of a virtual generated column can only be changed for a + * whole partition tree. See CheckPartitionVirtualGeneratedExprs. + */ +static void +ATPrepSetExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing) +{ + HeapTuple tuple; + char attgenerated; + + if (recursing) + return; + + tuple = SearchSysCacheAttName(RelationGetRelid(rel), cmd->name); + if (!HeapTupleIsValid(tuple)) + return; /* error will be reported later */ + attgenerated = ((Form_pg_attribute) GETSTRUCT(tuple))->attgenerated; + ReleaseSysCache(tuple); + + if (attgenerated != ATTRIBUTE_GENERATED_VIRTUAL) + return; + + if (rel->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter generation expression of virtual generated column \"%s\" of partition \"%s\"", + cmd->name, RelationGetRelationName(rel)), + errhint("Alter the generation expression of the partitioned table instead."))); + + if (!recurse && + rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE && + find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ALTER TABLE / SET EXPRESSION must be applied to child tables too"))); +} + /* * ALTER TABLE ALTER COLUMN DROP EXPRESSION */ @@ -17551,7 +17596,9 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc) * partitioned tables it's a foot-gun. * * The data type must match exactly. If the parent column is NOT NULL then - * the child must be as well. Defaults are not compared, however. + * the child must be as well. Defaults are not compared, however, and neither + * are generation expressions, except for virtual generated columns of a + * partition. */ static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispartition) @@ -17672,6 +17719,68 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispart } table_close(attrrel, RowExclusiveLock); + + if (ispartition) + CheckPartitionVirtualGeneratedExprs(child_rel, parent_rel); +} + +/* + * CheckPartitionVirtualGeneratedExprs + * Check that each virtual generated column of a partition has the same + * generation expression as in the partitioned table. + * + * Reading through the partitioned table computes the parent's expression, + * while constraints on the partition are checked with its own, so they must + * be the same. + */ +static void +CheckPartitionVirtualGeneratedExprs(Relation partrel, Relation parentrel) +{ + TupleDesc parent_desc = RelationGetDescr(parentrel); + TupleDesc part_desc = RelationGetDescr(partrel); + AttrMap *attmap; + + if (parent_desc->constr == NULL || + !parent_desc->constr->has_generated_virtual) + return; + + attmap = build_attrmap_by_name(part_desc, parent_desc, false); + + for (AttrNumber parent_attno = 1; parent_attno <= parent_desc->natts; parent_attno++) + { + Form_pg_attribute parent_att = TupleDescAttr(parent_desc, parent_attno - 1); + AttrNumber part_attno; + Node *parent_expr; + Node *part_expr; + bool found_whole_row; + + if (parent_att->attisdropped || + parent_att->attgenerated != ATTRIBUTE_GENERATED_VIRTUAL) + continue; + + part_attno = attmap->attnums[parent_attno - 1]; + Assert(part_attno > 0); + + parent_expr = TupleDescGetDefault(parent_desc, parent_attno); + part_expr = TupleDescGetDefault(part_desc, part_attno); + if (parent_expr == NULL || part_expr == NULL) + elog(ERROR, "missing generation expression for column \"%s\"", + NameStr(parent_att->attname)); + + parent_expr = map_variable_attnos(parent_expr, 1, 0, attmap, + InvalidOid, &found_whole_row); + /* generation expressions cannot contain whole-row Vars */ + Assert(!found_whole_row); + + if (!equal(parent_expr, part_expr)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), + errmsg("virtual generated column \"%s\" of partition \"%s\" must have the same generation expression as the partitioned table", + NameStr(parent_att->attname), + RelationGetRelationName(partrel)))); + } + + free_attrmap(attmap); } /* diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index 03698488907..6108fce2ae4 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -882,7 +882,11 @@ CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWA CREATE TABLE gtest_child PARTITION OF gtest_parent FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- inherits gen expr CREATE TABLE gtest_child2 PARTITION OF gtest_parent ( - f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 22) VIRTUAL -- overrides gen expr + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 22) VIRTUAL -- error, must match parent +) FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +ERROR: virtual generated column "f3" of partition "gtest_child2" must have the same generation expression as the partitioned table +CREATE TABLE gtest_child2 PARTITION OF gtest_parent ( + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) VIRTUAL -- same as parent, ok ) FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); CREATE TABLE gtest_child3 PARTITION OF gtest_parent ( f3 DEFAULT 42 -- error @@ -916,6 +920,11 @@ ERROR: column "f3" inherits from generated column of different kind DETAIL: Parent column is VIRTUAL, child column is STORED. DROP TABLE gtest_child3; CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 33) VIRTUAL); +ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -- error +ERROR: virtual generated column "f3" of partition "gtest_child3" must have the same generation expression as the partitioned table +DROP TABLE gtest_child3; +-- different column order, so the parent's expression has to be mapped +CREATE TABLE gtest_child3 (f3 bigint GENERATED ALWAYS AS (f2 * 2) VIRTUAL, f2 bigint, f1 date NOT NULL); ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); \d gtest_child Table "generated_virtual_tests.gtest_child" @@ -927,21 +936,21 @@ ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09 Partition of: gtest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016') \d gtest_child2 - Table "generated_virtual_tests.gtest_child2" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------- + Table "generated_virtual_tests.gtest_child2" + Column | Type | Collation | Nullable | Default +--------+--------+-----------+----------+------------------------------ f1 | date | | not null | f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 22) + f3 | bigint | | | generated always as (f2 * 2) Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016') \d gtest_child3 - Table "generated_virtual_tests.gtest_child3" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------- - f1 | date | | not null | + Table "generated_virtual_tests.gtest_child3" + Column | Type | Collation | Nullable | Default +--------+--------+-----------+----------+------------------------------ + f3 | bigint | | | generated always as (f2 * 2) f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 33) + f1 | date | | not null | 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); @@ -962,14 +971,14 @@ SELECT tableoid::regclass, * FROM gtest_child ORDER BY 1, 2, 3; gtest_child | 07-15-2016 | 2 | 4 (2 rows) -SELECT tableoid::regclass, * FROM gtest_child2 ORDER BY 1, 2, 3; -- uses child's generation expression, not parent's +SELECT tableoid::regclass, * FROM gtest_child2 ORDER BY 1, 2, 3; tableoid | f1 | f2 | f3 --------------+------------+----+---- - gtest_child2 | 08-15-2016 | 3 | 66 + gtest_child2 | 08-15-2016 | 3 | 6 (1 row) SELECT tableoid::regclass, * FROM gtest_child3 ORDER BY 1, 2, 3; - tableoid | f1 | f2 | f3 + tableoid | f3 | f2 | f1 ----------+----+----+---- (0 rows) @@ -982,46 +991,14 @@ SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; gtest_child3 | 09-13-2016 | 1 | 2 (3 rows) --- 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); -\d gtest_parent - Partitioned table "generated_virtual_tests.gtest_parent" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------ - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 4) -Partition key: RANGE (f1) -Number of partitions: 3 (Use \d+ to list them.) - -\d gtest_child - Table "generated_virtual_tests.gtest_child" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 10) -Partition of: gtest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016') - -\d gtest_child2 - Table "generated_virtual_tests.gtest_child2" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 22) -Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016') - -\d gtest_child3 - Table "generated_virtual_tests.gtest_child3" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 33) -Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016') - +-- cannot alter only parent's or one partition's generation expression +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); -- error +ERROR: ALTER TABLE / SET EXPRESSION must be applied to child tables too +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); -- error +ERROR: cannot alter generation expression of virtual generated column "f3" of partition "gtest_child" +HINT: Alter the generation expression of the partitioned table instead. +-- alter generation expression of parent and all its children altogether +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; tableoid | f1 | f2 | f3 --------------+------------+----+---- @@ -1030,7 +1007,10 @@ SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; gtest_child3 | 09-13-2016 | 1 | 4 (3 rows) --- alter generation expression of parent and all its children altogether +ALTER TABLE gtest_parent ADD CONSTRAINT cc1 CHECK (f3 < 12); -- error +ERROR: check constraint "cc1" of relation "gtest_child2" is violated by some row +ALTER TABLE gtest_parent ADD CONSTRAINT cc CHECK (f3 < 13); -- ok +ALTER TABLE gtest_parent DROP CONSTRAINT cc; ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2); \d gtest_parent Partitioned table "generated_virtual_tests.gtest_parent" @@ -1064,9 +1044,9 @@ Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016') Table "generated_virtual_tests.gtest_child3" Column | Type | Collation | Nullable | Default --------+--------+-----------+----------+------------------------------ - f1 | date | | not null | - f2 | bigint | | | f3 | bigint | | | generated always as (f2 * 2) + f2 | bigint | | | + f1 | date | | not null | Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016') SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; @@ -1812,3 +1792,20 @@ select * from gtest_cursor order by id; commit; drop table gtest_cursor; +-- a partition cannot have a different generation expression, which would let +-- the partitioned table's NOT NULL virtual column read as NULL +CREATE TABLE gtestnn_parent2 (a int NOT NULL, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL NOT NULL) PARTITION BY LIST (a); +CREATE TABLE gtestnn_child2 PARTITION OF gtestnn_parent2 (b GENERATED ALWAYS AS (a + 1) VIRTUAL) FOR VALUES IN (0); -- error +ERROR: virtual generated column "b" of partition "gtestnn_child2" must have the same generation expression as the partitioned table +CREATE TABLE gtestnn_child2 PARTITION OF gtestnn_parent2 FOR VALUES IN (0, 1); +INSERT INTO gtestnn_parent2 VALUES (0); -- error +ERROR: null value in column "b" of relation "gtestnn_child2" violates not-null constraint +DETAIL: Failing row contains (0, virtual). +INSERT INTO gtestnn_parent2 VALUES (1); +SELECT a, b FROM gtestnn_parent2; + a | b +---+--- + 1 | 1 +(1 row) + +DROP TABLE gtestnn_parent2; diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index b1b92b2ced5..d1bbf9352d7 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -514,7 +514,10 @@ CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWA CREATE TABLE gtest_child PARTITION OF gtest_parent FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- inherits gen expr CREATE TABLE gtest_child2 PARTITION OF gtest_parent ( - f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 22) VIRTUAL -- overrides gen expr + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 22) VIRTUAL -- error, must match parent +) FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +CREATE TABLE gtest_child2 PARTITION OF gtest_parent ( + f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) VIRTUAL -- same as parent, ok ) FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); CREATE TABLE gtest_child3 PARTITION OF gtest_parent ( f3 DEFAULT 42 -- error @@ -538,6 +541,10 @@ CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWA ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -- error DROP TABLE gtest_child3; CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 33) VIRTUAL); +ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -- error +DROP TABLE gtest_child3; +-- different column order, so the parent's expression has to be mapped +CREATE TABLE gtest_child3 (f3 bigint GENERATED ALWAYS AS (f2 * 2) VIRTUAL, f2 bigint, f1 date NOT NULL); ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); \d gtest_child \d gtest_child2 @@ -547,21 +554,21 @@ 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, 2, 3; SELECT tableoid::regclass, * FROM gtest_child ORDER BY 1, 2, 3; -SELECT tableoid::regclass, * FROM gtest_child2 ORDER BY 1, 2, 3; -- uses child's generation expression, not parent's +SELECT tableoid::regclass, * FROM gtest_child2 ORDER BY 1, 2, 3; SELECT tableoid::regclass, * FROM gtest_child3 ORDER BY 1, 2, 3; UPDATE gtest_parent SET f1 = f1 + 60 WHERE f2 = 1; 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); -\d gtest_parent -\d gtest_child -\d gtest_child2 -\d gtest_child3 -SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; +-- cannot alter only parent's or one partition's generation expression +ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); -- error +ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); -- error -- alter generation expression of parent and all its children altogether +ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); +SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; +ALTER TABLE gtest_parent ADD CONSTRAINT cc1 CHECK (f3 < 12); -- error +ALTER TABLE gtest_parent ADD CONSTRAINT cc CHECK (f3 < 13); -- ok +ALTER TABLE gtest_parent DROP CONSTRAINT cc; ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2); \d gtest_parent \d gtest_child @@ -969,3 +976,13 @@ select * from gtest_cursor order by id; commit; drop table gtest_cursor; + +-- a partition cannot have a different generation expression, which would let +-- the partitioned table's NOT NULL virtual column read as NULL +CREATE TABLE gtestnn_parent2 (a int NOT NULL, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL NOT NULL) PARTITION BY LIST (a); +CREATE TABLE gtestnn_child2 PARTITION OF gtestnn_parent2 (b GENERATED ALWAYS AS (a + 1) VIRTUAL) FOR VALUES IN (0); -- error +CREATE TABLE gtestnn_child2 PARTITION OF gtestnn_parent2 FOR VALUES IN (0, 1); +INSERT INTO gtestnn_parent2 VALUES (0); -- error +INSERT INTO gtestnn_parent2 VALUES (1); +SELECT a, b FROM gtestnn_parent2; +DROP TABLE gtestnn_parent2; -- 2.37.1 (Apple Git-137.1)