diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 4b219435d4..780713831c 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -330,9 +330,9 @@ CREATE TABLE people ( If a parent column is a generated column, a child column must also be - a generated column using the same expression. In the definition of - the child column, leave off the GENERATED clause, - as it will be copied from the parent. + a generated column, with or without the same expression. In the + definition of the child column, leave off the GENERATED + clause, as it will be copied from the parent. @@ -344,8 +344,8 @@ CREATE TABLE people ( - If a parent column is not a generated column, a child column may be - defined to be a generated column or not. + If a parent column is not a generated column, a child column must + not be generated either. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1db3bd9e2e..6ef23d138d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2931,6 +2931,11 @@ MergeAttributes(List *schema, List *supers, char relpersistence, * also check that the child column doesn't specify a default * value or identity, which matches the rules for a single * column in parse_util.c. + * + * Conversely, if the parent column is not generated, the + * child column can't be either. (We used to allow that, but + * it results in being able to override the generation + * expression via UPDATEs through the parent.) */ if (def->generated) { @@ -2951,15 +2956,14 @@ MergeAttributes(List *schema, List *supers, char relpersistence, errmsg("column \"%s\" inherits from generated column but specifies identity", def->colname))); } - - /* - * If the parent column is not generated, then take whatever - * the child column definition says. - */ else { if (newdef->generated) - def->generated = newdef->generated; + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), + errmsg("child column \"%s\" specifies generation expression", + def->colname), + errhint("A child table column cannot be generated unless its parent column is."))); } /* If new def has a default, override previous default */ @@ -3009,11 +3013,22 @@ MergeAttributes(List *schema, List *supers, char relpersistence, { ColumnDef *coldef = lfirst(l); + /* + * Like above, prevent generated columns in partitions that + * are not present in the parent. + */ if (strcmp(coldef->colname, restdef->colname) == 0) { found = true; coldef->is_not_null |= restdef->is_not_null; + if (restdef->generated && !coldef->generated) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), + errmsg("child column \"%s\" specifies generation expression", + restdef->colname), + errhint("A child table column cannot be generated unless its parent column is."))); + /* * Override the parent's default value for this column * (coldef->cooked_default) with the partition's local @@ -15038,64 +15053,18 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) attributeName))); /* - * If parent column is generated, child column must be, too. + * Child column must be generated if and only if parent column is. */ if (attribute->attgenerated && !childatt->attgenerated) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("column \"%s\" in child table must be a generated column", attributeName))); - - /* - * Check that both generation expressions match. - * - * The test we apply is to see whether they reverse-compile to the - * same source string. This insulates us from issues like whether - * attributes have the same physical column numbers in parent and - * child relations. (See also constraints_equivalent().) - */ - if (attribute->attgenerated && childatt->attgenerated) - { - TupleConstr *child_constr = child_rel->rd_att->constr; - TupleConstr *parent_constr = parent_rel->rd_att->constr; - char *child_expr = NULL; - char *parent_expr = NULL; - - Assert(child_constr != NULL); - Assert(parent_constr != NULL); - - for (int i = 0; i < child_constr->num_defval; i++) - { - if (child_constr->defval[i].adnum == childatt->attnum) - { - child_expr = - TextDatumGetCString(DirectFunctionCall2(pg_get_expr, - CStringGetTextDatum(child_constr->defval[i].adbin), - ObjectIdGetDatum(child_rel->rd_id))); - break; - } - } - Assert(child_expr != NULL); - - for (int i = 0; i < parent_constr->num_defval; i++) - { - if (parent_constr->defval[i].adnum == attribute->attnum) - { - parent_expr = - TextDatumGetCString(DirectFunctionCall2(pg_get_expr, - CStringGetTextDatum(parent_constr->defval[i].adbin), - ObjectIdGetDatum(parent_rel->rd_id))); - break; - } - } - Assert(parent_expr != NULL); - - if (strcmp(child_expr, parent_expr) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("column \"%s\" in child table has a conflicting generation expression", - attributeName))); - } + if (childatt->attgenerated && !attribute->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("column \"%s\" in child table must not be a generated column", + attributeName))); /* * OK, bump the child column's inheritance count. (If we fail diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index bffa9f8dd0..f9218f48aa 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -740,11 +740,6 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("generated columns are not supported on typed tables"))); - if (cxt->partbound) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("generated columns are not supported on partitions"))); - if (saw_generated) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out index 1db5f9ed47..585f3d482c 100644 --- a/src/test/regress/expected/generated.out +++ b/src/test/regress/expected/generated.out @@ -268,38 +268,17 @@ SELECT * FROM gtest1; 4 | 8 (2 rows) +-- can't have generated column that is a child of normal column CREATE TABLE gtest_normal (a int, b int); -CREATE TABLE gtest_normal_child (a int, b int GENERATED ALWAYS AS (a * 2) STORED) INHERITS (gtest_normal); +CREATE TABLE gtest_normal_child (a int, b int GENERATED ALWAYS AS (a * 2) STORED) INHERITS (gtest_normal); -- error NOTICE: merging column "a" with inherited definition NOTICE: merging column "b" with inherited definition -\d gtest_normal_child - Table "public.gtest_normal_child" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | | - b | integer | | | generated always as (a * 2) stored -Inherits: gtest_normal - -INSERT INTO gtest_normal (a) VALUES (1); -INSERT INTO gtest_normal_child (a) VALUES (2); -SELECT * FROM gtest_normal; - a | b ----+--- - 1 | - 2 | 4 -(2 rows) - -CREATE TABLE gtest_normal_child2 (a int, b int GENERATED ALWAYS AS (a * 3) STORED); -ALTER TABLE gtest_normal_child2 INHERIT gtest_normal; -INSERT INTO gtest_normal_child2 (a) VALUES (3); -SELECT * FROM gtest_normal; - a | b ----+--- - 1 | - 2 | 4 - 3 | 9 -(3 rows) - +ERROR: child column "b" specifies generation expression +HINT: A child table column cannot be generated unless its parent column is. +CREATE TABLE gtest_normal_child (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +ALTER TABLE gtest_normal_child INHERIT gtest_normal; -- error +ERROR: column "b" in child table must not be a generated column +DROP TABLE gtest_normal, gtest_normal_child; -- test inheritance mismatches between parent and child CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS (a * 22) STORED) INHERITS (gtest1); -- error NOTICE: merging column "b" with inherited definition @@ -314,9 +293,6 @@ ERROR: column "b" inherits from generated column but specifies identity CREATE TABLE gtestxx_1 (a int NOT NULL, b int); ALTER TABLE gtestxx_1 INHERIT gtest1; -- error ERROR: column "b" in child table must be a generated column -CREATE TABLE gtestxx_2 (a int NOT NULL, b int GENERATED ALWAYS AS (a * 22) STORED); -ALTER TABLE gtestxx_2 INHERIT gtest1; -- error -ERROR: column "b" in child table has a conflicting generation expression CREATE TABLE gtestxx_3 (a int NOT NULL, b int GENERATED ALWAYS AS (a * 2) STORED); ALTER TABLE gtestxx_3 INHERIT gtest1; -- ok CREATE TABLE gtestxx_4 (b int GENERATED ALWAYS AS (a * 2) STORED, a int NOT NULL); @@ -701,8 +677,12 @@ CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RA CREATE TABLE gtest_child PARTITION OF gtest_parent ( f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) STORED ) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error -ERROR: generated columns are not supported on partitions -DROP TABLE gtest_parent; +ERROR: child column "f3" specifies generation expression +HINT: A child table column cannot be generated unless its parent column is. +CREATE TABLE gtest_child (f1 date NOT NULL, f2 text, f3 bigint GENERATED ALWAYS AS (2 * 2) STORED); +ALTER TABLE gtest_parent ATTACH PARTITION gtest_child FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error +ERROR: column "f3" in child table must not be a generated column +DROP TABLE gtest_parent, gtest_child; -- partitioned table CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1); CREATE TABLE gtest_child PARTITION OF gtest_parent FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql index 39eec40bce..c962b1b226 100644 --- a/src/test/regress/sql/generated.sql +++ b/src/test/regress/sql/generated.sql @@ -110,17 +110,12 @@ INSERT INTO gtest1_1 VALUES (4); SELECT * FROM gtest1_1; SELECT * FROM gtest1; +-- can't have generated column that is a child of normal column CREATE TABLE gtest_normal (a int, b int); -CREATE TABLE gtest_normal_child (a int, b int GENERATED ALWAYS AS (a * 2) STORED) INHERITS (gtest_normal); -\d gtest_normal_child -INSERT INTO gtest_normal (a) VALUES (1); -INSERT INTO gtest_normal_child (a) VALUES (2); -SELECT * FROM gtest_normal; - -CREATE TABLE gtest_normal_child2 (a int, b int GENERATED ALWAYS AS (a * 3) STORED); -ALTER TABLE gtest_normal_child2 INHERIT gtest_normal; -INSERT INTO gtest_normal_child2 (a) VALUES (3); -SELECT * FROM gtest_normal; +CREATE TABLE gtest_normal_child (a int, b int GENERATED ALWAYS AS (a * 2) STORED) INHERITS (gtest_normal); -- error +CREATE TABLE gtest_normal_child (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +ALTER TABLE gtest_normal_child INHERIT gtest_normal; -- error +DROP TABLE gtest_normal, gtest_normal_child; -- test inheritance mismatches between parent and child CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS (a * 22) STORED) INHERITS (gtest1); -- error @@ -129,8 +124,6 @@ CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS IDENTITY) INHERITS (gtest1 CREATE TABLE gtestxx_1 (a int NOT NULL, b int); ALTER TABLE gtestxx_1 INHERIT gtest1; -- error -CREATE TABLE gtestxx_2 (a int NOT NULL, b int GENERATED ALWAYS AS (a * 22) STORED); -ALTER TABLE gtestxx_2 INHERIT gtest1; -- error CREATE TABLE gtestxx_3 (a int NOT NULL, b int GENERATED ALWAYS AS (a * 2) STORED); ALTER TABLE gtestxx_3 INHERIT gtest1; -- ok CREATE TABLE gtestxx_4 (b int GENERATED ALWAYS AS (a * 2) STORED, a int NOT NULL); @@ -370,7 +363,9 @@ CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RA CREATE TABLE gtest_child PARTITION OF gtest_parent ( f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) STORED ) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error -DROP TABLE gtest_parent; +CREATE TABLE gtest_child (f1 date NOT NULL, f2 text, f3 bigint GENERATED ALWAYS AS (2 * 2) STORED); +ALTER TABLE gtest_parent ATTACH PARTITION gtest_child FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error +DROP TABLE gtest_parent, gtest_child; -- partitioned table CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1);