Alvaro Herrera wrote:
> Tom Lane wrote:
> 
> > We could probably fix the specific issue being seen here by passing the
> > expression tree through a suitable attno remapping,
> 
> Here's a first attempt at fixing this.  It makes the test pass, but I
> have the feeling that more complex ones might need more work.

Here's another one with three main differences:

1. Make the whole-row check an ereport() not elog().  You can use a
whole-row expression in USING, which makes it fire, so better make it
translatable.  An artificial example is in the new regression tests,
  ALTER TABLE test_type_diff2 ALTER COLUMN int_four TYPE int4 USING 
(pg_column_size(test_type_diff2));
but I suppose somebody with more imagination could come up with
something actually interesting.

2. The foreign table case was broken, as evidenced by the foreign_table
regression test.

3. If there is no USING expression, there is no need to do the whole
map_variable_attnos() dance.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e217f57..77b4bf5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -7999,12 +7999,98 @@ ATPrepAlterColumnType(List **wqueue,
        ReleaseSysCache(tuple);
 
        /*
-        * The recursion case is handled by ATSimpleRecursion.  However, if we 
are
-        * told not to recurse, there had better not be any child tables; else 
the
-        * alter would put them out of step.
+        * Recurse manually by queueing a new command for each child, if
+        * necessary. We cannot apply ATSimpleRecursion here because we need to
+        * remap attribute numbers in the USING expression, if any.
+        *
+        * If we are told not to recurse, there had better not be any child
+        * tables; else the alter would put them out of step.
         */
        if (recurse)
-               ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+       {
+               Oid                     relid = RelationGetRelid(rel);
+               ListCell   *child;
+               List       *children;
+
+               children = find_all_inheritors(relid, lockmode, NULL);
+
+               /*
+                * find_all_inheritors does the recursive search of the 
inheritance
+                * hierarchy, so all we have to do is process all of the relids 
in the
+                * list that it returns.
+                */
+               foreach(child, children)
+               {
+                       Oid                     childrelid = lfirst_oid(child);
+                       Relation        childrel;
+
+                       if (childrelid == relid)
+                               continue;
+
+                       /* find_all_inheritors already got lock */
+                       childrel = relation_open(childrelid, NoLock);
+                       CheckTableNotInUse(childrel, "ALTER TABLE");
+
+                       /*
+                        * Remap the attribute numbers.  If no USING expression 
was
+                        * specified, there is no need for this step.
+                        */
+                       if (def->cooked_default)
+                       {
+                               TupleDesc       ptdesc;
+                               TupleDesc       ctdesc;
+                               AttrNumber *attmap;
+                               AttrNumber      pnum;
+                               bool            found_whole_row;
+
+                               /*
+                                * Build an attribute map for 
map_variable_attnos.  This is
+                                * O(N^2) on the number of attributes ...
+                                */
+                               ptdesc = RelationGetDescr(rel);
+                               ctdesc = RelationGetDescr(childrel);
+                               attmap = (AttrNumber *) 
palloc0(sizeof(AttrNumber) *
+                                                                               
                ptdesc->natts);
+                               for (pnum = 1; pnum <= ptdesc->natts; pnum++)
+                               {
+                                       bool            found = false;
+                                       AttrNumber      cnum;
+
+                                       for (cnum = 1; cnum <= ctdesc->natts; 
cnum++)
+                                       {
+                                               if 
(strcmp(NameStr(ptdesc->attrs[pnum - 1]->attname),
+                                                        
NameStr(ctdesc->attrs[cnum - 1]->attname)) == 0)
+                                               {
+                                                       attmap[pnum - 1] = cnum;
+                                                       found = true;
+                                                       break;
+                                               }
+                                       }
+
+                                       /* should not happen */
+                                       if (!found)
+                                               elog(ERROR, "column \"%s\" not 
found in child table \"%s\"",
+                                                        
NameStr(ptdesc->attrs[pnum - 1]->attname),
+                                                        
RelationGetRelationName(childrel));
+                               }
+
+                               cmd = copyObject(cmd);
+                               ((ColumnDef *) cmd->def)->cooked_default =
+                                       map_variable_attnos(def->cooked_default,
+                                                                               
1, 0,
+                                                                               
attmap, ptdesc->natts,
+                                                                               
&found_whole_row);
+                               if (found_whole_row)
+                                       ereport(ERROR,
+                                                       
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                 errmsg("cannot convert 
whole-row table reference"),
+                                                        errdetail("USING 
expression contains a whole-row table reference.")));
+                               pfree(attmap);
+                       }
+                       ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
+                       relation_close(childrel, NoLock);
+               }
+       }
        else if (!recursing &&
                         find_inheritance_children(RelationGetRelid(rel), 
NoLock) != NIL)
                ereport(ERROR,
diff --git a/src/test/regress/expected/alter_table.out 
b/src/test/regress/expected/alter_table.out
index de3c69a..c693f77 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2018,6 +2018,28 @@ select relname, conname, coninhcount, conislocal, 
connoinherit
  test_inh_check_child | test_inh_check_a_check |           1 | f          | f
 (6 rows)
 
+-- ALTER COLUMN TYPE with different schema in children
+-- Bug at https://postgr.es/message-id/20170102225618.ga10...@telsasoft.com
+CREATE TABLE test_type_diff (f1 int);
+CREATE TABLE test_type_diff_c (extra smallint) INHERITS (test_type_diff);
+ALTER TABLE test_type_diff ADD COLUMN f2 int;
+INSERT INTO test_type_diff_c VALUES (1, 2, 3);
+ALTER TABLE test_type_diff ALTER COLUMN f2 TYPE bigint USING f2::bigint;
+CREATE TABLE test_type_diff2 (int_two int2, int_four int4, int_eight int8);
+CREATE TABLE test_type_diff2_c1 (int_four int4, int_eight int8, int_two int2);
+CREATE TABLE test_type_diff2_c2 (int_eight int8, int_two int2, int_four int4);
+CREATE TABLE test_type_diff2_c3 (int_two int2, int_four int4, int_eight int8);
+ALTER TABLE test_type_diff2_c1 INHERIT test_type_diff2;
+ALTER TABLE test_type_diff2_c2 INHERIT test_type_diff2;
+ALTER TABLE test_type_diff2_c3 INHERIT test_type_diff2;
+INSERT INTO test_type_diff2_c1 VALUES (1, 2, 3);
+INSERT INTO test_type_diff2_c2 VALUES (4, 5, 6);
+INSERT INTO test_type_diff2_c3 VALUES (7, 8, 9);
+ALTER TABLE test_type_diff2 ALTER COLUMN int_four TYPE int8 USING 
int_four::int8;
+-- whole-row references are disallowed
+ALTER TABLE test_type_diff2 ALTER COLUMN int_four TYPE int4 USING 
(pg_column_size(test_type_diff2));
+ERROR:  cannot convert whole-row table reference
+DETAIL:  USING expression contains a whole-row table reference.
 -- check for rollback of ANALYZE corrupting table property flags (bug #11638)
 CREATE TABLE check_fk_presence_1 (id int PRIMARY KEY, t text);
 CREATE TABLE check_fk_presence_2 (id int REFERENCES check_fk_presence_1, t 
text);
diff --git a/src/test/regress/sql/alter_table.sql 
b/src/test/regress/sql/alter_table.sql
index 1459311..f3a7d8e 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1321,6 +1321,28 @@ select relname, conname, coninhcount, conislocal, 
connoinherit
   where relname like 'test_inh_check%' and c.conrelid = r.oid
   order by 1, 2;
 
+-- ALTER COLUMN TYPE with different schema in children
+-- Bug at https://postgr.es/message-id/20170102225618.ga10...@telsasoft.com
+CREATE TABLE test_type_diff (f1 int);
+CREATE TABLE test_type_diff_c (extra smallint) INHERITS (test_type_diff);
+ALTER TABLE test_type_diff ADD COLUMN f2 int;
+INSERT INTO test_type_diff_c VALUES (1, 2, 3);
+ALTER TABLE test_type_diff ALTER COLUMN f2 TYPE bigint USING f2::bigint;
+
+CREATE TABLE test_type_diff2 (int_two int2, int_four int4, int_eight int8);
+CREATE TABLE test_type_diff2_c1 (int_four int4, int_eight int8, int_two int2);
+CREATE TABLE test_type_diff2_c2 (int_eight int8, int_two int2, int_four int4);
+CREATE TABLE test_type_diff2_c3 (int_two int2, int_four int4, int_eight int8);
+ALTER TABLE test_type_diff2_c1 INHERIT test_type_diff2;
+ALTER TABLE test_type_diff2_c2 INHERIT test_type_diff2;
+ALTER TABLE test_type_diff2_c3 INHERIT test_type_diff2;
+INSERT INTO test_type_diff2_c1 VALUES (1, 2, 3);
+INSERT INTO test_type_diff2_c2 VALUES (4, 5, 6);
+INSERT INTO test_type_diff2_c3 VALUES (7, 8, 9);
+ALTER TABLE test_type_diff2 ALTER COLUMN int_four TYPE int8 USING 
int_four::int8;
+-- whole-row references are disallowed
+ALTER TABLE test_type_diff2 ALTER COLUMN int_four TYPE int4 USING 
(pg_column_size(test_type_diff2));
+
 -- check for rollback of ANALYZE corrupting table property flags (bug #11638)
 CREATE TABLE check_fk_presence_1 (id int PRIMARY KEY, t text);
 CREATE TABLE check_fk_presence_2 (id int REFERENCES check_fk_presence_1, t 
text);
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to