Fix several DDL issues of generated columns versus inheritance
authorPeter Eisentraut <[email protected]>
Wed, 6 May 2020 14:25:54 +0000 (16:25 +0200)
committerPeter Eisentraut <[email protected]>
Fri, 8 May 2020 09:31:57 +0000 (11:31 +0200)
Several combinations of generated columns and inheritance in CREATE
TABLE were not handled correctly.  Specifically:

- Disallow a child column specifying a generation expression if the
  parent column is a generated column.  The child column definition
  must be unadorned and the parent column's generation expression will
  be copied.

- Prohibit a child column of a generated parent column specifying
  default values or identity.

- Allow a child column of a not-generated parent column specifying
  itself as a generated column.  This previously did not work, but it
  was possible to arrive at the state via other means (involving ALTER
  TABLE), so it seems sensible to support it.

Add tests for each case.  Also add documentation about the rules
involving generated columns and inheritance.

Discussion:
    https://www.postgresql.org/message-id/flat/15830.1575468847%40sss.pgh.pa.us
    https://www.postgresql.org/message-id/flat/2678bad1-048f-519a-ef24-b12962f41807%40enterprisedb.com
    https://www.postgresql.org/message-id/flat/CAJvUf_u4h0DxkCMCeEKAWCuzGUTnDP-G5iVmSwxLQSXn0_FWNQ%40mail.gmail.com

doc/src/sgml/ddl.sgml
src/backend/commands/tablecmds.c
src/test/regress/expected/generated.out
src/test/regress/sql/generated.sql

index aae5d320309a6dd1101c8cd3bf12949bc2063a9a..bdfb221bc9851c406d4c05cb54542ebb745e5ceb 100644 (file)
@@ -324,6 +324,32 @@ CREATE TABLE people (
       linkend="sql-createforeigntable"/> for details.
      </para>
     </listitem>
+    <listitem>
+     <para>For inheritance:</para>
+     <itemizedlist>
+      <listitem>
+       <para>
+        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 <literal>GENERATED</literal> clause,
+        as it will be copied from the parent.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        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.
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        If a parent column is not a generated column, a child column may be
+        defined to be a generated column or not.
+       </para>
+      </listitem>
+     </itemizedlist>
+    </listitem>
    </itemizedlist>
   </para>
 
index 2fba83504b62647a78c5ef26683783a0b0332eab..a518b552b3d489d9f950b857d57606827fbdf012 100644 (file)
@@ -2618,12 +2618,55 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
                                def->is_local = true;
                                /* Merge of NOT NULL constraints = OR 'em together */
                                def->is_not_null |= newdef->is_not_null;
+
+                               /*
+                                * 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 (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),
+                                                                errmsg("column \"%s\" inherits from generated column but specifies default",
+                                                                               def->colname)));
+                                       if (newdef->identity)
+                                               ereport(ERROR,
+                                                               (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+                                                                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;
+                               }
+
                                /* If new def has a default, override previous default */
                                if (newdef->raw_default != NULL)
                                {
                                        def->raw_default = newdef->raw_default;
                                        def->cooked_default = newdef->cooked_default;
                                }
+
                        }
                        else
                        {
@@ -2709,11 +2752,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
                        ColumnDef  *def = lfirst(entry);
 
                        if (def->cooked_default == &bogus_marker)
-                               ereport(ERROR,
-                                               (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
-                                                errmsg("column \"%s\" inherits conflicting default values",
-                                                               def->colname),
-                                                errhint("To resolve the conflict, specify a default explicitly.")));
+                       {
+                               if (def->generated)
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+                                                        errmsg("column \"%s\" inherits conflicting generation expressions",
+                                                                       def->colname)));
+                               else
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+                                                        errmsg("column \"%s\" inherits conflicting default values",
+                                                                       def->colname),
+                                                        errhint("To resolve the conflict, specify a default explicitly.")));
+                       }
                }
        }
 
index 30fa320a3959cda3a5d6d1ad19e37b194a45a240..7ccc3c65ed17e01b8971285868e72a97fa468ffe 100644 (file)
@@ -219,12 +219,54 @@ SELECT * FROM gtest1;
  4 | 8
 (2 rows)
 
--- test inheritance mismatch
+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);
+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)
+
+-- 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.
+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
+-- test multiple inheritance mismatches
 CREATE TABLE gtesty (x int, b int);
 CREATE TABLE gtest1_2 () 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
+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
+NOTICE:  merging multiple inherited definitions of column "b"
+ERROR:  inherited column "b" has a generation conflict
+DROP TABLE gtesty;
 -- test stored update
 CREATE TABLE gtest3 (a int, b int GENERATED ALWAYS AS (a * 3) STORED);
 INSERT INTO gtest3 (a) VALUES (1), (2), (3), (NULL);
index c13ede4107579b89f4505af5916a1e2975dcab3f..4cff1279c779a713b8900d8216e0b49aa47beee5 100644 (file)
@@ -89,11 +89,31 @@ INSERT INTO gtest1_1 VALUES (4);
 SELECT * FROM gtest1_1;
 SELECT * FROM gtest1;
 
--- test inheritance mismatch
+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;
+
+-- 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
+
+-- test multiple inheritance mismatches
 CREATE TABLE gtesty (x int, b int);
 CREATE TABLE gtest1_2 () 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;
+
 -- test stored update
 CREATE TABLE gtest3 (a int, b int GENERATED ALWAYS AS (a * 3) STORED);
 INSERT INTO gtest3 (a) VALUES (1), (2), (3), (NULL);