Fix CREATE TABLE LIKE INCLUDING GENERATED column order issue
authorPeter Eisentraut <peter@eisentraut.org>
Thu, 9 Apr 2020 14:17:55 +0000 (16:17 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Thu, 9 Apr 2020 14:36:45 +0000 (16:36 +0200)
CREATE TABLE LIKE INCLUDING GENERATED would fail if a generated column
referred to a column with a higher attribute number.  This is because
the column mapping mechanism created the mapping incrementally as
columns are added.  This was sufficient for previous uses of that
mechanism (omitting dropped columns), and it also happened to work if
generated columns only referred to columns with lower attribute
numbers, but here it failed.

This fix is to build the attribute mapping in a separate loop before
processing the columns in detail.

Bug: #16342
Reported-by: Ethan Waldo <ewaldo@healthetechs.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
src/backend/parser/parse_utilcmd.c
src/test/regress/expected/create_table_like.out
src/test/regress/sql/create_table_like.sql

index 350959a5e0bac3d59fbc2000b6ea64f153b1dcc5..75c122fe3489f8dab7994a52ba54dd760157bf0a 100644 (file)
@@ -924,6 +924,7 @@ static void
 transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_clause)
 {
    AttrNumber  parent_attno;
+   AttrNumber  new_attno;
    Relation    relation;
    TupleDesc   tupleDesc;
    TupleConstr *constr;
@@ -986,6 +987,26 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
     */
    attmap = make_attrmap(tupleDesc->natts);
 
+   /*
+    * We must fill the attmap now so that it can be used to process generated
+    * column default expressions in the per-column loop below.
+   */
+   new_attno = 1;
+   for (parent_attno = 1; parent_attno <= tupleDesc->natts;
+        parent_attno++)
+   {
+       Form_pg_attribute attribute = TupleDescAttr(tupleDesc,
+                                                   parent_attno - 1);
+
+       /*
+        * Ignore dropped columns in the parent.  attmap entry is left zero.
+        */
+       if (attribute->attisdropped)
+           continue;
+
+       attmap->attnums[parent_attno - 1] = list_length(cxt->columns) + (new_attno++);
+   }
+
    /*
     * Insert the copied attributes into the cxt for the new table definition.
     */
@@ -998,7 +1019,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
        ColumnDef  *def;
 
        /*
-        * Ignore dropped columns in the parent.  attmap entry is left zero.
+        * Ignore dropped columns in the parent.
         */
        if (attribute->attisdropped)
            continue;
@@ -1030,8 +1051,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
         */
        cxt->columns = lappend(cxt->columns, def);
 
-       attmap->attnums[parent_attno - 1] = list_length(cxt->columns);
-
        /*
         * Copy default, if present and it should be copied.  We have separate
         * options for plain default expressions and GENERATED defaults.
index f686606e4237bfe3d6d78d37cfa179666d6ae8e2..655e8e41dd9037e0777bcea845c6aefd0ddd3976 100644 (file)
@@ -159,14 +159,15 @@ SELECT * FROM test_like_gen_3;
 (1 row)
 
 DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;
-CREATE TABLE test_like_4 (a int, b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) STORED);
+-- also test generated column with a "forward" reference (bug #16342)
+CREATE TABLE test_like_4 (b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) STORED, a int);
 \d test_like_4
                           Table "public.test_like_4"
  Column |  Type   | Collation | Nullable |              Default               
 --------+---------+-----------+----------+------------------------------------
- a      | integer |           |          | 
  b      | integer |           |          | 42
  c      | integer |           |          | generated always as (a * 2) stored
+ a      | integer |           |          | 
 
 CREATE TABLE test_like_4a (LIKE test_like_4);
 CREATE TABLE test_like_4b (LIKE test_like_4 INCLUDING DEFAULTS);
@@ -176,12 +177,12 @@ CREATE TABLE test_like_4d (LIKE test_like_4 INCLUDING DEFAULTS INCLUDING GENERAT
             Table "public.test_like_4a"
  Column |  Type   | Collation | Nullable | Default 
 --------+---------+-----------+----------+---------
- a      | integer |           |          | 
  b      | integer |           |          | 
  c      | integer |           |          | 
+ a      | integer |           |          | 
 
-INSERT INTO test_like_4a VALUES(11);
-TABLE test_like_4a;
+INSERT INTO test_like_4a (a) VALUES(11);
+SELECT a, b, c FROM test_like_4a;
  a  | b | c 
 ----+---+---
  11 |   |  
@@ -191,12 +192,12 @@ TABLE test_like_4a;
             Table "public.test_like_4b"
  Column |  Type   | Collation | Nullable | Default 
 --------+---------+-----------+----------+---------
- a      | integer |           |          | 
  b      | integer |           |          | 42
  c      | integer |           |          | 
+ a      | integer |           |          | 
 
-INSERT INTO test_like_4b VALUES(11);
-TABLE test_like_4b;
+INSERT INTO test_like_4b (a) VALUES(11);
+SELECT a, b, c FROM test_like_4b;
  a  | b  | c 
 ----+----+---
  11 | 42 |  
@@ -206,12 +207,12 @@ TABLE test_like_4b;
                          Table "public.test_like_4c"
  Column |  Type   | Collation | Nullable |              Default               
 --------+---------+-----------+----------+------------------------------------
- a      | integer |           |          | 
  b      | integer |           |          | 
  c      | integer |           |          | generated always as (a * 2) stored
+ a      | integer |           |          | 
 
-INSERT INTO test_like_4c VALUES(11);
-TABLE test_like_4c;
+INSERT INTO test_like_4c (a) VALUES(11);
+SELECT a, b, c FROM test_like_4c;
  a  | b | c  
 ----+---+----
  11 |   | 22
@@ -221,12 +222,12 @@ TABLE test_like_4c;
                          Table "public.test_like_4d"
  Column |  Type   | Collation | Nullable |              Default               
 --------+---------+-----------+----------+------------------------------------
- a      | integer |           |          | 
  b      | integer |           |          | 42
  c      | integer |           |          | generated always as (a * 2) stored
+ a      | integer |           |          | 
 
-INSERT INTO test_like_4d VALUES(11);
-TABLE test_like_4d;
+INSERT INTO test_like_4d (a) VALUES(11);
+SELECT a, b, c FROM test_like_4d;
  a  | b  | c  
 ----+----+----
  11 | 42 | 22
index afcb59b89700929b11bb5d990b6bd0ff6ddce199..6981ac0cbeeed11ad1069a71f2767ae7acfcc34b 100644 (file)
@@ -65,24 +65,25 @@ INSERT INTO test_like_gen_3 (a) VALUES (1);
 SELECT * FROM test_like_gen_3;
 DROP TABLE test_like_gen_1, test_like_gen_2, test_like_gen_3;
 
-CREATE TABLE test_like_4 (a int, b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) STORED);
+-- also test generated column with a "forward" reference (bug #16342)
+CREATE TABLE test_like_4 (b int DEFAULT 42, c int GENERATED ALWAYS AS (a * 2) STORED, a int);
 \d test_like_4
 CREATE TABLE test_like_4a (LIKE test_like_4);
 CREATE TABLE test_like_4b (LIKE test_like_4 INCLUDING DEFAULTS);
 CREATE TABLE test_like_4c (LIKE test_like_4 INCLUDING GENERATED);
 CREATE TABLE test_like_4d (LIKE test_like_4 INCLUDING DEFAULTS INCLUDING GENERATED);
 \d test_like_4a
-INSERT INTO test_like_4a VALUES(11);
-TABLE test_like_4a;
+INSERT INTO test_like_4a (a) VALUES(11);
+SELECT a, b, c FROM test_like_4a;
 \d test_like_4b
-INSERT INTO test_like_4b VALUES(11);
-TABLE test_like_4b;
+INSERT INTO test_like_4b (a) VALUES(11);
+SELECT a, b, c FROM test_like_4b;
 \d test_like_4c
-INSERT INTO test_like_4c VALUES(11);
-TABLE test_like_4c;
+INSERT INTO test_like_4c (a) VALUES(11);
+SELECT a, b, c FROM test_like_4c;
 \d test_like_4d
-INSERT INTO test_like_4d VALUES(11);
-TABLE test_like_4d;
+INSERT INTO test_like_4d (a) VALUES(11);
+SELECT a, b, c FROM test_like_4d;
 DROP TABLE test_like_4, test_like_4a, test_like_4b, test_like_4c, test_like_4d;
 
 CREATE TABLE inhg (x text, LIKE inhx INCLUDING INDEXES, y text); /* copies indexes */