[BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

Started by KaiGai Koheiabout 16 years ago71 messages
#1KaiGai Kohei
kaigai@ak.jp.nec.com

Is it an expected behavior?

postgres=# CREATE TABLE t1 (a int, b int);
CREATE TABLE
postgres=# CREATE TABLE t2 (b int, c int);
CREATE TABLE
postgres=# CREATE TABLE t3 (d int) inherits (t1, t2);
NOTICE: merging multiple inherited definitions of column "b"
CREATE TABLE

The t3.d is inherited from t1 and t2. Its attinhcount is 2.

postgres=# ALTER TABLE t1 RENAME b TO x;
ALTER TABLE

It alters name of the column 'b' in the t1 and its child tables ('t3').

postgres=# SELECT * FROM t1;
a | x
---+---
(0 rows)

postgres=# SELECT * FROM t2;
ERROR: could not find inherited attribute "b" of relation "t3"

Because t3.b is also inherited from the t2, but ALTER TABLE does not
care about multiple inherited columns well.

I think we should not allow to rename a column with attinhcount > 1.

Any comments?

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#2Alvaro Herrera
alvherre@commandprompt.com
In reply to: KaiGai Kohei (#1)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

KaiGai Kohei wrote:

postgres=# SELECT * FROM t2;
ERROR: could not find inherited attribute "b" of relation "t3"

Because t3.b is also inherited from the t2, but ALTER TABLE does not
care about multiple inherited columns well.

I think we should not allow to rename a column with attinhcount > 1.

I think we should fix ALTER TABLE to cope with multiple inheritance.

--
Alvaro Herrera http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#3Thom Brown
thombrown@gmail.com
In reply to: Alvaro Herrera (#2)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2009/11/4 Alvaro Herrera <alvherre@commandprompt.com>:

KaiGai Kohei wrote:

  postgres=# SELECT * FROM t2;
  ERROR:  could not find inherited attribute "b" of relation "t3"

Because t3.b is also inherited from the t2, but ALTER TABLE does not
care about multiple inherited columns well.

I think we should not allow to rename a column with attinhcount > 1.

I think we should fix ALTER TABLE to cope with multiple inheritance.

I'd be interested to see how this should work. Given KaiGai's
example, how would that be resolved? That column would already be
merged for the inheriting table, so would renaming it somehow unmerge
it? Given an insertion into t3 would propagate to both column b's in
table t1 and t2, and then renaming b in t1 wouldn't make sense. Or
would renaming it be prevented due to dependants? Or should t3's
inheritance of t1 and t2 implicitly bind t1's and t2's column b to one
another so that one affects the other? (i.e. renaming column b in t1
would also rename column b in t2, or both would require renaming
during the same transaction.)

...or something less confusing. :)

Thom

#4Tom Lane
tgl@sss.pgh.pa.us
In reply to: Thom Brown (#3)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

Thom Brown <thombrown@gmail.com> writes:

2009/11/4 Alvaro Herrera <alvherre@commandprompt.com>:

KaiGai Kohei wrote:

I think we should not allow to rename a column with attinhcount > 1.

I think we should fix ALTER TABLE to cope with multiple inheritance.

I'd be interested to see how this should work.

Yeah. I don't think a "fix" is possible, because there is no
non-astonishing way for it to behave. I think KaiGai is right that
forbidding the rename is the best solution.

regards, tom lane

#5KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Tom Lane (#4)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

Tom Lane wrote:

Thom Brown <thombrown@gmail.com> writes:

2009/11/4 Alvaro Herrera <alvherre@commandprompt.com>:

KaiGai Kohei wrote:

I think we should not allow to rename a column with attinhcount > 1.

I think we should fix ALTER TABLE to cope with multiple inheritance.

I'd be interested to see how this should work.

Yeah. I don't think a "fix" is possible, because there is no
non-astonishing way for it to behave. I think KaiGai is right that
forbidding the rename is the best solution.

The attached patch forbids rename when the attribute is inherited
from multiple parents.

postgres=# CREATE TABLE t1 (a int, b int);
CREATE TABLE
postgres=# CREATE TABLE t2 (b int, c int);
CREATE TABLE
postgres=# CREATE TABLE t3 (d int) INHERITS (t1, t2);
NOTICE: merging multiple inherited definitions of column "b"
CREATE TABLE
postgres=# SELECT * FROM t3;
a | b | c | d
---+---+---+---
(0 rows)

postgres=# ALTER TABLE t1 RENAME b TO x;
ERROR: cannot rename multiple inherited column "b"

The regression test detected a matter in the misc test.

It tries to rename column "a" of "a_star" table, but it failed due to
the new restriction.

--
-- test the "star" operators a bit more thoroughly -- this time,
-- throw in lots of NULL fields...
--
-- a is the type root
-- b and c inherit from a (one-level single inheritance)
-- d inherits from b and c (two-level multiple inheritance)
-- e inherits from c (two-level single inheritance)
-- f inherits from e (three-level single inheritance)
--
CREATE TABLE a_star (
class char,
a int4
);

CREATE TABLE b_star (
b text
) INHERITS (a_star);

CREATE TABLE c_star (
c name
) INHERITS (a_star);

CREATE TABLE d_star (
d float8
) INHERITS (b_star, c_star);

At the misc test,

  --- 242,278 ----
    ALTER TABLE c_star* RENAME COLUMN c TO cc;
    ALTER TABLE b_star* RENAME COLUMN b TO bb;
    ALTER TABLE a_star* RENAME COLUMN a TO aa;
  + ERROR:  cannot rename multiple inherited column "a"
    SELECT class, aa
       FROM a_star* x
       WHERE aa ISNULL;
  ! ERROR:  column "aa" does not exist
  ! LINE 1: SELECT class, aa
  !

It seems to me it is a case the regression test to be fixed up.
(We don't have any reasonable way to know whether a certain attribute
has a same source, or not.)

Any comments?
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-renameatt-multiple-inheritance.patchtext/x-patch; name=pgsql-renameatt-multiple-inheritance.patchDownload
Index: src/test/regress/sql/inherit.sql
===================================================================
*** src/test/regress/sql/inherit.sql	(revision 2388)
--- src/test/regress/sql/inherit.sql	(working copy)
*************** CREATE TABLE inh_error1 () INHERITS (t1,
*** 336,338 ****
--- 336,352 ----
  CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
  
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ 
+ -- Test for renaming
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE t2 (b int, c int);
+ CREATE TABLE t3 (d int) INHERITS(t1, t2);
+ ALTER TABLE t1 RENAME a TO x;
+ ALTER TABLE t1 RENAME b TO y;	-- to be failed
+ ALTER TABLE t3 RENAME d TO z;
+ SELECT * FROM t3;
+ DROP TABLE t3;
+ DROP TABLE t2;
+ DROP TABLE t1;
+ 
+ 
Index: src/test/regress/expected/inherit.out
===================================================================
*** src/test/regress/expected/inherit.out	(revision 2388)
--- src/test/regress/expected/inherit.out	(working copy)
*************** NOTICE:  merging column "a" with inherit
*** 1057,1059 ****
--- 1057,1076 ----
  ERROR:  column "a" has a storage parameter conflict
  DETAIL:  MAIN versus EXTENDED
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ -- Test for renaming
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE t2 (b int, c int);
+ CREATE TABLE t3 (d int) INHERITS(t1, t2);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 RENAME a TO x;
+ ALTER TABLE t1 RENAME b TO y;	-- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ ALTER TABLE t3 RENAME d TO z;
+ SELECT * FROM t3;
+  x | b | c | z 
+ ---+---+---+---
+ (0 rows)
+ 
+ DROP TABLE t3;
+ DROP TABLE t2;
+ DROP TABLE t1;
Index: src/backend/commands/tablecmds.c
===================================================================
*** src/backend/commands/tablecmds.c	(revision 2388)
--- src/backend/commands/tablecmds.c	(working copy)
*************** renameatt(Oid myrelid,
*** 2024,2029 ****
--- 2024,2040 ----
  				 errmsg("cannot rename inherited column \"%s\"",
  						oldattname)));
  
+ 	/*
+ 	 * If the attribute is inherited from multiple parents, forbid
+ 	 * the renaming, because we don't have any reasonable way to keep
+ 	 * integrity in whole of the inheritance relationship.
+ 	 */
+ 	if (attform->attinhcount > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ 				 (errmsg("cannot rename multiple inherited column \"%s\"",
+ 						 oldattname))));
+ 
  	/* should not already exist */
  	/* this test is deliberately not attisdropped-aware */
  	if (SearchSysCacheExists(ATTNAME,
#6KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#5)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

It is a patch for the matter which I reported before.

When a column is inherited from multiple relations, ALTER TABLE with
RENAME TO option is problematic.
This patch fixes the matter. In correctly, it prevent to rename columns
inherited from multiple relations and merged.

Also see the past discussion:
http://archives.postgresql.org/pgsql-hackers/2009-11/msg00138.php

postgres=# CREATE TABLE t1 (a int, b int);
CREATE TABLE
postgres=# CREATE TABLE t2 (b int, c int);
CREATE TABLE
postgres=# CREATE TABLE t3 (x text) inherits (t1, t2);
NOTICE: merging multiple inherited definitions of column "b"
CREATE TABLE
postgres=# SELECT * FROM t3;
a | b | c | x
---+---+---+---
(0 rows)

It looks to me fine.

postgres=# ALTER TABLE t1 RENAME b TO y;
ALTER TABLE
postgres=# SELECT * FROM t3;
a | y | c | x
---+---+---+---
(0 rows)

postgres=# SELECT * FROM t1;
a | y
---+---
(0 rows)

It looks to me fine.

postgres=# SELECT * FROM t2;
ERROR: could not find inherited attribute "b" of relation "t3"

Oops, when we refer the t3 via t2, it expects the inherited relation
also has the column "b", but it was already renamed.

One trouble is regression test. The misc_create test create a_star
table, then it is inherited by b_star and c_star, then these are
inherited to d_star table. Then misc test rename the a_star.a, but
this patch prevent it.

It looks like works well, but it is a corner case, because d_star.a
is eventually inherited from a_star via b_star and c_star, and these
are all the inherited relations.
In generally, we don't have reasonable way to rename all the related
columns upper and lower of the inheritance relationship.

Thanks,

(2009/11/05 9:57), KaiGai Kohei wrote:

Tom Lane wrote:

Thom Brown<thombrown@gmail.com> writes:

2009/11/4 Alvaro Herrera<alvherre@commandprompt.com>:

KaiGai Kohei wrote:

I think we should not allow to rename a column with attinhcount> 1.

I think we should fix ALTER TABLE to cope with multiple inheritance.

I'd be interested to see how this should work.

Yeah. I don't think a "fix" is possible, because there is no
non-astonishing way for it to behave. I think KaiGai is right that
forbidding the rename is the best solution.

The attached patch forbids rename when the attribute is inherited
from multiple parents.

postgres=# CREATE TABLE t1 (a int, b int);
CREATE TABLE
postgres=# CREATE TABLE t2 (b int, c int);
CREATE TABLE
postgres=# CREATE TABLE t3 (d int) INHERITS (t1, t2);
NOTICE: merging multiple inherited definitions of column "b"
CREATE TABLE
postgres=# SELECT * FROM t3;
a | b | c | d
---+---+---+---
(0 rows)

postgres=# ALTER TABLE t1 RENAME b TO x;
ERROR: cannot rename multiple inherited column "b"

The regression test detected a matter in the misc test.

It tries to rename column "a" of "a_star" table, but it failed due to
the new restriction.

--
-- test the "star" operators a bit more thoroughly -- this time,
-- throw in lots of NULL fields...
--
-- a is the type root
-- b and c inherit from a (one-level single inheritance)
-- d inherits from b and c (two-level multiple inheritance)
-- e inherits from c (two-level single inheritance)
-- f inherits from e (three-level single inheritance)
--
CREATE TABLE a_star (
class char,
a int4
);

CREATE TABLE b_star (
b text
) INHERITS (a_star);

CREATE TABLE c_star (
c name
) INHERITS (a_star);

CREATE TABLE d_star (
d float8
) INHERITS (b_star, c_star);

At the misc test,

--- 242,278 ----
ALTER TABLE c_star* RENAME COLUMN c TO cc;
ALTER TABLE b_star* RENAME COLUMN b TO bb;
ALTER TABLE a_star* RENAME COLUMN a TO aa;
+ ERROR:  cannot rename multiple inherited column "a"
SELECT class, aa
FROM a_star* x
WHERE aa ISNULL;
! ERROR:  column "aa" does not exist
! LINE 1: SELECT class, aa
!

It seems to me it is a case the regression test to be fixed up.
(We don't have any reasonable way to know whether a certain attribute
has a same source, or not.)

Any comments?

--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-fix-inherit-rename.patchtext/x-patch; name=pgsql-fix-inherit-rename.patchDownload
*** src/test/regress/sql/inherit.sql	(revision 2486)
--- src/test/regress/sql/inherit.sql	(working copy)
***************
*** 336,338 ****
--- 336,350 ----
  CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
  
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ 
+ -- Test for renaming
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE t2 (b int, c int);
+ CREATE TABLE t3 (d int) INHERITS(t1, t2);
+ ALTER TABLE t1 RENAME a TO x;
+ ALTER TABLE t1 RENAME b TO y;		-- to be failed
+ ALTER TABLE t3 RENAME d TO z;
+ SELECT * FROM t3;
+ DROP TABLE t3;
+ DROP TABLE t2;
+ DROP TABLE t1;
*** src/test/regress/expected/inherit.out	(revision 2486)
--- src/test/regress/expected/inherit.out	(working copy)
***************
*** 1057,1059 ****
--- 1057,1076 ----
  ERROR:  column "a" has a storage parameter conflict
  DETAIL:  MAIN versus EXTENDED
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ -- Test for renaming
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE t2 (b int, c int);
+ CREATE TABLE t3 (d int) INHERITS(t1, t2);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 RENAME a TO x;
+ ALTER TABLE t1 RENAME b TO y;		-- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ ALTER TABLE t3 RENAME d TO z;
+ SELECT * FROM t3;
+  x | b | c | z 
+ ---+---+---+---
+ (0 rows)
+ 
+ DROP TABLE t3;
+ DROP TABLE t2;
+ DROP TABLE t1;
*** src/backend/commands/tablecmds.c	(revision 2486)
--- src/backend/commands/tablecmds.c	(working copy)
***************
*** 2024,2029 ****
--- 2024,2040 ----
  				 errmsg("cannot rename inherited column \"%s\"",
  						oldattname)));
  
+ 	/*
+ 	 * if the attribute is multiple inherited, forbid the renaming,
+ 	 * even if we are already inside a recursive rename, because we
+ 	 * have no reasonable way to keep its integrity.
+ 	 */
+ 	if (attform->attinhcount > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ 				 (errmsg("cannot rename multiple inherited column \"%s\"",
+ 						 oldattname))));
+ 
  	/* should not already exist */
  	/* this test is deliberately not attisdropped-aware */
  	if (SearchSysCacheExists(ATTNAME,
#7Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#6)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2009/12/16 KaiGai Kohei <kaigai@ak.jp.nec.com>:

It is a patch for the matter which I reported before.

When a column is inherited from multiple relations, ALTER TABLE with
RENAME TO option is problematic.
This patch fixes the matter. In correctly, it prevent to rename columns
inherited from multiple relations and merged.

No longer applies. Can you rebase?

Thanks,

...Robert

#8KaiGai Kohei
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#7)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2009/12/30 10:38), Robert Haas wrote:

2009/12/16 KaiGai Kohei<kaigai@ak.jp.nec.com>:

It is a patch for the matter which I reported before.

When a column is inherited from multiple relations, ALTER TABLE with
RENAME TO option is problematic.
This patch fixes the matter. In correctly, it prevent to rename columns
inherited from multiple relations and merged.

No longer applies. Can you rebase?

The attached patch is the rebased revision.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-fix-inherit-rename.2.patchapplication/octect-stream; name=pgsql-fix-inherit-rename.2.patchDownload
Index: src/test/regress/sql/inherit.sql
===================================================================
*** src/test/regress/sql/inherit.sql	(revision 2531)
--- src/test/regress/sql/inherit.sql	(working copy)
*************** CREATE TABLE inh_error1 () INHERITS (t1,
*** 334,336 ****
--- 334,348 ----
  CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
  
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ 
+ -- Test for renaming
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE t2 (b int, c int);
+ CREATE TABLE t3 (d int) INHERITS(t1, t2);
+ ALTER TABLE t1 RENAME a TO x;
+ ALTER TABLE t1 RENAME b TO y;		-- to be failed
+ ALTER TABLE t3 RENAME d TO z;
+ SELECT * FROM t3;
+ DROP TABLE t3;
+ DROP TABLE t2;
+ DROP TABLE t1;
Index: src/test/regress/expected/inherit.out
===================================================================
*** src/test/regress/expected/inherit.out	(revision 2531)
--- src/test/regress/expected/inherit.out	(working copy)
*************** NOTICE:  merging column "a" with inherit
*** 1053,1055 ****
--- 1053,1072 ----
  ERROR:  column "a" has a storage parameter conflict
  DETAIL:  MAIN versus EXTENDED
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ -- Test for renaming
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE t2 (b int, c int);
+ CREATE TABLE t3 (d int) INHERITS(t1, t2);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 RENAME a TO x;
+ ALTER TABLE t1 RENAME b TO y;		-- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ ALTER TABLE t3 RENAME d TO z;
+ SELECT * FROM t3;
+  x | b | c | z 
+ ---+---+---+---
+ (0 rows)
+ 
+ DROP TABLE t3;
+ DROP TABLE t2;
+ DROP TABLE t1;
Index: src/backend/commands/tablecmds.c
===================================================================
*** src/backend/commands/tablecmds.c	(revision 2531)
--- src/backend/commands/tablecmds.c	(working copy)
*************** renameatt(Oid myrelid,
*** 2009,2014 ****
--- 2009,2025 ----
  				 errmsg("cannot rename inherited column \"%s\"",
  						oldattname)));
  
+ 	/*
+ 	 * if the attribute is multiple inherited, forbid the renaming,
+ 	 * even if we are already inside a recursive rename, because we
+ 	 * have no reasonable way to keep its integrity.
+ 	 */
+ 	if (attform->attinhcount > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ 				 (errmsg("cannot rename multiple inherited column \"%s\"",
+ 						 oldattname))));
+ 
  	/* new name should not already exist */
  
  	/* this test is deliberately not attisdropped-aware */
#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: KaiGai Kohei (#8)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

KaiGai Kohei <kaigai@kaigai.gr.jp> writes:

(2009/12/30 10:38), Robert Haas wrote:

No longer applies. Can you rebase?

The attached patch is the rebased revision.

I'm not really impressed with this patch, because it will reject
perfectly legitimate multiple-inheritance cases (ie, cases where there's
more than one inheritance path from the same parent). This works fine
at the moment:

regression=# create table p1(f1 int, f2 int);
CREATE TABLE
regression=# create table c1(f3 int) inherits (p1);
CREATE TABLE
regression=# create table c2(f4 int) inherits (p1);
CREATE TABLE
regression=# create table cc(f5 int) inherits (c1,c2);
NOTICE: merging multiple inherited definitions of column "f1"
NOTICE: merging multiple inherited definitions of column "f2"
CREATE TABLE
regression=# \d cc
Table "public.cc"
Column | Type | Modifiers
--------+---------+-----------
f1 | integer |
f2 | integer |
f3 | integer |
f4 | integer |
f5 | integer |
Inherits: c1,
c2

regression=# alter table p1 rename f2 to ff2;
ALTER TABLE
regression=# \d cc
Table "public.cc"
Column | Type | Modifiers
--------+---------+-----------
f1 | integer |
ff2 | integer |
f3 | integer |
f4 | integer |
f5 | integer |
Inherits: c1,
c2

I don't think that protecting against cases where things won't work
is an adequate reason for breaking cases that do work.

regards, tom lane

#10Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#9)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Sat, Jan 2, 2010 at 2:32 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

KaiGai Kohei <kaigai@kaigai.gr.jp> writes:

(2009/12/30 10:38), Robert Haas wrote:

No longer applies.  Can you rebase?

The attached patch is the rebased revision.

I'm not really impressed with this patch, because it will reject
perfectly legitimate multiple-inheritance cases (ie, cases where there's
more than one inheritance path from the same parent).  This works fine
at the moment:

[...]

I don't think that protecting against cases where things won't work
is an adequate reason for breaking cases that do work.

Upthread you appeared to be endorsing what KaiGai has implemented here:

http://archives.postgresql.org/pgsql-hackers/2009-11/msg00147.php

Rereading this a few times, perhaps you meant that we should prohibit
renaming an ancestor when one of its descendents has a second and
distinct ancestor, but the email you actually sent reads as if you
were endorsing a blanket prohibition when attinhcount > 1. Can you
clarify?

Thanks,

...Robert

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#10)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

Robert Haas <robertmhaas@gmail.com> writes:

Upthread you appeared to be endorsing what KaiGai has implemented here:
http://archives.postgresql.org/pgsql-hackers/2009-11/msg00147.php

No, I said that forbidding conflicting renames would be a good solution.
I did not endorse any specific means of testing for such a conflict.
The one in this patch is not good enough to avoid breaking cases that
actually are perfectly OK.

It would be possible to detect the problematic cases correctly by first
descending the inheritance tree and counting the number of arrivals at
different children, and then doing it again and complaining if
attinhcount was different from the count obtained the first time.
This could probably be done by modifying find_all_inheritors to count
duplicate occurrences rather than just discarding them. Whether it's
worth it is not clear.

In practice the reasonable engineering alternatives may just be to do
what KaiGai's patch does, or to do nothing. In that case I think a good
argument can be made for the latter. Nobody has ever complained about
this from the field AFAIR; but we might get complaints if we disable
cases that used to work fine.

regards, tom lane

#12Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#11)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Jan 3, 2010, at 12:31 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

In practice the reasonable engineering alternatives may just be to do
what KaiGai's patch does, or to do nothing. In that case I think a
good
argument can be made for the latter. Nobody has ever complained about
this from the field AFAIR; but we might get complaints if we disable
cases that used to work fine.

Maybe. The current behavior of allowing the rename but then breaking
queries certainly isn't awesome. I think if someone is willing to
implement a more careful check we should accept it.

...Robert

#13KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#12)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/04 4:06), Robert Haas wrote:

On Jan 3, 2010, at 12:31 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

In practice the reasonable engineering alternatives may just be to do
what KaiGai's patch does, or to do nothing. In that case I think a good
argument can be made for the latter. Nobody has ever complained about
this from the field AFAIR; but we might get complaints if we disable
cases that used to work fine.

Maybe. The current behavior of allowing the rename but then breaking
queries certainly isn't awesome. I think if someone is willing to
implement a more careful check we should accept it.

The condition to prevent problematic renaming might be modified to handle
diamond inheritances correctly.

The current patch raises an error when pg_attribute.inhcount > 1.
But, in actually, it should raise an error when the number of origins
of the attribute to be renamed is larger than 1.
It shall be match with the inhcount unless it does not have diamond
inheritances.

We can easily check the number of origins with walking on the pg_inherits
catalog. So, it seems to me the condition should be rewritten like:

BEFORE:
if (attform->attinhcount > 1)
ereport(ERROR, ...);

AFTER:
if (number_of_attribute_origin(myrelid, oldattname) > 1)
ereport(ERROR, ...);

Am I missing something?

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#14Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#13)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/1/3 KaiGai Kohei <kaigai@ak.jp.nec.com>:

(2010/01/04 4:06), Robert Haas wrote:

On Jan 3, 2010, at 12:31 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

In practice the reasonable engineering alternatives may just be to do
what KaiGai's patch does, or to do nothing. In that case I think a good
argument can be made for the latter. Nobody has ever complained about
this from the field AFAIR; but we might get complaints if we disable
cases that used to work fine.

Maybe. The current behavior of allowing the rename but then breaking
queries certainly isn't awesome. I think if someone is willing to
implement a more careful check we should accept it.

The condition to prevent problematic renaming might be modified to handle
diamond inheritances correctly.

The current patch raises an error when pg_attribute.inhcount > 1.
But, in actually, it should raise an error when the number of origins
of the attribute to be renamed is larger than 1.
It shall be match with the inhcount unless it does not have diamond
inheritances.

We can easily check the number of origins with walking on the pg_inherits
catalog. So, it seems to me the condition should be rewritten like:

BEFORE:
 if (attform->attinhcount > 1)
     ereport(ERROR, ...);

AFTER:
 if (number_of_attribute_origin(myrelid, oldattname) > 1)
     ereport(ERROR, ...);

Am I missing something?

That sounds about right to me, though that function doesn't exist yet. :-)

...Robert

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#14)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

Robert Haas <robertmhaas@gmail.com> writes:

2010/1/3 KaiGai Kohei <kaigai@ak.jp.nec.com>:

�if (number_of_attribute_origin(myrelid, oldattname) > 1)
� � �ereport(ERROR, ...);

Am I missing something?

That sounds about right to me,

It looks remarkably inefficient to me. Do you propose to search the
entire database's inheritance tree to derive that number? And do it
over again at each child table? The method I suggested would allow the
necessary information to be extracted during the initial search for
child tables, which we have to do anyway.

regards, tom lane

#16KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Tom Lane (#15)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/04 13:18), Tom Lane wrote:

Robert Haas<robertmhaas@gmail.com> writes:

2010/1/3 KaiGai Kohei<kaigai@ak.jp.nec.com>:

�if (number_of_attribute_origin(myrelid, oldattname)> 1)
� � �ereport(ERROR, ...);

Am I missing something?

That sounds about right to me,

It looks remarkably inefficient to me. Do you propose to search the
entire database's inheritance tree to derive that number? And do it
over again at each child table?

Yes,

The method I suggested would allow the
necessary information to be extracted during the initial search for
child tables, which we have to do anyway.

find_all_inheritors() returns a clean list which does not contain
duplicated OID of the inherited relation, so it seems to me we need
to change the function prototype but it affects other parts, or to add
a new function which also returns number of duplications, not only OIDs.

Or, we can call find_inheritance_children() in renameatt() as if
find_all_inheritors() doing except for list_concat_unique_oid().

What is the most preferable?
I don't have any preference in the way to fix the problem.

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#17KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#16)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

The method I suggested would allow the
necessary information to be extracted during the initial search for
child tables, which we have to do anyway.

find_all_inheritors() returns a clean list which does not contain
duplicated OID of the inherited relation, so it seems to me we need
to change the function prototype but it affects other parts, or to add
a new function which also returns number of duplications, not only OIDs.

Or, we can call find_inheritance_children() in renameatt() as if
find_all_inheritors() doing except for list_concat_unique_oid().

The attached patch modified the condition to prevent renaming.

It computes an expected inhcount for each child relations on the initial
call of the renameatt() for the parent relation.
The find_all_inheritors_with_inhcount() returns OID of the inherited
relations and the expected inhcoundt. If a child relation has diamond
inheritance tree, it has its expected inhcount larger than 1.

This patch raises an error, if pg_attribute.inhcount is larger than
the expected inhcount. It can be happen when the attribute to be
renamed is merged from any other unrelated relations in the child
relations.

See the example:

postgres=# CREATE TABLE t1 (a int);
CREATE TABLE
postgres=# CREATE TABLE t2 (b int) inherits (t1);
CREATE TABLE
postgres=# CREATE TABLE t3 (c int) inherits (t1);
CREATE TABLE
postgres=# CREATE TABLE t4 (d int) inherits (t2, t3);
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE

postgres=# ALTER TABLE t1 RENAME a TO x;
ALTER TABLE
postgres=# \d t4
Table "public.t4"
Column | Type | Modifiers
--------+---------+-----------
x | integer |
b | integer |
c | integer |
d | integer |
Inherits: t2,
t3

We can rename a of t1, t2, t3 and t4 correctly, although t4.a has inherited from
multiple relations.

postgres=# CREATE TABLE s1 (x int);
CREATE TABLE
postgres=# CREATE TABLE t5 (e int) inherits (t2, t3, s1);
NOTICE: merging multiple inherited definitions of column "x"
NOTICE: merging multiple inherited definitions of column "x"
CREATE TABLE
postgres=# ALTER TABLE t1 RENAME x TO y;
ERROR: cannot rename multiple inherited column "x"

But, the new t5 prevent to rename "x" to "y", because t5.x is also inherited from
the s1 and merged. So, its inhcount is 3 larger than expected inhcount (=2).

postgres=# SELECT attname, attinhcount FROM pg_attribute where attname='x' and attrelid='t5'::regclass;
attname | attinhcount
---------+-------------
x | 3
(1 row)

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-fix-inherit-rename.3.patchapplication/octect-stream; name=pgsql-fix-inherit-rename.3.patchDownload
Index: base/src/test/regress/sql/inherit.sql
===================================================================
*** base/src/test/regress/sql/inherit.sql	(revision 2538)
--- base/src/test/regress/sql/inherit.sql	(working copy)
*************** CREATE TABLE inh_error1 () INHERITS (t1,
*** 334,336 ****
--- 334,348 ----
  CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
  
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ 
+ -- Test for renaming
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE t2 (b int, c int);
+ CREATE TABLE t3 (d int) INHERITS(t1, t2);
+ ALTER TABLE t1 RENAME a TO x;
+ ALTER TABLE t1 RENAME b TO y;		-- to be failed
+ ALTER TABLE t3 RENAME d TO z;
+ SELECT * FROM t3;
+ DROP TABLE t3;
+ DROP TABLE t2;
+ DROP TABLE t1;
Index: base/src/test/regress/expected/inherit.out
===================================================================
*** base/src/test/regress/expected/inherit.out	(revision 2538)
--- base/src/test/regress/expected/inherit.out	(working copy)
*************** NOTICE:  merging column "a" with inherit
*** 1053,1055 ****
--- 1053,1072 ----
  ERROR:  column "a" has a storage parameter conflict
  DETAIL:  MAIN versus EXTENDED
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ -- Test for renaming
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE t2 (b int, c int);
+ CREATE TABLE t3 (d int) INHERITS(t1, t2);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 RENAME a TO x;
+ ALTER TABLE t1 RENAME b TO y;		-- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ ALTER TABLE t3 RENAME d TO z;
+ SELECT * FROM t3;
+  x | b | c | z 
+ ---+---+---+---
+ (0 rows)
+ 
+ DROP TABLE t3;
+ DROP TABLE t2;
+ DROP TABLE t1;
Index: base/src/include/commands/tablecmds.h
===================================================================
*** base/src/include/commands/tablecmds.h	(revision 2538)
--- base/src/include/commands/tablecmds.h	(working copy)
*************** extern void ExecuteTruncate(TruncateStmt
*** 42,47 ****
--- 42,48 ----
  extern void renameatt(Oid myrelid,
  		  const char *oldattname,
  		  const char *newattname,
+ 		  int expected_inhcount,
  		  bool recurse,
  		  bool recursing);
  
Index: base/src/include/catalog/pg_inherits_fn.h
===================================================================
*** base/src/include/catalog/pg_inherits_fn.h	(revision 2538)
--- base/src/include/catalog/pg_inherits_fn.h	(working copy)
***************
*** 19,24 ****
--- 19,28 ----
  
  extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
  extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode);
+ extern void find_all_inheritors_with_inhcount(Oid parentrelId,
+ 											  LOCKMODE lockmode,
+ 											  List **child_oids,
+ 											  List **child_inhs);
  extern bool has_subclass(Oid relationId);
  extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
  
Index: base/src/backend/commands/tablecmds.c
===================================================================
*** base/src/backend/commands/tablecmds.c	(revision 2538)
--- base/src/backend/commands/tablecmds.c	(working copy)
*************** void
*** 1908,1913 ****
--- 1908,1914 ----
  renameatt(Oid myrelid,
  		  const char *oldattname,
  		  const char *newattname,
+ 		  int expected_inhcount,
  		  bool recurse,
  		  bool recursing)
  {
*************** renameatt(Oid myrelid,
*** 1948,1971 ****
  	 */
  	if (recurse)
  	{
! 		ListCell   *child;
! 		List	   *children;
! 
! 		children = find_all_inheritors(myrelid, AccessExclusiveLock);
  
  		/*
! 		 * 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);
  
! 			if (childrelid == myrelid)
  				continue;
  			/* note we need not recurse again */
! 			renameatt(childrelid, oldattname, newattname, false, true);
  		}
  	}
  	else
--- 1949,1998 ----
  	 */
  	if (recurse)
  	{
! 		List	   *child_oids, *child_inhs;
! 		ListCell   *lo, *li;
  
  		/*
! 		 * If we cound find an expected inhcount larger than 1,
! 		 * t means the relation has diamond inheritance tree.
! 		 *
! 		 * If an attribute to be renamed is inherited from multiple
! 		 * parent relations independently defined then merged, it
! 		 * is problematic to rename the attribute, because anohter
! 		 * inheritance tree assumes the child relation still has the
! 		 * inherited attributes, but it will be incorrect.
! 		 * So, we have to forbide to rename an attribute which is
! 		 * inherited from multiple parent relations.
! 		 *
! 		 * However, we have an exception case when the attribute is
! 		 * inherited from multiple parent relations, but these ones
! 		 * eventually have same origion.
! 		 * For example, when TBL_A is inherited by TBL_X and TBL_Y,
! 		 * these tables are inherited by TBL_XY, the TBL_XY indeed
! 		 * has multiple parent relations and its attributes was
! 		 * merged. But, these have same origin, and we have no reason
! 		 * to prevent renaming them.
! 		 *
! 		 * So, we check whether the child relation has diamond
! 		 * inheritance, or not, here. If pg_attribute.attinhcount is
! 		 * larger than expected inhcount, it means the attribute was
! 		 * merged with any other attributes come from other parent
! 		 * relations.
  		 */
! 		find_all_inheritors_with_inhcount(myrelid, AccessExclusiveLock,
! 										  &child_oids, &child_inhs);
! 
! 		forboth (lo, child_oids, li, child_inhs)
  		{
! 			Oid		child_relid = lfirst_oid(lo);
! 			int		child_inhcount = lfirst_int(li);
  
! 			if (child_relid == myrelid)
  				continue;
+ 
  			/* note we need not recurse again */
! 			renameatt(child_relid, oldattname, newattname,
! 					  child_inhcount, false, true);
  		}
  	}
  	else
*************** renameatt(Oid myrelid,
*** 2009,2014 ****
--- 2036,2052 ----
  				 errmsg("cannot rename inherited column \"%s\"",
  						oldattname)));
  
+ 	/*
+ 	 * if the attribute is multiple inherited, forbid the renaming,
+ 	 * even if we are already inside a recursive rename, because we
+ 	 * have no reasonable way to keep its integrity.
+ 	 */
+ 	if (attform->attinhcount > expected_inhcount)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ 				 (errmsg("cannot rename multiple inherited column \"%s\"",
+ 						 oldattname))));
+ 
  	/* new name should not already exist */
  
  	/* this test is deliberately not attisdropped-aware */
Index: base/src/backend/commands/alter.c
===================================================================
*** base/src/backend/commands/alter.c	(revision 2538)
--- base/src/backend/commands/alter.c	(working copy)
*************** ExecRenameStmt(RenameStmt *stmt)
*** 125,130 ****
--- 125,131 ----
  						renameatt(relid,
  								  stmt->subname,		/* old att name */
  								  stmt->newname,		/* new att name */
+ 								  1,			/* expected inhcount */
  								  interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
  								  false);		/* recursing already? */
  						break;
Index: base/src/backend/catalog/pg_inherits.c
===================================================================
*** base/src/backend/catalog/pg_inherits.c	(revision 2538)
--- base/src/backend/catalog/pg_inherits.c	(working copy)
*************** find_all_inheritors(Oid parentrelId, LOC
*** 190,195 ****
--- 190,256 ----
  	return rels_list;
  }
  
+ /*
+  * find_all_inheritors_with_inhcount
+  *
+  * It is same as find_all_inheritors, except for it returns an excepted
+  * inhcount for each children.
+  */
+ void
+ find_all_inheritors_with_inhcount(Oid parentrelId, LOCKMODE lockmode,
+ 								  List **child_oids, List **child_inhs)
+ {
+ 	List	   *children, *rels_oids, *rels_inhs;
+ 	ListCell   *lc, *lo, *li;
+ 
+ 	/*
+ 	 * The root of the inheritance tree always has 1 as an expected
+ 	 * inhcount.
+ 	 */
+ 	rels_oids = list_make1_oid(parentrelId);
+ 	rels_inhs = list_make1_int(1);
+ 
+ 	children = find_inheritance_children(parentrelId, lockmode);
+ 	foreach (lc, children)
+ 	{
+ 		Oid		cur_relid = lfirst_oid(lc);
+ 		List   *cur_children;
+ 		bool	found = false;
+ 
+ 		forboth (lo, rels_oids, li, rels_inhs)
+ 		{
+ 			/*
+ 			 * If we found a duplicated relation within a certain inheritance
+ 			 * tree, it means a diamond-inheritance. So, we increment its
+ 			 * expected inhcount without appending an element to the list.
+ 			 */
+ 			if (lfirst_oid(lo) == cur_relid)
+ 			{
+ 				lfirst_int(li)++;
+ 				found = true;
+ 				break;
+ 			}
+ 		}
+ 
+ 		if (!found)
+ 		{
+ 			rels_oids = lappend_oid(rels_oids, cur_relid);
+ 			rels_inhs = lappend_int(rels_inhs, 1);
+ 		}
+ 
+ 		/*
+ 		 * Unlike find_all_inheritors(), we need to walk on child relations
+ 		 * that have diamond inheritance tree, because this function has to
+ 		 * return correct expected inhecount to the caller.
+ 		 */
+ 		cur_children = find_inheritance_children(cur_relid, lockmode);
+ 
+ 		children = list_concat(children, cur_children);
+ 	}
+ 
+ 	*child_oids = rels_oids;
+ 	*child_inhs = rels_inhs;
+ }
  
  /*
   * has_subclass - does this relation have any children?
#18Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#15)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Sun, Jan 3, 2010 at 11:18 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

2010/1/3 KaiGai Kohei <kaigai@ak.jp.nec.com>:

 if (number_of_attribute_origin(myrelid, oldattname) > 1)
     ereport(ERROR, ...);

Am I missing something?

That sounds about right to me,

It looks remarkably inefficient to me.  Do you propose to search the
entire database's inheritance tree to derive that number?  And do it
over again at each child table?  The method I suggested would allow the
necessary information to be extracted during the initial search for
child tables, which we have to do anyway.

I haven't read the code in enough detail to have an educated opinion
about whether that would induce enough overhead to be worth worrying
about it, so I will refrain from comment on this until I have done my
homework.

...Robert

#19KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#17)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

The similar matter can be reproduced with ALTER TABLE ... TYPE statement,
not only RENAME TO option.

postgres=# CREATE TABLE t1 (a int);
CREATE TABLE
postgres=# CREATE TABLE s1 (a int);
CREATE TABLE

postgres=# CREATE TABLE ts (b int) inherits (t1, s1);
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE
postgres=# ALTER TABLE t1 ALTER a TYPE text;
ALTER TABLE

postgres=# insert into t1 values ('aaa');
INSERT 0 1
postgres=# insert into ts values ('bbb', 2);
INSERT 0 1
postgres=# SELECT * FROM t1;
a
-----
aaa
bbb
(2 rows)

postgres=# SELECT * FROM s1;
ERROR: attribute "a" of relation "ts" does not match parent's type

In the renameatt(), we can count an expected inhcount of the column to be
renamed on find_all_inheritors() at the top-level recursion.
But it does not work well for the new one, because it is handled within
the common ATPrepCmd() scheme.

I reconsidered that we need a function to check whether the given column
is inherited from multiple root parents, or not, for each levels.
Then, it can be called from both of renameatt() and ATPrepAlterColumnType().

(2010/01/04 18:55), KaiGai Kohei wrote:

The method I suggested would allow the
necessary information to be extracted during the initial search for
child tables, which we have to do anyway.

find_all_inheritors() returns a clean list which does not contain
duplicated OID of the inherited relation, so it seems to me we need
to change the function prototype but it affects other parts, or to add
a new function which also returns number of duplications, not only OIDs.

Or, we can call find_inheritance_children() in renameatt() as if
find_all_inheritors() doing except for list_concat_unique_oid().

The attached patch modified the condition to prevent renaming.

It computes an expected inhcount for each child relations on the initial
call of the renameatt() for the parent relation.
The find_all_inheritors_with_inhcount() returns OID of the inherited
relations and the expected inhcoundt. If a child relation has diamond
inheritance tree, it has its expected inhcount larger than 1.

This patch raises an error, if pg_attribute.inhcount is larger than
the expected inhcount. It can be happen when the attribute to be
renamed is merged from any other unrelated relations in the child
relations.

See the example:

postgres=# CREATE TABLE t1 (a int);
CREATE TABLE
postgres=# CREATE TABLE t2 (b int) inherits (t1);
CREATE TABLE
postgres=# CREATE TABLE t3 (c int) inherits (t1);
CREATE TABLE
postgres=# CREATE TABLE t4 (d int) inherits (t2, t3);
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE

postgres=# ALTER TABLE t1 RENAME a TO x;
ALTER TABLE
postgres=# \d t4
Table "public.t4"
Column | Type | Modifiers
--------+---------+-----------
x | integer |
b | integer |
c | integer |
d | integer |
Inherits: t2,
t3

We can rename a of t1, t2, t3 and t4 correctly, although t4.a has inherited from
multiple relations.

postgres=# CREATE TABLE s1 (x int);
CREATE TABLE
postgres=# CREATE TABLE t5 (e int) inherits (t2, t3, s1);
NOTICE: merging multiple inherited definitions of column "x"
NOTICE: merging multiple inherited definitions of column "x"
CREATE TABLE
postgres=# ALTER TABLE t1 RENAME x TO y;
ERROR: cannot rename multiple inherited column "x"

But, the new t5 prevent to rename "x" to "y", because t5.x is also inherited from
the s1 and merged. So, its inhcount is 3 larger than expected inhcount (=2).

postgres=# SELECT attname, attinhcount FROM pg_attribute where attname='x' and attrelid='t5'::regclass;
attname | attinhcount
---------+-------------
x | 3
(1 row)

Thanks,

--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#20KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#19)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

The attached patch fixes bugs when we try to rename (and change type) on
a column inherited from the different origin and merged.

This patch adds:

List *find_column_origin(Oid relOid, const char *colName)

It returns the list of relation OIDs which originally defines the given
column. In most cases, it returns a list with an element. But, if the column
is inherited from multiple parent relations and merged during the inheritance
tree, the returned list contains multiple OIDs.
In this case, we have to forbid changing type and renaming to keep correctness
of the table definition.

Example)
postgres=# CREATE TABLE t1 (a int, b int);
CREATE TABLE
postgres=# CREATE TABLE s1 (b int, c int);
CREATE TABLE
postgres=# CREATE TABLE ts (d int) INHERITS (t1, s1);
NOTICE: merging multiple inherited definitions of column "b"
CREATE TABLE

postgres=# ALTER TABLE t1 ALTER a TYPE text;
ALTER TABLE
postgres=# ALTER TABLE t1 ALTER b TYPE text;
ERROR: cannot alter multiple inherited column "b" <---- (*)
(*) ts.b is also inherited from s1, not only t1, so forbid changing type

postgres=# ALTER TABLE s1 RENAME c TO cc;
ALTER TABLE
postgres=# ALTER TABLE s1 RENAME b TO bb;
ERROR: cannot rename multiple inherited column "b" <---- (*)
(*) ts.b is also inherited from s1, not only t1, so forbid renaming

postgres=# \d+ ts
Table "public.ts"
Column | Type | Modifiers | Storage | Description
--------+---------+-----------+----------+-------------
a | text | | extended |
b | integer | | plain |
cc | integer | | plain |
d | integer | | plain |
Inherits: t1,
s1
Has OIDs: no

In the case when a column is inherited from multiple relations but it
eventually has same origin (diamond inheritance tree case), we don't
need to forbid these operations.
In this case, find_column_origin() does not return duplicated OIDs,
all the caller has to do is to check whether length of the returned
list is larger than 1, or no.

Thanks,

(2010/01/14 12:43), KaiGai Kohei wrote:

The similar matter can be reproduced with ALTER TABLE ... TYPE statement,
not only RENAME TO option.

postgres=# CREATE TABLE t1 (a int);
CREATE TABLE
postgres=# CREATE TABLE s1 (a int);
CREATE TABLE

postgres=# CREATE TABLE ts (b int) inherits (t1, s1);
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE
postgres=# ALTER TABLE t1 ALTER a TYPE text;
ALTER TABLE

postgres=# insert into t1 values ('aaa');
INSERT 0 1
postgres=# insert into ts values ('bbb', 2);
INSERT 0 1
postgres=# SELECT * FROM t1;
a
-----
aaa
bbb
(2 rows)

postgres=# SELECT * FROM s1;
ERROR: attribute "a" of relation "ts" does not match parent's type

In the renameatt(), we can count an expected inhcount of the column to be
renamed on find_all_inheritors() at the top-level recursion.
But it does not work well for the new one, because it is handled within
the common ATPrepCmd() scheme.

I reconsidered that we need a function to check whether the given column
is inherited from multiple root parents, or not, for each levels.
Then, it can be called from both of renameatt() and ATPrepAlterColumnType().

(2010/01/04 18:55), KaiGai Kohei wrote:

The method I suggested would allow the
necessary information to be extracted during the initial search for
child tables, which we have to do anyway.

find_all_inheritors() returns a clean list which does not contain
duplicated OID of the inherited relation, so it seems to me we need
to change the function prototype but it affects other parts, or to add
a new function which also returns number of duplications, not only OIDs.

Or, we can call find_inheritance_children() in renameatt() as if
find_all_inheritors() doing except for list_concat_unique_oid().

The attached patch modified the condition to prevent renaming.

It computes an expected inhcount for each child relations on the initial
call of the renameatt() for the parent relation.
The find_all_inheritors_with_inhcount() returns OID of the inherited
relations and the expected inhcoundt. If a child relation has diamond
inheritance tree, it has its expected inhcount larger than 1.

This patch raises an error, if pg_attribute.inhcount is larger than
the expected inhcount. It can be happen when the attribute to be
renamed is merged from any other unrelated relations in the child
relations.

See the example:

postgres=# CREATE TABLE t1 (a int);
CREATE TABLE
postgres=# CREATE TABLE t2 (b int) inherits (t1);
CREATE TABLE
postgres=# CREATE TABLE t3 (c int) inherits (t1);
CREATE TABLE
postgres=# CREATE TABLE t4 (d int) inherits (t2, t3);
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE

postgres=# ALTER TABLE t1 RENAME a TO x;
ALTER TABLE
postgres=# \d t4
Table "public.t4"
Column | Type | Modifiers
--------+---------+-----------
x | integer |
b | integer |
c | integer |
d | integer |
Inherits: t2,
t3

We can rename a of t1, t2, t3 and t4 correctly, although t4.a has inherited from
multiple relations.

postgres=# CREATE TABLE s1 (x int);
CREATE TABLE
postgres=# CREATE TABLE t5 (e int) inherits (t2, t3, s1);
NOTICE: merging multiple inherited definitions of column "x"
NOTICE: merging multiple inherited definitions of column "x"
CREATE TABLE
postgres=# ALTER TABLE t1 RENAME x TO y;
ERROR: cannot rename multiple inherited column "x"

But, the new t5 prevent to rename "x" to "y", because t5.x is also inherited from
the s1 and merged. So, its inhcount is 3 larger than expected inhcount (=2).

postgres=# SELECT attname, attinhcount FROM pg_attribute where attname='x' and attrelid='t5'::regclass;
attname | attinhcount
---------+-------------
x | 3
(1 row)

Thanks,

--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-fix-inherit-rename.4.patchapplication/octect-stream; name=pgsql-fix-inherit-rename.4.patchDownload
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 28,33 ****
--- 28,34 ----
  #include "parser/parse_type.h"
  #include "storage/lmgr.h"
  #include "utils/fmgroids.h"
+ #include "utils/lsyscache.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
  
***************
*** 190,195 **** find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
--- 191,257 ----
  	return rels_list;
  }
  
+ /*
+  * find_column_origin
+  *
+  * It returns a list of relation OIDs that defines the given column originally.
+  * In most cases, a certain column has one origin, unless it (or its parent)
+  * has multiple inheritance tree.
+  * Note that it returns a unique OID for diamond-type inheritance tree, because
+  * it shares same origin.
+  */
+ List *
+ find_column_origin(Oid relOid, const char *colName)
+ {
+ 	Relation	inhRel;
+ 	SysScanDesc	scan;
+ 	ScanKeyData	skey[1];
+ 	HeapTuple	tuple;
+ 	List	   *results = NIL;
+ 
+ 	/*
+ 	 * This relation does not contain the given column, is also means
+ 	 * the parent relation also.
+ 	 */
+ 	if (get_attnum(relOid, colName) == InvalidAttrNumber)
+ 		return NIL;
+ 
+ 	inhRel = heap_open(InheritsRelationId, AccessShareLock);
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_inherits_inhrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(relOid));
+ 
+ 	scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId,
+ 							  true, SnapshotNow, 1, &skey);
+ 
+ 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ 	{
+ 		Form_pg_inherits	inhForm = (Form_pg_inherits) GETSTRUCT(tuple);
+ 		List	   *temp;
+ 
+ 		temp = find_column_origin(inhForm->inhparent, colName);
+ 		if (temp == NIL)
+ 			continue;
+ 
+ 		/*
+ 		 * Add to the queue only those children not already seen. This avoids
+ 		 * making duplicate entries in case of multiple inheritance paths from
+ 		 * the same parent.  (It'll also keep us from getting into an infinite
+ 		 * loop, though theoretically there can't be any cycles in the
+ 		 * inheritance graph anyway.)
+ 		 */
+ 		results = list_concat_unique_oid(results, temp);
+ 	}
+ 	systable_endscan(scan);
+ 	heap_close(inhRel, AccessShareLock);
+ 
+ 	if (results == NIL)
+ 		results = list_make1_oid(relOid);
+ 
+ 	return results;
+ }
  
  /*
   * has_subclass - does this relation have any children?
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 2009,2014 **** renameatt(Oid myrelid,
--- 2009,2025 ----
  				 errmsg("cannot rename inherited column \"%s\"",
  						oldattname)));
  
+ 	/*
+ 	 * if the attribute is multiple inherited, forbid the renaming,
+ 	 * even if we are already inside a recursive rename, because we
+ 	 * have no reasonable way to keep its integrity.
+ 	 */
+ 	if (list_length(find_column_origin(myrelid, oldattname)) > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ 				 (errmsg("cannot rename multiple inherited column \"%s\"",
+ 						 oldattname))));
+ 
  	/* new name should not already exist */
  
  	/* this test is deliberately not attisdropped-aware */
***************
*** 5771,5776 **** ATPrepAlterColumnType(List **wqueue,
--- 5782,5788 ----
  					  bool recurse, bool recursing,
  					  AlterTableCmd *cmd)
  {
+ 	Oid			relOid = RelationGetRelid(rel);
  	char	   *colName = cmd->name;
  	TypeName   *typeName = (TypeName *) cmd->def;
  	HeapTuple	tuple;
***************
*** 5806,5811 **** ATPrepAlterColumnType(List **wqueue,
--- 5818,5830 ----
  				 errmsg("cannot alter inherited column \"%s\"",
  						colName)));
  
+ 	/* Don't alter multiple inherited columns */
+ 	if (list_length(find_column_origin(relOid, colName)) > 1)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ 				 (errmsg("cannot alter multiple inherited column \"%s\"",
+ 						 colName))));
+ 
  	/* Look up the target type */
  	targettype = typenameTypeId(NULL, typeName, &targettypmod);
  
*** a/src/include/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 19,24 ****
--- 19,25 ----
  
  extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
  extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode);
+ extern List *find_column_origin(Oid relOid, const char *colName);
  extern bool has_subclass(Oid relationId);
  extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
  
*** a/src/test/regress/expected/inherit.out
--- b/src/test/regress/expected/inherit.out
***************
*** 1053,1055 **** NOTICE:  merging column "a" with inherited definition
--- 1053,1133 ----
  ERROR:  column "a" has a storage parameter conflict
  DETAIL:  MAIN versus EXTENDED
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ -- Test for renaming and type changes (simple multiple inheritance)
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE s1 (b int, c int);
+ CREATE TABLE ts (d int) INHERITS (t1, s1);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE s1 ALTER b TYPE float;	-- to be failed
+ ERROR:  cannot alter multiple inherited column "b"
+ ALTER TABLE s1 ALTER c TYPE float;
+ ALTER TABLE ts ALTER c TYPE text;	-- to be failed
+ ERROR:  cannot alter inherited column "c"
+ ALTER TABLE ts ALTER d TYPE text;
+ ALTER TABLE t1 RENAME a TO aa;
+ ALTER TABLE t1 RENAME b TO bb;		-- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ ALTER TABLE ts RENAME aa TO aaa;	-- to be failed
+ ERROR:  cannot rename inherited column "aa"
+ ALTER TABLE ts RENAME d TO dd;
+ \d+ ts
+                        Table "public.ts"
+  Column |       Type       | Modifiers | Storage  | Description 
+ --------+------------------+-----------+----------+-------------
+  aa     | integer          |           | plain    | 
+  b      | integer          |           | plain    | 
+  c      | double precision |           | plain    | 
+  dd     | text             |           | extended | 
+ Inherits: t1,
+           s1
+ Has OIDs: no
+ 
+ DROP TABLE ts;
+ -- Test for renaming and type changes (diamond inheritance)
+ CREATE TABLE t2 (x int) INHERITS (t1);
+ CREATE TABLE t3 (y int) INHERITS (t1);
+ CREATE TABLE t4 (z int) INHERITS (t2, t3);
+ NOTICE:  merging multiple inherited definitions of column "aa"
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 ALTER aa TYPE float;
+ ALTER TABLE t1 RENAME aa TO aaa;
+ \d+ t4
+                        Table "public.t4"
+  Column |       Type       | Modifiers | Storage | Description 
+ --------+------------------+-----------+---------+-------------
+  aaa    | double precision |           | plain   | 
+  b      | integer          |           | plain   | 
+  x      | integer          |           | plain   | 
+  y      | integer          |           | plain   | 
+  z      | integer          |           | plain   | 
+ Inherits: t2,
+           t3
+ Has OIDs: no
+ 
+ CREATE TABLE ts (d int) INHERITS (t2, s1);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 ALTER aaa TYPE text;
+ ALTER TABLE t1 ALTER b TYPE text;	-- to be failed
+ ERROR:  cannot alter multiple inherited column "b"
+ ALTER TABLE t1 RENAME aaa TO aaaa;
+ ALTER TABLE t1 RENAME b TO bb;		-- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ \d+ ts
+                        Table "public.ts"
+  Column |       Type       | Modifiers | Storage  | Description 
+ --------+------------------+-----------+----------+-------------
+  aaaa   | text             |           | extended | 
+  b      | integer          |           | plain    | 
+  x      | integer          |           | plain    | 
+  c      | double precision |           | plain    | 
+  d      | integer          |           | plain    | 
+ Inherits: t2,
+           s1
+ Has OIDs: no
+ 
+ DROP TABLE t1, s1 CASCADE;
+ NOTICE:  drop cascades to 4 other objects
+ DETAIL:  drop cascades to table t2
+ drop cascades to table ts
+ drop cascades to table t3
+ drop cascades to table t4
*** a/src/test/regress/sql/inherit.sql
--- b/src/test/regress/sql/inherit.sql
***************
*** 334,336 **** CREATE TABLE inh_error1 () INHERITS (t1, t4);
--- 334,372 ----
  CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
  
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ 
+ -- Test for renaming and type changes (simple multiple inheritance)
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE s1 (b int, c int);
+ CREATE TABLE ts (d int) INHERITS (t1, s1);
+ 
+ ALTER TABLE s1 ALTER b TYPE float;	-- to be failed
+ ALTER TABLE s1 ALTER c TYPE float;
+ ALTER TABLE ts ALTER c TYPE text;	-- to be failed
+ ALTER TABLE ts ALTER d TYPE text;
+ 
+ ALTER TABLE t1 RENAME a TO aa;
+ ALTER TABLE t1 RENAME b TO bb;		-- to be failed
+ ALTER TABLE ts RENAME aa TO aaa;	-- to be failed
+ ALTER TABLE ts RENAME d TO dd;
+ \d+ ts
+ 
+ DROP TABLE ts;
+ 
+ -- Test for renaming and type changes (diamond inheritance)
+ CREATE TABLE t2 (x int) INHERITS (t1);
+ CREATE TABLE t3 (y int) INHERITS (t1);
+ CREATE TABLE t4 (z int) INHERITS (t2, t3);
+ 
+ ALTER TABLE t1 ALTER aa TYPE float;
+ ALTER TABLE t1 RENAME aa TO aaa;
+ \d+ t4
+ 
+ CREATE TABLE ts (d int) INHERITS (t2, s1);
+ ALTER TABLE t1 ALTER aaa TYPE text;
+ ALTER TABLE t1 ALTER b TYPE text;	-- to be failed
+ ALTER TABLE t1 RENAME aaa TO aaaa;
+ ALTER TABLE t1 RENAME b TO bb;		-- to be failed
+ \d+ ts
+ 
+ DROP TABLE t1, s1 CASCADE;
#21Bernd Helmle
mailings@oopsware.de
In reply to: KaiGai Kohei (#20)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

--On 14. Januar 2010 16:04:17 +0900 KaiGai Kohei <kaigai@ak.jp.nec.com>
wrote:

This patch adds:

List *find_column_origin(Oid relOid, const char *colName)

It returns the list of relation OIDs which originally defines the given
column. In most cases, it returns a list with an element. But, if the
column is inherited from multiple parent relations and merged during the
inheritance tree, the returned list contains multiple OIDs.
In this case, we have to forbid changing type and renaming to keep
correctness of the table definition.

Here's a slightly edited version of this patch from reviewing, fixing the
following:

* Fix a compiler warning by passing a pointer to skey to
systable_beginscan() (it's an array already)

* Edit some comments

The patch works as expected (at least, i don't see any remaining issues).
I'm going to mark this ready for committer.

--
Thanks

Bernd

Attachments:

pg-fix-inherit-rename-type-review.patchapplication/octet-stream; name=pg-fix-inherit-rename-type-review.patchDownload
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index e256f6f..fbde33a 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -28,6 +28,7 @@
 #include "parser/parse_type.h"
 #include "storage/lmgr.h"
 #include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
@@ -190,6 +191,67 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
 	return rels_list;
 }
 
+/*
+ * find_column_origin
+ *
+ * Returns a list of relation OIDs the given column is inherited from.
+ * In most cases, a column has one origin, unless it (or its parent)
+ * has multiple ancestors.
+ * Note that find_column_origin() returns a unique OID for diamond-type 
+ * inheritance trees, because all relations shares the same origin.
+ */
+List *
+find_column_origin(Oid relOid, const char *colName)
+{
+	Relation	inhRel;
+	SysScanDesc	scan;
+	ScanKeyData	skey[1];
+	HeapTuple	tuple;
+	List	   *results = NIL;
+
+	/*
+	 * This relation does not contain the given column, so its
+	 * parent neither.
+	 */
+	if (get_attnum(relOid, colName) == InvalidAttrNumber)
+		return NIL;
+
+	inhRel = heap_open(InheritsRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey[0],
+				Anum_pg_inherits_inhrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relOid));
+
+	scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId,
+							  true, SnapshotNow, 1, skey);
+
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_inherits	inhForm = (Form_pg_inherits) GETSTRUCT(tuple);
+		List	   *temp;
+
+		temp = find_column_origin(inhForm->inhparent, colName);
+		if (temp == NIL)
+			continue;
+
+		/*
+		 * Add to the queue only those children not already seen. This avoids
+		 * making duplicate entries in case of multiple inheritance paths from
+		 * the same parent.  (It'll also keep us from getting into an infinite
+		 * loop, though theoretically there can't be any cycles in the
+		 * inheritance graph anyway.)
+		 */
+		results = list_concat_unique_oid(results, temp);
+	}
+	systable_endscan(scan);
+	heap_close(inhRel, AccessShareLock);
+
+	if (results == NIL)
+		results = list_make1_oid(relOid);
+
+	return results;
+}
 
 /*
  * has_subclass - does this relation have any children?
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 10f0a94..4cd988c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2009,6 +2009,17 @@ renameatt(Oid myrelid,
 				 errmsg("cannot rename inherited column \"%s\"",
 						oldattname)));
 
+	/*
+	 * if the attribute is inherited multiple times, forbid the renaming,
+	 * even if we are already inside a recursive rename, because we
+	 * have no reasonable way to keep its integrity.
+	 */
+	if (list_length(find_column_origin(myrelid, oldattname)) > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 (errmsg("cannot rename multiple inherited column \"%s\"",
+						 oldattname))));
+
 	/* new name should not already exist */
 
 	/* this test is deliberately not attisdropped-aware */
@@ -5768,6 +5779,7 @@ ATPrepAlterColumnType(List **wqueue,
 					  bool recurse, bool recursing,
 					  AlterTableCmd *cmd)
 {
+	Oid			relOid = RelationGetRelid(rel);
 	char	   *colName = cmd->name;
 	TypeName   *typeName = (TypeName *) cmd->def;
 	HeapTuple	tuple;
@@ -5803,6 +5815,13 @@ ATPrepAlterColumnType(List **wqueue,
 				 errmsg("cannot alter inherited column \"%s\"",
 						colName)));
 
+	/* Don't alter multiple inherited columns */
+	if (list_length(find_column_origin(relOid, colName)) > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 (errmsg("cannot alter multiple inherited column \"%s\"",
+						 colName))));
+
 	/* Look up the target type */
 	targettype = typenameTypeId(NULL, typeName, &targettypmod);
 
diff --git a/src/include/catalog/pg_inherits_fn.h b/src/include/catalog/pg_inherits_fn.h
index 91baec3..a23f62b 100644
--- a/src/include/catalog/pg_inherits_fn.h
+++ b/src/include/catalog/pg_inherits_fn.h
@@ -19,6 +19,7 @@
 
 extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
 extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode);
+extern List *find_column_origin(Oid relOid, const char *colName);
 extern bool has_subclass(Oid relationId);
 extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
 
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 9c83a32..3a049cc 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1053,3 +1053,81 @@ NOTICE:  merging column "a" with inherited definition
 ERROR:  column "a" has a storage parameter conflict
 DETAIL:  MAIN versus EXTENDED
 DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+-- Test for renaming and type changes (simple multiple inheritance)
+CREATE TABLE t1 (a int, b int);
+CREATE TABLE s1 (b int, c int);
+CREATE TABLE ts (d int) INHERITS (t1, s1);
+NOTICE:  merging multiple inherited definitions of column "b"
+ALTER TABLE s1 ALTER b TYPE float;	-- to be failed
+ERROR:  cannot alter multiple inherited column "b"
+ALTER TABLE s1 ALTER c TYPE float;
+ALTER TABLE ts ALTER c TYPE text;	-- to be failed
+ERROR:  cannot alter inherited column "c"
+ALTER TABLE ts ALTER d TYPE text;
+ALTER TABLE t1 RENAME a TO aa;
+ALTER TABLE t1 RENAME b TO bb;		-- to be failed
+ERROR:  cannot rename multiple inherited column "b"
+ALTER TABLE ts RENAME aa TO aaa;	-- to be failed
+ERROR:  cannot rename inherited column "aa"
+ALTER TABLE ts RENAME d TO dd;
+\d+ ts
+                       Table "public.ts"
+ Column |       Type       | Modifiers | Storage  | Description 
+--------+------------------+-----------+----------+-------------
+ aa     | integer          |           | plain    | 
+ b      | integer          |           | plain    | 
+ c      | double precision |           | plain    | 
+ dd     | text             |           | extended | 
+Inherits: t1,
+          s1
+Has OIDs: no
+
+DROP TABLE ts;
+-- Test for renaming and type changes (diamond inheritance)
+CREATE TABLE t2 (x int) INHERITS (t1);
+CREATE TABLE t3 (y int) INHERITS (t1);
+CREATE TABLE t4 (z int) INHERITS (t2, t3);
+NOTICE:  merging multiple inherited definitions of column "aa"
+NOTICE:  merging multiple inherited definitions of column "b"
+ALTER TABLE t1 ALTER aa TYPE float;
+ALTER TABLE t1 RENAME aa TO aaa;
+\d+ t4
+                       Table "public.t4"
+ Column |       Type       | Modifiers | Storage | Description 
+--------+------------------+-----------+---------+-------------
+ aaa    | double precision |           | plain   | 
+ b      | integer          |           | plain   | 
+ x      | integer          |           | plain   | 
+ y      | integer          |           | plain   | 
+ z      | integer          |           | plain   | 
+Inherits: t2,
+          t3
+Has OIDs: no
+
+CREATE TABLE ts (d int) INHERITS (t2, s1);
+NOTICE:  merging multiple inherited definitions of column "b"
+ALTER TABLE t1 ALTER aaa TYPE text;
+ALTER TABLE t1 ALTER b TYPE text;	-- to be failed
+ERROR:  cannot alter multiple inherited column "b"
+ALTER TABLE t1 RENAME aaa TO aaaa;
+ALTER TABLE t1 RENAME b TO bb;		-- to be failed
+ERROR:  cannot rename multiple inherited column "b"
+\d+ ts
+                       Table "public.ts"
+ Column |       Type       | Modifiers | Storage  | Description 
+--------+------------------+-----------+----------+-------------
+ aaaa   | text             |           | extended | 
+ b      | integer          |           | plain    | 
+ x      | integer          |           | plain    | 
+ c      | double precision |           | plain    | 
+ d      | integer          |           | plain    | 
+Inherits: t2,
+          s1
+Has OIDs: no
+
+DROP TABLE t1, s1 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table t2
+drop cascades to table ts
+drop cascades to table t3
+drop cascades to table t4
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 192e860..82ee7d3 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -334,3 +334,39 @@ CREATE TABLE inh_error1 () INHERITS (t1, t4);
 CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
 
 DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+
+-- Test for renaming and type changes (simple multiple inheritance)
+CREATE TABLE t1 (a int, b int);
+CREATE TABLE s1 (b int, c int);
+CREATE TABLE ts (d int) INHERITS (t1, s1);
+
+ALTER TABLE s1 ALTER b TYPE float;	-- to be failed
+ALTER TABLE s1 ALTER c TYPE float;
+ALTER TABLE ts ALTER c TYPE text;	-- to be failed
+ALTER TABLE ts ALTER d TYPE text;
+
+ALTER TABLE t1 RENAME a TO aa;
+ALTER TABLE t1 RENAME b TO bb;		-- to be failed
+ALTER TABLE ts RENAME aa TO aaa;	-- to be failed
+ALTER TABLE ts RENAME d TO dd;
+\d+ ts
+
+DROP TABLE ts;
+
+-- Test for renaming and type changes (diamond inheritance)
+CREATE TABLE t2 (x int) INHERITS (t1);
+CREATE TABLE t3 (y int) INHERITS (t1);
+CREATE TABLE t4 (z int) INHERITS (t2, t3);
+
+ALTER TABLE t1 ALTER aa TYPE float;
+ALTER TABLE t1 RENAME aa TO aaa;
+\d+ t4
+
+CREATE TABLE ts (d int) INHERITS (t2, s1);
+ALTER TABLE t1 ALTER aaa TYPE text;
+ALTER TABLE t1 ALTER b TYPE text;	-- to be failed
+ALTER TABLE t1 RENAME aaa TO aaaa;
+ALTER TABLE t1 RENAME b TO bb;		-- to be failed
+\d+ ts
+
+DROP TABLE t1, s1 CASCADE;
#22Robert Haas
robertmhaas@gmail.com
In reply to: Bernd Helmle (#21)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Sat, Jan 23, 2010 at 1:45 PM, Bernd Helmle <mailings@oopsware.de> wrote:

--On 14. Januar 2010 16:04:17 +0900 KaiGai Kohei <kaigai@ak.jp.nec.com>
wrote:

This patch adds:

 List *find_column_origin(Oid relOid, const char *colName)

It returns the list of relation OIDs which originally defines the given
column. In most cases, it returns a list with an element. But, if the
column is inherited from multiple parent relations and merged during the
inheritance tree, the returned list contains multiple OIDs.
In this case, we have to forbid changing type and renaming to keep
correctness of the table definition.

Here's a slightly edited version of this patch from reviewing, fixing the
following:

* Fix a compiler warning by passing a pointer to skey to
systable_beginscan() (it's an array already)

* Edit some comments

The patch works as expected (at least, i don't see any remaining issues).
I'm going to mark this ready for committer.

I don't think this is ready for committer, becauseTom previously
objected to the approach taken by this patch here, and no one has
answered his objections:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00144.php

I think someone needs to figure out what the worst-case scenario for
this is performance-wise and submit a reproducible test case with
benchmark results. In the meantime, I'm going to set this to Waiting
on Author.

...Robert

#23KaiGai Kohei
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#22)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/24 12:29), Robert Haas wrote:

On Sat, Jan 23, 2010 at 1:45 PM, Bernd Helmle<mailings@oopsware.de> wrote:

--On 14. Januar 2010 16:04:17 +0900 KaiGai Kohei<kaigai@ak.jp.nec.com>
wrote:

This patch adds:

List *find_column_origin(Oid relOid, const char *colName)

It returns the list of relation OIDs which originally defines the given
column. In most cases, it returns a list with an element. But, if the
column is inherited from multiple parent relations and merged during the
inheritance tree, the returned list contains multiple OIDs.
In this case, we have to forbid changing type and renaming to keep
correctness of the table definition.

Here's a slightly edited version of this patch from reviewing, fixing the
following:

* Fix a compiler warning by passing a pointer to skey to
systable_beginscan() (it's an array already)

* Edit some comments

The patch works as expected (at least, i don't see any remaining issues).
I'm going to mark this ready for committer.

I don't think this is ready for committer, becauseTom previously
objected to the approach taken by this patch here, and no one has
answered his objections:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00144.php

I think someone needs to figure out what the worst-case scenario for
this is performance-wise and submit a reproducible test case with
benchmark results. In the meantime, I'm going to set this to Waiting
on Author.

Basically, I don't think it is not a performance focused command,
because we may be able to assume ALTER TABLE RENAME TO is not much
frequent operations.

However, I'm interested in between two approaches.
I'll check both of them. Isn't is necessary to compare the vanilla code base,
because the matter is a bug to be fixed?

http://archives.postgresql.org/message-id/4B41BB04.2070609@ak.jp.nec.com
http://archives.postgresql.org/message-id/A7739F610FB0BD89E310D85E@[172.26.14.62]

Please wait for weekday, because I don't have physical (not virtual) machine
in my home.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#24Bernd Helmle
mailings@oopsware.de
In reply to: Robert Haas (#22)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

--On 23. Januar 2010 22:29:23 -0500 Robert Haas <robertmhaas@gmail.com>
wrote:

I don't think this is ready for committer, becauseTom previously
objected to the approach taken by this patch here, and no one has
answered his objections:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00144.php

Ugh, i thought KaiGai's revised patch here

<http://archives.postgresql.org/message-id/4B41BB04.2070609@ak.jp.nec.com&gt;

was in response to Tom's complaint, since it modifies the method to identify
column origins by recursivly scanning pg_inherits with its current
inhrelid.

I think someone needs to figure out what the worst-case scenario for
this is performance-wise and submit a reproducible test case with
benchmark results. In the meantime, I'm going to set this to Waiting
on Author.

Makes sense.

--
Thanks

Bernd

#25Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#23)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Sat, Jan 23, 2010 at 10:48 PM, KaiGai Kohei <kaigai@kaigai.gr.jp> wrote:

(2010/01/24 12:29), Robert Haas wrote:

I don't think this is ready for committer, becauseTom previously
objected to the approach taken by this patch here, and no one has
answered his objections:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00144.php

I think someone needs to figure out what the worst-case scenario for
this is performance-wise and submit a reproducible test case with
benchmark results.  In the meantime, I'm going to set this to Waiting
on Author.

Basically, I don't think it is not a performance focused command,
because we may be able to assume ALTER TABLE RENAME TO is not much
frequent operations.

I agree - the requirements here are much looser than for, say, SELECT
or UPDATE. But it still has to not suck.

I think the problem case here might be something like this... create
ten tables A1 through A10. Now create 10 more tables B1 through B10
each of which inherits from all of A1 through A10. Now create 10 more
tables C1 through C10 that inherit from B1 through B10. Now create
1000 tables D1 through D1000 that inherit from C1 through C10. Now
drop a column from A1.

...Robert

#26Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#25)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Sun, Jan 24, 2010 at 8:36 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Sat, Jan 23, 2010 at 10:48 PM, KaiGai Kohei <kaigai@kaigai.gr.jp> wrote:

(2010/01/24 12:29), Robert Haas wrote:

I don't think this is ready for committer, becauseTom previously
objected to the approach taken by this patch here, and no one has
answered his objections:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00144.php

I think someone needs to figure out what the worst-case scenario for
this is performance-wise and submit a reproducible test case with
benchmark results.  In the meantime, I'm going to set this to Waiting
on Author.

Basically, I don't think it is not a performance focused command,
because we may be able to assume ALTER TABLE RENAME TO is not much
frequent operations.

I agree - the requirements here are much looser than for, say, SELECT
or UPDATE.  But it still has to not suck.

I think the problem case here might be something like this...  create
ten tables A1 through A10.  Now create 10 more tables B1 through B10
each of which inherits from all of A1 through A10.  Now create 10 more
tables C1 through C10 that inherit from B1 through B10.  Now create
1000 tables D1 through D1000 that inherit from C1 through C10.  Now
drop a column from A1.

Er... rename a column from A1, not drop.

...Robert

#27Bernd Helmle
mailings@oopsware.de
In reply to: Robert Haas (#26)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

--On 24. Januar 2010 08:37:13 -0500 Robert Haas <robertmhaas@gmail.com>
wrote:

I agree - the requirements here are much looser than for, say, SELECT
or UPDATE.  But it still has to not suck.

Yeah, i think the meaning of "suck" can be much weakier than for a DML
command. However, if it would degrade the performance of a formerly well
running command in a way, that it would be unusable, that would be annoying.

I think the problem case here might be something like this...  create
ten tables A1 through A10.  Now create 10 more tables B1 through B10
each of which inherits from all of A1 through A10.  Now create 10 more
tables C1 through C10 that inherit from B1 through B10.  Now create
1000 tables D1 through D1000 that inherit from C1 through C10.  Now
drop a column from A1.

Er... rename a column from A1, not drop.

Did that with a crude pl/pgsql script, and got the following numbers:

Current -HEAD:

Phenom-II 2.6 GHz: Time: 282,471 ms
MacBook: Time: 499,866 ms

With KaiGais recent patch (which covers the TYPE case, too):

Phenom-II 2.6 GHz: Time: 476,800 ms
MacBook: Time: 753,161 ms

--
Thanks

Bernd

#28Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bernd Helmle (#27)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

Bernd Helmle <mailings@oopsware.de> writes:

--On 24. Januar 2010 08:37:13 -0500 Robert Haas <robertmhaas@gmail.com>
wrote:

I think the problem case here might be something like this...

Did that with a crude pl/pgsql script, and got the following numbers:

I think my concern about the original proposal was that the time to
perform an ALTER RENAME would increase with the number of tables in the
database, even if they were entirely unrelated to the one you're trying
to rename. It's not clear to me whether the present coding of the patch
avoids that. But in any case, the alternative implementation I
suggested would have added essentially zero runtime, so even a 50%
slowdown seems like sacrificing a lot.

regards, tom lane

#29Bernd Helmle
mailings@oopsware.de
In reply to: Tom Lane (#28)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

--On 24. Januar 2010 13:23:14 -0500 Tom Lane <tgl@sss.pgh.pa.us> wrote:

I think my concern about the original proposal was that the time to
perform an ALTER RENAME would increase with the number of tables in the
database, even if they were entirely unrelated to the one you're trying
to rename.

Uhm, find_column_origin() scans pg_inherits recursively by looking up
inhparent from the given inhrelid. I don't see where this should be related
to the number of tables not part of the inheritance tree (or inheritance at
all).

--
Thanks

Bernd

#30Bernd Helmle
mailings@oopsware.de
In reply to: Bernd Helmle (#29)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

--On 24. Januar 2010 19:45:33 +0100 Bernd Helmle <mailings@oopsware.de>
wrote:

I don't see where this should be related to the number of tables not
part of the inheritance tree (or inheritance at all).

To answer that myself: it seems get_attname() introduces the overhead here
(forgot about that). Creating additional 16384 tables without any
connection to the inheritance increases the times on my Phenom-II Box to
round about 2 seconds:

Current -HEAD

bernd=# ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;
ALTER TABLE
Time: 409,045 ms

With KaiGai's recent patch:

bernd=# ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;
ALTER TABLE
Time: 2402,306 ms

--
Thanks

Bernd

#31Robert Haas
robertmhaas@gmail.com
In reply to: Bernd Helmle (#30)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Sun, Jan 24, 2010 at 2:01 PM, Bernd Helmle <mailings@oopsware.de> wrote:

--On 24. Januar 2010 19:45:33 +0100 Bernd Helmle <mailings@oopsware.de>
wrote:

 I don't see where this should be related to the number of tables not
part of the inheritance tree (or inheritance at all).

To answer that myself: it seems get_attname() introduces the overhead here
(forgot about that). Creating additional 16384 tables without any connection
to the inheritance increases the times on my Phenom-II Box to round about 2
seconds:

Current -HEAD

bernd=# ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;
ALTER TABLE
Time: 409,045 ms

With KaiGai's recent patch:

bernd=# ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;
ALTER TABLE
Time: 2402,306 ms

Hmm, so adding those tables slowed down HEAD by <50%, but the time
with KaiGai's patch more than trippled. So I think we definitely need
KaiGai to try it the other way and see how that shakes out...

...Robert

#32KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Bernd Helmle (#30)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/25 4:01), Bernd Helmle wrote:

--On 24. Januar 2010 19:45:33 +0100 Bernd Helmle <mailings@oopsware.de>
wrote:

I don't see where this should be related to the number of tables not
part of the inheritance tree (or inheritance at all).

To answer that myself: it seems get_attname() introduces the overhead
here (forgot about that). Creating additional 16384 tables without any
connection to the inheritance increases the times on my Phenom-II Box to
round about 2 seconds:

Current -HEAD

bernd=# ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;
ALTER TABLE
Time: 409,045 ms

With KaiGai's recent patch:

bernd=# ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;
ALTER TABLE
Time: 2402,306 ms

Hmm....

Bernd, could you try same test with previous patch?
http://archives.postgresql.org/message-id/4B41BB04.2070609@ak.jp.nec.com

It computes an expected inhcount during find_all_inheritors(), and
compares it with the target pg_attribute entry?

I'll also try to measure performance in three cases by myself.
Please wait for a while...

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#33KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#32)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/25 8:45), KaiGai Kohei wrote:

(2010/01/25 4:01), Bernd Helmle wrote:

--On 24. Januar 2010 19:45:33 +0100 Bernd Helmle<mailings@oopsware.de>
wrote:

I don't see where this should be related to the number of tables not
part of the inheritance tree (or inheritance at all).

To answer that myself: it seems get_attname() introduces the overhead
here (forgot about that). Creating additional 16384 tables without any
connection to the inheritance increases the times on my Phenom-II Box to
round about 2 seconds:

Current -HEAD

bernd=# ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;
ALTER TABLE
Time: 409,045 ms

With KaiGai's recent patch:

bernd=# ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;
ALTER TABLE
Time: 2402,306 ms

Hmm....

Bernd, could you try same test with previous patch?
http://archives.postgresql.org/message-id/4B41BB04.2070609@ak.jp.nec.com

It computes an expected inhcount during find_all_inheritors(), and
compares it with the target pg_attribute entry?

I'll also try to measure performance in three cases by myself.
Please wait for a while...

I set up 11,111 of tables inherited from a common table.

(echo "CREATE TABLE t (a int);"
for i in `seq 0 9`; do
echo "CREATE TABLE s$i (b int) INHERITS(t);"
for j in `seq 0 9`; do
echo "CREATE TABLE v$i$j (c int) INHERITS(s$i);"
for k in `seq 0 9`; do
echo "CREATE TABLE w$i$j$k (d int) INHERITS(v$i$j);"
for l in `seq 0 9`; do
echo "CREATE TABLE x$i$j$k$l (e int) INHERITS(w$i$j$k);"
done
done
done
done) | psql test

And, I measured time to execute the following query using /usr/bin/time.

ALTER TABLE t RENAME a TO aa;
ALTER TABLE t RENAME aa TO aaa;
ALTER TABLE t RENAME aaa TO aaaa;
ALTER TABLE t RENAME aaaa TO aaaaa;
ALTER TABLE t RENAME aaaaa TO a;

The platform is Pentium4 (3.20GHz) and generic SATA drive.

* CVS HEAD - does not care anything
Avg: 25.840s (25.663s 24.214s 26.958s 26.525s)

* My rev.3 patch - find_all_inheritors_with_inhcount()
Avg: 26.488s (25.197s 27.847s 25.487s 27.421s)

* My rev.4 patch - find_column_origin(), also fixes AT_AlterColumnType case
Avg: 28.693s (27.936s 30.295s 29.385s 27.159s)

It seems to me the result is different from Bernd's report.

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#34Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#33)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/1/24 KaiGai Kohei <kaigai@ak.jp.nec.com>:

It seems to me the result is different from Bernd's report.

Well, you tested something different, so you got a different answer.
Your case doesn't have any multiple inheritance.

...Robert

#35KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#34)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/25 14:08), Robert Haas wrote:

2010/1/24 KaiGai Kohei<kaigai@ak.jp.nec.com>:

It seems to me the result is different from Bernd's report.

Well, you tested something different, so you got a different answer.
Your case doesn't have any multiple inheritance.

If it tries ALTER TABLE xxx RENAME TO on any multiple inheritance column,
this patch will raise an error when it founds the first column unable to
rename. (Of course, it takes inconsistency in table definitions, so we
need to prevent it.) It does not make sense in performance comparison.

The issue is whether we need to check pg_inherits for each recursion
level in renameatt(), or not. So, I checked the case when we try to
rename the root of inheritance tree.

Or, are you saying to test diamond-inheritance cases?

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#36Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#35)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/1/25 KaiGai Kohei <kaigai@ak.jp.nec.com>:

Or, are you saying to test diamond-inheritance cases?

Please go back and read the test case that I already proposed.

...Robert

#37Bernd Helmle
mailings@oopsware.de
In reply to: KaiGai Kohei (#33)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

--On 25. Januar 2010 11:39:21 +0900 KaiGai Kohei <kaigai@ak.jp.nec.com>
wrote:

(echo "CREATE TABLE t (a int);"
for i in `seq 0 9`; do
echo "CREATE TABLE s$i (b int) INHERITS(t);"
for j in `seq 0 9`; do
echo "CREATE TABLE v$i$j (c int) INHERITS(s$i);"
for k in `seq 0 9`; do
echo "CREATE TABLE w$i$j$k (d int) INHERITS(v$i$j);"
for l in `seq 0 9`; do
echo "CREATE TABLE x$i$j$k$l (e int) INHERITS(w$i$j$k);"
done
done
done
done) | psql test

Well, each table inherits one table in your test. In my test, I inherit
from multiple tables for each table. My script generates the following
inheritance tree (and wins a price of copy & paste ugliness, see
attachment):

A1, A2, A3, ..., Am
B1 INHERITS(A1...A10), B2 INHERITS(A1...A10, B3 INHERITS(A1...A10), ...Bn
C1 INHERITS(B1...B10), C2 INHERITS(B1...B10), ... Co
D1 INHERITS(C1...C10), ..., Dp

m = 10
n = 10
o = 10
p = 1000

Repeating this on my MacBook gives:

ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;

-HEAD:

Time: 382,427 ms
Time: 375,974 ms
Time: 385,478 ms
Time: 371,067 ms
Time: 410,834 ms
Time: 386,382 ms

Recent V4 patch:

Time: 6065,673 ms
Time: 3823,206 ms
Time: 4037,933 ms
Time: 3873,029 ms
Time: 3899,607 ms
Time: 3963,308 ms

Note that you have to increase max_locks_per_transaction to run the script,
i used

pg_ctl -o '--checkpoint-segments=32 --max-locks-per-transaction=128' start

--
Thanks

Bernd

Attachments:

test_rename.sqlapplication/octet-stream; name=test_rename.sqlDownload
#38KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Bernd Helmle (#37)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/26 1:11), Bernd Helmle wrote:

--On 25. Januar 2010 11:39:21 +0900 KaiGai Kohei <kaigai@ak.jp.nec.com>
wrote:

(echo "CREATE TABLE t (a int);"
for i in `seq 0 9`; do
echo "CREATE TABLE s$i (b int) INHERITS(t);"
for j in `seq 0 9`; do
echo "CREATE TABLE v$i$j (c int) INHERITS(s$i);"
for k in `seq 0 9`; do
echo "CREATE TABLE w$i$j$k (d int) INHERITS(v$i$j);"
for l in `seq 0 9`; do
echo "CREATE TABLE x$i$j$k$l (e int) INHERITS(w$i$j$k);"
done
done
done
done) | psql test

Well, each table inherits one table in your test. In my test, I inherit
from multiple tables for each table. My script generates the following
inheritance tree (and wins a price of copy & paste ugliness, see
attachment):

A1, A2, A3, ..., Am
B1 INHERITS(A1...A10), B2 INHERITS(A1...A10, B3 INHERITS(A1...A10), ...Bn
C1 INHERITS(B1...B10), C2 INHERITS(B1...B10), ... Co
D1 INHERITS(C1...C10), ..., Dp

m = 10
n = 10
o = 10
p = 1000

Repeating this on my MacBook gives:

ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;

-HEAD:

Time: 382,427 ms
Time: 375,974 ms
Time: 385,478 ms
Time: 371,067 ms
Time: 410,834 ms
Time: 386,382 ms

Recent V4 patch:

Time: 6065,673 ms
Time: 3823,206 ms
Time: 4037,933 ms
Time: 3873,029 ms
Time: 3899,607 ms
Time: 3963,308 ms

Hmm... I also could observe similar result in 4 times iteration of
ALTER TABLE with your test_rename.sql.
I agree the recent V4 patch is poor in performance perspective.

* CVS HEAD
0.828s
0.828s
0.833s
0.829s
0.838s

* Rcent V4 patch:
10.283s
10.135s
10.107s
10.382s
10.162s

* Previous V3 patch:
2.607s
2.429s
2.431s
2.436s
2.428s

The V3 patch is designed to compute an expected inhcount for each relations
to be altered at first, then it shall be compared to pg_attribute.inhcount
to be renamed.

Basically, its execution cost is same order except for a case when a relation
has diamond inheritance tree.

The find_all_inheritors() does not check child relations which is already
scanned. However, in this case, we have to check how many times is the child
relation inherited from a common origin.
I guess it is reason of the different between -HEAD and V3.

For example, if we have the following inheritance tree,

A2 A5
/ \ \
A1 A4
\ / \
A3 -- A6

The find_all_inheritors() checks existence of directly inherited relations
at A1, ... , A6 without any duplications, because this function does not
intend to compute how many times was it inherited.

The find_all_inheritors_with_inhcount() in V3 patch checks existence of
directly inherited relations, even if the target relation is already checked,
because it also has to return the times to be inherited from a common origin.
In this example, it considers the above structure is same as the following
tree. In this diagram, we can find A4 and A5 twice, and A6 thrice.

A5
/
A2 - A4 - A6
\
A1
\
A3 - A4 - A6
\ \
A6 A5

Thus, the test_rename.sql was the worst case test for V3 also.
However, I don't think we should keep the bug in the next release.
The CVS HEAD's performance is the result of omission for necessary checks.

I think we should back to the V3 patch approach, and also reconsider
the logic in ATPrepAlterColumnType().

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#39KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#38)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

The attached patch is revised one based on the V3 approach.
The only difference from V3 is that it also applies checks on the
AT_AlterColumnType option, not only renameatt().

The performance was almost same as the V3 case.

* CVS HEAD
0.828s
0.828s
0.833s
0.829s
0.838s

- ALTER RENAME TO with V5 patch
2.419s
2.418s
2.418s
2.426s

I also checked ALTER ... TYPE cases. It is relatively heavy operation than
renameatt(), so its affects was relatively smaller.

- ALTER ... TYPE with CVS HEAD
28.888s
29.948s
30.738s
30.600s

- ALTER ... TYPE with V5 patch
28.067s
28.212s
28.038s
29.497s

(2010/01/26 10:10), KaiGai Kohei wrote:

(2010/01/26 1:11), Bernd Helmle wrote:

--On 25. Januar 2010 11:39:21 +0900 KaiGai Kohei<kaigai@ak.jp.nec.com>
wrote:

(echo "CREATE TABLE t (a int);"
for i in `seq 0 9`; do
echo "CREATE TABLE s$i (b int) INHERITS(t);"
for j in `seq 0 9`; do
echo "CREATE TABLE v$i$j (c int) INHERITS(s$i);"
for k in `seq 0 9`; do
echo "CREATE TABLE w$i$j$k (d int) INHERITS(v$i$j);"
for l in `seq 0 9`; do
echo "CREATE TABLE x$i$j$k$l (e int) INHERITS(w$i$j$k);"
done
done
done
done) | psql test

Well, each table inherits one table in your test. In my test, I inherit
from multiple tables for each table. My script generates the following
inheritance tree (and wins a price of copy& paste ugliness, see
attachment):

A1, A2, A3, ..., Am
B1 INHERITS(A1...A10), B2 INHERITS(A1...A10, B3 INHERITS(A1...A10), ...Bn
C1 INHERITS(B1...B10), C2 INHERITS(B1...B10), ... Co
D1 INHERITS(C1...C10), ..., Dp

m = 10
n = 10
o = 10
p = 1000

Repeating this on my MacBook gives:

ALTER TABLE a1 RENAME COLUMN acol1 TO xyz;

-HEAD:

Time: 382,427 ms
Time: 375,974 ms
Time: 385,478 ms
Time: 371,067 ms
Time: 410,834 ms
Time: 386,382 ms

Recent V4 patch:

Time: 6065,673 ms
Time: 3823,206 ms
Time: 4037,933 ms
Time: 3873,029 ms
Time: 3899,607 ms
Time: 3963,308 ms

Hmm... I also could observe similar result in 4 times iteration of
ALTER TABLE with your test_rename.sql.
I agree the recent V4 patch is poor in performance perspective.

* CVS HEAD
0.828s
0.828s
0.833s
0.829s
0.838s

* Rcent V4 patch:
10.283s
10.135s
10.107s
10.382s
10.162s

* Previous V3 patch:
2.607s
2.429s
2.431s
2.436s
2.428s

The V3 patch is designed to compute an expected inhcount for each relations
to be altered at first, then it shall be compared to pg_attribute.inhcount
to be renamed.

Basically, its execution cost is same order except for a case when a relation
has diamond inheritance tree.

The find_all_inheritors() does not check child relations which is already
scanned. However, in this case, we have to check how many times is the child
relation inherited from a common origin.
I guess it is reason of the different between -HEAD and V3.

For example, if we have the following inheritance tree,

A2 A5
/ \ \
A1 A4
\ / \
A3 -- A6

The find_all_inheritors() checks existence of directly inherited relations
at A1, ... , A6 without any duplications, because this function does not
intend to compute how many times was it inherited.

The find_all_inheritors_with_inhcount() in V3 patch checks existence of
directly inherited relations, even if the target relation is already checked,
because it also has to return the times to be inherited from a common origin.
In this example, it considers the above structure is same as the following
tree. In this diagram, we can find A4 and A5 twice, and A6 thrice.

A5
/
A2 - A4 - A6
\
A1
\
A3 - A4 - A6
\ \
A6 A5

Thus, the test_rename.sql was the worst case test for V3 also.
However, I don't think we should keep the bug in the next release.
The CVS HEAD's performance is the result of omission for necessary checks.

I think we should back to the V3 patch approach, and also reconsider
the logic in ATPrepAlterColumnType().

Thanks,

--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-fix-inherit-rename.5.patchapplication/octect-stream; name=pgsql-fix-inherit-rename.5.patchDownload
 src/backend/catalog/pg_inherits.c     |   85 ++++++++++++++++++++++++
 src/backend/commands/alter.c          |    1 +
 src/backend/commands/tablecmds.c      |  114 +++++++++++++++++++++++++++------
 src/include/catalog/pg_inherits_fn.h  |    4 +
 src/include/commands/tablecmds.h      |    1 +
 src/test/regress/expected/inherit.out |   78 ++++++++++++++++++++++
 src/test/regress/sql/inherit.sql      |   36 ++++++++++
 7 files changed, 299 insertions(+), 20 deletions(-)

diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index e256f6f..4fb8a25 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -190,6 +190,91 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
 	return rels_list;
 }
 
+/*
+ * find_all_inheritors_with_inhcount
+ *
+ * It is same as find_all_inheritors, except for it returns an excepted
+ * inhcount for each child relations.
+ * The expected inhcount means the number of times a relation is inherited
+ * from a certain origin. It shall be 1 in normal inheritance tree cases.
+ * However, if a relation has 'diamond-inheritance' structure, it may be
+ * more than 1.
+ *
+ *    T2
+ *   /  \
+ * T1    T4
+ *   \  /
+ *    T3-T5
+ *      /
+ *    S1
+ *
+ * In this case, T2 and T3 are inherited from T1, and T4 is inherited from
+ * T2 and T3. In T4, all the columns inherited from T1 are merged.
+ * The expected of T1, T2 and T3 are 1, but 2 for T4. The pg_attribute.inhcount
+ * of T4 will be 2 (if no corruption or bugs). It is not over the expected
+ * inhcount, so we can alter the column inherited from T1.
+ *
+ * If T5 is inherited from T3 and S1, the pg_attribute.inhcount of T5 will
+ * be 2. However, its expected inhcount when we try to alter T1 and its child
+ * relation is 1, because it does not have any diamond-inheritance from the
+ * series of T1. In this case, its inhcount will over the expected one, so
+ * we can prevent incorrect ALTER TABLE.
+ */
+void
+find_all_inheritors_with_inhcount(Oid parentrelId, LOCKMODE lockmode,
+								  List **child_oids, List **child_inhs)
+{
+	List	   *children, *rels_oids, *rels_inhs;
+	ListCell   *lc, *lo, *li;
+
+	/*
+	 * The root of the inheritance tree always has 1 as an expected
+	 * inhcount.
+	 */
+	rels_oids = list_make1_oid(parentrelId);
+	rels_inhs = list_make1_int(1);
+
+	children = find_inheritance_children(parentrelId, lockmode);
+	foreach (lc, children)
+	{
+		Oid		cur_relid = lfirst_oid(lc);
+		List   *cur_children;
+		bool	found = false;
+
+		forboth (lo, rels_oids, li, rels_inhs)
+		{
+			/*
+			 * If we found a duplicated relation within a certain inheritance
+			 * tree, it means a diamond-inheritance. So, we increment its
+			 * expected inhcount without appending an element to the list.
+			 */
+			if (lfirst_oid(lo) == cur_relid)
+			{
+				lfirst_int(li)++;
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+		{
+			rels_oids = lappend_oid(rels_oids, cur_relid);
+			rels_inhs = lappend_int(rels_inhs, 1);
+		}
+
+		/*
+		 * Unlike find_all_inheritors(), we need to walk on child relations
+		 * that have diamond inheritance tree, because this function has to
+		 * return correct expected inhecount to the caller.
+		 */
+		cur_children = find_inheritance_children(cur_relid, lockmode);
+
+		children = list_concat(children, cur_children);
+	}
+
+	*child_oids = rels_oids;
+	*child_inhs = rels_inhs;
+}
 
 /*
  * has_subclass - does this relation have any children?
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 8c81eaa..fa3279a 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -125,6 +125,7 @@ ExecRenameStmt(RenameStmt *stmt)
 						renameatt(relid,
 								  stmt->subname,		/* old att name */
 								  stmt->newname,		/* new att name */
+								  1,			/* expected inhcount */
 								  interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
 								  false);		/* recursing already? */
 						break;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2db97dd..60d7687 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1906,6 +1906,7 @@ void
 renameatt(Oid myrelid,
 		  const char *oldattname,
 		  const char *newattname,
+		  int expected_inhcount,
 		  bool recurse,
 		  bool recursing)
 {
@@ -1946,24 +1947,32 @@ renameatt(Oid myrelid,
 	 */
 	if (recurse)
 	{
-		ListCell   *child;
-		List	   *children;
-
-		children = find_all_inheritors(myrelid, AccessExclusiveLock);
+		List	   *child_oids, *child_inhs;
+		ListCell   *lo, *li;
 
 		/*
-		 * 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.
+		 * It is same as find_all_inheritors, except for it returns the
+		 * list of expected inhcount for each child relations.
+		 * It is a value to be set on pg_attribute.inhcount when the column
+		 * to be renamed is not merged with any other column which have
+		 * different origin.
+		 * If pg_attribute.inhcount is larger than the expected inhcount,
+		 * it means the column is merged from different serieses.
 		 */
-		foreach(child, children)
+		find_all_inheritors_with_inhcount(myrelid, AccessExclusiveLock,
+										  &child_oids, &child_inhs);
+
+		forboth (lo, child_oids, li, child_inhs)
 		{
-			Oid			childrelid = lfirst_oid(child);
+			Oid		child_relid = lfirst_oid(lo);
+			int		child_inhcount = lfirst_int(li);
 
-			if (childrelid == myrelid)
+			if (child_relid == myrelid)
 				continue;
+
 			/* note we need not recurse again */
-			renameatt(childrelid, oldattname, newattname, false, true);
+			renameatt(child_relid, oldattname, newattname,
+					  child_inhcount, false, true);
 		}
 	}
 	else
@@ -2007,6 +2016,17 @@ renameatt(Oid myrelid,
 				 errmsg("cannot rename inherited column \"%s\"",
 						oldattname)));
 
+	/*
+	 * if the attribute is multiple inherited, forbid the renaming,
+	 * even if we are already inside a recursive rename, because we
+	 * have no reasonable way to keep its integrity.
+	 */
+	if (attform->attinhcount > expected_inhcount)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 (errmsg("cannot rename multiple inherited column \"%s\"",
+						 oldattname))));
+
 	/* new name should not already exist */
 
 	/* this test is deliberately not attisdropped-aware */
@@ -5771,11 +5791,29 @@ ATPrepAlterColumnType(List **wqueue,
 						colName)));
 
 	/* Don't alter inherited columns */
-	if (attTup->attinhcount > 0 && !recursing)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				 errmsg("cannot alter inherited column \"%s\"",
-						colName)));
+	if (!recursing)
+	{
+		if (attTup->attinhcount > 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter inherited column \"%s\"",
+							colName)));
+	}
+	else
+	{
+		/*
+		 * The TypeName->location is not used in this code path,
+		 * so we utilize it to store an enpected inhconunt for each
+		 * child relations.
+		 */
+		int		expected_inhcount = typeName->location;
+
+		if (attTup->attinhcount > expected_inhcount)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("cannot alter multiple inherited column \"%s\"",
+							colName)));
+	}
 
 	/* Look up the target type */
 	targettype = typenameTypeId(NULL, typeName, &targettypmod);
@@ -5857,12 +5895,48 @@ 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.
+	 * The recursion case is not handled by ATSimpleRecursion, because we need
+	 * to compute an expected inhcount for each child relations. This value is
+	 * delivered to the recursive invocation to check unexpected type changes.
+	 * 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);
+	{
+		Relation	child_rel;
+		List	   *child_oids;
+		List	   *child_inhs;
+		ListCell   *lo;
+		ListCell   *li;
+
+		find_all_inheritors_with_inhcount(RelationGetRelid(rel),
+										  AccessExclusiveLock,
+										  &child_oids, &child_inhs);
+		forboth(lo, child_oids, li, child_inhs)
+		{
+			AlterTableCmd  *child_cmd;
+			Relation		child_rel;
+			Oid				child_relid = lfirst_oid(lo);
+			int				child_inhcount = lfirst_int(li);
+
+			if (child_relid == RelationGetRelid(rel))
+				continue;
+
+			/*
+			 * The TypeName->location is not used in this code path,
+			 * so we utilize it to store an expected inhcount for each
+			 * child relation.
+			 */
+			child_cmd = copyObject(cmd);
+			((TypeName *)child_cmd->def)->location = child_inhcount;
+
+			/* find_all_inheritors_with_inhcount already got lock  */
+			child_rel = relation_open(child_relid, NoLock);
+			CheckTableNotInUse(child_rel, "ALTER TABLE");
+			ATPrepCmd(wqueue, child_rel, child_cmd, false, true);
+			relation_close(child_rel, NoLock);
+		}
+	}
 	else if (!recursing &&
 			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
 		ereport(ERROR,
diff --git a/src/include/catalog/pg_inherits_fn.h b/src/include/catalog/pg_inherits_fn.h
index 91baec3..e750fcd 100644
--- a/src/include/catalog/pg_inherits_fn.h
+++ b/src/include/catalog/pg_inherits_fn.h
@@ -19,6 +19,10 @@
 
 extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
 extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode);
+extern void find_all_inheritors_with_inhcount(Oid parentrelId,
+											  LOCKMODE lockmode,
+											  List **child_oids,
+											  List **child_inhs);
 extern bool has_subclass(Oid relationId);
 extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 8bc716a..7f90306 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -42,6 +42,7 @@ extern void ExecuteTruncate(TruncateStmt *stmt);
 extern void renameatt(Oid myrelid,
 		  const char *oldattname,
 		  const char *newattname,
+		  int expected_inhcount,
 		  bool recurse,
 		  bool recursing);
 
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 9c83a32..8cb3db8 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1053,3 +1053,81 @@ NOTICE:  merging column "a" with inherited definition
 ERROR:  column "a" has a storage parameter conflict
 DETAIL:  MAIN versus EXTENDED
 DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+-- Test for renaming and type changes (simple multiple inheritance)
+CREATE TABLE t1 (a int, b int);
+CREATE TABLE s1 (b int, c int);
+CREATE TABLE ts (d int) INHERITS (t1, s1);
+NOTICE:  merging multiple inherited definitions of column "b"
+ALTER TABLE s1 ALTER b TYPE float;    -- to be failed
+ERROR:  cannot alter multiple inherited column "b"
+ALTER TABLE s1 ALTER c TYPE float;
+ALTER TABLE ts ALTER c TYPE text;     -- to be failed
+ERROR:  cannot alter inherited column "c"
+ALTER TABLE ts ALTER d TYPE text;
+ALTER TABLE t1 RENAME a TO aa;
+ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ERROR:  cannot rename multiple inherited column "b"
+ALTER TABLE ts RENAME aa TO aaa;      -- to be failed
+ERROR:  cannot rename inherited column "aa"
+ALTER TABLE ts RENAME d TO dd;
+\d+ ts
+                       Table "public.ts"
+ Column |       Type       | Modifiers | Storage  | Description 
+--------+------------------+-----------+----------+-------------
+ aa     | integer          |           | plain    | 
+ b      | integer          |           | plain    | 
+ c      | double precision |           | plain    | 
+ dd     | text             |           | extended | 
+Inherits: t1,
+          s1
+Has OIDs: no
+
+DROP TABLE ts;
+-- Test for renaming and type changes (diamond inheritance)
+CREATE TABLE t2 (x int) INHERITS (t1);
+CREATE TABLE t3 (y int) INHERITS (t1);
+CREATE TABLE t4 (z int) INHERITS (t2, t3);
+NOTICE:  merging multiple inherited definitions of column "aa"
+NOTICE:  merging multiple inherited definitions of column "b"
+ALTER TABLE t1 ALTER aa TYPE float;
+ALTER TABLE t1 RENAME aa TO aaa;
+\d+ t4
+                       Table "public.t4"
+ Column |       Type       | Modifiers | Storage | Description 
+--------+------------------+-----------+---------+-------------
+ aaa    | double precision |           | plain   | 
+ b      | integer          |           | plain   | 
+ x      | integer          |           | plain   | 
+ y      | integer          |           | plain   | 
+ z      | integer          |           | plain   | 
+Inherits: t2,
+          t3
+Has OIDs: no
+
+CREATE TABLE ts (d int) INHERITS (t2, s1);
+NOTICE:  merging multiple inherited definitions of column "b"
+ALTER TABLE t1 ALTER aaa TYPE text;
+ALTER TABLE t1 ALTER b TYPE text;     -- to be failed
+ERROR:  cannot alter multiple inherited column "b"
+ALTER TABLE t1 RENAME aaa TO aaaa;
+ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ERROR:  cannot rename multiple inherited column "b"
+\d+ ts
+                       Table "public.ts"
+ Column |       Type       | Modifiers | Storage  | Description 
+--------+------------------+-----------+----------+-------------
+ aaaa   | text             |           | extended | 
+ b      | integer          |           | plain    | 
+ x      | integer          |           | plain    | 
+ c      | double precision |           | plain    | 
+ d      | integer          |           | plain    | 
+Inherits: t2,
+          s1
+Has OIDs: no
+
+DROP TABLE t1, s1 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table t2
+drop cascades to table ts
+drop cascades to table t3
+drop cascades to table t4
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 192e860..76fe589 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -334,3 +334,39 @@ CREATE TABLE inh_error1 () INHERITS (t1, t4);
 CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
 
 DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+
+-- Test for renaming and type changes (simple multiple inheritance)
+CREATE TABLE t1 (a int, b int);
+CREATE TABLE s1 (b int, c int);
+CREATE TABLE ts (d int) INHERITS (t1, s1);
+
+ALTER TABLE s1 ALTER b TYPE float;    -- to be failed
+ALTER TABLE s1 ALTER c TYPE float;
+ALTER TABLE ts ALTER c TYPE text;     -- to be failed
+ALTER TABLE ts ALTER d TYPE text;
+
+ALTER TABLE t1 RENAME a TO aa;
+ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ALTER TABLE ts RENAME aa TO aaa;      -- to be failed
+ALTER TABLE ts RENAME d TO dd;
+\d+ ts
+
+DROP TABLE ts;
+
+-- Test for renaming and type changes (diamond inheritance)
+CREATE TABLE t2 (x int) INHERITS (t1);
+CREATE TABLE t3 (y int) INHERITS (t1);
+CREATE TABLE t4 (z int) INHERITS (t2, t3);
+
+ALTER TABLE t1 ALTER aa TYPE float;
+ALTER TABLE t1 RENAME aa TO aaa;
+\d+ t4
+
+CREATE TABLE ts (d int) INHERITS (t2, s1);
+ALTER TABLE t1 ALTER aaa TYPE text;
+ALTER TABLE t1 ALTER b TYPE text;     -- to be failed
+ALTER TABLE t1 RENAME aaa TO aaaa;
+ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+\d+ ts
+
+DROP TABLE t1, s1 CASCADE;
#40Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#39)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/1/27 KaiGai Kohei <kaigai@ak.jp.nec.com>:

The attached patch is revised one based on the V3 approach.
The only difference from V3 is that it also applies checks on the
AT_AlterColumnType option, not only renameatt().

I think I was clear about what the next step was for this patch in my
previous email, but let me try again.

http://archives.postgresql.org/pgsql-hackers/2010-01/msg02407.php

See also Tom's comments here:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00110.php

I don't believe that either Tom or I are prepared to commit a patch
based on this approach, at least not unless someone makes an attempt
to do it the other way and finds an even more serious problem. If
you're not interested in rewriting the patch along the lines Tom
suggested, then we should just mark this as Returned with Feedback and
move on.

...Robert

#41KaiGai Kohei
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#40)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/27 23:29), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

The attached patch is revised one based on the V3 approach.
The only difference from V3 is that it also applies checks on the
AT_AlterColumnType option, not only renameatt().

I think I was clear about what the next step was for this patch in my
previous email, but let me try again.

http://archives.postgresql.org/pgsql-hackers/2010-01/msg02407.php

See also Tom's comments here:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00110.php

I don't believe that either Tom or I are prepared to commit a patch
based on this approach, at least not unless someone makes an attempt
to do it the other way and finds an even more serious problem. If
you're not interested in rewriting the patch along the lines Tom
suggested, then we should just mark this as Returned with Feedback and
move on.

The V3/V5 patch was the rewritten one based on the Tom's comment, as is.
It counts the expected inhcount at the first find_all_inheritors() time
at once, and it compares the pg_attribute.attinhcount.
(In actually, find_all_inheritors() does not have a capability to count
the number of merged from a common origin, so I newly defined the
find_all_inheritors_with_inhcount().)

Am I missing something?

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#42Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#41)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Wed, Jan 27, 2010 at 10:17 AM, KaiGai Kohei <kaigai@kaigai.gr.jp> wrote:

(2010/01/27 23:29), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

The attached patch is revised one based on the V3 approach.
The only difference from V3 is that it also applies checks on the
AT_AlterColumnType option, not only renameatt().

I think I was clear about what the next step was for this patch in my
previous email, but let me try again.

http://archives.postgresql.org/pgsql-hackers/2010-01/msg02407.php

See also Tom's comments here:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00110.php

I don't believe that either Tom or I are prepared to commit a patch
based on this approach, at least not unless someone makes an attempt
to do it the other way and finds an even more serious problem.  If
you're not interested in rewriting the patch along the lines Tom
suggested, then we should just mark this as Returned with Feedback and
move on.

The V3/V5 patch was the rewritten one based on the Tom's comment, as is.
It counts the expected inhcount at the first find_all_inheritors() time
at once, and it compares the pg_attribute.attinhcount.
(In actually, find_all_inheritors() does not have a capability to count
the number of merged from a common origin, so I newly defined the
find_all_inheritors_with_inhcount().)

Am I missing something?

Err... I'm not sure. I thought I understood what the different
versions of this patch were doing, but apparently I'm all confused.
I'll take another look at this.

Bernd (or anyone), feel free to take a look in parallel. More eyes
would be helpful...

...Robert

#43Bernd Helmle
mailings@oopsware.de
In reply to: Robert Haas (#42)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

--On 27. Januar 2010 15:42:45 -0500 Robert Haas <robertmhaas@gmail.com>
wrote:

Bernd (or anyone), feel free to take a look in parallel. More eyes
would be helpful...

I've planned to look at this tomorrow when i'm back in office.

--
Thanks

Bernd

#44Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#42)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Wed, Jan 27, 2010 at 3:42 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Wed, Jan 27, 2010 at 10:17 AM, KaiGai Kohei <kaigai@kaigai.gr.jp> wrote:

(2010/01/27 23:29), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

The attached patch is revised one based on the V3 approach.
The only difference from V3 is that it also applies checks on the
AT_AlterColumnType option, not only renameatt().

I think I was clear about what the next step was for this patch in my
previous email, but let me try again.

http://archives.postgresql.org/pgsql-hackers/2010-01/msg02407.php

See also Tom's comments here:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00110.php

I don't believe that either Tom or I are prepared to commit a patch
based on this approach, at least not unless someone makes an attempt
to do it the other way and finds an even more serious problem.  If
you're not interested in rewriting the patch along the lines Tom
suggested, then we should just mark this as Returned with Feedback and
move on.

The V3/V5 patch was the rewritten one based on the Tom's comment, as is.
It counts the expected inhcount at the first find_all_inheritors() time
at once, and it compares the pg_attribute.attinhcount.
(In actually, find_all_inheritors() does not have a capability to count
the number of merged from a common origin, so I newly defined the
find_all_inheritors_with_inhcount().)

Am I missing something?

Err... I'm not sure.  I thought I understood what the different
versions of this patch were doing, but apparently I'm all confused.
I'll take another look at this.

OK, I took a look at this. It's busted:

test=# create table top (a integer);
CREATE TABLE
test=# create table upper1 () inherits (top);
CREATE TABLE
test=# create table upper2 () inherits (top);
CREATE TABLE
test=# create table lower1 () inherits (upper1, upper2);
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE
test=# create table lower2 () inherits (upper1, upper2);
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE
test=# create table spoiler (a integer);
CREATE TABLE
test=# create table bottom () inherits (lower1, lower2, spoiler);
NOTICE: merging multiple inherited definitions of column "a"
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE
test=# alter table top rename a to b;
ALTER TABLE
test=# select * from spoiler;
ERROR: could not find inherited attribute "a" of relation "bottom"

Also, there is a compiler warning due to an unused variable that
should be fixed.

I'll mark this Waiting on Author.

...Robert

#45KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#42)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/28 5:42), Robert Haas wrote:

On Wed, Jan 27, 2010 at 10:17 AM, KaiGai Kohei<kaigai@kaigai.gr.jp> wrote:

(2010/01/27 23:29), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

The attached patch is revised one based on the V3 approach.
The only difference from V3 is that it also applies checks on the
AT_AlterColumnType option, not only renameatt().

I think I was clear about what the next step was for this patch in my
previous email, but let me try again.

http://archives.postgresql.org/pgsql-hackers/2010-01/msg02407.php

See also Tom's comments here:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00110.php

I don't believe that either Tom or I are prepared to commit a patch
based on this approach, at least not unless someone makes an attempt
to do it the other way and finds an even more serious problem. If
you're not interested in rewriting the patch along the lines Tom
suggested, then we should just mark this as Returned with Feedback and
move on.

The V3/V5 patch was the rewritten one based on the Tom's comment, as is.
It counts the expected inhcount at the first find_all_inheritors() time
at once, and it compares the pg_attribute.attinhcount.
(In actually, find_all_inheritors() does not have a capability to count
the number of merged from a common origin, so I newly defined the
find_all_inheritors_with_inhcount().)

Am I missing something?

Err... I'm not sure. I thought I understood what the different
versions of this patch were doing, but apparently I'm all confused.
I'll take another look at this.

Just to be safe, I'd like to introduce the differences between revisions.

* The V2 patch
http://archives.postgresql.org/message-id/4B3F6353.3080105@kaigai.gr.jp

It just checked whether the pg_attribute.inhcount is larger than 1, or not.
But it was problematic when diamond-inheritance case.

* The V3 patch
http://archives.postgresql.org/message-id/4B41BB04.2070609@ak.jp.nec.com

It computed an expected-inhcount for each relations when renameatt()
picks up all the child relations on the top of recursion level.
Its cost to walk on the inheritance tree is similar to existing code
except for a case when we have many diamond-inheritance, because
find_all_inheritors() does not need to walk on the child relations
that was already checked, but find_all_inheritors_with_inhcount()
has to walk on these children to compute multiplicity.

See the following example,

T2
/ \
T1 T4 - T5
\ /
T3

In this case, find_all_inheritors() encounter T4 with T1-T2 path and
T1-T3 path. But it does not need to scan T5 on the second time,
because it already chains OID of the T4 and T5 with the first walks,
and list_concat_unique_oid() does not add duplicated OIDs anymore.

But find_all_inheritors_with_inhcount() needs to walks on the T4-T5 path
to compute the number of times being inherited, even if second walks.
Your testcase emphasized this difference so much. But, in my opinion,
it is quite natural because the existing code does not apply necessary
checks.

* The V4 patch
http://archives.postgresql.org/message-id/4B4EC1F1.30108@ak.jp.nec.com

I found out ALTER COLUMN TYPE also has same issue.
But ATPrepAlterColumnType() did recursive scan using ATSimpleRecursion(),
so it needs to modify the logic in this function. At that time, I preferred
to compute an expected inhcount for each recursion level, rather than
modifying the logic in ATPrepAlterColumnType(), although its cost was
larger than find_all_inheritors_with_inhcount().

* The V5 patch
http://archives.postgresql.org/message-id/4B5FFE4B.1060505@ak.jp.nec.com

We can find out a performance matter in the V4 patch, so I reverted the
base logic into V3 approach. In addition to the reverting, I also modified
the ATPrepAlterColumnType() to check whether the column to be altered
is inherited from multiple origin, or not.

Bernd (or anyone), feel free to take a look in parallel. More eyes
would be helpful...

...Robert

--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#46KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#44)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/28 6:58), Robert Haas wrote:

On Wed, Jan 27, 2010 at 3:42 PM, Robert Haas<robertmhaas@gmail.com> wrote:

On Wed, Jan 27, 2010 at 10:17 AM, KaiGai Kohei<kaigai@kaigai.gr.jp> wrote:

(2010/01/27 23:29), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

The attached patch is revised one based on the V3 approach.
The only difference from V3 is that it also applies checks on the
AT_AlterColumnType option, not only renameatt().

I think I was clear about what the next step was for this patch in my
previous email, but let me try again.

http://archives.postgresql.org/pgsql-hackers/2010-01/msg02407.php

See also Tom's comments here:

http://archives.postgresql.org/pgsql-hackers/2010-01/msg00110.php

I don't believe that either Tom or I are prepared to commit a patch
based on this approach, at least not unless someone makes an attempt
to do it the other way and finds an even more serious problem. If
you're not interested in rewriting the patch along the lines Tom
suggested, then we should just mark this as Returned with Feedback and
move on.

The V3/V5 patch was the rewritten one based on the Tom's comment, as is.
It counts the expected inhcount at the first find_all_inheritors() time
at once, and it compares the pg_attribute.attinhcount.
(In actually, find_all_inheritors() does not have a capability to count
the number of merged from a common origin, so I newly defined the
find_all_inheritors_with_inhcount().)

Am I missing something?

Err... I'm not sure. I thought I understood what the different
versions of this patch were doing, but apparently I'm all confused.
I'll take another look at this.

OK, I took a look at this. It's busted:

test=# create table top (a integer);
CREATE TABLE
test=# create table upper1 () inherits (top);
CREATE TABLE
test=# create table upper2 () inherits (top);
CREATE TABLE
test=# create table lower1 () inherits (upper1, upper2);
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE
test=# create table lower2 () inherits (upper1, upper2);
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE
test=# create table spoiler (a integer);
CREATE TABLE
test=# create table bottom () inherits (lower1, lower2, spoiler);
NOTICE: merging multiple inherited definitions of column "a"
NOTICE: merging multiple inherited definitions of column "a"
CREATE TABLE
test=# alter table top rename a to b;
ALTER TABLE
test=# select * from spoiler;
ERROR: could not find inherited attribute "a" of relation "bottom"

Hmm, indeed, this logic (V3/V5) is busted.

The idea of V4 patch can also handle this case correctly, although it
is lesser in performance.
I wonder whether it is really unacceptable cost in performance, or not.
Basically, I assume ALTER TABLE RENAME/TYPE is not frequent operations,
and I don't think this bugfix will damage to the reputation of PostgreSQL.

Where should we go on the next?

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#47Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#46)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/1/27 KaiGai Kohei <kaigai@ak.jp.nec.com>:

Hmm, indeed, this logic (V3/V5) is busted.

The idea of V4 patch can also handle this case correctly, although it
is lesser in performance.
I wonder whether it is really unacceptable cost in performance, or not.
Basically, I assume ALTER TABLE RENAME/TYPE is not frequent operations,
and I don't think this bugfix will damage to the reputation of PostgreSQL.

Where should we go on the next?

Isn't the problem here just that the following comment is 100% wrong?

/*
* Unlike find_all_inheritors(), we need to walk on
child relations
* that have diamond inheritance tree, because this
function has to
* return correct expected inhecount to the caller.
*/

It seems to me that the right solution here is to just add one more
argument to find_all_inheritors(), something like List
**expected_inh_count.

Am I missing something?

...Robert

#48KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#47)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/29 0:46), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

Hmm, indeed, this logic (V3/V5) is busted.

The idea of V4 patch can also handle this case correctly, although it
is lesser in performance.
I wonder whether it is really unacceptable cost in performance, or not.
Basically, I assume ALTER TABLE RENAME/TYPE is not frequent operations,
and I don't think this bugfix will damage to the reputation of PostgreSQL.

Where should we go on the next?

Isn't the problem here just that the following comment is 100% wrong?

/*
* Unlike find_all_inheritors(), we need to walk on
child relations
* that have diamond inheritance tree, because this
function has to
* return correct expected inhecount to the caller.
*/

It seems to me that the right solution here is to just add one more
argument to find_all_inheritors(), something like List
**expected_inh_count.

Am I missing something?

The find_all_inheritors() does not walk on child relations more than
two times, even if a child has multiple parents inherited from common
origin, because list_concat_unique_oid() ignores the given OID if it
is already on the list. It means all the child relations under the
relation already walked on does not checked anywhere. (Of course,
this assumption is correct for the purpose of find_all_inheritors()
with minimum cost.)

What we want to do here is to compute the number of times a certain
child relation is inherited from a common origin; it shall be the
expected-inhcount. So, we need an arrangement to the logic.

For example, see the following diagram.

T2
/ \
T1 T4---T5
\ /
T3

If we call find_all_inheritors() with T1. The find_inheritance_children()
returns T2 and T3 for T1.
Then, it calls find_inheritance_children() for T2, and it returns T4.
Then, it calls find_inheritance_children() for T3, and it returns T4, but
it is already in the "rels_list", so list_concat_unique_oid() ignores it.
Then, it calls find_inheritance_children() for T4, and it returns T5.

In this example, we want the expected inhcount for T2 and T3 should be 1,
for T4 and T5 should be 2. However, it walks on T4 and T5 only once, so
they will have 1 incorrectly.
Even if we count up the ignored OID (T4), find_all_inheritors() does not
walk on T5, because it is already walked on obviously when T4 is ignored.

However, my V3/V5 logic is also busted when a certain relation is inherited
from a relation which has multiple parents.

Right now, we have only the V4 logic which works correctly....

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#49Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#48)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/1/28 KaiGai Kohei <kaigai@ak.jp.nec.com>:

(2010/01/29 0:46), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

Hmm, indeed, this logic (V3/V5) is busted.

The idea of V4 patch can also handle this case correctly, although it
is lesser in performance.
I wonder whether it is really unacceptable cost in performance, or not.
Basically, I assume ALTER TABLE RENAME/TYPE is not frequent operations,
and I don't think this bugfix will damage to the reputation of PostgreSQL.

Where should we go on the next?

Isn't the problem here just that the following comment is 100% wrong?

                 /*
                  * Unlike find_all_inheritors(), we need to walk on
child relations
                  * that have diamond inheritance tree, because this
function has to
                  * return correct expected inhecount to the caller.
                  */

It seems to me that the right solution here is to just add one more
argument to find_all_inheritors(), something like List
**expected_inh_count.

Am I missing something?

The find_all_inheritors() does not walk on child relations more than
two times, even if a child has multiple parents inherited from common
origin, because list_concat_unique_oid() ignores the given OID if it
is already on the list. It means all the child relations under the
relation already walked on does not checked anywhere. (Of course,
this assumption is correct for the purpose of find_all_inheritors()
with minimum cost.)

What we want to do here is to compute the number of times a certain
child relation is inherited from a common origin; it shall be the
expected-inhcount. So, we need an arrangement to the logic.

For example, see the following diagram.

  T2
 /  \
T1    T4---T5
 \  /
  T3

If we call find_all_inheritors() with T1. The find_inheritance_children()
returns T2 and T3 for T1.
Then, it calls find_inheritance_children() for T2, and it returns T4.
Then, it calls find_inheritance_children() for T3, and it returns T4, but
it is already in the "rels_list", so list_concat_unique_oid() ignores it.
Then, it calls find_inheritance_children() for T4, and it returns T5.

In this example, we want the expected inhcount for T2 and T3 should be 1,
for T4 and T5 should be 2. However, it walks on T4 and T5 only once, so
they will have 1 incorrectly.
Even if we count up the ignored OID (T4), find_all_inheritors() does not
walk on T5, because it is already walked on obviously when T4 is ignored.

I think the count for T5 should be 1, and I think that the count for
T4 can easily be made to be 2 by coding the algorithm correctly.

...Robert

#50KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#49)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/29 9:29), Robert Haas wrote:

2010/1/28 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/01/29 0:46), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

Hmm, indeed, this logic (V3/V5) is busted.

The idea of V4 patch can also handle this case correctly, although it
is lesser in performance.
I wonder whether it is really unacceptable cost in performance, or not.
Basically, I assume ALTER TABLE RENAME/TYPE is not frequent operations,
and I don't think this bugfix will damage to the reputation of PostgreSQL.

Where should we go on the next?

Isn't the problem here just that the following comment is 100% wrong?

/*
* Unlike find_all_inheritors(), we need to walk on
child relations
* that have diamond inheritance tree, because this
function has to
* return correct expected inhecount to the caller.
*/

It seems to me that the right solution here is to just add one more
argument to find_all_inheritors(), something like List
**expected_inh_count.

Am I missing something?

The find_all_inheritors() does not walk on child relations more than
two times, even if a child has multiple parents inherited from common
origin, because list_concat_unique_oid() ignores the given OID if it
is already on the list. It means all the child relations under the
relation already walked on does not checked anywhere. (Of course,
this assumption is correct for the purpose of find_all_inheritors()
with minimum cost.)

What we want to do here is to compute the number of times a certain
child relation is inherited from a common origin; it shall be the
expected-inhcount. So, we need an arrangement to the logic.

For example, see the following diagram.

T2
/ \
T1 T4---T5
\ /
T3

If we call find_all_inheritors() with T1. The find_inheritance_children()
returns T2 and T3 for T1.
Then, it calls find_inheritance_children() for T2, and it returns T4.
Then, it calls find_inheritance_children() for T3, and it returns T4, but
it is already in the "rels_list", so list_concat_unique_oid() ignores it.
Then, it calls find_inheritance_children() for T4, and it returns T5.

In this example, we want the expected inhcount for T2 and T3 should be 1,
for T4 and T5 should be 2. However, it walks on T4 and T5 only once, so
they will have 1 incorrectly.
Even if we count up the ignored OID (T4), find_all_inheritors() does not
walk on T5, because it is already walked on obviously when T4 is ignored.

I think the count for T5 should be 1, and I think that the count for
T4 can easily be made to be 2 by coding the algorithm correctly.

Ahh, it is right. I was confused.

Is it possible to introduce the logic mathematical-strictly?
Now I'm considering whether the find_all_inheritors() logic is suitable
for any cases, or not.

What we want to compute here is:

WITH RECURSIVE r AS (
SELECT 't1'::regclass AS inhrelid
UNION ALL
SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
) -- r is all the child relations inherited from 't1'
SELECT inhrelid::regclass, count(*) FROM pg_inherits
WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid;

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#51KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#50)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/29 9:58), KaiGai Kohei wrote:

(2010/01/29 9:29), Robert Haas wrote:

2010/1/28 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/01/29 0:46), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

Hmm, indeed, this logic (V3/V5) is busted.

The idea of V4 patch can also handle this case correctly, although it
is lesser in performance.
I wonder whether it is really unacceptable cost in performance, or not.
Basically, I assume ALTER TABLE RENAME/TYPE is not frequent operations,
and I don't think this bugfix will damage to the reputation of PostgreSQL.

Where should we go on the next?

Isn't the problem here just that the following comment is 100% wrong?

/*
* Unlike find_all_inheritors(), we need to walk on
child relations
* that have diamond inheritance tree, because this
function has to
* return correct expected inhecount to the caller.
*/

It seems to me that the right solution here is to just add one more
argument to find_all_inheritors(), something like List
**expected_inh_count.

Am I missing something?

The find_all_inheritors() does not walk on child relations more than
two times, even if a child has multiple parents inherited from common
origin, because list_concat_unique_oid() ignores the given OID if it
is already on the list. It means all the child relations under the
relation already walked on does not checked anywhere. (Of course,
this assumption is correct for the purpose of find_all_inheritors()
with minimum cost.)

What we want to do here is to compute the number of times a certain
child relation is inherited from a common origin; it shall be the
expected-inhcount. So, we need an arrangement to the logic.

For example, see the following diagram.

T2
/ \
T1 T4---T5
\ /
T3

If we call find_all_inheritors() with T1. The find_inheritance_children()
returns T2 and T3 for T1.
Then, it calls find_inheritance_children() for T2, and it returns T4.
Then, it calls find_inheritance_children() for T3, and it returns T4, but
it is already in the "rels_list", so list_concat_unique_oid() ignores it.
Then, it calls find_inheritance_children() for T4, and it returns T5.

In this example, we want the expected inhcount for T2 and T3 should be 1,
for T4 and T5 should be 2. However, it walks on T4 and T5 only once, so
they will have 1 incorrectly.
Even if we count up the ignored OID (T4), find_all_inheritors() does not
walk on T5, because it is already walked on obviously when T4 is ignored.

I think the count for T5 should be 1, and I think that the count for
T4 can easily be made to be 2 by coding the algorithm correctly.

Ahh, it is right. I was confused.

Is it possible to introduce the logic mathematical-strictly?
Now I'm considering whether the find_all_inheritors() logic is suitable
for any cases, or not.

I modified the logic to compute an expected inhcount of the child relations.

What we want to compute here is to compute the number of times a certain
relation being inherited directly from any relations delivered from a unique
origin. If the column to be renamed is eventually inherited from a common
origin, its attinhcount is not larger than the expected inhcount.

WITH RECURSIVE r AS (
SELECT 't1'::regclass AS inhrelid
UNION ALL
SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
) -- r is all the child relations inherited from 't1'
SELECT inhrelid::regclass, count(*) FROM pg_inherits
WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid;

The modified logic increments the expected inhcount of the relation already
walked on. If we found a child relation twice or more, it means the child
relation is at the junction of the inheritance tree.
In this case, we don't walk on the child relations any more, but it is not
necessary, because the first path already checked it.

The find_all_inheritors() is called from several points more than renameatt(),
so I added find_all_inheritors_with_inhcount() which takes argument of the
expected inhcount list. And, find_all_inheritors() was also modified to call
find_all_inheritors_with_inhcount() with NULL argument to avoid code duplication.

It is the result of Berrnd's test case.

- CVS HEAD
0.895s
0.903s
0.884s
0.896s
0.892s

- with V6 patch
0.972s
0.941s
0.961s
0.949s
0.946s

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-fix-inherit-rename.6.patchapplication/octect-stream; name=pgsql-fix-inherit-rename.6.patchDownload
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 158,164 **** List *
  find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
  {
  	List	   *rels_list;
! 	ListCell   *l;
  
  	/*
  	 * We build a list starting with the given rel and adding all direct and
--- 158,212 ----
  find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
  {
  	List	   *rels_list;
! 
! 	find_all_inheritors_with_inhcount(parentrelId, lockmode,
! 									  &rels_list, NULL);
! 
! 	return rels_list;
! }
! 
! /*
!  * find_all_inheritors_with_inhcount
!  *
!  *		It returns a list of relations OIDs including the given rel plus
!  *		all relations that inherit from it, directly or indirectly.
!  *		In addition, it also returns the expected inhcount for them.
!  *
!  * The expected inhcount means the number of times a certain relation being
!  * inherited from any other relations which were inherited from the root
!  * of inheritance tree directly or indirectly.
!  *
!  * It is used to control some of ALTER TABLE operations on columns inherited
!  * from a relation. For example, an inherited column must has same name
!  * within the series of inheritance. So, we need to prevent renaming when
!  * the column to be renamed is delivered from multiple independent relations.
!  *
!  * The specified lock type is acquired on all child relations (but not on the
!  * given rel; caller should already have locked it).  If lockmode is NoLock
!  * then no locks are acquired, but caller must beware of race conditions
!  * against possible DROPs of child relations.
!  *
!  *    T2     For example, T2 and T3 are inherited from T1, T4 is inherited
!  *   /  \    from T2 and T3. And, T5 is inherited from T3 and S1.
!  * T1    T4  When we try to rename a column within T1, it needs to rename
!  *   \  /    inherited column in the child relations also.
!  *    T3-T5  If attinhcount of the column is larger than its expected inhcount,
!  *      /    it means the column is delivered from the different origin, so
!  *    S1     we have to prevent this operations to keep database integrity.
!  *
!  * In this example, the expected inhcount is 1 in T2, T3 and T5, and 2 in T4.
!  * The column inherited from T1 has attinhcount = 2 in T4, so it does not
!  * prevent renaming.
!  *
!  * If the column inherited from T1 has attinhcount = 2 in T5, it means the
!  * column was also inherited from S1, so we can prevent renaming it.
!  */
! void
! find_all_inheritors_with_inhcount(Oid parentrelId, LOCKMODE lockmode,
! 								  List **child_oids, List **child_inhs)
! {
! 	List	   *rels_oids, *rels_inhs;
! 	ListCell   *cell;
  
  	/*
  	 * We build a list starting with the given rel and adding all direct and
***************
*** 166,196 **** find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
  	 * already-found rels and the agenda of rels yet to be scanned for more
  	 * children.  This is a bit tricky but works because the foreach() macro
  	 * doesn't fetch the next list element until the bottom of the loop.
  	 */
! 	rels_list = list_make1_oid(parentrelId);
  
! 	foreach(l, rels_list)
  	{
! 		Oid			currentrel = lfirst_oid(l);
! 		List	   *currentchildren;
  
  		/* Get the direct children of this rel */
! 		currentchildren = find_inheritance_children(currentrel, lockmode);
  
! 		/*
! 		 * Add to the queue only those children not already seen. This avoids
! 		 * making duplicate entries in case of multiple inheritance paths from
! 		 * the same parent.  (It'll also keep us from getting into an infinite
! 		 * loop, though theoretically there can't be any cycles in the
! 		 * inheritance graph anyway.)
! 		 */
! 		rels_list = list_concat_unique_oid(rels_list, currentchildren);
  	}
  
! 	return rels_list;
  }
  
- 
  /*
   * has_subclass - does this relation have any children?
   *
--- 214,274 ----
  	 * already-found rels and the agenda of rels yet to be scanned for more
  	 * children.  This is a bit tricky but works because the foreach() macro
  	 * doesn't fetch the next list element until the bottom of the loop.
+ 	 *
+ 	 * The root of the inheritance tree always has 0 as an expected inhcount,
+ 	 * because its parent, even if exists, it is never contained within the
+ 	 * inheritance tree originally delivered from the root.
  	 */
! 	rels_oids = list_make1_oid(parentrelId);
! 	rels_inhs = list_make1_int(0);
  
! 	foreach (cell, rels_oids)
  	{
! 		Oid			cur_relid = lfirst_oid(cell);
! 		Oid			temp_relid;
! 		List	   *temp;
! 		ListCell   *lt, *lo, *li;
  
  		/* Get the direct children of this rel */
! 		temp = find_inheritance_children(cur_relid, lockmode);
  
! 		foreach (lt, temp)
! 		{
! 			Oid		temp_relid = lfirst_oid(lt);
! 			bool	found = false;
! 
! 			/*
! 			 * Add to the queue only those children not already seen.
! 			 * This avoids making duplicate entries in case of multiple
! 			 * inheritance paths from the same parent.  (It'll also keep
! 			 * us from getting into an infinite loop, though theoretically
! 			 * there can't be any cycles in the inheritance graph anyway.)
! 			 */
! 			forboth (lo, rels_oids, li, rels_inhs)
! 			{
! 				if (lfirst_oid(lo) == temp_relid)
! 				{
! 					/* increment its expected inhcount */
! 					lfirst_int(li)++;
! 					found = true;
! 					break;
! 				}
! 			}
! 
! 			if (!found)
! 			{
! 				rels_oids = lappend_oid(rels_oids, temp_relid);
! 				rels_inhs = lappend_int(rels_inhs, 1);
! 			}
! 		}
  	}
  
! 	if (child_oids)
! 		*child_oids = rels_oids;
! 	if (child_inhs)
! 		*child_inhs = rels_inhs;
  }
  
  /*
   * has_subclass - does this relation have any children?
   *
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 125,130 **** ExecRenameStmt(RenameStmt *stmt)
--- 125,131 ----
  						renameatt(relid,
  								  stmt->subname,		/* old att name */
  								  stmt->newname,		/* new att name */
+ 								  0,			/* expected inhcount */
  								  interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
  								  false);		/* recursing already? */
  						break;
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 1906,1911 **** void
--- 1906,1912 ----
  renameatt(Oid myrelid,
  		  const char *oldattname,
  		  const char *newattname,
+ 		  int expected_inhcount,
  		  bool recurse,
  		  bool recursing)
  {
***************
*** 1946,1969 **** renameatt(Oid myrelid,
  	 */
  	if (recurse)
  	{
! 		ListCell   *child;
! 		List	   *children;
! 
! 		children = find_all_inheritors(myrelid, AccessExclusiveLock);
  
  		/*
! 		 * 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);
  
! 			if (childrelid == myrelid)
  				continue;
  			/* note we need not recurse again */
! 			renameatt(childrelid, oldattname, newattname, false, true);
  		}
  	}
  	else
--- 1947,1978 ----
  	 */
  	if (recurse)
  	{
! 		List	   *child_oids, *child_inhs;
! 		ListCell   *lo, *li;
  
  		/*
! 		 * It is same as find_all_inheritors, except for it returns the
! 		 * list of expected inhcount for each child relations.
! 		 * It is a value to be set on pg_attribute.inhcount when the column
! 		 * to be renamed is not merged with any other column which have
! 		 * different origin.
! 		 * If pg_attribute.inhcount is larger than the expected inhcount,
! 		 * it means the column is merged from different serieses.
  		 */
! 		find_all_inheritors_with_inhcount(myrelid, AccessExclusiveLock,
! 										  &child_oids, &child_inhs);
! 
! 		forboth (lo, child_oids, li, child_inhs)
  		{
! 			Oid		child_relid = lfirst_oid(lo);
! 			int		child_inhcount = lfirst_int(li);
  
! 			if (child_relid == myrelid)
  				continue;
+ 
  			/* note we need not recurse again */
! 			renameatt(child_relid, oldattname, newattname,
! 					  child_inhcount, false, true);
  		}
  	}
  	else
***************
*** 2007,2012 **** renameatt(Oid myrelid,
--- 2016,2032 ----
  				 errmsg("cannot rename inherited column \"%s\"",
  						oldattname)));
  
+ 	/*
+ 	 * if the attribute is multiple inherited, forbid the renaming,
+ 	 * even if we are already inside a recursive rename, because we
+ 	 * have no reasonable way to keep its integrity.
+ 	 */
+ 	if (attform->attinhcount > expected_inhcount)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ 				 (errmsg("cannot rename multiple inherited column \"%s\"",
+ 						 oldattname))));
+ 
  	/* new name should not already exist */
  
  	/* this test is deliberately not attisdropped-aware */
***************
*** 5772,5782 **** ATPrepAlterColumnType(List **wqueue,
  						colName)));
  
  	/* Don't alter inherited columns */
! 	if (attTup->attinhcount > 0 && !recursing)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! 				 errmsg("cannot alter inherited column \"%s\"",
! 						colName)));
  
  	/* Look up the target type */
  	targettype = typenameTypeId(NULL, typeName, &targettypmod);
--- 5792,5820 ----
  						colName)));
  
  	/* Don't alter inherited columns */
! 	if (!recursing)
! 	{
! 		if (attTup->attinhcount > 0)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! 					 errmsg("cannot alter inherited column \"%s\"",
! 							colName)));
! 	}
! 	else
! 	{
! 		/*
! 		 * The TypeName->location is not used in this code path,
! 		 * so we utilize it to store an enpected inhconunt for each
! 		 * child relations.
! 		 */
! 		int		expected_inhcount = typeName->location;
! 
! 		if (attTup->attinhcount > expected_inhcount)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! 					 errmsg("cannot alter multiple inherited column \"%s\"",
! 							colName)));
! 	}
  
  	/* Look up the target type */
  	targettype = typenameTypeId(NULL, typeName, &targettypmod);
***************
*** 5858,5869 **** 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.
  	 */
  	if (recurse)
! 		ATSimpleRecursion(wqueue, rel, cmd, recurse);
  	else if (!recursing &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
--- 5896,5943 ----
  	ReleaseSysCache(tuple);
  
  	/*
! 	 * The recursion case is not handled by ATSimpleRecursion, because we need
! 	 * to compute an expected inhcount for each child relations. This value is
! 	 * delivered to the recursive invocation to check unexpected type changes.
! 	 * 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)
! 	{
! 		Relation	child_rel;
! 		List	   *child_oids;
! 		List	   *child_inhs;
! 		ListCell   *lo;
! 		ListCell   *li;
! 
! 		find_all_inheritors_with_inhcount(RelationGetRelid(rel),
! 										  AccessExclusiveLock,
! 										  &child_oids, &child_inhs);
! 		forboth(lo, child_oids, li, child_inhs)
! 		{
! 			AlterTableCmd  *child_cmd;
! 			Relation		child_rel;
! 			Oid				child_relid = lfirst_oid(lo);
! 			int				child_inhcount = lfirst_int(li);
! 
! 			if (child_relid == RelationGetRelid(rel))
! 				continue;
! 
! 			/*
! 			 * The TypeName->location is not used in this code path,
! 			 * so we utilize it to store an expected inhcount for each
! 			 * child relation.
! 			 */
! 			child_cmd = copyObject(cmd);
! 			((TypeName *)child_cmd->def)->location = child_inhcount;
! 
! 			/* find_all_inheritors_with_inhcount already got lock  */
! 			child_rel = relation_open(child_relid, NoLock);
! 			CheckTableNotInUse(child_rel, "ALTER TABLE");
! 			ATPrepCmd(wqueue, child_rel, child_cmd, false, true);
! 			relation_close(child_rel, NoLock);
! 		}
! 	}
  	else if (!recursing &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
*** a/src/include/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 19,24 ****
--- 19,28 ----
  
  extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
  extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode);
+ extern void find_all_inheritors_with_inhcount(Oid parentrelId,
+ 											  LOCKMODE lockmode,
+ 											  List **child_oids,
+ 											  List **child_inhs);
  extern bool has_subclass(Oid relationId);
  extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
  
*** a/src/include/commands/tablecmds.h
--- b/src/include/commands/tablecmds.h
***************
*** 42,47 **** extern void ExecuteTruncate(TruncateStmt *stmt);
--- 42,48 ----
  extern void renameatt(Oid myrelid,
  		  const char *oldattname,
  		  const char *newattname,
+ 		  int expected_inhcount,
  		  bool recurse,
  		  bool recursing);
  
*** a/src/test/regress/expected/inherit.out
--- b/src/test/regress/expected/inherit.out
***************
*** 1053,1055 **** NOTICE:  merging column "a" with inherited definition
--- 1053,1158 ----
  ERROR:  column "a" has a storage parameter conflict
  DETAIL:  MAIN versus EXTENDED
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ -- Test for renaming and type changes (simple multiple inheritance)
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE s1 (b int, c int);
+ CREATE TABLE ts (d int) INHERITS (t1, s1);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE s1 ALTER b TYPE float;    -- to be failed
+ ERROR:  cannot alter multiple inherited column "b"
+ ALTER TABLE s1 ALTER c TYPE float;
+ ALTER TABLE ts ALTER c TYPE text;     -- to be failed
+ ERROR:  cannot alter inherited column "c"
+ ALTER TABLE ts ALTER d TYPE text;
+ ALTER TABLE t1 RENAME a TO aa;
+ ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ ALTER TABLE ts RENAME aa TO aaa;      -- to be failed
+ ERROR:  cannot rename inherited column "aa"
+ ALTER TABLE ts RENAME d TO dd;
+ \d+ ts
+                        Table "public.ts"
+  Column |       Type       | Modifiers | Storage  | Description 
+ --------+------------------+-----------+----------+-------------
+  aa     | integer          |           | plain    | 
+  b      | integer          |           | plain    | 
+  c      | double precision |           | plain    | 
+  dd     | text             |           | extended | 
+ Inherits: t1,
+           s1
+ Has OIDs: no
+ 
+ DROP TABLE ts;
+ -- Test for renaming and type changes (diamond inheritance)
+ CREATE TABLE t2 (x int) INHERITS (t1);
+ CREATE TABLE t3 (y int) INHERITS (t1);
+ CREATE TABLE t4 (z int) INHERITS (t2, t3);
+ NOTICE:  merging multiple inherited definitions of column "aa"
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 ALTER aa TYPE float;
+ ALTER TABLE t1 RENAME aa TO aaa;
+ \d+ t4
+                        Table "public.t4"
+  Column |       Type       | Modifiers | Storage | Description 
+ --------+------------------+-----------+---------+-------------
+  aaa    | double precision |           | plain   | 
+  b      | integer          |           | plain   | 
+  x      | integer          |           | plain   | 
+  y      | integer          |           | plain   | 
+  z      | integer          |           | plain   | 
+ Inherits: t2,
+           t3
+ Has OIDs: no
+ 
+ CREATE TABLE ts (d int) INHERITS (t2, s1);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 ALTER aaa TYPE text;
+ ALTER TABLE t1 ALTER b TYPE text;             -- to be failed
+ ERROR:  cannot alter multiple inherited column "b"
+ ALTER TABLE t1 RENAME aaa TO aaaa;
+ ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ \d+ ts
+                        Table "public.ts"
+  Column |       Type       | Modifiers | Storage  | Description 
+ --------+------------------+-----------+----------+-------------
+  aaaa   | text             |           | extended | 
+  b      | integer          |           | plain    | 
+  x      | integer          |           | plain    | 
+  c      | double precision |           | plain    | 
+  d      | integer          |           | plain    | 
+ Inherits: t2,
+           s1
+ Has OIDs: no
+ 
+ WITH RECURSIVE r AS (
+   SELECT 't1'::regclass AS inhrelid
+ UNION ALL
+   SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
+ )
+ SELECT attrelid::regclass, attname, attinhcount, r.expected
+   FROM (SELECT inhrelid, count(*) AS expected FROM pg_inherits
+         WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid) r
+   JOIN pg_attribute a ON r.inhrelid = a.attrelid WHERE attislocal = false;
+  attrelid | attname | attinhcount | expected 
+ ----------+---------+-------------+----------
+  t2       | b       |           1 |        1
+  t3       | b       |           1 |        1
+  t4       | b       |           2 |        2
+  t4       | x       |           1 |        2
+  t4       | y       |           1 |        2
+  t2       | aaaa    |           1 |        1
+  t3       | aaaa    |           1 |        1
+  t4       | aaaa    |           2 |        2
+  ts       | b       |           2 |        1
+  ts       | x       |           1 |        1
+  ts       | c       |           1 |        1
+  ts       | aaaa    |           1 |        1
+ (12 rows)
+ 
+ DROP TABLE t1, s1 CASCADE;
+ NOTICE:  drop cascades to 4 other objects
+ DETAIL:  drop cascades to table t2
+ drop cascades to table ts
+ drop cascades to table t3
+ drop cascades to table t4
*** a/src/test/regress/sql/inherit.sql
--- b/src/test/regress/sql/inherit.sql
***************
*** 334,336 **** CREATE TABLE inh_error1 () INHERITS (t1, t4);
--- 334,382 ----
  CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
  
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ 
+ -- Test for renaming and type changes (simple multiple inheritance)
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE s1 (b int, c int);
+ CREATE TABLE ts (d int) INHERITS (t1, s1);
+ 
+ ALTER TABLE s1 ALTER b TYPE float;    -- to be failed
+ ALTER TABLE s1 ALTER c TYPE float;
+ ALTER TABLE ts ALTER c TYPE text;     -- to be failed
+ ALTER TABLE ts ALTER d TYPE text;
+ 
+ ALTER TABLE t1 RENAME a TO aa;
+ ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ ALTER TABLE ts RENAME aa TO aaa;      -- to be failed
+ ALTER TABLE ts RENAME d TO dd;
+ \d+ ts
+ 
+ DROP TABLE ts;
+ 
+ -- Test for renaming and type changes (diamond inheritance)
+ CREATE TABLE t2 (x int) INHERITS (t1);
+ CREATE TABLE t3 (y int) INHERITS (t1);
+ CREATE TABLE t4 (z int) INHERITS (t2, t3);
+ 
+ ALTER TABLE t1 ALTER aa TYPE float;
+ ALTER TABLE t1 RENAME aa TO aaa;
+ \d+ t4
+ 
+ CREATE TABLE ts (d int) INHERITS (t2, s1);
+ ALTER TABLE t1 ALTER aaa TYPE text;
+ ALTER TABLE t1 ALTER b TYPE text;             -- to be failed
+ ALTER TABLE t1 RENAME aaa TO aaaa;
+ ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ \d+ ts
+ 
+ WITH RECURSIVE r AS (
+   SELECT 't1'::regclass AS inhrelid
+ UNION ALL
+   SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
+ )
+ SELECT attrelid::regclass, attname, attinhcount, r.expected
+   FROM (SELECT inhrelid, count(*) AS expected FROM pg_inherits
+         WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid) r
+   JOIN pg_attribute a ON r.inhrelid = a.attrelid WHERE attislocal = false;
+ 
+ DROP TABLE t1, s1 CASCADE;
#52Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#51)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/1/28 KaiGai Kohei <kaigai@ak.jp.nec.com>:

(2010/01/29 9:58), KaiGai Kohei wrote:

(2010/01/29 9:29), Robert Haas wrote:

2010/1/28 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/01/29 0:46), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

Hmm, indeed, this logic (V3/V5) is busted.

The idea of V4 patch can also handle this case correctly, although it
is lesser in performance.
I wonder whether it is really unacceptable cost in performance, or not.
Basically, I assume ALTER TABLE RENAME/TYPE is not frequent operations,
and I don't think this bugfix will damage to the reputation of PostgreSQL.

Where should we go on the next?

Isn't the problem here just that the following comment is 100% wrong?

                   /*
                    * Unlike find_all_inheritors(), we need to walk on
child relations
                    * that have diamond inheritance tree, because this
function has to
                    * return correct expected inhecount to the caller.
                    */

It seems to me that the right solution here is to just add one more
argument to find_all_inheritors(), something like List
**expected_inh_count.

Am I missing something?

The find_all_inheritors() does not walk on child relations more than
two times, even if a child has multiple parents inherited from common
origin, because list_concat_unique_oid() ignores the given OID if it
is already on the list. It means all the child relations under the
relation already walked on does not checked anywhere. (Of course,
this assumption is correct for the purpose of find_all_inheritors()
with minimum cost.)

What we want to do here is to compute the number of times a certain
child relation is inherited from a common origin; it shall be the
expected-inhcount. So, we need an arrangement to the logic.

For example, see the following diagram.

    T2
   /  \
T1    T4---T5
   \  /
    T3

If we call find_all_inheritors() with T1. The find_inheritance_children()
returns T2 and T3 for T1.
Then, it calls find_inheritance_children() for T2, and it returns T4.
Then, it calls find_inheritance_children() for T3, and it returns T4, but
it is already in the "rels_list", so list_concat_unique_oid() ignores it.
Then, it calls find_inheritance_children() for T4, and it returns T5.

In this example, we want the expected inhcount for T2 and T3 should be 1,
for T4 and T5 should be 2. However, it walks on T4 and T5 only once, so
they will have 1 incorrectly.
Even if we count up the ignored OID (T4), find_all_inheritors() does not
walk on T5, because it is already walked on obviously when T4 is ignored.

I think the count for T5 should be 1, and I think that the count for
T4 can easily be made to be 2 by coding the algorithm correctly.

Ahh, it is right. I was confused.

Is it possible to introduce the logic mathematical-strictly?
Now I'm considering whether the find_all_inheritors() logic is suitable
for any cases, or not.

I modified the logic to compute an expected inhcount of the child relations.

What we want to compute here is to compute the number of times a certain
relation being inherited directly from any relations delivered from a unique
origin. If the column to be renamed is eventually inherited from a common
origin, its attinhcount is not larger than the expected inhcount.

   WITH RECURSIVE r AS (
     SELECT 't1'::regclass AS inhrelid
   UNION ALL
     SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
   )  -- r is all the child relations inherited from 't1'
   SELECT inhrelid::regclass, count(*) FROM pg_inherits
     WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid;

The modified logic increments the expected inhcount of the relation already
walked on. If we found a child relation twice or more, it means the child
relation is at the junction of the inheritance tree.
In this case, we don't walk on the child relations any more, but it is not
necessary, because the first path already checked it.

The find_all_inheritors() is called from several points more than renameatt(),
so I added find_all_inheritors_with_inhcount() which takes argument of the
expected inhcount list. And, find_all_inheritors() was also modified to call
find_all_inheritors_with_inhcount() with NULL argument to avoid code duplication.

It is the result of Berrnd's test case.

- CVS HEAD
 0.895s
 0.903s
 0.884s
 0.896s
 0.892s

- with V6 patch
 0.972s
 0.941s
 0.961s
 0.949s
 0.946s

Well, that seems a lot better. Unfortunately, I can't commit this
code: it's mind-bogglingly ugly. I don't believe that duplicating
find_all_inheritors is the right solution (a point I've mentioned
previously), and it's certainly not right to use typeName->location as
a place to store an unrelated attribute inheritance count. There is
also at least one superfluous variable renaming; and the recursion
handling looks pretty grotty to me, too.

I wonder if we should just leave this alone for 9.0 and revisit the
issue after doing some of the previously-proposed refactoring for 9.1.
The amount of time we're spending trying to fix this is somewhat out
of proportion to the importance of the problem.

...Robert

#53KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#52)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/01/30 3:36), Robert Haas wrote:

2010/1/28 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/01/29 9:58), KaiGai Kohei wrote:

(2010/01/29 9:29), Robert Haas wrote:

2010/1/28 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/01/29 0:46), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

Hmm, indeed, this logic (V3/V5) is busted.

The idea of V4 patch can also handle this case correctly, although it
is lesser in performance.
I wonder whether it is really unacceptable cost in performance, or not.
Basically, I assume ALTER TABLE RENAME/TYPE is not frequent operations,
and I don't think this bugfix will damage to the reputation of PostgreSQL.

Where should we go on the next?

Isn't the problem here just that the following comment is 100% wrong?

/*
* Unlike find_all_inheritors(), we need to walk on
child relations
* that have diamond inheritance tree, because this
function has to
* return correct expected inhecount to the caller.
*/

It seems to me that the right solution here is to just add one more
argument to find_all_inheritors(), something like List
**expected_inh_count.

Am I missing something?

The find_all_inheritors() does not walk on child relations more than
two times, even if a child has multiple parents inherited from common
origin, because list_concat_unique_oid() ignores the given OID if it
is already on the list. It means all the child relations under the
relation already walked on does not checked anywhere. (Of course,
this assumption is correct for the purpose of find_all_inheritors()
with minimum cost.)

What we want to do here is to compute the number of times a certain
child relation is inherited from a common origin; it shall be the
expected-inhcount. So, we need an arrangement to the logic.

For example, see the following diagram.

T2
/ \
T1 T4---T5
\ /
T3

If we call find_all_inheritors() with T1. The find_inheritance_children()
returns T2 and T3 for T1.
Then, it calls find_inheritance_children() for T2, and it returns T4.
Then, it calls find_inheritance_children() for T3, and it returns T4, but
it is already in the "rels_list", so list_concat_unique_oid() ignores it.
Then, it calls find_inheritance_children() for T4, and it returns T5.

In this example, we want the expected inhcount for T2 and T3 should be 1,
for T4 and T5 should be 2. However, it walks on T4 and T5 only once, so
they will have 1 incorrectly.
Even if we count up the ignored OID (T4), find_all_inheritors() does not
walk on T5, because it is already walked on obviously when T4 is ignored.

I think the count for T5 should be 1, and I think that the count for
T4 can easily be made to be 2 by coding the algorithm correctly.

Ahh, it is right. I was confused.

Is it possible to introduce the logic mathematical-strictly?
Now I'm considering whether the find_all_inheritors() logic is suitable
for any cases, or not.

I modified the logic to compute an expected inhcount of the child relations.

What we want to compute here is to compute the number of times a certain
relation being inherited directly from any relations delivered from a unique
origin. If the column to be renamed is eventually inherited from a common
origin, its attinhcount is not larger than the expected inhcount.

WITH RECURSIVE r AS (
SELECT 't1'::regclass AS inhrelid
UNION ALL
SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
) -- r is all the child relations inherited from 't1'
SELECT inhrelid::regclass, count(*) FROM pg_inherits
WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid;

The modified logic increments the expected inhcount of the relation already
walked on. If we found a child relation twice or more, it means the child
relation is at the junction of the inheritance tree.
In this case, we don't walk on the child relations any more, but it is not
necessary, because the first path already checked it.

The find_all_inheritors() is called from several points more than renameatt(),
so I added find_all_inheritors_with_inhcount() which takes argument of the
expected inhcount list. And, find_all_inheritors() was also modified to call
find_all_inheritors_with_inhcount() with NULL argument to avoid code duplication.

It is the result of Berrnd's test case.

- CVS HEAD
0.895s
0.903s
0.884s
0.896s
0.892s

- with V6 patch
0.972s
0.941s
0.961s
0.949s
0.946s

Well, that seems a lot better. Unfortunately, I can't commit this
code: it's mind-bogglingly ugly. I don't believe that duplicating
find_all_inheritors is the right solution (a point I've mentioned
previously), and it's certainly not right to use typeName->location as
a place to store an unrelated attribute inheritance count. There is
also at least one superfluous variable renaming; and the recursion
handling looks pretty grotty to me, too.

I wonder if we should just leave this alone for 9.0 and revisit the
issue after doing some of the previously-proposed refactoring for 9.1.
The amount of time we're spending trying to fix this is somewhat out
of proportion to the importance of the problem.

At least, I think we can fix the bug on renameatt() case in clean way,
although we need an additional recursion handling in ATPrepAlterColumnType()
to fix ALTER COLUMN TYPE cases.

Should it focus on only the original renameatt() matter in this stage?

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#54KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#53)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/02/01 8:41), KaiGai Kohei wrote:

(2010/01/30 3:36), Robert Haas wrote:

2010/1/28 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/01/29 9:58), KaiGai Kohei wrote:

(2010/01/29 9:29), Robert Haas wrote:

2010/1/28 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/01/29 0:46), Robert Haas wrote:

2010/1/27 KaiGai Kohei<kaigai@ak.jp.nec.com>:

Hmm, indeed, this logic (V3/V5) is busted.

The idea of V4 patch can also handle this case correctly, although it
is lesser in performance.
I wonder whether it is really unacceptable cost in performance, or not.
Basically, I assume ALTER TABLE RENAME/TYPE is not frequent operations,
and I don't think this bugfix will damage to the reputation of PostgreSQL.

Where should we go on the next?

Isn't the problem here just that the following comment is 100% wrong?

/*
* Unlike find_all_inheritors(), we need to walk on
child relations
* that have diamond inheritance tree, because this
function has to
* return correct expected inhecount to the caller.
*/

It seems to me that the right solution here is to just add one more
argument to find_all_inheritors(), something like List
**expected_inh_count.

Am I missing something?

The find_all_inheritors() does not walk on child relations more than
two times, even if a child has multiple parents inherited from common
origin, because list_concat_unique_oid() ignores the given OID if it
is already on the list. It means all the child relations under the
relation already walked on does not checked anywhere. (Of course,
this assumption is correct for the purpose of find_all_inheritors()
with minimum cost.)

What we want to do here is to compute the number of times a certain
child relation is inherited from a common origin; it shall be the
expected-inhcount. So, we need an arrangement to the logic.

For example, see the following diagram.

T2
/ \
T1 T4---T5
\ /
T3

If we call find_all_inheritors() with T1. The find_inheritance_children()
returns T2 and T3 for T1.
Then, it calls find_inheritance_children() for T2, and it returns T4.
Then, it calls find_inheritance_children() for T3, and it returns T4, but
it is already in the "rels_list", so list_concat_unique_oid() ignores it.
Then, it calls find_inheritance_children() for T4, and it returns T5.

In this example, we want the expected inhcount for T2 and T3 should be 1,
for T4 and T5 should be 2. However, it walks on T4 and T5 only once, so
they will have 1 incorrectly.
Even if we count up the ignored OID (T4), find_all_inheritors() does not
walk on T5, because it is already walked on obviously when T4 is ignored.

I think the count for T5 should be 1, and I think that the count for
T4 can easily be made to be 2 by coding the algorithm correctly.

Ahh, it is right. I was confused.

Is it possible to introduce the logic mathematical-strictly?
Now I'm considering whether the find_all_inheritors() logic is suitable
for any cases, or not.

I modified the logic to compute an expected inhcount of the child relations.

What we want to compute here is to compute the number of times a certain
relation being inherited directly from any relations delivered from a unique
origin. If the column to be renamed is eventually inherited from a common
origin, its attinhcount is not larger than the expected inhcount.

WITH RECURSIVE r AS (
SELECT 't1'::regclass AS inhrelid
UNION ALL
SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
) -- r is all the child relations inherited from 't1'
SELECT inhrelid::regclass, count(*) FROM pg_inherits
WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid;

The modified logic increments the expected inhcount of the relation already
walked on. If we found a child relation twice or more, it means the child
relation is at the junction of the inheritance tree.
In this case, we don't walk on the child relations any more, but it is not
necessary, because the first path already checked it.

The find_all_inheritors() is called from several points more than renameatt(),
so I added find_all_inheritors_with_inhcount() which takes argument of the
expected inhcount list. And, find_all_inheritors() was also modified to call
find_all_inheritors_with_inhcount() with NULL argument to avoid code duplication.

It is the result of Berrnd's test case.

- CVS HEAD
0.895s
0.903s
0.884s
0.896s
0.892s

- with V6 patch
0.972s
0.941s
0.961s
0.949s
0.946s

Well, that seems a lot better. Unfortunately, I can't commit this
code: it's mind-bogglingly ugly. I don't believe that duplicating
find_all_inheritors is the right solution (a point I've mentioned
previously), and it's certainly not right to use typeName->location as
a place to store an unrelated attribute inheritance count. There is
also at least one superfluous variable renaming; and the recursion
handling looks pretty grotty to me, too.

I wonder if we should just leave this alone for 9.0 and revisit the
issue after doing some of the previously-proposed refactoring for 9.1.
The amount of time we're spending trying to fix this is somewhat out
of proportion to the importance of the problem.

At least, I think we can fix the bug on renameatt() case in clean way,
although we need an additional recursion handling in ATPrepAlterColumnType()
to fix ALTER COLUMN TYPE cases.

Should it focus on only the original renameatt() matter in this stage?

The attached patch modified find_all_inheritors() to return the list of
expected inhcount, if List * pointer is given. And, it focuses on only
the bugs in renameatt() case.

Also, the ALTER COLUMN TYPE case should be also fixed in the 9.1 release
(or 9.0.1?).

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-fix-inherit-rename.7.patchapplication/octect-stream; name=pgsql-fix-inherit-rename.7.patchDownload
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 148,163 **** find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
   * find_all_inheritors -
   *		Returns a list of relation OIDs including the given rel plus
   *		all relations that inherit from it, directly or indirectly.
   *
   * The specified lock type is acquired on all child relations (but not on the
   * given rel; caller should already have locked it).  If lockmode is NoLock
   * then no locks are acquired, but caller must beware of race conditions
   * against possible DROPs of child relations.
   */
! List *
! find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
  {
! 	List	   *rels_list;
  	ListCell   *l;
  
  	/*
--- 148,194 ----
   * find_all_inheritors -
   *		Returns a list of relation OIDs including the given rel plus
   *		all relations that inherit from it, directly or indirectly.
+  *		In addition, it also returns the expected inhcount for them.
   *
   * The specified lock type is acquired on all child relations (but not on the
   * given rel; caller should already have locked it).  If lockmode is NoLock
   * then no locks are acquired, but caller must beware of race conditions
   * against possible DROPs of child relations.
+  *
+  * The expected inhcount means the number of times a certain relation being
+  * inherited from any other relations which were inherited from the root
+  * of inheritance tree directly or indirectly.
+  *
+  * It is used to control some of ALTER TABLE operations on columns inherited
+  * from a relation. For example, an inherited column must has same name
+  * within the series of inheritance. So, we need to prevent renaming when
+  * the column to be renamed is delivered from multiple independent relations.
+  *
+  * The specified lock type is acquired on all child relations (but not on the
+  * given rel; caller should already have locked it).  If lockmode is NoLock
+  * then no locks are acquired, but caller must beware of race conditions
+  * against possible DROPs of child relations.
+  *
+  *    T2     For example, T2 and T3 are inherited from T1, T4 is inherited
+  *   /  \    from T2 and T3. And, T5 is inherited from T3 and S1.
+  * T1    T4  When we try to rename a column within T1, it needs to rename
+  *   \  /    inherited column in the child relations also.
+  *    T3-T5  If attinhcount of the column is larger than its expected inhcount,
+  *      /    it means the column is delivered from the different origin, so
+  *    S1     we have to prevent this operations to keep database integrity.
+  *
+  * In this example, the expected inhcount is 1 in T2, T3 and T5, and 2 in T4.
+  * The column inherited from T1 has attinhcount = 2 in T4, so it does not
+  * prevent renaming.
+  *
+  * If the column inherited from T1 has attinhcount = 2 in T5, it means the
+  * column was also inherited from S1, so we can prevent renaming it.
   */
! void
! find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
! 					List **child_oids, List **child_inhs)
  {
! 	List	   *rel_oids, *rel_inhs;
  	ListCell   *l;
  
  	/*
***************
*** 167,193 **** find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
  	 * children.  This is a bit tricky but works because the foreach() macro
  	 * doesn't fetch the next list element until the bottom of the loop.
  	 */
! 	rels_list = list_make1_oid(parentrelId);
  
! 	foreach(l, rels_list)
  	{
! 		Oid			currentrel = lfirst_oid(l);
! 		List	   *currentchildren;
  
  		/* Get the direct children of this rel */
! 		currentchildren = find_inheritance_children(currentrel, lockmode);
  
! 		/*
! 		 * Add to the queue only those children not already seen. This avoids
! 		 * making duplicate entries in case of multiple inheritance paths from
! 		 * the same parent.  (It'll also keep us from getting into an infinite
! 		 * loop, though theoretically there can't be any cycles in the
! 		 * inheritance graph anyway.)
! 		 */
! 		rels_list = list_concat_unique_oid(rels_list, currentchildren);
  	}
  
! 	return rels_list;
  }
  
  
--- 198,257 ----
  	 * children.  This is a bit tricky but works because the foreach() macro
  	 * doesn't fetch the next list element until the bottom of the loop.
  	 */
! 	rel_oids = list_make1_oid(parentrelId);
! 	rel_inhs = list_make1_int(0);
  
! 	foreach(l, rel_oids)
  	{
! 		Oid			cur_relid = lfirst_oid(l);
! 		Oid			temp_relid;
! 		List	   *temp;
! 		ListCell   *lt, *lo, *li;
  
  		/* Get the direct children of this rel */
! 		temp = find_inheritance_children(cur_relid, lockmode);
  
! 		foreach(lt, temp)
! 		{
! 			Oid		temp_relid = lfirst_oid(lt);
! 			bool	found = false;
! 
! 			/*
! 			 * Add to the queue only those children not already seen.
! 			 * This avoids making duplicate entries in case of multiple
! 			 * inheritance paths from the same parent.  (It'll also keep
! 			 * us from getting into an infinite loop, though theoretically
! 			 * there can't be any cycles in the inheritance graph anyway.)
! 			 */
! 			forboth(lo, rel_oids, li, rel_inhs)
! 			{
! 				/*
! 				 * Increment its expected inhcount, if the child relation
! 				 * is already chained to the rel_oids due to the diamond
! 				 * inheritance tree. In this case, we don't need to chain
! 				 * a new entry into the list.
! 				 */
! 
! 				if (lfirst_oid(lo) == temp_relid)
! 				{
! 					lfirst_int(li)++;
! 					found = true;
! 					break;
! 				}
! 			}
! 
! 			if (!found)
! 			{
! 				rel_oids = lappend_oid(rel_oids, temp_relid);
! 				rel_inhs = lappend_int(rel_inhs, 1);
! 			}
! 		}
  	}
  
! 	if (child_oids)
! 		*child_oids = rel_oids;
! 	if (child_inhs)
! 		*child_inhs = rel_inhs;
  }
  
  
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 125,130 **** ExecRenameStmt(RenameStmt *stmt)
--- 125,131 ----
  						renameatt(relid,
  								  stmt->subname,		/* old att name */
  								  stmt->newname,		/* new att name */
+ 								  0,			/* expected inhcount */
  								  interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
  								  false);		/* recursing already? */
  						break;
*** a/src/backend/commands/analyze.c
--- b/src/backend/commands/analyze.c
***************
*** 1390,1396 **** acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
  	 * Find all members of inheritance set.  We only need AccessShareLock on
  	 * the children.
  	 */
! 	tableOIDs = find_all_inheritors(RelationGetRelid(onerel), AccessShareLock);
  
  	/*
  	 * Check that there's at least one descendant, else fail.  This could
--- 1390,1397 ----
  	 * Find all members of inheritance set.  We only need AccessShareLock on
  	 * the children.
  	 */
! 	find_all_inheritors(RelationGetRelid(onerel), AccessShareLock,
! 						&tableOIDs, NULL);
  
  	/*
  	 * Check that there's at least one descendant, else fail.  This could
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 824,830 **** ExecuteTruncate(TruncateStmt *stmt)
  			ListCell   *child;
  			List	   *children;
  
! 			children = find_all_inheritors(myrelid, AccessExclusiveLock);
  
  			foreach(child, children)
  			{
--- 824,831 ----
  			ListCell   *child;
  			List	   *children;
  
! 			find_all_inheritors(myrelid, AccessExclusiveLock,
! 								&children, NULL);
  
  			foreach(child, children)
  			{
***************
*** 1942,1947 **** void
--- 1943,1949 ----
  renameatt(Oid myrelid,
  		  const char *oldattname,
  		  const char *newattname,
+ 		  int expected_inhcount,
  		  bool recurse,
  		  bool recursing)
  {
***************
*** 1987,2010 **** renameatt(Oid myrelid,
  	 */
  	if (recurse)
  	{
! 		ListCell   *child;
! 		List	   *children;
! 
! 		children = find_all_inheritors(myrelid, AccessExclusiveLock);
  
  		/*
  		 * 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);
  
! 			if (childrelid == myrelid)
  				continue;
  			/* note we need not recurse again */
! 			renameatt(childrelid, oldattname, newattname, false, true);
  		}
  	}
  	else
--- 1989,2019 ----
  	 */
  	if (recurse)
  	{
! 		List	   *child_oids, *child_inhs;
! 		ListCell   *lo, *li;
  
  		/*
  		 * 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.
+ 		 * Also, we need the list of expected inhcount, since it has to be
+ 		 * forbidden to rename a column that is inherited from different
+ 		 * origin. (Note that we allow diamond-inheritance tree)
  		 */
! 		find_all_inheritors(myrelid, AccessExclusiveLock,
! 							&child_oids, &child_inhs);
! 
! 		forboth(lo, child_oids, li, child_inhs)
  		{
! 			Oid		child_relid = lfirst_oid(lo);
! 			int		child_inhcount = lfirst_int(li);
  
! 			if (child_relid == myrelid)
  				continue;
+ 
  			/* note we need not recurse again */
! 			renameatt(child_relid, oldattname, newattname,
! 					  child_inhcount, false, true);
  		}
  	}
  	else
***************
*** 2048,2053 **** renameatt(Oid myrelid,
--- 2057,2073 ----
  				 errmsg("cannot rename inherited column \"%s\"",
  						oldattname)));
  
+ 	/*
+ 	 * if the attribute is multiple inherited, forbid the renaming,
+ 	 * even if we are already inside a recursive rename, because we
+ 	 * have no reasonable way to keep its integrity.
+ 	 */
+ 	if (attform->attinhcount > expected_inhcount)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ 				 (errmsg("cannot rename multiple inherited column \"%s\"",
+ 						 oldattname))));
+ 
  	/* new name should not already exist */
  
  	/* this test is deliberately not attisdropped-aware */
***************
*** 3410,3416 **** ATSimpleRecursion(List **wqueue, Relation rel,
  		ListCell   *child;
  		List	   *children;
  
! 		children = find_all_inheritors(relid, AccessExclusiveLock);
  
  		/*
  		 * find_all_inheritors does the recursive search of the inheritance
--- 3430,3436 ----
  		ListCell   *child;
  		List	   *children;
  
! 		find_all_inheritors(relid, AccessExclusiveLock, &children, NULL);
  
  		/*
  		 * find_all_inheritors does the recursive search of the inheritance
***************
*** 7232,7239 **** ATExecAddInherit(Relation child_rel, RangeVar *parent)
  	 *
  	 * We use weakest lock we can on child's children, namely AccessShareLock.
  	 */
! 	children = find_all_inheritors(RelationGetRelid(child_rel),
! 								   AccessShareLock);
  
  	if (list_member_oid(children, RelationGetRelid(parent_rel)))
  		ereport(ERROR,
--- 7252,7259 ----
  	 *
  	 * We use weakest lock we can on child's children, namely AccessShareLock.
  	 */
! 	find_all_inheritors(RelationGetRelid(child_rel), AccessShareLock,
! 						&children, NULL);
  
  	if (list_member_oid(children, RelationGetRelid(parent_rel)))
  		ereport(ERROR,
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
***************
*** 1180,1186 **** expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
  		lockmode = AccessShareLock;
  
  	/* Scan for all members of inheritance set, acquire needed locks */
! 	inhOIDs = find_all_inheritors(parentOID, lockmode);
  
  	/*
  	 * Check that there's at least one descendant, else treat as no-child
--- 1180,1186 ----
  		lockmode = AccessShareLock;
  
  	/* Scan for all members of inheritance set, acquire needed locks */
! 	find_all_inheritors(parentOID, lockmode, &inhOIDs, NULL);
  
  	/*
  	 * Check that there's at least one descendant, else treat as no-child
*** a/src/include/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 18,24 ****
  #include "storage/lock.h"
  
  extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
! extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode);
  extern bool has_subclass(Oid relationId);
  extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
  
--- 18,25 ----
  #include "storage/lock.h"
  
  extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
! extern void find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
! 								List **child_oids, List **child_inhs);
  extern bool has_subclass(Oid relationId);
  extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
  
*** a/src/include/commands/tablecmds.h
--- b/src/include/commands/tablecmds.h
***************
*** 42,47 **** extern void ExecuteTruncate(TruncateStmt *stmt);
--- 42,48 ----
  extern void renameatt(Oid myrelid,
  		  const char *oldattname,
  		  const char *newattname,
+ 		  int expected_inhcount,
  		  bool recurse,
  		  bool recursing);
  
*** a/src/test/regress/expected/inherit.out
--- b/src/test/regress/expected/inherit.out
***************
*** 1053,1055 **** NOTICE:  merging column "a" with inherited definition
--- 1053,1148 ----
  ERROR:  column "a" has a storage parameter conflict
  DETAIL:  MAIN versus EXTENDED
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ -- Test for renaming in simple multiple inheritance
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE s1 (b int, c int);
+ CREATE TABLE ts (d int) INHERITS (t1, s1);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 RENAME a TO aa;
+ ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ ALTER TABLE ts RENAME aa TO aaa;      -- to be failed
+ ERROR:  cannot rename inherited column "aa"
+ ALTER TABLE ts RENAME d TO dd;
+ \d+ ts
+                   Table "public.ts"
+  Column |  Type   | Modifiers | Storage | Description 
+ --------+---------+-----------+---------+-------------
+  aa     | integer |           | plain   | 
+  b      | integer |           | plain   | 
+  c      | integer |           | plain   | 
+  dd     | integer |           | plain   | 
+ Inherits: t1,
+           s1
+ Has OIDs: no
+ 
+ DROP TABLE ts;
+ -- Test for renaming in diamond inheritance
+ CREATE TABLE t2 (x int) INHERITS (t1);
+ CREATE TABLE t3 (y int) INHERITS (t1);
+ CREATE TABLE t4 (z int) INHERITS (t2, t3);
+ NOTICE:  merging multiple inherited definitions of column "aa"
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 RENAME aa TO aaa;
+ \d+ t4
+                   Table "public.t4"
+  Column |  Type   | Modifiers | Storage | Description 
+ --------+---------+-----------+---------+-------------
+  aaa    | integer |           | plain   | 
+  b      | integer |           | plain   | 
+  x      | integer |           | plain   | 
+  y      | integer |           | plain   | 
+  z      | integer |           | plain   | 
+ Inherits: t2,
+           t3
+ Has OIDs: no
+ 
+ CREATE TABLE ts (d int) INHERITS (t2, s1);
+ NOTICE:  merging multiple inherited definitions of column "b"
+ ALTER TABLE t1 RENAME aaa TO aaaa;
+ ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ ERROR:  cannot rename multiple inherited column "b"
+ \d+ ts
+                   Table "public.ts"
+  Column |  Type   | Modifiers | Storage | Description 
+ --------+---------+-----------+---------+-------------
+  aaaa   | integer |           | plain   | 
+  b      | integer |           | plain   | 
+  x      | integer |           | plain   | 
+  c      | integer |           | plain   | 
+  d      | integer |           | plain   | 
+ Inherits: t2,
+           s1
+ Has OIDs: no
+ 
+ WITH RECURSIVE r AS (
+   SELECT 't1'::regclass AS inhrelid
+ UNION ALL
+   SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
+ )
+ SELECT attrelid::regclass, attname, attinhcount, r.expected
+   FROM (SELECT inhrelid, count(*) AS expected FROM pg_inherits
+         WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid) r
+   JOIN pg_attribute a ON r.inhrelid = a.attrelid WHERE attislocal = false;
+  attrelid | attname | attinhcount | expected 
+ ----------+---------+-------------+----------
+  t2       | b       |           1 |        1
+  t3       | b       |           1 |        1
+  t4       | b       |           2 |        2
+  t4       | x       |           1 |        2
+  t4       | y       |           1 |        2
+  ts       | b       |           2 |        1
+  ts       | x       |           1 |        1
+  ts       | c       |           1 |        1
+  t2       | aaaa    |           1 |        1
+  t3       | aaaa    |           1 |        1
+  t4       | aaaa    |           2 |        2
+  ts       | aaaa    |           1 |        1
+ (12 rows)
+ 
+ DROP TABLE t1, s1 CASCADE;
+ NOTICE:  drop cascades to 4 other objects
+ DETAIL:  drop cascades to table t2
+ drop cascades to table ts
+ drop cascades to table t3
+ drop cascades to table t4
*** a/src/test/regress/sql/inherit.sql
--- b/src/test/regress/sql/inherit.sql
***************
*** 334,336 **** CREATE TABLE inh_error1 () INHERITS (t1, t4);
--- 334,374 ----
  CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
  
  DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+ 
+ -- Test for renaming in simple multiple inheritance
+ CREATE TABLE t1 (a int, b int);
+ CREATE TABLE s1 (b int, c int);
+ CREATE TABLE ts (d int) INHERITS (t1, s1);
+ 
+ ALTER TABLE t1 RENAME a TO aa;
+ ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ ALTER TABLE ts RENAME aa TO aaa;      -- to be failed
+ ALTER TABLE ts RENAME d TO dd;
+ \d+ ts
+ 
+ DROP TABLE ts;
+ 
+ -- Test for renaming in diamond inheritance
+ CREATE TABLE t2 (x int) INHERITS (t1);
+ CREATE TABLE t3 (y int) INHERITS (t1);
+ CREATE TABLE t4 (z int) INHERITS (t2, t3);
+ 
+ ALTER TABLE t1 RENAME aa TO aaa;
+ \d+ t4
+ 
+ CREATE TABLE ts (d int) INHERITS (t2, s1);
+ ALTER TABLE t1 RENAME aaa TO aaaa;
+ ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ \d+ ts
+ 
+ WITH RECURSIVE r AS (
+   SELECT 't1'::regclass AS inhrelid
+ UNION ALL
+   SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
+ )
+ SELECT attrelid::regclass, attname, attinhcount, r.expected
+   FROM (SELECT inhrelid, count(*) AS expected FROM pg_inherits
+         WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid) r
+   JOIN pg_attribute a ON r.inhrelid = a.attrelid WHERE attislocal = false;
+ 
+ DROP TABLE t1, s1 CASCADE;
#55Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#54)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/1/31 KaiGai Kohei <kaigai@ak.jp.nec.com>:

The attached patch modified find_all_inheritors() to return the list of
expected inhcount, if List * pointer is given. And, it focuses on only
the bugs in renameatt() case.

I have cleaned up and simplified this patch. Attached is the version
I intend to commit. Changes:

- make find_all_inheritors return the list of OIDs, as previously,
instead of using an out parameter
- undo some useless variable renamings
- undo some useless whitespace changes
- rework comments
- fix regression tests to avoid using the same alias twice in the same query
- add an ORDER BY clause to the regression tests so that they pass on my machine
- improve the names of some of the new variables
- instead of adding an additional argument to renameatt(), just
replace the existing 'bool recursing' with 'int expected_parents'.
This allows merging the two versions of the "cannot rename inherited
column" message together, which seems like a reasonably good idea to
me, though if someone has a better idea that's fine. I didn't think
the one additional word added to the message provided enough clarity
to make it worth creating another translatable string.

Also, the ALTER COLUMN TYPE case should be also fixed in the 9.1 release
(or 9.0.1?).

If the fix is something we could commit for 9.0.1, then we ought to do
it now before 9.0 is released. If you want to submit a follow-on
patch to address ALTER COLUMN TYPE once this is committed, then please
do so.

...Robert

Attachments:

fix-inherit-rename-rmh.patchtext/x-patch; charset=US-ASCII; name=fix-inherit-rename-rmh.patchDownload
diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index e256f6f..b53f44d 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -148,6 +148,9 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
  * find_all_inheritors -
  *		Returns a list of relation OIDs including the given rel plus
  *		all relations that inherit from it, directly or indirectly.
+ *		Optionally, it also returns the number of parents found for
+ *		each such relation within the inheritance tree rooted at the
+ *		given rel.
  *
  * The specified lock type is acquired on all child relations (but not on the
  * given rel; caller should already have locked it).  If lockmode is NoLock
@@ -155,9 +158,9 @@ find_inheritance_children(Oid parentrelId, LOCKMODE lockmode)
  * against possible DROPs of child relations.
  */
 List *
-find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
+find_all_inheritors(Oid parentrelId, LOCKMODE lockmode, List **parents)
 {
-	List	   *rels_list;
+	List	   *rels_list, *rel_parents;
 	ListCell   *l;
 
 	/*
@@ -168,11 +171,13 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
 	 * doesn't fetch the next list element until the bottom of the loop.
 	 */
 	rels_list = list_make1_oid(parentrelId);
+	rel_parents = list_make1_int(0);
 
 	foreach(l, rels_list)
 	{
 		Oid			currentrel = lfirst_oid(l);
 		List	   *currentchildren;
+		ListCell   *lc;
 
 		/* Get the direct children of this rel */
 		currentchildren = find_inheritance_children(currentrel, lockmode);
@@ -184,9 +189,35 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
 		 * loop, though theoretically there can't be any cycles in the
 		 * inheritance graph anyway.)
 		 */
-		rels_list = list_concat_unique_oid(rels_list, currentchildren);
+		foreach(lc, currentchildren)
+		{
+			Oid		child_oid = lfirst_oid(lc);
+			bool	found = false;
+			ListCell   *lo;
+			ListCell   *li;
+
+			forboth(lo, rels_list, li, rel_parents)
+			{
+				if (lfirst_oid(lo) == child_oid)
+				{
+					lfirst_int(li)++;
+					found = true;
+					break;
+				}
+			}
+
+			if (!found)
+			{
+				rels_list = lappend_oid(rels_list, child_oid);
+				rel_parents = lappend_int(rel_parents, 1);
+			}
+		}
 	}
 
+	if (parents)
+		*parents = rel_parents;
+	else
+		list_free(rel_parents);
 	return rels_list;
 }
 
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 8c81eaa..efbd088 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -126,7 +126,7 @@ ExecRenameStmt(RenameStmt *stmt)
 								  stmt->subname,		/* old att name */
 								  stmt->newname,		/* new att name */
 								  interpretInhOption(stmt->relation->inhOpt),	/* recursive? */
-								  false);		/* recursing already? */
+								  0);			/* expected inhcount */
 						break;
 					case OBJECT_TRIGGER:
 						renametrig(relid,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 9062c15..c93b57f 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -1390,7 +1390,8 @@ acquire_inherited_sample_rows(Relation onerel, HeapTuple *rows, int targrows,
 	 * Find all members of inheritance set.  We only need AccessShareLock on
 	 * the children.
 	 */
-	tableOIDs = find_all_inheritors(RelationGetRelid(onerel), AccessShareLock);
+	tableOIDs =
+		find_all_inheritors(RelationGetRelid(onerel), AccessShareLock, NULL);
 
 	/*
 	 * Check that there's at least one descendant, else fail.  This could
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 50e4244..52f32fc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -824,7 +824,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 			ListCell   *child;
 			List	   *children;
 
-			children = find_all_inheritors(myrelid, AccessExclusiveLock);
+			children = find_all_inheritors(myrelid, AccessExclusiveLock, NULL);
 
 			foreach(child, children)
 			{
@@ -1943,7 +1943,7 @@ renameatt(Oid myrelid,
 		  const char *oldattname,
 		  const char *newattname,
 		  bool recurse,
-		  bool recursing)
+		  int expected_parents)
 {
 	Relation	targetrelation;
 	Relation	attrelation;
@@ -1987,24 +1987,31 @@ renameatt(Oid myrelid,
 	 */
 	if (recurse)
 	{
-		ListCell   *child;
-		List	   *children;
+		List	   *child_oids, *child_parents;
+		ListCell   *lo, *li;
 
-		children = find_all_inheritors(myrelid, AccessExclusiveLock);
+		/*
+		 * we need the number of parents for each child so that the recursive
+		 * calls to renameatt() can determine whether there are any parents
+		 * outside the inheritance hierarchy being processed.
+		 */
+		child_oids =
+			find_all_inheritors(myrelid, AccessExclusiveLock, &child_parents);
 
 		/*
 		 * 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)
+		forboth(lo, child_oids, li, child_parents)
 		{
-			Oid			childrelid = lfirst_oid(child);
+			Oid			childrelid = lfirst_oid(lo);
+			int			numparents = lfirst_int(li);
 
 			if (childrelid == myrelid)
 				continue;
 			/* note we need not recurse again */
-			renameatt(childrelid, oldattname, newattname, false, true);
+			renameatt(childrelid, oldattname, newattname, false, numparents);
 		}
 	}
 	else
@@ -2012,8 +2019,10 @@ renameatt(Oid myrelid,
 		/*
 		 * If we are told not to recurse, there had better not be any child
 		 * tables; else the rename would put them out of step.
+		 *
+		 * expected_parents will only be 0 if we are not already recursing.
 		 */
-		if (!recursing &&
+		if (expected_parents == 0 &&
 			find_inheritance_children(myrelid, NoLock) != NIL)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
@@ -2039,10 +2048,15 @@ renameatt(Oid myrelid,
 						oldattname)));
 
 	/*
-	 * if the attribute is inherited, forbid the renaming, unless we are
-	 * already inside a recursive rename.
+	 * if the attribute is inherited, forbid the renaming.  if this is a
+	 * top-level call to renameatt(), then expected_parents will be 0, so the
+	 * effect of this code will be to prohibit the renaming if the attribute
+	 * is inherited at all.  if this is a recursive call to renameatt(),
+	 * expected_parents will be the number of parents the current relation has
+	 * within the inheritance hierarchy being processed, so we'll prohibit
+	 * the renaming only if there are additional parents from elsewhere.
 	 */
-	if (attform->attinhcount > 0 && !recursing)
+	if (attform->attinhcount > expected_parents)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("cannot rename inherited column \"%s\"",
@@ -3410,7 +3424,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
 		ListCell   *child;
 		List	   *children;
 
-		children = find_all_inheritors(relid, AccessExclusiveLock);
+		children = find_all_inheritors(relid, AccessExclusiveLock, NULL);
 
 		/*
 		 * find_all_inheritors does the recursive search of the inheritance
@@ -7233,7 +7247,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent)
 	 * We use weakest lock we can on child's children, namely AccessShareLock.
 	 */
 	children = find_all_inheritors(RelationGetRelid(child_rel),
-								   AccessShareLock);
+								   AccessShareLock, NULL);
 
 	if (list_member_oid(children, RelationGetRelid(parent_rel)))
 		ereport(ERROR,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index c7e9c85..50a6ab7 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1180,7 +1180,7 @@ expand_inherited_rtentry(PlannerInfo *root, RangeTblEntry *rte, Index rti)
 		lockmode = AccessShareLock;
 
 	/* Scan for all members of inheritance set, acquire needed locks */
-	inhOIDs = find_all_inheritors(parentOID, lockmode);
+	inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
 
 	/*
 	 * Check that there's at least one descendant, else treat as no-child
diff --git a/src/include/catalog/pg_inherits_fn.h b/src/include/catalog/pg_inherits_fn.h
index 91baec3..70a624b 100644
--- a/src/include/catalog/pg_inherits_fn.h
+++ b/src/include/catalog/pg_inherits_fn.h
@@ -18,7 +18,8 @@
 #include "storage/lock.h"
 
 extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
-extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode);
+extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode,
+					List **parents);
 extern bool has_subclass(Oid relationId);
 extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
 
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 8bc716a..f9269cc 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -43,7 +43,7 @@ extern void renameatt(Oid myrelid,
 		  const char *oldattname,
 		  const char *newattname,
 		  bool recurse,
-		  bool recursing);
+		  int expected_parents);
 
 extern void RenameRelation(Oid myrelid,
 			   const char *newrelname,
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 9c83a32..dc54e56 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1053,3 +1053,97 @@ NOTICE:  merging column "a" with inherited definition
 ERROR:  column "a" has a storage parameter conflict
 DETAIL:  MAIN versus EXTENDED
 DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+-- Test for renaming in simple multiple inheritance
+CREATE TABLE t1 (a int, b int);
+CREATE TABLE s1 (b int, c int);
+CREATE TABLE ts (d int) INHERITS (t1, s1);
+NOTICE:  merging multiple inherited definitions of column "b"
+ALTER TABLE t1 RENAME a TO aa;
+ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ERROR:  cannot rename inherited column "b"
+ALTER TABLE ts RENAME aa TO aaa;      -- to be failed
+ERROR:  cannot rename inherited column "aa"
+ALTER TABLE ts RENAME d TO dd;
+\d+ ts
+                  Table "public.ts"
+ Column |  Type   | Modifiers | Storage | Description 
+--------+---------+-----------+---------+-------------
+ aa     | integer |           | plain   | 
+ b      | integer |           | plain   | 
+ c      | integer |           | plain   | 
+ dd     | integer |           | plain   | 
+Inherits: t1,
+          s1
+Has OIDs: no
+
+DROP TABLE ts;
+-- Test for renaming in diamond inheritance
+CREATE TABLE t2 (x int) INHERITS (t1);
+CREATE TABLE t3 (y int) INHERITS (t1);
+CREATE TABLE t4 (z int) INHERITS (t2, t3);
+NOTICE:  merging multiple inherited definitions of column "aa"
+NOTICE:  merging multiple inherited definitions of column "b"
+ALTER TABLE t1 RENAME aa TO aaa;
+\d+ t4
+                  Table "public.t4"
+ Column |  Type   | Modifiers | Storage | Description 
+--------+---------+-----------+---------+-------------
+ aaa    | integer |           | plain   | 
+ b      | integer |           | plain   | 
+ x      | integer |           | plain   | 
+ y      | integer |           | plain   | 
+ z      | integer |           | plain   | 
+Inherits: t2,
+          t3
+Has OIDs: no
+
+CREATE TABLE ts (d int) INHERITS (t2, s1);
+NOTICE:  merging multiple inherited definitions of column "b"
+ALTER TABLE t1 RENAME aaa TO aaaa;
+ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ERROR:  cannot rename inherited column "b"
+\d+ ts
+                  Table "public.ts"
+ Column |  Type   | Modifiers | Storage | Description 
+--------+---------+-----------+---------+-------------
+ aaaa   | integer |           | plain   | 
+ b      | integer |           | plain   | 
+ x      | integer |           | plain   | 
+ c      | integer |           | plain   | 
+ d      | integer |           | plain   | 
+Inherits: t2,
+          s1
+Has OIDs: no
+
+WITH RECURSIVE r AS (
+  SELECT 't1'::regclass AS inhrelid
+UNION ALL
+  SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
+)
+SELECT a.attrelid::regclass, a.attname, a.attinhcount, e.expected
+  FROM (SELECT inhrelid, count(*) AS expected FROM pg_inherits
+        WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid) e
+  JOIN pg_attribute a ON e.inhrelid = a.attrelid WHERE NOT attislocal
+  ORDER BY a.attrelid, a.attnum;
+ attrelid | attname | attinhcount | expected 
+----------+---------+-------------+----------
+ t2       | aaaa    |           1 |        1
+ t2       | b       |           1 |        1
+ t3       | aaaa    |           1 |        1
+ t3       | b       |           1 |        1
+ t4       | aaaa    |           2 |        2
+ t4       | b       |           2 |        2
+ t4       | x       |           1 |        2
+ t4       | y       |           1 |        2
+ ts       | aaaa    |           1 |        1
+ ts       | b       |           2 |        1
+ ts       | x       |           1 |        1
+ ts       | c       |           1 |        1
+(12 rows)
+
+DROP TABLE t1, s1 CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table t2
+drop cascades to table ts
+drop cascades to table t3
+drop cascades to table t4
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 192e860..bbd5752 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -334,3 +334,42 @@ CREATE TABLE inh_error1 () INHERITS (t1, t4);
 CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
 
 DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all;
+
+-- Test for renaming in simple multiple inheritance
+CREATE TABLE t1 (a int, b int);
+CREATE TABLE s1 (b int, c int);
+CREATE TABLE ts (d int) INHERITS (t1, s1);
+
+ALTER TABLE t1 RENAME a TO aa;
+ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+ALTER TABLE ts RENAME aa TO aaa;      -- to be failed
+ALTER TABLE ts RENAME d TO dd;
+\d+ ts
+
+DROP TABLE ts;
+
+-- Test for renaming in diamond inheritance
+CREATE TABLE t2 (x int) INHERITS (t1);
+CREATE TABLE t3 (y int) INHERITS (t1);
+CREATE TABLE t4 (z int) INHERITS (t2, t3);
+
+ALTER TABLE t1 RENAME aa TO aaa;
+\d+ t4
+
+CREATE TABLE ts (d int) INHERITS (t2, s1);
+ALTER TABLE t1 RENAME aaa TO aaaa;
+ALTER TABLE t1 RENAME b TO bb;                -- to be failed
+\d+ ts
+
+WITH RECURSIVE r AS (
+  SELECT 't1'::regclass AS inhrelid
+UNION ALL
+  SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
+)
+SELECT a.attrelid::regclass, a.attname, a.attinhcount, e.expected
+  FROM (SELECT inhrelid, count(*) AS expected FROM pg_inherits
+        WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid) e
+  JOIN pg_attribute a ON e.inhrelid = a.attrelid WHERE NOT attislocal
+  ORDER BY a.attrelid, a.attnum;
+
+DROP TABLE t1, s1 CASCADE;
#56Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#55)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

Robert Haas <robertmhaas@gmail.com> writes:

I have cleaned up and simplified this patch. Attached is the version
I intend to commit. Changes:

Minor suggestions:

I think the names like "rel_parents" would read better as
"rel_numparents" etc. As-is, the reader could be forgiven for expecting
that this will be a list of parent relation OIDs or some such.

The new loop added within find_all_inheritors could really do with an
addition to the comments, along the line of "If a child is already
seen, increment the corresponding numparents count".

I don't trust the proposed "order by attrelid" business in the
regression test --- once in a blue moon, that will fail because the
OID counter wrapped around mid-test, and we'll get an unreproducible
bug report. I'd suggest order by attrelid::regclass::text.

Looks sane otherwise.

regards, tom lane

#57Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#56)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Mon, Feb 1, 2010 at 1:31 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

I have cleaned up and simplified this patch.  Attached is the version
I intend to commit.  Changes:

Minor suggestions:

I think the names like "rel_parents" would read better as
"rel_numparents" etc.  As-is, the reader could be forgiven for expecting
that this will be a list of parent relation OIDs or some such.

I thought about that but ended up being lazy and leaving it the way I
had it. I'll go be un-lazy.

The new loop added within find_all_inheritors could really do with an
addition to the comments, along the line of "If a child is already
seen, increment the corresponding numparents count".

OK.

I don't trust the proposed "order by attrelid" business in the
regression test --- once in a blue moon, that will fail because the
OID counter wrapped around mid-test, and we'll get an unreproducible
bug report.  I'd suggest order by attrelid::regclass::text.

Wow, didn't think of that. Will change.

Looks sane otherwise.

Thanks for the review.

...Robert

#58Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#57)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Mon, Feb 1, 2010 at 1:40 PM, Robert Haas <robertmhaas@gmail.com> wrote:

Looks sane otherwise.

Thanks for the review.

Oh, one other thing. Should we backpatch this, or just apply to HEAD?

...Robert

#59Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#58)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

Robert Haas <robertmhaas@gmail.com> writes:

Oh, one other thing. Should we backpatch this, or just apply to HEAD?

Just HEAD imo. Without any complaints from the field, I don't think
it's worth taking any risks for. It's not like multiple inheritance
is heavily used ...

regards, tom lane

#60Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#59)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

On Mon, Feb 1, 2010 at 2:03 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

Oh, one other thing.  Should we backpatch this, or just apply to HEAD?

Just HEAD imo.  Without any complaints from the field, I don't think
it's worth taking any risks for.  It's not like multiple inheritance
is heavily used ...

OK, done.

...Robert

#61KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#55)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/02/02 3:01), Robert Haas wrote:

2010/1/31 KaiGai Kohei<kaigai@ak.jp.nec.com>:

The attached patch modified find_all_inheritors() to return the list of
expected inhcount, if List * pointer is given. And, it focuses on only
the bugs in renameatt() case.

I have cleaned up and simplified this patch. Attached is the version
I intend to commit. Changes:

- make find_all_inheritors return the list of OIDs, as previously,
instead of using an out parameter
- undo some useless variable renamings
- undo some useless whitespace changes
- rework comments
- fix regression tests to avoid using the same alias twice in the same query
- add an ORDER BY clause to the regression tests so that they pass on my machine
- improve the names of some of the new variables
- instead of adding an additional argument to renameatt(), just
replace the existing 'bool recursing' with 'int expected_parents'.
This allows merging the two versions of the "cannot rename inherited
column" message together, which seems like a reasonably good idea to
me, though if someone has a better idea that's fine. I didn't think
the one additional word added to the message provided enough clarity
to make it worth creating another translatable string.

Thanks for your checks.

Also, the ALTER COLUMN TYPE case should be also fixed in the 9.1 release
(or 9.0.1?).

If the fix is something we could commit for 9.0.1, then we ought to do
it now before 9.0 is released. If you want to submit a follow-on
patch to address ALTER COLUMN TYPE once this is committed, then please
do so.

The attached patch also fixes ALTER COLUMN TYPE case.

It replaced the 'recursing' argument in ATPrepCmd() by 'expected_parents',
and it is delivered to ATPrepAlterColumnType(). The logic to forbid altering
the column type is same as renameatt().
ATSimpleRecursion() is also modified to use forboth() to call ATPrepCmd()
recursively.

One concern is at ATOneLevelRecursion() which is called by ATPrepAddColumn()
only, and it also calls ATPrepCmd() for the direct children.
Right now, I set 1 on the 'expected_parents'. However, IMO, here is no reason
why we cannot rewrite the ATPrepAddColumn() using ATSimpleRecursion().

Eventually, ATExecAddColumn() shall be invoked several times for same column,
if the inheritance tree has diamond-inheritance structure. And, it increments
pg_attribute.attinhcount except for the first invocation.
If we store the 'expected_parents' on the ColumnDef->inhcount, we don't need
to call the ATExecAddColumn() more than once in a single ALTER TABLE command.

Any comments? And, when should we do it? 9.0? 9.1?

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-fix-inherit-attype.1.patchapplication/octect-stream; name=pgsql-fix-inherit-attype.1.patchDownload
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 255,261 **** static void createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
  						 Oid constraintOid, Oid indexOid);
  static void ATController(Relation rel, List *cmds, bool recurse);
  static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
! 		  bool recurse, bool recursing);
  static void ATRewriteCatalogs(List **wqueue);
  static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
  		  AlterTableCmd *cmd);
--- 255,261 ----
  						 Oid constraintOid, Oid indexOid);
  static void ATController(Relation rel, List *cmds, bool recurse);
  static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
! 		  bool recurse, int expected_parents);
  static void ATRewriteCatalogs(List **wqueue);
  static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
  		  AlterTableCmd *cmd);
***************
*** 309,315 **** static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 bool missing_ok);
  static void ATPrepAlterColumnType(List **wqueue,
  					  AlteredTableInfo *tab, Relation rel,
! 					  bool recurse, bool recursing,
  					  AlterTableCmd *cmd);
  static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
  					  const char *colName, TypeName *typeName);
--- 309,315 ----
  								 bool missing_ok);
  static void ATPrepAlterColumnType(List **wqueue,
  					  AlteredTableInfo *tab, Relation rel,
! 					  bool recurse, int expected_parents,
  					  AlterTableCmd *cmd);
  static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
  					  const char *colName, TypeName *typeName);
***************
*** 2388,2394 **** ATController(Relation rel, List *cmds, bool recurse)
  	{
  		AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
  
! 		ATPrepCmd(&wqueue, rel, cmd, recurse, false);
  	}
  
  	/* Close the relation, but keep lock until commit */
--- 2388,2394 ----
  	{
  		AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
  
! 		ATPrepCmd(&wqueue, rel, cmd, recurse, 0);
  	}
  
  	/* Close the relation, but keep lock until commit */
***************
*** 2412,2418 **** ATController(Relation rel, List *cmds, bool recurse)
   */
  static void
  ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
! 		  bool recurse, bool recursing)
  {
  	AlteredTableInfo *tab;
  	int			pass;
--- 2412,2418 ----
   */
  static void
  ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
! 		  bool recurse, int expected_parents)
  {
  	AlteredTableInfo *tab;
  	int			pass;
***************
*** 2523,2529 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  		case AT_AlterColumnType:		/* ALTER COLUMN TYPE */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd);
  			pass = AT_PASS_ALTER_TYPE;
  			break;
  		case AT_ChangeOwner:	/* ALTER OWNER */
--- 2523,2530 ----
  		case AT_AlterColumnType:		/* ALTER COLUMN TYPE */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			ATPrepAlterColumnType(wqueue, tab, rel,
! 								  recurse, expected_parents, cmd);
  			pass = AT_PASS_ALTER_TYPE;
  			break;
  		case AT_ChangeOwner:	/* ALTER OWNER */
***************
*** 2541,2547 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  		case AT_AddOids:		/* SET WITH OIDS */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			if (!rel->rd_rel->relhasoids || recursing)
  				ATPrepAddOids(wqueue, rel, recurse, cmd);
  			pass = AT_PASS_ADD_COL;
  			break;
--- 2542,2548 ----
  		case AT_AddOids:		/* SET WITH OIDS */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			if (!rel->rd_rel->relhasoids || expected_parents > 0)
  				ATPrepAddOids(wqueue, rel, recurse, cmd);
  			pass = AT_PASS_ADD_COL;
  			break;
***************
*** 2555,2561 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  				dropCmd->subtype = AT_DropColumn;
  				dropCmd->name = pstrdup("oid");
  				dropCmd->behavior = cmd->behavior;
! 				ATPrepCmd(wqueue, rel, dropCmd, recurse, false);
  			}
  			pass = AT_PASS_DROP;
  			break;
--- 2556,2562 ----
  				dropCmd->subtype = AT_DropColumn;
  				dropCmd->name = pstrdup("oid");
  				dropCmd->behavior = cmd->behavior;
! 				ATPrepCmd(wqueue, rel, dropCmd, recurse, expected_parents);
  			}
  			pass = AT_PASS_DROP;
  			break;
***************
*** 3421,3439 **** ATSimpleRecursion(List **wqueue, Relation rel,
  	if (recurse && rel->rd_rel->relkind == RELKIND_RELATION)
  	{
  		Oid			relid = RelationGetRelid(rel);
! 		ListCell   *child;
  		List	   *children;
  
! 		children = find_all_inheritors(relid, AccessExclusiveLock, 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)
--- 3422,3443 ----
  	if (recurse && rel->rd_rel->relkind == RELKIND_RELATION)
  	{
  		Oid			relid = RelationGetRelid(rel);
! 		ListCell   *lc, *lp;
  		List	   *children;
+ 		List	   *numparents;
  
! 		children = find_all_inheritors(relid, AccessExclusiveLock, &numparents);
  
  		/*
  		 * 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.
  		 */
! 		
! 		forboth(lc, children, lp, numparents)
  		{
! 			Oid			childrelid = lfirst_oid(lc);
! 			int			expected_parents = lfirst_int(lp);
  			Relation	childrel;
  
  			if (childrelid == relid)
***************
*** 3441,3447 **** ATSimpleRecursion(List **wqueue, Relation rel,
  			/* find_all_inheritors already got lock */
  			childrel = relation_open(childrelid, NoLock);
  			CheckTableNotInUse(childrel, "ALTER TABLE");
! 			ATPrepCmd(wqueue, childrel, cmd, false, true);
  			relation_close(childrel, NoLock);
  		}
  	}
--- 3445,3451 ----
  			/* find_all_inheritors already got lock */
  			childrel = relation_open(childrelid, NoLock);
  			CheckTableNotInUse(childrel, "ALTER TABLE");
! 			ATPrepCmd(wqueue, childrel, cmd, false, expected_parents);
  			relation_close(childrel, NoLock);
  		}
  	}
***************
*** 3473,3479 **** ATOneLevelRecursion(List **wqueue, Relation rel,
  		/* find_inheritance_children already got lock */
  		childrel = relation_open(childrelid, NoLock);
  		CheckTableNotInUse(childrel, "ALTER TABLE");
! 		ATPrepCmd(wqueue, childrel, cmd, true, true);
  		relation_close(childrel, NoLock);
  	}
  }
--- 3477,3488 ----
  		/* find_inheritance_children already got lock */
  		childrel = relation_open(childrelid, NoLock);
  		CheckTableNotInUse(childrel, "ALTER TABLE");
! 		/*
! 		 * XXX - Is it possible to assume `expected_numparents' is
! 		 * always 1 in this code path. Or, is there any reason why
! 		 * we should use find_all_inheritors() for ATAddColumn?
! 		 */
! 		ATPrepCmd(wqueue, childrel, cmd, true, 1);
  		relation_close(childrel, NoLock);
  	}
  }
***************
*** 5805,5811 **** ATExecDropConstraint(Relation rel, const char *constrName,
  static void
  ATPrepAlterColumnType(List **wqueue,
  					  AlteredTableInfo *tab, Relation rel,
! 					  bool recurse, bool recursing,
  					  AlterTableCmd *cmd)
  {
  	char	   *colName = cmd->name;
--- 5814,5820 ----
  static void
  ATPrepAlterColumnType(List **wqueue,
  					  AlteredTableInfo *tab, Relation rel,
! 					  bool recurse, int expected_parents,
  					  AlterTableCmd *cmd)
  {
  	char	   *colName = cmd->name;
***************
*** 5837,5843 **** ATPrepAlterColumnType(List **wqueue,
  						colName)));
  
  	/* Don't alter inherited columns */
! 	if (attTup->attinhcount > 0 && !recursing)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
  				 errmsg("cannot alter inherited column \"%s\"",
--- 5846,5852 ----
  						colName)));
  
  	/* Don't alter inherited columns */
! 	if (attTup->attinhcount > expected_parents)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
  				 errmsg("cannot alter inherited column \"%s\"",
***************
*** 5929,5935 **** ATPrepAlterColumnType(List **wqueue,
  	 */
  	if (recurse)
  		ATSimpleRecursion(wqueue, rel, cmd, recurse);
! 	else if (!recursing &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
--- 5938,5944 ----
  	 */
  	if (recurse)
  		ATSimpleRecursion(wqueue, rel, cmd, recurse);
! 	else if (expected_parents == 0 &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
#62KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#61)
1 attachment(s)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/02/02 9:48), KaiGai Kohei wrote:

Also, the ALTER COLUMN TYPE case should be also fixed in the 9.1 release
(or 9.0.1?).

If the fix is something we could commit for 9.0.1, then we ought to do
it now before 9.0 is released. If you want to submit a follow-on
patch to address ALTER COLUMN TYPE once this is committed, then please
do so.

The attached patch also fixes ALTER COLUMN TYPE case.

It replaced the 'recursing' argument in ATPrepCmd() by 'expected_parents',
and it is delivered to ATPrepAlterColumnType(). The logic to forbid altering
the column type is same as renameatt().
ATSimpleRecursion() is also modified to use forboth() to call ATPrepCmd()
recursively.

One concern is at ATOneLevelRecursion() which is called by ATPrepAddColumn()
only, and it also calls ATPrepCmd() for the direct children.
Right now, I set 1 on the 'expected_parents'. However, IMO, here is no reason
why we cannot rewrite the ATPrepAddColumn() using ATSimpleRecursion().

Eventually, ATExecAddColumn() shall be invoked several times for same column,
if the inheritance tree has diamond-inheritance structure. And, it increments
pg_attribute.attinhcount except for the first invocation.
If we store the 'expected_parents' on the ColumnDef->inhcount, we don't need
to call the ATExecAddColumn() more than once in a single ALTER TABLE command.

Any comments? And, when should we do it? 9.0? 9.1?

The attached one also clean up ATPrepAddColumn() and ATExecAddColumn() code,
not only ATPrepAlterColumnType(), according to what I mentioned above.

There are two regression test fails, because it does not call ATExecAddColumn()
twice or more in diamond-inheritance cases, so it does not notice merging
definitions of columns.

If we should go on right now, I'll add and fix regression tests, and submit
a formal patch again. If not, I'll work it later.

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-fix-inherit-attype.2.patchapplication/octect-stream; name=pgsql-fix-inherit-attype.2.patchDownload
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 255,261 **** static void createForeignKeyTriggers(Relation rel, Constraint *fkconstraint,
  						 Oid constraintOid, Oid indexOid);
  static void ATController(Relation rel, List *cmds, bool recurse);
  static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
! 		  bool recurse, bool recursing);
  static void ATRewriteCatalogs(List **wqueue);
  static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
  		  AlterTableCmd *cmd);
--- 255,261 ----
  						 Oid constraintOid, Oid indexOid);
  static void ATController(Relation rel, List *cmds, bool recurse);
  static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
! 		  bool recurse, int expected_parents);
  static void ATRewriteCatalogs(List **wqueue);
  static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
  		  AlterTableCmd *cmd);
***************
*** 266,280 **** static void ATSimplePermissions(Relation rel, bool allowView);
  static void ATSimplePermissionsRelationOrIndex(Relation rel);
  static void ATSimpleRecursion(List **wqueue, Relation rel,
  				  AlterTableCmd *cmd, bool recurse);
- static void ATOneLevelRecursion(List **wqueue, Relation rel,
- 					AlterTableCmd *cmd);
  static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse,
! 				AlterTableCmd *cmd);
  static void ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
  				ColumnDef *colDef, bool isOid);
  static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
  static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
! 			  AlterTableCmd *cmd);
  static void ATExecDropNotNull(Relation rel, const char *colName);
  static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
  				 const char *colName);
--- 266,278 ----
  static void ATSimplePermissionsRelationOrIndex(Relation rel);
  static void ATSimpleRecursion(List **wqueue, Relation rel,
  				  AlterTableCmd *cmd, bool recurse);
  static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse,
! 				int expected_parents, AlterTableCmd *cmd);
  static void ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
  				ColumnDef *colDef, bool isOid);
  static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
  static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
! 				int expected_parents, AlterTableCmd *cmd);
  static void ATExecDropNotNull(Relation rel, const char *colName);
  static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
  				 const char *colName);
***************
*** 309,315 **** static void ATExecDropConstraint(Relation rel, const char *constrName,
  								 bool missing_ok);
  static void ATPrepAlterColumnType(List **wqueue,
  					  AlteredTableInfo *tab, Relation rel,
! 					  bool recurse, bool recursing,
  					  AlterTableCmd *cmd);
  static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
  					  const char *colName, TypeName *typeName);
--- 307,313 ----
  								 bool missing_ok);
  static void ATPrepAlterColumnType(List **wqueue,
  					  AlteredTableInfo *tab, Relation rel,
! 					  bool recurse, int expected_parents,
  					  AlterTableCmd *cmd);
  static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
  					  const char *colName, TypeName *typeName);
***************
*** 2388,2394 **** ATController(Relation rel, List *cmds, bool recurse)
  	{
  		AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
  
! 		ATPrepCmd(&wqueue, rel, cmd, recurse, false);
  	}
  
  	/* Close the relation, but keep lock until commit */
--- 2386,2392 ----
  	{
  		AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
  
! 		ATPrepCmd(&wqueue, rel, cmd, recurse, 0);
  	}
  
  	/* Close the relation, but keep lock until commit */
***************
*** 2412,2418 **** ATController(Relation rel, List *cmds, bool recurse)
   */
  static void
  ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
! 		  bool recurse, bool recursing)
  {
  	AlteredTableInfo *tab;
  	int			pass;
--- 2410,2416 ----
   */
  static void
  ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
! 		  bool recurse, int expected_parents)
  {
  	AlteredTableInfo *tab;
  	int			pass;
***************
*** 2437,2450 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  		case AT_AddColumn:		/* ADD COLUMN */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			ATPrepAddColumn(wqueue, rel, recurse, cmd);
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
  										 * VIEW */
  			ATSimplePermissions(rel, true);
  			/* Performs own recursion */
! 			ATPrepAddColumn(wqueue, rel, recurse, cmd);
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
--- 2435,2448 ----
  		case AT_AddColumn:		/* ADD COLUMN */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			ATPrepAddColumn(wqueue, rel, recurse, expected_parents, cmd);
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
  										 * VIEW */
  			ATSimplePermissions(rel, true);
  			/* Performs own recursion */
! 			ATPrepAddColumn(wqueue, rel, recurse, expected_parents, cmd);
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
***************
*** 2523,2529 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  		case AT_AlterColumnType:		/* ALTER COLUMN TYPE */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd);
  			pass = AT_PASS_ALTER_TYPE;
  			break;
  		case AT_ChangeOwner:	/* ALTER OWNER */
--- 2521,2528 ----
  		case AT_AlterColumnType:		/* ALTER COLUMN TYPE */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			ATPrepAlterColumnType(wqueue, tab, rel,
! 								  recurse, expected_parents, cmd);
  			pass = AT_PASS_ALTER_TYPE;
  			break;
  		case AT_ChangeOwner:	/* ALTER OWNER */
***************
*** 2541,2548 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  		case AT_AddOids:		/* SET WITH OIDS */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			if (!rel->rd_rel->relhasoids || recursing)
! 				ATPrepAddOids(wqueue, rel, recurse, cmd);
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_DropOids:		/* SET WITHOUT OIDS */
--- 2540,2547 ----
  		case AT_AddOids:		/* SET WITH OIDS */
  			ATSimplePermissions(rel, false);
  			/* Performs own recursion */
! 			if (!rel->rd_rel->relhasoids || expected_parents > 0)
! 				ATPrepAddOids(wqueue, rel, recurse, expected_parents, cmd);
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_DropOids:		/* SET WITHOUT OIDS */
***************
*** 2555,2561 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  				dropCmd->subtype = AT_DropColumn;
  				dropCmd->name = pstrdup("oid");
  				dropCmd->behavior = cmd->behavior;
! 				ATPrepCmd(wqueue, rel, dropCmd, recurse, false);
  			}
  			pass = AT_PASS_DROP;
  			break;
--- 2554,2560 ----
  				dropCmd->subtype = AT_DropColumn;
  				dropCmd->name = pstrdup("oid");
  				dropCmd->behavior = cmd->behavior;
! 				ATPrepCmd(wqueue, rel, dropCmd, recurse, expected_parents);
  			}
  			pass = AT_PASS_DROP;
  			break;
***************
*** 3421,3439 **** ATSimpleRecursion(List **wqueue, Relation rel,
  	if (recurse && rel->rd_rel->relkind == RELKIND_RELATION)
  	{
  		Oid			relid = RelationGetRelid(rel);
! 		ListCell   *child;
  		List	   *children;
  
! 		children = find_all_inheritors(relid, AccessExclusiveLock, 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)
--- 3420,3440 ----
  	if (recurse && rel->rd_rel->relkind == RELKIND_RELATION)
  	{
  		Oid			relid = RelationGetRelid(rel);
! 		ListCell   *lc, *lp;
  		List	   *children;
+ 		List	   *numparents;
  
! 		children = find_all_inheritors(relid, AccessExclusiveLock, &numparents);
  
  		/*
  		 * 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.
  		 */
! 		forboth(lc, children, lp, numparents)
  		{
! 			Oid			childrelid = lfirst_oid(lc);
! 			int			expected_parents = lfirst_int(lp);
  			Relation	childrel;
  
  			if (childrelid == relid)
***************
*** 3441,3485 **** ATSimpleRecursion(List **wqueue, Relation rel,
  			/* find_all_inheritors already got lock */
  			childrel = relation_open(childrelid, NoLock);
  			CheckTableNotInUse(childrel, "ALTER TABLE");
! 			ATPrepCmd(wqueue, childrel, cmd, false, true);
  			relation_close(childrel, NoLock);
  		}
  	}
  }
  
  /*
-  * ATOneLevelRecursion
-  *
-  * Here, we visit only direct inheritance children.  It is expected that
-  * the command's prep routine will recurse again to find indirect children.
-  * When using this technique, a multiply-inheriting child will be visited
-  * multiple times.
-  */
- static void
- ATOneLevelRecursion(List **wqueue, Relation rel,
- 					AlterTableCmd *cmd)
- {
- 	Oid			relid = RelationGetRelid(rel);
- 	ListCell   *child;
- 	List	   *children;
- 
- 	children = find_inheritance_children(relid, AccessExclusiveLock);
- 
- 	foreach(child, children)
- 	{
- 		Oid			childrelid = lfirst_oid(child);
- 		Relation	childrel;
- 
- 		/* find_inheritance_children already got lock */
- 		childrel = relation_open(childrelid, NoLock);
- 		CheckTableNotInUse(childrel, "ALTER TABLE");
- 		ATPrepCmd(wqueue, childrel, cmd, true, true);
- 		relation_close(childrel, NoLock);
- 	}
- }
- 
- 
- /*
   * find_composite_type_dependencies
   *
   * Check to see if a composite type is being used as a column in some
--- 3442,3454 ----
  			/* find_all_inheritors already got lock */
  			childrel = relation_open(childrelid, NoLock);
  			CheckTableNotInUse(childrel, "ALTER TABLE");
! 			ATPrepCmd(wqueue, childrel, cmd, false, expected_parents);
  			relation_close(childrel, NoLock);
  		}
  	}
  }
  
  /*
   * find_composite_type_dependencies
   *
   * Check to see if a composite type is being used as a column in some
***************
*** 3591,3627 **** find_composite_type_dependencies(Oid typeOid,
   */
  static void
  ATPrepAddColumn(List **wqueue, Relation rel, bool recurse,
! 				AlterTableCmd *cmd)
  {
  	/*
  	 * Recurse to add the column to child classes, if requested.
  	 *
! 	 * We must recurse one level at a time, so that multiply-inheriting
! 	 * children are visited the right number of times and end up with the
! 	 * right attinhcount.
  	 */
  	if (recurse)
! 	{
! 		AlterTableCmd *childCmd = copyObject(cmd);
! 		ColumnDef  *colDefChild = (ColumnDef *) childCmd->def;
! 
! 		/* Child should see column as singly inherited */
! 		colDefChild->inhcount = 1;
! 		colDefChild->is_local = false;
! 
! 		ATOneLevelRecursion(wqueue, rel, childCmd);
! 	}
! 	else
! 	{
! 		/*
! 		 * If we are told not to recurse, there had better not be any child
! 		 * tables; else the addition would put them out of step.
! 		 */
! 		if (find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! 					 errmsg("column must be added to child tables too")));
! 	}
  }
  
  static void
--- 3560,3587 ----
   */
  static void
  ATPrepAddColumn(List **wqueue, Relation rel, bool recurse,
! 				int expected_parents, AlterTableCmd *cmd)
  {
+ 	ColumnDef  *colDef = (ColumnDef *)cmd->def;
+ 
+ 	colDef->inhcount = expected_parents;
+ 
+ 	colDef->is_local = (expected_parents == 0 ? true : false);
+ 
  	/*
  	 * Recurse to add the column to child classes, if requested.
  	 *
! 	 * ATExecAddColumn creates a new column with attinhcount which shall be
! 	 * initialized by colDef->inhcount, or increments attinhcount of the
! 	 * existing column to be merged by colDef->inhcount.
  	 */
  	if (recurse)
! 		ATSimpleRecursion(wqueue, rel, cmd, recurse);
! 	else if (expected_parents == 0 &&
! 			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! 				 errmsg("column must be added to child tables too")));
  }
  
  static void
***************
*** 3681,3687 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
  						RelationGetRelationName(rel), colDef->colname)));
  
  			/* Bump the existing child att's inhcount */
! 			childatt->attinhcount++;
  			simple_heap_update(attrdesc, &tuple->t_self, tuple);
  			CatalogUpdateIndexes(attrdesc, tuple);
  
--- 3641,3647 ----
  						RelationGetRelationName(rel), colDef->colname)));
  
  			/* Bump the existing child att's inhcount */
! 			childatt->attinhcount += colDef->inhcount;
  			simple_heap_update(attrdesc, &tuple->t_self, tuple);
  			CatalogUpdateIndexes(attrdesc, tuple);
  
***************
*** 3915,3921 **** add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
   * to cons up a ColumnDef node because the ADD COLUMN code needs one.
   */
  static void
! ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd)
  {
  	/* If we're recursing to a child table, the ColumnDef is already set up */
  	if (cmd->def == NULL)
--- 3875,3882 ----
   * to cons up a ColumnDef node because the ADD COLUMN code needs one.
   */
  static void
! ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
! 			  int expected_parents, AlterTableCmd *cmd)
  {
  	/* If we're recursing to a child table, the ColumnDef is already set up */
  	if (cmd->def == NULL)
***************
*** 3930,3936 **** ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd)
  		cdef->storage = 0;
  		cmd->def = (Node *) cdef;
  	}
! 	ATPrepAddColumn(wqueue, rel, recurse, cmd);
  }
  
  /*
--- 3891,3897 ----
  		cdef->storage = 0;
  		cmd->def = (Node *) cdef;
  	}
! 	ATPrepAddColumn(wqueue, rel, recurse, expected_parents, cmd);
  }
  
  /*
***************
*** 5805,5811 **** ATExecDropConstraint(Relation rel, const char *constrName,
  static void
  ATPrepAlterColumnType(List **wqueue,
  					  AlteredTableInfo *tab, Relation rel,
! 					  bool recurse, bool recursing,
  					  AlterTableCmd *cmd)
  {
  	char	   *colName = cmd->name;
--- 5766,5772 ----
  static void
  ATPrepAlterColumnType(List **wqueue,
  					  AlteredTableInfo *tab, Relation rel,
! 					  bool recurse, int expected_parents,
  					  AlterTableCmd *cmd)
  {
  	char	   *colName = cmd->name;
***************
*** 5837,5843 **** ATPrepAlterColumnType(List **wqueue,
  						colName)));
  
  	/* Don't alter inherited columns */
! 	if (attTup->attinhcount > 0 && !recursing)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
  				 errmsg("cannot alter inherited column \"%s\"",
--- 5798,5804 ----
  						colName)));
  
  	/* Don't alter inherited columns */
! 	if (attTup->attinhcount > expected_parents)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
  				 errmsg("cannot alter inherited column \"%s\"",
***************
*** 5929,5935 **** ATPrepAlterColumnType(List **wqueue,
  	 */
  	if (recurse)
  		ATSimpleRecursion(wqueue, rel, cmd, recurse);
! 	else if (!recursing &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
--- 5890,5896 ----
  	 */
  	if (recurse)
  		ATSimpleRecursion(wqueue, rel, cmd, recurse);
! 	else if (expected_parents == 0 &&
  			 find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
  		ereport(ERROR,
  				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
#63Tom Lane
tgl@sss.pgh.pa.us
In reply to: KaiGai Kohei (#62)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

KaiGai Kohei <kaigai@ak.jp.nec.com> writes:

The attached one also clean up ATPrepAddColumn() and ATExecAddColumn() code,
not only ATPrepAlterColumnType(), according to what I mentioned above.

What exactly do you claim is wrong with the ADD COLUMN case?

regards, tom lane

#64KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Tom Lane (#63)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/02/02 11:09), Tom Lane wrote:

KaiGai Kohei<kaigai@ak.jp.nec.com> writes:

The attached one also clean up ATPrepAddColumn() and ATExecAddColumn() code,
not only ATPrepAlterColumnType(), according to what I mentioned above.

What exactly do you claim is wrong with the ADD COLUMN case?

ADD COLUMN case works correctly, but it takes unnecessary loops,
because the find_all_inheritors() didn't provide the value to be
set on the new pg_attribute.attinhcount.

I'm saying it can be rewritten in more graceful manner using the
new expected_parents argument.

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#65Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#64)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/2/1 KaiGai Kohei <kaigai@ak.jp.nec.com>:

(2010/02/02 11:09), Tom Lane wrote:

KaiGai Kohei<kaigai@ak.jp.nec.com>  writes:

The attached one also clean up ATPrepAddColumn() and ATExecAddColumn() code,
not only ATPrepAlterColumnType(), according to what I mentioned above.

What exactly do you claim is wrong with the ADD COLUMN case?

ADD COLUMN case works correctly, but it takes unnecessary loops,
because the find_all_inheritors() didn't provide the value to be
set on the new pg_attribute.attinhcount.

I'm saying it can be rewritten in more graceful manner using the
new expected_parents argument.

The subject line of this thread is getting less and less appropriate
to the content thereof.

I am not in favor of doing anything for 9.0 that is not a bug fix.

...Robert

#66Tom Lane
tgl@sss.pgh.pa.us
In reply to: KaiGai Kohei (#64)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

KaiGai Kohei <kaigai@ak.jp.nec.com> writes:

(2010/02/02 11:09), Tom Lane wrote:

KaiGai Kohei<kaigai@ak.jp.nec.com> writes:

The attached one also clean up ATPrepAddColumn() and ATExecAddColumn() code,
not only ATPrepAlterColumnType(), according to what I mentioned above.

What exactly do you claim is wrong with the ADD COLUMN case?

ADD COLUMN case works correctly, but it takes unnecessary loops,
because the find_all_inheritors() didn't provide the value to be
set on the new pg_attribute.attinhcount.

I'm saying it can be rewritten in more graceful manner using the
new expected_parents argument.

I tend to think that if it ain't broke don't fix it; the odds of
actually breaking it are too high. I don't really find the new coding
more graceful, anyway ...

regards, tom lane

#67KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#65)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/02/02 11:31), Robert Haas wrote:

2010/2/1 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/02/02 11:09), Tom Lane wrote:

KaiGai Kohei<kaigai@ak.jp.nec.com> writes:

The attached one also clean up ATPrepAddColumn() and ATExecAddColumn() code,
not only ATPrepAlterColumnType(), according to what I mentioned above.

What exactly do you claim is wrong with the ADD COLUMN case?

ADD COLUMN case works correctly, but it takes unnecessary loops,
because the find_all_inheritors() didn't provide the value to be
set on the new pg_attribute.attinhcount.

I'm saying it can be rewritten in more graceful manner using the
new expected_parents argument.

The subject line of this thread is getting less and less appropriate
to the content thereof.

I am not in favor of doing anything for 9.0 that is not a bug fix.

Are you talking about ATPrepAddColumn() only? Or both of ATPrepAddColumn()
and ATPrepAlterColumnType()?

My motivation to clean up ATPrepAddColumn() is less than the bugfix.

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#68Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#67)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/2/1 KaiGai Kohei <kaigai@ak.jp.nec.com>:

(2010/02/02 11:31), Robert Haas wrote:

2010/2/1 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/02/02 11:09), Tom Lane wrote:

KaiGai Kohei<kaigai@ak.jp.nec.com>    writes:

The attached one also clean up ATPrepAddColumn() and ATExecAddColumn() code,
not only ATPrepAlterColumnType(), according to what I mentioned above.

What exactly do you claim is wrong with the ADD COLUMN case?

ADD COLUMN case works correctly, but it takes unnecessary loops,
because the find_all_inheritors() didn't provide the value to be
set on the new pg_attribute.attinhcount.

I'm saying it can be rewritten in more graceful manner using the
new expected_parents argument.

The subject line of this thread is getting less and less appropriate
to the content thereof.

I am not in favor of doing anything for 9.0 that is not a bug fix.

Are you talking about ATPrepAddColumn() only? Or both of ATPrepAddColumn()
and ATPrepAlterColumnType()?

My motivation to clean up ATPrepAddColumn() is less than the bugfix.

I'm making a general statement - if something is BROKEN (like the
rename case we just dealt with), we should look at fixing it. If it's
just something that could be cleaned up or done more nicely, we should
leave it alone for now.

...Robert

#69KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#68)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/02/02 11:44), Robert Haas wrote:

2010/2/1 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/02/02 11:31), Robert Haas wrote:

2010/2/1 KaiGai Kohei<kaigai@ak.jp.nec.com>:

(2010/02/02 11:09), Tom Lane wrote:

KaiGai Kohei<kaigai@ak.jp.nec.com> writes:

The attached one also clean up ATPrepAddColumn() and ATExecAddColumn() code,
not only ATPrepAlterColumnType(), according to what I mentioned above.

What exactly do you claim is wrong with the ADD COLUMN case?

ADD COLUMN case works correctly, but it takes unnecessary loops,
because the find_all_inheritors() didn't provide the value to be
set on the new pg_attribute.attinhcount.

I'm saying it can be rewritten in more graceful manner using the
new expected_parents argument.

The subject line of this thread is getting less and less appropriate
to the content thereof.

I am not in favor of doing anything for 9.0 that is not a bug fix.

Are you talking about ATPrepAddColumn() only? Or both of ATPrepAddColumn()
and ATPrepAlterColumnType()?

My motivation to clean up ATPrepAddColumn() is less than the bugfix.

I'm making a general statement - if something is BROKEN (like the
rename case we just dealt with), we should look at fixing it. If it's
just something that could be cleaned up or done more nicely, we should
leave it alone for now.

OK, Please forget the second patch.

The former patch (pgsql-fix-inherit-attype.1.patch) just fixes the matter
in ALTER COLUMN TYPE case. Do you think it is a reasonable change for
the 9.0 release?

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>

#70Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#69)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

2010/2/1 KaiGai Kohei <kaigai@ak.jp.nec.com>:

I'm making a general statement - if something is BROKEN (like the
rename case we just dealt with), we should look at fixing it.  If it's
just something that could be cleaned up or done more nicely, we should
leave it alone for now.

OK, Please forget the second patch.

The former patch (pgsql-fix-inherit-attype.1.patch) just fixes the matter
in ALTER COLUMN TYPE case. Do you think it is a reasonable change for
the 9.0 release?

After reviewing this, I see that it doesn't really make sense to fix
ALTER COLUMN TYPE without rewriting ADD COLUMN to use
ATSimpleRecursion(). If we're going to go to the trouble of
refactoring the ATPrepCmd() interface, we certainly want to get as
much benefit out of that as we can.

That having been said... I'm leery about undertaking a substantial
refactoring of this code at this point in the release cycle. We have
less than two weeks remaining before the end of the final CommitFest,
so it doesn't seem like a good time to be starting new projects,
especially because we still have 16 patches that need we need to grind
through in less than 2 weeks, and I want to give some of those my
attention, too. So I would like to push this out to 9.1 and revisit
it when I can give it the amount of time that I believe is needed to
do it right.

...Robert

#71KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#70)
Re: [BUG?] strange behavior in ALTER TABLE ... RENAME TO on inherited columns

(2010/02/02 23:50), Robert Haas wrote:

2010/2/1 KaiGai Kohei<kaigai@ak.jp.nec.com>:

I'm making a general statement - if something is BROKEN (like the
rename case we just dealt with), we should look at fixing it. If it's
just something that could be cleaned up or done more nicely, we should
leave it alone for now.

OK, Please forget the second patch.

The former patch (pgsql-fix-inherit-attype.1.patch) just fixes the matter
in ALTER COLUMN TYPE case. Do you think it is a reasonable change for
the 9.0 release?

After reviewing this, I see that it doesn't really make sense to fix
ALTER COLUMN TYPE without rewriting ADD COLUMN to use
ATSimpleRecursion(). If we're going to go to the trouble of
refactoring the ATPrepCmd() interface, we certainly want to get as
much benefit out of that as we can.

That having been said... I'm leery about undertaking a substantial
refactoring of this code at this point in the release cycle. We have
less than two weeks remaining before the end of the final CommitFest,
so it doesn't seem like a good time to be starting new projects,
especially because we still have 16 patches that need we need to grind
through in less than 2 weeks, and I want to give some of those my
attention, too. So I would like to push this out to 9.1 and revisit
it when I can give it the amount of time that I believe is needed to
do it right.

OK, I'll work on remained part of this fix in the v9.1.

Thanks,
--
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>