diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 4b219435d4..1c0700d347 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -325,27 +325,54 @@ CREATE TABLE people ( - For inheritance: + For inheritance and partitioning: - 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. + If a parent column is a generated column, its child column must also + be a generated column; however, the child column can have a + different generation expression. The generation expression that is + actually applied during insert or update of a row is the one + associated with the table that the row is physically in. + (This is unlike the behavior for column defaults: for those, the + default value associated with the table named in the query applies.) + + + + + If a parent column is not a generated column, its child column must + not be generated either. + + + + + For inherited tables, if you write a child column definition without + any GENERATED clause in CREATE TABLE + ... INHERITS, then its GENERATED clause + will automatically be copied from the parent. ALTER TABLE + ... INHERIT will insist that parent and child columns + already match as to generation status, but it will not require their + generation expressions to match. + + + + + Similarly for partitioned tables, if you write a child column + definition without any GENERATED clause + in CREATE TABLE ... PARTITION OF, then + its GENERATED clause will automatically be copied + from the parent. ALTER TABLE ... ADD PARTITION + will insist that parent and child columns already match as to + generation status, but it will not require their generation + expressions to match. In case of multiple inheritance, if one parent column is a generated - column, then all parent columns must be generated columns and with the - same expression. - - - - - If a parent column is not a generated column, a child column may be - defined to be a generated column or not. + column, then all parent columns must be generated columns. If they + do not all have the same generation expression, then the desired + expression for the child must be specified explicitly. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1db3bd9e2e..1fbdad4b64 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2320,8 +2320,12 @@ storage_name(char c) * (3) If conflicting defaults are inherited from different parents * (and not overridden by the child), an error is raised. * (4) Otherwise the inherited default is used. - * Rule (3) is new in Postgres 7.1; in earlier releases you got a - * rather arbitrary choice of which parent default to use. + * + * Note that the default-value infrastructure is used for generated + * columns' expressions too, so most of the preceding paragraph applies + * to generation expressions too. We insist that a child column be + * generated if and only if its parent(s) are, but it need not have + * the same generation expression. *---------- */ static List * @@ -2659,7 +2663,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence, } /* - * Locate default if any + * Locate default/generation expression if any */ if (attribute->atthasdef) { @@ -2923,23 +2927,20 @@ MergeAttributes(List *schema, List *supers, char relpersistence, /* * Check for conflicts related to generated columns. * - * If the parent column is generated, the child column must be - * unadorned and will be made a generated column. (We could - * in theory allow the child column definition specifying the - * exact same generation expression, but that's a bit - * complicated to implement and doesn't seem very useful.) We - * 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. + * If the parent column is generated, the child column will be + * made a generated column if it isn't already. If it is a + * generated column, we'll take its generation expression in + * preference to the parent's. We must 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) { - if (newdef->generated) - ereport(ERROR, - (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), - errmsg("child column \"%s\" specifies generation expression", - def->colname), - errhint("Omit the generation expression in the definition of the child table column to inherit the generation expression from the parent table."))); if (newdef->raw_default && !newdef->generated) ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), @@ -2951,15 +2952,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 */ @@ -2994,8 +2994,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence, /* * Now that we have the column definition list for a partition, we can * check whether the columns referenced in the column constraint specs - * actually exist. Also, we merge NOT NULL and defaults into each - * corresponding column definition. + * actually exist. Also, we merge parent's NOT NULL constraints and + * defaults into each corresponding column definition. */ if (is_partition) { @@ -3014,6 +3014,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence, found = true; coldef->is_not_null |= restdef->is_not_null; + /* + * As above, reject generated columns in partitions that + * are not generated in the parent. + */ + 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."))); + /* Other way around should have been dealt with above */ + Assert(!(coldef->generated && !restdef->generated)); + /* * Override the parent's default value for this column * (coldef->cooked_default) with the partition's local @@ -3058,7 +3071,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence, ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), errmsg("column \"%s\" inherits conflicting generation expressions", - def->colname))); + def->colname), + errhint("To resolve the conflict, specify a generation expression explicitly."))); else ereport(ERROR, (errcode(ERRCODE_INVALID_COLUMN_DEFINITION), @@ -15038,64 +15052,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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 409bc9b8e2..a43f2e5553 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -452,14 +452,15 @@ flagInhIndexes(Archive *fout, TableInfo tblinfo[], int numTables) * that we'll correctly emit the necessary DEFAULT NULL clause; otherwise * the backend will apply an inherited default to the column. * - * - Detect child columns that have a generation expression when their parents - * also have one. Generation expressions are always inherited, so there is - * no need to set them again in child tables, and there is no syntax for it - * either. Exceptions: If it's a partition or we are in binary upgrade - * mode, we dump them because in those cases inherited tables are recreated - * standalone first and then reattached to the parent. (See also the logic - * in dumpTableSchema().) In that situation, the generation expressions - * must match the parent, enforced by ALTER TABLE. + * - Detect child columns that have a generation expression and all their + * parents also have the same generation expression, and if so suppress the + * child's expression. The child will inherit the generation expression + * automatically, so there's no need to dump it. This improves the dump's + * compatibility with pre-v16 servers, which didn't allow the child's + * expression to be given explicitly. Exceptions: If it's a partition or + * we are in binary upgrade mode, we dump such expressions anyway because + * in those cases inherited tables are recreated standalone first and then + * reattached to the parent. (See also the logic in dumpTableSchema().) * * modifies tblinfo */ @@ -497,7 +498,8 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables) { bool foundNotNull; /* Attr was NOT NULL in a parent */ bool foundDefault; /* Found a default in a parent */ - bool foundGenerated; /* Found a generated in a parent */ + bool foundSameGenerated; /* Found matching GENERATED */ + bool foundDiffGenerated; /* Found non-matching GENERATED */ /* no point in examining dropped columns */ if (tbinfo->attisdropped[j]) @@ -505,7 +507,8 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables) foundNotNull = false; foundDefault = false; - foundGenerated = false; + foundSameGenerated = false; + foundDiffGenerated = false; for (k = 0; k < numParents; k++) { TableInfo *parent = parents[k]; @@ -517,8 +520,19 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables) if (inhAttrInd >= 0) { foundNotNull |= parent->notnull[inhAttrInd]; - foundDefault |= (parent->attrdefs[inhAttrInd] != NULL && !parent->attgenerated[inhAttrInd]); - foundGenerated |= parent->attgenerated[inhAttrInd]; + foundDefault |= (parent->attrdefs[inhAttrInd] != NULL && + !parent->attgenerated[inhAttrInd]); + if (parent->attgenerated[inhAttrInd]) + { + /* these pointer nullness checks are just paranoia */ + if (parent->attrdefs[inhAttrInd] != NULL && + tbinfo->attrdefs[j] != NULL && + strcmp(parent->attrdefs[inhAttrInd]->adef_expr, + tbinfo->attrdefs[j]->adef_expr) == 0) + foundSameGenerated = true; + else + foundDiffGenerated = true; + } } } @@ -561,8 +575,9 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables) tbinfo->attrdefs[j] = attrDef; } - /* Remove generation expression from child */ - if (foundGenerated && !tbinfo->ispartition && !dopt->binary_upgrade) + /* Remove generation expression from child if possible */ + if (foundSameGenerated && !foundDiffGenerated && + !tbinfo->ispartition && !dopt->binary_upgrade) tbinfo->attrdefs[j] = NULL; } } diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 5e800dc79a..2c0a969972 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -8529,17 +8529,11 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) { /* * Column generation expressions cannot be dumped separately, - * because there is no syntax for it. The !shouldPrintColumn - * case below will be tempted to set them to separate if they - * are attached to an inherited column without a local - * definition, but that would be wrong and unnecessary, - * because generation expressions are always inherited, so - * there is no need to set them again in child tables, and - * there is no syntax for it either. By setting separate to + * because there is no syntax for it. By setting separate to * false here we prevent the "default" from being processed as * its own dumpable object, and flagInhAttrs() will remove it - * from the table when it detects that it belongs to an - * inherited column. + * from the table if possible (that is, if it can be inherited + * from a parent). */ attrdefs[j].separate = false; } diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out index 1db5f9ed47..9867748c0f 100644 --- a/src/test/regress/expected/generated.out +++ b/src/test/regress/expected/generated.out @@ -268,75 +268,72 @@ 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) - --- 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 ERROR: child column "b" specifies generation expression -HINT: Omit the generation expression in the definition of the child table column to inherit the generation expression from the parent table. +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 DEFAULT 10) INHERITS (gtest1); -- error NOTICE: merging column "b" with inherited definition ERROR: column "b" inherits from generated column but specifies default CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS IDENTITY) INHERITS (gtest1); -- error NOTICE: merging column "b" with inherited definition ERROR: column "b" inherits from generated column but specifies identity +CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS (a * 22) STORED) INHERITS (gtest1); -- ok, overrides parent +NOTICE: merging column "b" with inherited definition +\d+ gtestx + Table "public.gtestx" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+-------------------------------------+---------+--------------+------------- + a | integer | | not null | | plain | | + b | integer | | | generated always as (a * 22) stored | plain | | + x | integer | | | | plain | | +Inherits: gtest1 + 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); ALTER TABLE gtestxx_4 INHERIT gtest1; -- ok -- test multiple inheritance mismatches +CREATE TABLE gtesty (x int, b int DEFAULT 55); +CREATE TABLE gtest1_y () INHERITS (gtest0, gtesty); -- error +NOTICE: merging multiple inherited definitions of column "b" +ERROR: inherited column "b" has a generation conflict +DROP TABLE gtesty; CREATE TABLE gtesty (x int, b int); -CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error +CREATE TABLE gtest1_y () INHERITS (gtest1, gtesty); -- error NOTICE: merging multiple inherited definitions of column "b" ERROR: inherited column "b" has a generation conflict DROP TABLE gtesty; CREATE TABLE gtesty (x int, b int GENERATED ALWAYS AS (x * 22) STORED); -CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error +CREATE TABLE gtest1_y () INHERITS (gtest1, gtesty); -- error NOTICE: merging multiple inherited definitions of column "b" ERROR: column "b" inherits conflicting generation expressions -DROP TABLE gtesty; -CREATE TABLE gtesty (x int, b int DEFAULT 55); -CREATE TABLE gtest1_2 () INHERITS (gtest0, gtesty); -- error +HINT: To resolve the conflict, specify a generation expression explicitly. +CREATE TABLE gtest1_y (b int GENERATED ALWAYS AS (x + 1) STORED) INHERITS (gtest1, gtesty); -- ok NOTICE: merging multiple inherited definitions of column "b" -ERROR: inherited column "b" has a generation conflict -DROP TABLE gtesty; +NOTICE: moving and merging column "b" with inherited definition +DETAIL: User-specified column moved to the position of the inherited column. +\d gtest1_y + Table "public.gtest1_y" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+------------------------------------ + a | integer | | not null | + b | integer | | | generated always as (x + 1) stored + x | integer | | | +Inherits: gtest1, + gtesty + -- test correct handling of GENERATED column that's only in child CREATE TABLE gtestp (f1 int); CREATE TABLE gtestc (f2 int GENERATED ALWAYS AS (f1+1) STORED) INHERITS(gtestp); @@ -696,16 +693,56 @@ 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); ERROR: generated columns are not supported on typed tables DROP TYPE gtest_type CASCADE; --- table partitions (currently not supported) -CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); +-- partitioning cases +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint) PARTITION BY RANGE (f1); 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; --- partitioned table +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 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 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; 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'); +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) STORED -- overrides gen expr +) FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint); +ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -- error +ERROR: column "f3" in child table must be a generated column +DROP TABLE gtest_child3; +CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 33) STORED); +ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); +\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 * 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') + INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1); SELECT * FROM gtest_parent; f1 | f2 | f3 @@ -719,14 +756,14 @@ SELECT * FROM gtest_child; 07-15-2016 | 1 | 2 (1 row) -DROP TABLE gtest_parent; +-- we leave these tables around for purposes of testing dump/reload/upgrade -- generated columns in partition key (not allowed) -CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); +CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); ERROR: cannot use generated column in partition key LINE 1: ...ENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); ^ DETAIL: Column "f3" is a generated column. -CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); +CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); ERROR: cannot use generated column in partition key LINE 1: ...ED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); ^ diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql index 39eec40bce..92d373b638 100644 --- a/src/test/regress/sql/generated.sql +++ b/src/test/regress/sql/generated.sql @@ -110,44 +110,39 @@ 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 CREATE TABLE gtestx (x int, b int DEFAULT 10) INHERITS (gtest1); -- error CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS IDENTITY) INHERITS (gtest1); -- error +CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS (a * 22) STORED) INHERITS (gtest1); -- ok, overrides parent +\d+ gtestx 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); ALTER TABLE gtestxx_4 INHERIT gtest1; -- ok -- test multiple inheritance mismatches +CREATE TABLE gtesty (x int, b int DEFAULT 55); +CREATE TABLE gtest1_y () INHERITS (gtest0, gtesty); -- error +DROP TABLE gtesty; + CREATE TABLE gtesty (x int, b int); -CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error +CREATE TABLE gtest1_y () INHERITS (gtest1, gtesty); -- error DROP TABLE gtesty; CREATE TABLE gtesty (x int, b int GENERATED ALWAYS AS (x * 22) STORED); -CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty); -- error -DROP TABLE gtesty; - -CREATE TABLE gtesty (x int, b int DEFAULT 55); -CREATE TABLE gtest1_2 () INHERITS (gtest0, gtesty); -- error -DROP TABLE gtesty; +CREATE TABLE gtest1_y () INHERITS (gtest1, gtesty); -- error +CREATE TABLE gtest1_y (b int GENERATED ALWAYS AS (x + 1) STORED) INHERITS (gtest1, gtesty); -- ok +\d gtest1_y -- test correct handling of GENERATED column that's only in child CREATE TABLE gtestp (f1 int); @@ -365,24 +360,37 @@ 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); DROP TYPE gtest_type CASCADE; --- table partitions (currently not supported) -CREATE TABLE gtest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); +-- partitioning cases +CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint) PARTITION BY RANGE (f1); 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 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 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); -CREATE TABLE gtest_child PARTITION OF gtest_parent FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +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) STORED -- overrides gen expr +) FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint); +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) STORED); +ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); +\d gtest_child +\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; -DROP TABLE gtest_parent; +-- we leave these tables around for purposes of testing dump/reload/upgrade -- generated columns in partition key (not allowed) -CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); -CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); +CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); +CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); -- ALTER TABLE ... ADD COLUMN CREATE TABLE gtest25 (a int PRIMARY KEY);