Fix ALTER TABLE / INHERIT with generated columns
authorPeter Eisentraut
Tue, 4 May 2021 09:45:37 +0000 (11:45 +0200)
committerPeter Eisentraut
Tue, 4 May 2021 10:10:22 +0000 (12:10 +0200)
When running ALTER TABLE t2 INHERIT t1, we must check that columns in
t2 that correspond to a generated column in t1 are also generated and
have the same generation expression.  Otherwise, this would allow
creating setups that a normal CREATE TABLE sequence would not allow.

Discussion: https://www.postgresql.org/message-id/22de27f6-7096-8d96-4619-7b882932ca25@2ndquadrant.com

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

index faba26413526e64a23f25506745a2d3f69928a80..44499ec08b37eef65e0e3abcb205b37bd5023086 100644 (file)
@@ -13909,6 +13909,66 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel)
                         errmsg("column \"%s\" in child table must be marked NOT NULL",
                                attributeName)));
 
+           /*
+            * If parent column is generated, child column must be, too.
+            */
+           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)));
+           }
+
            /*
             * OK, bump the child column's inheritance count.  (If we fail
             * later on, this change will just roll back.)
index 4b062603044250873e08053c905212ddf66bd85f..ea58de36670cba8e5385a5d75eb1362f2a309d4d 100644 (file)
@@ -240,6 +240,17 @@ SELECT * FROM gtest_normal;
  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
@@ -251,6 +262,16 @@ 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 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);
 CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty);  -- error
index c86ad34b0064d2ad0659f1e8c07a52113640d886..5400510edca373cf02543a29e622b193e736f5ca 100644 (file)
@@ -96,11 +96,25 @@ 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;
+
 -- 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 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);
 CREATE TABLE gtest1_2 () INHERITS (gtest1, gtesty);  -- error