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