BUG #5856: pg_attribute.attinhcount is not correct.

Started by Naoya Anzaialmost 15 years ago13 messages
#1Naoya Anzai
anzai-naoya@mxu.nes.nec.co.jp

The following bug has been logged online:

Bug reference: 5856
Logged by: Naoya Anzai
Email address: anzai-naoya@mxu.nes.nec.co.jp
PostgreSQL version: 8.4.5
Operating system: Red Hat Enterprise Linux Server release 5.5
Description: pg_attribute.attinhcount is not correct.
Details:

In PostgreSQL8.4.5, I found that the catalog pg_attribute.attinhcount is not
correct.

I executed the following queries.

--------------------------------------------------------------------------
create table "3_grandchild"();
create table "2_child"();
create table "1_parent"();

alter table "3_grandchild" inherit "2_child";
alter table "2_child" inherit "1_parent";

alter table "3_grandchild" add column c1 text;
alter table "2_child" add column c1 text;
alter table "1_parent" add column c1 text;

select c.relname,a.attname,a.attinhcount from pg_attribute a,pg_class c
where a.attrelid=c.oid
and relname in ('1_parent','2_child','3_grandchild')
and attname not in('xmax','xmin','cmin','cmax','tableoid','ctid')
order by relname;

relname | attname | attinhcount
--------------+---------+-------------
1_parent | c1 | 0
2_child | c1 | 1
3_grandchild | c1 | 2
(3 rows)
--------------------------------------------------------------------------

"3_grandchild"'s attinhcount should be 1.

When column "c1" is added to "1_parent", "ATExecAddColumn" is
executed for "2_child" and "3_grandchild" too.
If column "c1" already exists on "2_child" and "3_grandchild",
"ATExecAddColumn" increment pg_attribute.attinhcount of "c1".

childatt->attinhcount++; # src/backend/commands/tablecmds.c:3560

But pg_attribute.attinhcount should be the number of parent table (column)
that inherited directly.
So pg_attribute.attinhcount of "3_grandchild"."c1" should not be
incremented.

In this case,an error occurs when the following operations are
executed.

--------------------------------------------------------------------------
alter table "1_parent" drop column c1;
alter table "2_child" drop column c1;
alter table "3_grandchild" drop column c1;
ERROR: cannot drop inherited column "c1"

select c.relname,a.attname,a.attinhcount from pg_attribute a,pg_class c
where a.attrelid=c.oid
and relname in ('1_parent','2_child','3_grandchild')
and attname not in('xmax','xmin','cmin','cmax','tableoid','ctid')
order by relname;

relname | attname | attinhcount
--------------+---------+-------------
1_parent | c1 | 0
2_child | c1 | 0
3_grandchild | c1 | 1
(3 rows)
--------------------------------------------------------------------------

#2Robert Haas
robertmhaas@gmail.com
In reply to: Naoya Anzai (#1)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Mon, Jan 31, 2011 at 6:42 AM, Naoya Anzai
<anzai-naoya@mxu.nes.nec.co.jp> wrote:

In PostgreSQL8.4.5, I found that the catalog pg_attribute.attinhcount is not
correct.

I executed the following queries.

--------------------------------------------------------------------------
create table "3_grandchild"();
create table "2_child"();
create table "1_parent"();

alter table "3_grandchild" inherit "2_child";
alter table "2_child" inherit "1_parent";

alter table "3_grandchild" add column c1 text;
alter table "2_child" add column c1 text;
alter table "1_parent" add column c1 text;

select c.relname,a.attname,a.attinhcount from pg_attribute a,pg_class c
where a.attrelid=c.oid
and relname in ('1_parent','2_child','3_grandchild')
and attname not in('xmax','xmin','cmin','cmax','tableoid','ctid')
order by relname;

   relname    | attname | attinhcount
 --------------+---------+-------------
 1_parent     | c1      |           0
 2_child      | c1      |           1
 3_grandchild | c1      |           2
 (3 rows)
--------------------------------------------------------------------------

"3_grandchild"'s attinhcount should be 1.

I think this is a manifestation the same problem mentioned here:

http://git.postgresql.org/gitweb?p=postgresql.git;a=commit;h=31b6fc06d83c6de3644c8f2921eb7de0eb92fac3

I believe this requires some refactoring to fix. It would be good to do that.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#3Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#2)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

[moving to pgsql-hackers]

On Thu, Feb 03, 2011 at 11:24:42AM -0500, Robert Haas wrote:

On Mon, Jan 31, 2011 at 6:42 AM, Naoya Anzai
<anzai-naoya@mxu.nes.nec.co.jp> wrote:

In PostgreSQL8.4.5, I found that the catalog pg_attribute.attinhcount is not
correct.

I executed the following queries.

--------------------------------------------------------------------------
create table "3_grandchild"();
create table "2_child"();
create table "1_parent"();

alter table "3_grandchild" inherit "2_child";
alter table "2_child" inherit "1_parent";

alter table "3_grandchild" add column c1 text;
alter table "2_child" add column c1 text;
alter table "1_parent" add column c1 text;

select c.relname,a.attname,a.attinhcount from pg_attribute a,pg_class c
where a.attrelid=c.oid
and relname in ('1_parent','2_child','3_grandchild')
and attname not in('xmax','xmin','cmin','cmax','tableoid','ctid')
order by relname;

? ?relname ? ?| attname | attinhcount
?--------------+---------+-------------
?1_parent ? ? | c1 ? ? ?| ? ? ? ? ? 0
?2_child ? ? ?| c1 ? ? ?| ? ? ? ? ? 1
?3_grandchild | c1 ? ? ?| ? ? ? ? ? 2
?(3 rows)
--------------------------------------------------------------------------

"3_grandchild"'s attinhcount should be 1.

I think this is a manifestation the same problem mentioned here:

http://git.postgresql.org/gitweb?p=postgresql.git;a=commit;h=31b6fc06d83c6de3644c8f2921eb7de0eb92fac3

I believe this requires some refactoring to fix. It would be good to do that.

The best way I can see is to make ATExecAddColumn more like ATExecDropColumn,
ATAddCheckConstraint, and ATExecDropConstraint. Namely, recurse at Exec-time
rather than Prep-time, and cease recursing when we satisfy the ADD COLUMN with a
merge. Did you have something else in mind?

Incidentally, when we satisfy an ADD COLUMN with a merge, we do not check or
update attnotnull:

create table parent();
create table child(c1 text) inherits (parent);
alter table parent add column c1 text not null;
\d child

We could either update attnotnull (and schedule a phase-3 scan of the table) or
throw an error. For ALTER TABLE ... INHERIT, we throw the error. For CREATE
TABLE ... INHERITS, we add the NOT NULL (and no scan is needed). I'd weakly
lean toward throwing the error. Opinions?

nm

#4Bernd Helmle
mailings@oopsware.de
In reply to: Noah Misch (#3)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

--On 31. März 2011 06:06:49 -0400 Noah Misch <noah@leadboat.com> wrote:

The best way I can see is to make ATExecAddColumn more like ATExecDropColumn,
ATAddCheckConstraint, and ATExecDropConstraint. Namely, recurse at Exec-time
rather than Prep-time, and cease recursing when we satisfy the ADD COLUMN
with a merge. Did you have something else in mind?

Incidentally, when we satisfy an ADD COLUMN with a merge, we do not check or
update attnotnull:

create table parent();
create table child(c1 text) inherits (parent);
alter table parent add column c1 text not null;
\d child

We could either update attnotnull (and schedule a phase-3 scan of the table)
or throw an error. For ALTER TABLE ... INHERIT, we throw the error. For
CREATE TABLE ... INHERITS, we add the NOT NULL (and no scan is needed). I'd
weakly lean toward throwing the error. Opinions?

Hmm this looks like the same kind of problem i'm currently faced with when
working on tracking inheritance counters for NOT NULL constraint at the moment
(see
<http://git.postgresql.org/gitweb?p=users/bernd/postgres.git;a=shortlog;h=refs/heads/notnull_constraint&gt;
for a heavy WIP patch). It currently recurses and seems to do the right thing
(tm) for your example above, but i'm far from being certain that the way i'm
undertaking here is correct. It indeed discovered a bug i had in my recursion
code...

--
Thanks

Bernd

#5Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#3)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Thu, Mar 31, 2011 at 6:06 AM, Noah Misch <noah@leadboat.com> wrote:

I think this is a manifestation the same problem mentioned here:

http://git.postgresql.org/gitweb?p=postgresql.git;a=commit;h=31b6fc06d83c6de3644c8f2921eb7de0eb92fac3

I believe this requires some refactoring to fix.  It would be good to do that.

The best way I can see is to make ATExecAddColumn more like ATExecDropColumn,
ATAddCheckConstraint, and ATExecDropConstraint.  Namely, recurse at Exec-time
rather than Prep-time, and cease recursing when we satisfy the ADD COLUMN with a
merge.  Did you have something else in mind?

I had exactly what you just said in mind.

Incidentally, when we satisfy an ADD COLUMN with a merge, we do not check or
update attnotnull:

create table parent();
create table child(c1 text) inherits (parent);
alter table parent add column c1 text not null;
\d child

We could either update attnotnull (and schedule a phase-3 scan of the table) or
throw an error.  For ALTER TABLE ... INHERIT, we throw the error.  For CREATE
TABLE ... INHERITS, we add the NOT NULL (and no scan is needed).  I'd weakly
lean toward throwing the error.  Opinions?

Not sure. I think that anything we do here is bound to have some
corner cases that are not quite right for so long as NOT NULL
constraints aren't represented in pg_constraint, and it's way too late
to dredge up that issue again for 9.1. I'm somewhat inclined to just
defer fixing it until we get that work committed.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#6Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#5)
1 attachment(s)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Thu, Mar 31, 2011 at 11:11:49AM -0400, Robert Haas wrote:

On Thu, Mar 31, 2011 at 6:06 AM, Noah Misch <noah@leadboat.com> wrote:

I think this is a manifestation the same problem mentioned here:

http://git.postgresql.org/gitweb?p=postgresql.git;a=commit;h=31b6fc06d83c6de3644c8f2921eb7de0eb92fac3

I believe this requires some refactoring to fix. ?It would be good to do that.

The best way I can see is to make ATExecAddColumn more like ATExecDropColumn,
ATAddCheckConstraint, and ATExecDropConstraint. ?Namely, recurse at Exec-time
rather than Prep-time, and cease recursing when we satisfy the ADD COLUMN with a
merge. ?Did you have something else in mind?

I had exactly what you just said in mind.

Patch attached, then.

Incidentally, when we satisfy an ADD COLUMN with a merge, we do not check or
update attnotnull:

<details ... what should we do?>

Not sure. I think that anything we do here is bound to have some
corner cases that are not quite right for so long as NOT NULL
constraints aren't represented in pg_constraint, and it's way too late
to dredge up that issue again for 9.1. I'm somewhat inclined to just
defer fixing it until we get that work committed.

OK.

Attachments:

attinhcount-merge-v1.patchtext/plain; charset=us-asciiDownload
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 737ab1a..c64ffac 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 285,300 **** static void ATSimplePermissions(Relation rel, int allowed_targets);
  static void ATWrongRelkindError(Relation rel, int allowed_targets);
  static void ATSimpleRecursion(List **wqueue, Relation rel,
  				  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
- static void ATOneLevelRecursion(List **wqueue, Relation rel,
- 					AlterTableCmd *cmd, LOCKMODE lockmode);
  static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  								  LOCKMODE lockmode);
  static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
  										   DropBehavior behavior);
  static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  				AlterTableCmd *cmd, LOCKMODE lockmode);
! static void ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
! 				ColumnDef *colDef, bool isOid, LOCKMODE lockmode);
  static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid, Oid collid);
  static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
  			  AlterTableCmd *cmd, LOCKMODE lockmode);
--- 285,299 ----
  static void ATWrongRelkindError(Relation rel, int allowed_targets);
  static void ATSimpleRecursion(List **wqueue, Relation rel,
  				  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
  static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
  								  LOCKMODE lockmode);
  static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
  										   DropBehavior behavior);
  static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  				AlterTableCmd *cmd, LOCKMODE lockmode);
! static void ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
! 				ColumnDef *colDef, bool isOid,
! 				bool recurse, bool recursing, LOCKMODE lockmode);
  static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid, Oid collid);
  static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
  			  AlterTableCmd *cmd, LOCKMODE lockmode);
***************
*** 2775,2789 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  		case AT_AddColumn:		/* ADD COLUMN */
  			ATSimplePermissions(rel,
  				ATT_TABLE|ATT_COMPOSITE_TYPE|ATT_FOREIGN_TABLE);
- 			/* Performs own recursion */
  			ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
  										 * VIEW */
  			ATSimplePermissions(rel, ATT_VIEW);
- 			/* Performs own recursion */
  			ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
--- 2774,2788 ----
  		case AT_AddColumn:		/* ADD COLUMN */
  			ATSimplePermissions(rel,
  				ATT_TABLE|ATT_COMPOSITE_TYPE|ATT_FOREIGN_TABLE);
  			ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
+ 			/* Recursion occurs during execution phase */
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
  										 * VIEW */
  			ATSimplePermissions(rel, ATT_VIEW);
  			ATPrepAddColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
+ 			/* Recursion occurs during execution phase */
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
***************
*** 2885,2893 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  			break;
  		case AT_AddOids:		/* SET WITH OIDS */
  			ATSimplePermissions(rel, ATT_TABLE);
- 			/* Performs own recursion */
  			if (!rel->rd_rel->relhasoids || recursing)
  				ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_DropOids:		/* SET WITHOUT OIDS */
--- 2884,2892 ----
  			break;
  		case AT_AddOids:		/* SET WITH OIDS */
  			ATSimplePermissions(rel, ATT_TABLE);
  			if (!rel->rd_rel->relhasoids || recursing)
  				ATPrepAddOids(wqueue, rel, recurse, cmd, lockmode);
+ 			/* Recursion occurs during execution phase */
  			pass = AT_PASS_ADD_COL;
  			break;
  		case AT_DropOids:		/* SET WITHOUT OIDS */
***************
*** 3043,3049 **** ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
  		case AT_AddColumn:		/* ADD COLUMN */
  		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
  										 * VIEW */
! 			ATExecAddColumn(tab, rel, (ColumnDef *) cmd->def, false, lockmode);
  			break;
  		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
  			ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
--- 3042,3053 ----
  		case AT_AddColumn:		/* ADD COLUMN */
  		case AT_AddColumnToView:		/* add column via CREATE OR REPLACE
  										 * VIEW */
! 			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
! 							false, false, false, lockmode);
! 			break;
! 		case AT_AddColumnRecurse:
! 			ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
! 							false, true, false, lockmode);
  			break;
  		case AT_ColumnDefault:	/* ALTER COLUMN DEFAULT */
  			ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
***************
*** 3121,3127 **** ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
  		case AT_AddOids:		/* SET WITH OIDS */
  			/* Use the ADD COLUMN code, unless prep decided to do nothing */
  			if (cmd->def != NULL)
! 				ATExecAddColumn(tab, rel, (ColumnDef *) cmd->def, true, lockmode);
  			break;
  		case AT_DropOids:		/* SET WITHOUT OIDS */
  
--- 3125,3138 ----
  		case AT_AddOids:		/* SET WITH OIDS */
  			/* Use the ADD COLUMN code, unless prep decided to do nothing */
  			if (cmd->def != NULL)
! 				ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
! 								true, false, false, lockmode);
! 			break;
! 		case AT_AddOidsRecurse:	/* SET WITH OIDS */
! 			/* Use the ADD COLUMN code, unless prep decided to do nothing */
! 			if (cmd->def != NULL)
! 				ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
! 								true, true, false, lockmode);
  			break;
  		case AT_DropOids:		/* SET WITHOUT OIDS */
  
***************
*** 3843,3879 **** ATSimpleRecursion(List **wqueue, Relation rel,
  }
  
  /*
-  * 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, LOCKMODE lockmode)
- {
- 	Oid			relid = RelationGetRelid(rel);
- 	ListCell   *child;
- 	List	   *children;
- 
- 	children = find_inheritance_children(relid, lockmode);
- 
- 	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, lockmode);
- 		relation_close(childrel, NoLock);
- 	}
- }
- 
- /*
   * ATTypedTableRecursion
   *
   * Propagate ALTER TYPE operations to the typed tables of that type.
--- 3854,3859 ----
***************
*** 4060,4065 **** find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
--- 4040,4051 ----
   * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
   * AT_AddColumn AlterTableCmd by parse_utilcmd.c and added as independent
   * AlterTableCmd's.
+  *
+  * ADD COLUMN cannot use the normal ALTER TABLE recursion mechanism, because we
+  * have to decide at runtime whether to recurse or not depending on whether we
+  * actually add a column or merely merge with an existing column.  (We can't
+  * check this in a static pre-pass because it won't handle multiple inheritance
+  * situations correctly.)
   */
  static void
  ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
***************
*** 4070,4112 **** ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  				 errmsg("cannot add column to typed table")));
  
- 	/*
- 	 * 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, lockmode);
- 	}
- 	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")));
- 	}
- 
  	if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
  		ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
  }
  
  static void
! ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
! 				ColumnDef *colDef, bool isOid, LOCKMODE lockmode)
  {
  	Oid			myrelid = RelationGetRelid(rel);
  	Relation	pgclass,
--- 4056,4072 ----
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
  				 errmsg("cannot add column to typed table")));
  
  	if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
  		ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+ 
+ 	if (recurse)
+ 		cmd->subtype = AT_AddColumnRecurse;
  }
  
  static void
! ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
! 				ColumnDef *colDef, bool isOid,
! 				bool recurse, bool recursing, LOCKMODE lockmode)
  {
  	Oid			myrelid = RelationGetRelid(rel);
  	Relation	pgclass,
***************
*** 4121,4132 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
  	Oid			collOid;
  	Form_pg_type tform;
  	Expr	   *defval;
  
  	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
  
  	/*
  	 * Are we adding the column to a recursion child?  If so, check whether to
! 	 * merge with an existing definition for the column.
  	 */
  	if (colDef->inhcount > 0)
  	{
--- 4081,4099 ----
  	Oid			collOid;
  	Form_pg_type tform;
  	Expr	   *defval;
+ 	List	   *children;
+ 	ListCell   *child;
+ 
+ 	/* At top level, permission check was done in ATPrepCmd, else do it */
+ 	if (recursing)
+ 		ATSimplePermissions(rel, ATT_TABLE);
  
  	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
  
  	/*
  	 * Are we adding the column to a recursion child?  If so, check whether to
! 	 * merge with an existing definition for the column.  If we do merge,
! 	 * there's also no need to recurse.  Children will already have the column.
  	 */
  	if (colDef->inhcount > 0)
  	{
***************
*** 4389,4394 **** ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
--- 4356,4405 ----
  	 * Add needed dependency entries for the new column.
  	 */
  	add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid, attribute.attcollation);
+ 
+ 	/*
+ 	 * Propagate to children as appropriate.  Unlike most other ALTER
+ 	 * routines, we have to do this one level of recursion at a time; we can't
+ 	 * use find_all_inheritors to do it in one pass.
+ 	 */
+ 	children = find_inheritance_children(RelationGetRelid(rel), lockmode);
+ 
+ 	/*
+ 	 * 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 (children && !recurse)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ 				 errmsg("column must be added to child tables too")));
+ 
+ 	/* Children should see column as singly inherited */
+ 	if (!recursing)
+ 	{
+ 		colDef = copyObject(colDef);
+ 		colDef->inhcount = 1;
+ 		colDef->is_local = false;
+ 	}
+ 
+ 	foreach(child, children)
+ 	{
+ 		Oid			childrelid = lfirst_oid(child);
+ 		Relation	childrel;
+ 		AlteredTableInfo *childtab;
+ 
+ 		/* find_inheritance_children already got lock */
+ 		childrel = heap_open(childrelid, NoLock);
+ 		CheckTableNotInUse(childrel, "ALTER TABLE");
+ 
+ 		/* Find or create work queue entry for this table */
+ 		childtab = ATGetQueueEntry(wqueue, childrel);
+ 
+ 		/* Recurse to child */
+ 		ATExecAddColumn(wqueue, childtab, childrel,
+ 						colDef, isOid, recurse, true, lockmode);
+ 
+ 		heap_close(childrel, NoLock);
+ 	}
  }
  
  /*
***************
*** 4440,4445 **** ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd, LOC
--- 4451,4459 ----
  		cmd->def = (Node *) cdef;
  	}
  	ATPrepAddColumn(wqueue, rel, recurse, false, cmd, lockmode);
+ 
+ 	if (recurse)
+ 		cmd->subtype = AT_AddOidsRecurse;
  }
  
  /*
diff --git a/src/include/nodes/parsenodindex 670b12f..d9eac76 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1173,1178 **** typedef struct AlterTableStmt
--- 1173,1179 ----
  typedef enum AlterTableType
  {
  	AT_AddColumn,				/* add column */
+ 	AT_AddColumnRecurse,		/* internal to commands/tablecmds.c */
  	AT_AddColumnToView,			/* implicitly via CREATE OR REPLACE VIEW */
  	AT_ColumnDefault,			/* alter column default */
  	AT_DropNotNull,				/* alter column drop not null */
***************
*** 1198,1203 **** typedef enum AlterTableType
--- 1199,1205 ----
  	AT_ClusterOn,				/* CLUSTER ON */
  	AT_DropCluster,				/* SET WITHOUT CLUSTER */
  	AT_AddOids,					/* SET WITH OIDS */
+ 	AT_AddOidsRecurse,			/* internal to commands/tablecmds.c */
  	AT_DropOids,				/* SET WITHOUT OIDS */
  	AT_SetTableSpace,			/* SET TABLESPACE */
  	AT_SetRelOptions,			/* SET (...) -- AM specific parameters */
diff --git a/src/test/regress/expecteindex 578f94b..d7d1b64 100644
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
***************
*** 1198,1203 **** drop table p1, p2 cascade;
--- 1198,1220 ----
  NOTICE:  drop cascades to 2 other objects
  DETAIL:  drop cascades to table c1
  drop cascades to table gc1
+ -- test attinhcount tracking with merged columns
+ create table depth0();
+ create table depth1(c text) inherits (depth0);
+ create table depth2() inherits (depth1);
+ alter table depth0 add c text;
+ NOTICE:  merging definition of column "c" for child "depth1"
+ select attrelid::regclass, attname, attinhcount, attislocal
+ from pg_attribute
+ where attnum > 0 and attrelid::regclass in ('depth0', 'depth1', 'depth2')
+ order by attrelid::regclass::text, attnum;
+  attrelid | attname | attinhcount | attislocal 
+ ----------+---------+-------------+------------
+  depth0   | c       |           0 | t
+  depth1   | c       |           1 | t
+  depth2   | c       |           1 | f
+ (3 rows)
+ 
  --
  -- Test the ALTER TABLE SET WITH/WITHOUT OIDS command
  --
diff --git a/src/test/regress/sql/alter_table.sqindex 54dbb8e..749584d 100644
*** a/src/test/regress/sql/alter_table.sql
--- b/src/test/regress/sql/alter_table.sql
***************
*** 944,949 **** order by relname, attnum;
--- 944,961 ----
  
  drop table p1, p2 cascade;
  
+ -- test attinhcount tracking with merged columns
+ 
+ create table depth0();
+ create table depth1(c text) inherits (depth0);
+ create table depth2() inherits (depth1);
+ alter table depth0 add c text;
+ 
+ select attrelid::regclass, attname, attinhcount, attislocal
+ from pg_attribute
+ where attnum > 0 and attrelid::regclass in ('depth0', 'depth1', 'depth2')
+ order by attrelid::regclass::text, attnum;
+ 
  --
  -- Test the ALTER TABLE SET WITH/WITHOUT OIDS command
  --
#7Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#6)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Fri, Apr 1, 2011 at 12:56 AM, Noah Misch <noah@leadboat.com> wrote:

On Thu, Mar 31, 2011 at 11:11:49AM -0400, Robert Haas wrote:

On Thu, Mar 31, 2011 at 6:06 AM, Noah Misch <noah@leadboat.com> wrote:

I think this is a manifestation the same problem mentioned here:

http://git.postgresql.org/gitweb?p=postgresql.git;a=commit;h=31b6fc06d83c6de3644c8f2921eb7de0eb92fac3

I believe this requires some refactoring to fix. ?It would be good to do that.

The best way I can see is to make ATExecAddColumn more like ATExecDropColumn,
ATAddCheckConstraint, and ATExecDropConstraint. ?Namely, recurse at Exec-time
rather than Prep-time, and cease recursing when we satisfy the ADD COLUMN with a
merge. ?Did you have something else in mind?

I had exactly what you just said in mind.

Patch attached, then.

Committed.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#8Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#7)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Sun, Apr 03, 2011 at 09:53:57PM -0400, Robert Haas wrote:

On Fri, Apr 1, 2011 at 12:56 AM, Noah Misch <noah@leadboat.com> wrote:

On Thu, Mar 31, 2011 at 11:11:49AM -0400, Robert Haas wrote:

On Thu, Mar 31, 2011 at 6:06 AM, Noah Misch <noah@leadboat.com> wrote:

The best way I can see is to make ATExecAddColumn more like ATExecDropColumn,
ATAddCheckConstraint, and ATExecDropConstraint. ?Namely, recurse at Exec-time
rather than Prep-time, and cease recursing when we satisfy the ADD COLUMN with a
merge. ?Did you have something else in mind?

I had exactly what you just said in mind.

Patch attached, then.

Committed.

Thanks. This turns out to have caused that TOAST creation regression:

On Fri, Apr 08, 2011 at 08:12:19PM -0400, Noah Misch wrote:
[pg_upgrade/typed table business]

I also tested a regular dump+reload of the regression database, and a pg_upgrade
of the same. The latter failed further along, due (indirectly) to this failure
to create a TOAST table:

create table p ();
create table ch () inherits (p);
alter table p add column a text;
select oid::regclass,reltoastrelid from pg_class where oid::regclass IN ('p','ch');
insert into ch values (repeat('x', 1000000));

If I "drop table a_star cascade" in the regression database before attempting
pg_upgrade, it completes cleanly.

Since ATExecAddColumn now handles the recursion, child table work queue entries
no longer have AT_PASS_ADD_COL subcommands. Consequently, this heuristic in
ATRewriteCatalogs skips over them:

if (tab->relkind == RELKIND_RELATION &&
(tab->subcmds[AT_PASS_ADD_COL] ||
tab->subcmds[AT_PASS_ALTER_TYPE] ||
tab->subcmds[AT_PASS_COL_ATTRS]))
AlterTableCreateToastTable(tab->relid, (Datum) 0);

SET STORAGE uses AT_PASS_MISC, so this test case also omits a TOAST table:

set client_min_messages = debug1; -- display toast creation
create table t (a text); -- makes toast
alter table t alter a set storage plain;
alter table t add b int default 0; -- rewrite the table - no toast
alter table t alter a set storage extended; -- no toast added?
insert into t (a) values (repeat('x', 1000000)); -- fails

My first thought was to figure that the cost of needs_toast_table() is not a
concern and simply remove the pass-usage heuristic. However, we don't want
AlterTableCreateToastTable acquiring an AccessExclusiveLock for ALTER TABLE
recipes that only acquired ShareUpdateExclusiveLock. I see these options:

1. Upgrade AT_SetStorage to take AccessExclusiveLock. Add a maybe_create_toast
field to AlteredTableInfo. Have the AT_SetStorage, AT_AlterColumnType and
AT_AddColumn implementations set it.

2. Upgrade AT_SetStorage to take AccessExclusiveLock. In ATRewriteCatalogs,
replace the pass-usage heuristic with a test for locklevel ==
AccessExclusiveLock. This smells more like a hack, but it might be less
vulnerable to omissions. On the other hand, the set of operations that could
add TOAST tables are not particularly liable to grow.

3. Make AlterTableCreateToastTable acquire only ShareUpdateExclusiveLock and
remove the pass-usage heuristic from ATRewriteCatalogs. For this to be valid,
toast_insert_or_update() must behave reasonably in the face of a relation
concurrently acquiring a TOAST table. Since it takes reltoastrelid from the
relcache, toast_insert_or_update() will not act on the change in the middle of a
single call. Even if it did, I don't see any risks.

I'd lean toward #3 if someone else is also confident in its correctness.
Otherwise, #1 seems like the way to go. Preferences? Other ideas?

Thanks,
nm

#9Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#8)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Sun, Apr 10, 2011 at 6:36 AM, Noah Misch <noah@leadboat.com> wrote:

I had exactly what you just said in mind.

Patch attached, then.

Committed.

Thanks.  This turns out to have caused that TOAST creation regression:

Crap. I am not going to be able to look at this today; I am getting
on a plane to Santa Clara. I will look at it while I'm there if I
can, but it's going to be a busy week for me so if anyone else can
step in...

On Fri, Apr 08, 2011 at 08:12:19PM -0400, Noah Misch wrote:
[pg_upgrade/typed table business]

I also tested a regular dump+reload of the regression database, and a pg_upgrade
of the same.  The latter failed further along, due (indirectly) to this failure
to create a TOAST table:

  create table p ();
  create table ch () inherits (p);
  alter table p add column a text;
  select oid::regclass,reltoastrelid from pg_class where oid::regclass IN ('p','ch');
  insert into ch values (repeat('x', 1000000));

If I "drop table a_star cascade" in the regression database before attempting
pg_upgrade, it completes cleanly.

Since ATExecAddColumn now handles the recursion, child table work queue entries
no longer have AT_PASS_ADD_COL subcommands.  Consequently, this heuristic in
ATRewriteCatalogs skips over them:

               if (tab->relkind == RELKIND_RELATION &&
                       (tab->subcmds[AT_PASS_ADD_COL] ||
                        tab->subcmds[AT_PASS_ALTER_TYPE] ||
                        tab->subcmds[AT_PASS_COL_ATTRS]))
                       AlterTableCreateToastTable(tab->relid, (Datum) 0);

SET STORAGE uses AT_PASS_MISC, so this test case also omits a TOAST table:

 set client_min_messages = debug1; -- display toast creation
 create table t (a text); -- makes toast
 alter table t alter a set storage plain;
 alter table t add b int default 0; -- rewrite the table - no toast
 alter table t alter a set storage extended; -- no toast added?
 insert into t (a) values (repeat('x', 1000000)); -- fails

My first thought was to figure that the cost of needs_toast_table() is not a
concern and simply remove the pass-usage heuristic.  However, we don't want
AlterTableCreateToastTable acquiring an AccessExclusiveLock for ALTER TABLE
recipes that only acquired ShareUpdateExclusiveLock.  I see these options:

1. Upgrade AT_SetStorage to take AccessExclusiveLock.  Add a maybe_create_toast
field to AlteredTableInfo.  Have the AT_SetStorage, AT_AlterColumnType and
AT_AddColumn implementations set it.

2. Upgrade AT_SetStorage to take AccessExclusiveLock.  In ATRewriteCatalogs,
replace the pass-usage heuristic with a test for locklevel ==
AccessExclusiveLock.  This smells more like a hack, but it might be less
vulnerable to omissions.  On the other hand, the set of operations that could
add TOAST tables are not particularly liable to grow.

3. Make AlterTableCreateToastTable acquire only ShareUpdateExclusiveLock and
remove the pass-usage heuristic from ATRewriteCatalogs.  For this to be valid,
toast_insert_or_update() must behave reasonably in the face of a relation
concurrently acquiring a TOAST table.  Since it takes reltoastrelid from the
relcache, toast_insert_or_update() will not act on the change in the middle of a
single call.  Even if it did, I don't see any risks.

I'd lean toward #3 if someone else is also confident in its correctness.
Otherwise, #1 seems like the way to go.  Preferences?  Other ideas?

I haven't scrutinized the code but I would prefer #3 if it's viable
without too much of a code footprint.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#10Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#9)
1 attachment(s)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Sun, Apr 10, 2011 at 07:35:53AM -0400, Robert Haas wrote:

On Sun, Apr 10, 2011 at 6:36 AM, Noah Misch <noah@leadboat.com> wrote:

3. Make AlterTableCreateToastTable acquire only ShareUpdateExclusiveLock and
remove the pass-usage heuristic from ATRewriteCatalogs. �For this to be valid,
toast_insert_or_update() must behave reasonably in the face of a relation
concurrently acquiring a TOAST table. �Since it takes reltoastrelid from the
relcache, toast_insert_or_update() will not act on the change in the middle of a
single call. �Even if it did, I don't see any risks.

I'd lean toward #3 if someone else is also confident in its correctness.
Otherwise, #1 seems like the way to go. �Preferences? �Other ideas?

I haven't scrutinized the code but I would prefer #3 if it's viable
without too much of a code footprint.

It's certainly compact; patch attached.

Attachments:

alter-table-toast-v1.patchtext/plain; charset=us-asciiDownload
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 5d5496d..cb0df40 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -59,11 +59,11 @@ AlterTableCreateToastTable(Oid relOid, Datum reloptions)
 	Relation	rel;
 
 	/*
-	 * Grab an exclusive lock on the target table, which we will NOT release
-	 * until end of transaction.  (This is probably redundant in all present
-	 * uses...)
+	 * Grab a DDL-exclusive lock on the target table, since we'll update the
+	 * pg_class tuple.  This is redundant for all present users.  Tuple toasting
+	 * behaves safely in the face of a concurrent TOAST table add.
 	 */
-	rel = heap_open(relOid, AccessExclusiveLock);
+	rel = heap_open(relOid, ShareUpdateExclusiveLock);
 
 	/* create_toast_table does all the work */
 	(void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 886b656..03d1efa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3014,18 +3014,12 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
 		}
 	}
 
-	/*
-	 * Check to see if a toast table must be added, if we executed any
-	 * subcommands that might have added a column or changed column storage.
-	 */
+	/* Check to see if a toast table must be added. */
 	foreach(ltab, *wqueue)
 	{
 		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-		if (tab->relkind == RELKIND_RELATION &&
-			(tab->subcmds[AT_PASS_ADD_COL] ||
-			 tab->subcmds[AT_PASS_ALTER_TYPE] ||
-			 tab->subcmds[AT_PASS_COL_ATTRS]))
+		if (tab->relkind == RELKIND_RELATION)
 			AlterTableCreateToastTable(tab->relid, (Datum) 0);
 	}
 }
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index d7d1b64..315b915 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1522,6 +1522,19 @@ ERROR:  composite type recur1 cannot be made a member of itself
 alter table recur1 add column f2 int;
 alter table recur1 alter column f2 type recur2; -- fails
 ERROR:  composite type recur1 cannot be made a member of itself
+-- SET STORAGE may need to add a TOAST table
+create table test_storage (a text);
+alter table test_storage alter a set storage plain;
+alter table test_storage add b int default 0; -- rewrite table to remove its TOAST table
+alter table test_storage alter a set storage extended; -- re-add TOAST table
+select reltoastrelid <> 0 as has_toast_table
+from pg_class
+where oid = 'test_storage'::regclass;
+ has_toast_table 
+-----------------
+ t
+(1 row)
+
 --
 -- lock levels
 --
diff --git a/src/test/regress/input/misc.source b/src/test/regress/input/misc.source
index 0930a6a..7cd26cb 100644
--- a/src/test/regress/input/misc.source
+++ b/src/test/regress/input/misc.source
@@ -153,6 +153,12 @@ SELECT * FROM e_star*;
 
 ALTER TABLE a_star* ADD COLUMN a text;
 
+-- That ALTER TABLE should have added TOAST tables.
+SELECT relname, reltoastrelid <> 0 AS has_toast_table
+   FROM pg_class
+   WHERE oid::regclass IN ('a_star', 'c_star')
+   ORDER BY 1;
+
 --UPDATE b_star*
 --   SET a = text 'gazpacho'
 --   WHERE aa > 4;
diff --git a/src/test/regress/output/misc.source b/src/test/regress/output/misc.source
index c225d0f..34bde31 100644
--- a/src/test/regress/output/misc.source
+++ b/src/test/regress/output/misc.source
@@ -376,6 +376,17 @@ SELECT * FROM e_star*;
 
 ALTER TABLE a_star* ADD COLUMN a text;
 NOTICE:  merging definition of column "a" for child "d_star"
+-- That ALTER TABLE should have added TOAST tables.
+SELECT relname, reltoastrelid <> 0 AS has_toast_table
+   FROM pg_class
+   WHERE oid::regclass IN ('a_star', 'c_star')
+   ORDER BY 1;
+ relname | has_toast_table 
+---------+-----------------
+ a_star  | t
+ c_star  | t
+(2 rows)
+
 --UPDATE b_star*
 --   SET a = text 'gazpacho'
 --   WHERE aa > 4;
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 749584d..43a9ce9 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1133,6 +1133,16 @@ alter table recur1 add column f2 recur2; -- fails
 alter table recur1 add column f2 int;
 alter table recur1 alter column f2 type recur2; -- fails
 
+-- SET STORAGE may need to add a TOAST table
+create table test_storage (a text);
+alter table test_storage alter a set storage plain;
+alter table test_storage add b int default 0; -- rewrite table to remove its TOAST table
+alter table test_storage alter a set storage extended; -- re-add TOAST table
+
+select reltoastrelid <> 0 as has_toast_table
+from pg_class
+where oid = 'test_storage'::regclass;
+
 --
 -- lock levels
 --
#11Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#8)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Sun, Apr 10, 2011 at 6:36 AM, Noah Misch <noah@leadboat.com> wrote:

On Sun, Apr 03, 2011 at 09:53:57PM -0400, Robert Haas wrote:

On Fri, Apr 1, 2011 at 12:56 AM, Noah Misch <noah@leadboat.com> wrote:

On Thu, Mar 31, 2011 at 11:11:49AM -0400, Robert Haas wrote:

On Thu, Mar 31, 2011 at 6:06 AM, Noah Misch <noah@leadboat.com> wrote:

The best way I can see is to make ATExecAddColumn more like ATExecDropColumn,
ATAddCheckConstraint, and ATExecDropConstraint. ?Namely, recurse at Exec-time
rather than Prep-time, and cease recursing when we satisfy the ADD COLUMN with a
merge. ?Did you have something else in mind?

I had exactly what you just said in mind.

Patch attached, then.

Committed.

Thanks.  This turns out to have caused that TOAST creation regression:

On Fri, Apr 08, 2011 at 08:12:19PM -0400, Noah Misch wrote:
[pg_upgrade/typed table business]

I also tested a regular dump+reload of the regression database, and a pg_upgrade
of the same.  The latter failed further along, due (indirectly) to this failure
to create a TOAST table:

  create table p ();
  create table ch () inherits (p);
  alter table p add column a text;
  select oid::regclass,reltoastrelid from pg_class where oid::regclass IN ('p','ch');
  insert into ch values (repeat('x', 1000000));

If I "drop table a_star cascade" in the regression database before attempting
pg_upgrade, it completes cleanly.

Since ATExecAddColumn now handles the recursion, child table work queue entries
no longer have AT_PASS_ADD_COL subcommands.  Consequently, this heuristic in
ATRewriteCatalogs skips over them:

               if (tab->relkind == RELKIND_RELATION &&
                       (tab->subcmds[AT_PASS_ADD_COL] ||
                        tab->subcmds[AT_PASS_ALTER_TYPE] ||
                        tab->subcmds[AT_PASS_COL_ATTRS]))
                       AlterTableCreateToastTable(tab->relid, (Datum) 0);

SET STORAGE uses AT_PASS_MISC, so this test case also omits a TOAST table:

 set client_min_messages = debug1; -- display toast creation
 create table t (a text); -- makes toast
 alter table t alter a set storage plain;
 alter table t add b int default 0; -- rewrite the table - no toast
 alter table t alter a set storage extended; -- no toast added?
 insert into t (a) values (repeat('x', 1000000)); -- fails

Checking my understanding here, the first of these is a regression
introduced by the patch for $SUBJECT, but the second one is a
pre-existing bug. Is that right?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Noah Misch
noah@leadboat.com
In reply to: Robert Haas (#11)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Sun, Apr 10, 2011 at 11:19:26AM -0400, Robert Haas wrote:

On Sun, Apr 10, 2011 at 6:36 AM, Noah Misch <noah@leadboat.com> wrote:

On Sun, Apr 03, 2011 at 09:53:57PM -0400, Robert Haas wrote:

On Fri, Apr 1, 2011 at 12:56 AM, Noah Misch <noah@leadboat.com> wrote:

On Thu, Mar 31, 2011 at 11:11:49AM -0400, Robert Haas wrote:

On Thu, Mar 31, 2011 at 6:06 AM, Noah Misch <noah@leadboat.com> wrote:

The best way I can see is to make ATExecAddColumn more like ATExecDropColumn,
ATAddCheckConstraint, and ATExecDropConstraint. ?Namely, recurse at Exec-time
rather than Prep-time, and cease recursing when we satisfy the ADD COLUMN with a
merge. ?Did you have something else in mind?

I had exactly what you just said in mind.

Patch attached, then.

Committed.

Thanks. ?This turns out to have caused that TOAST creation regression:

On Fri, Apr 08, 2011 at 08:12:19PM -0400, Noah Misch wrote:
[pg_upgrade/typed table business]

I also tested a regular dump+reload of the regression database, and a pg_upgrade
of the same. ?The latter failed further along, due (indirectly) to this failure
to create a TOAST table:

? create table p ();
? create table ch () inherits (p);
? alter table p add column a text;
? select oid::regclass,reltoastrelid from pg_class where oid::regclass IN ('p','ch');
? insert into ch values (repeat('x', 1000000));

If I "drop table a_star cascade" in the regression database before attempting
pg_upgrade, it completes cleanly.

Since ATExecAddColumn now handles the recursion, child table work queue entries
no longer have AT_PASS_ADD_COL subcommands. ?Consequently, this heuristic in
ATRewriteCatalogs skips over them:

? ? ? ? ? ? ? ?if (tab->relkind == RELKIND_RELATION &&
? ? ? ? ? ? ? ? ? ? ? ?(tab->subcmds[AT_PASS_ADD_COL] ||
? ? ? ? ? ? ? ? ? ? ? ? tab->subcmds[AT_PASS_ALTER_TYPE] ||
? ? ? ? ? ? ? ? ? ? ? ? tab->subcmds[AT_PASS_COL_ATTRS]))
? ? ? ? ? ? ? ? ? ? ? ?AlterTableCreateToastTable(tab->relid, (Datum) 0);

SET STORAGE uses AT_PASS_MISC, so this test case also omits a TOAST table:

?set client_min_messages = debug1; -- display toast creation
?create table t (a text); -- makes toast
?alter table t alter a set storage plain;
?alter table t add b int default 0; -- rewrite the table - no toast
?alter table t alter a set storage extended; -- no toast added?
?insert into t (a) values (repeat('x', 1000000)); -- fails

Checking my understanding here, the first of these is a regression
introduced by the patch for $SUBJECT, but the second one is a
pre-existing bug. Is that right?

The patch for $SUBJECT just introduced the first regression; commit
04e17bae50a73af524731fa11210d5c3f7d8e1f9 introduced the second regression near
the beginning of the 9.1 development cycle.

#13Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#10)
Re: BUG #5856: pg_attribute.attinhcount is not correct.

On Sun, Apr 10, 2011 at 5:23 AM, Noah Misch <noah@leadboat.com> wrote:

On Sun, Apr 10, 2011 at 07:35:53AM -0400, Robert Haas wrote:

On Sun, Apr 10, 2011 at 6:36 AM, Noah Misch <noah@leadboat.com> wrote:

3. Make AlterTableCreateToastTable acquire only ShareUpdateExclusiveLock and
remove the pass-usage heuristic from ATRewriteCatalogs.  For this to be valid,
toast_insert_or_update() must behave reasonably in the face of a relation
concurrently acquiring a TOAST table.  Since it takes reltoastrelid from the
relcache, toast_insert_or_update() will not act on the change in the middle of a
single call.  Even if it did, I don't see any risks.

I'd lean toward #3 if someone else is also confident in its correctness.
Otherwise, #1 seems like the way to go.  Preferences?  Other ideas?

I haven't scrutinized the code but I would prefer #3 if it's viable
without too much of a code footprint.

It's certainly compact; patch attached.

Thanks. Committed.

It occurred to me to worry that it would be quite unsafe if a TOAST
table got *removed* while holding less than AccessExclusiveLock, but
it appears we're safe enough from that; I believe it can only happen
on a table rewrite, and there's not much chance of that ever requiring
a lesser lock strength.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company