CREATE LIKE INCLUDING COMMENTS and STORAGES

Started by Itagaki Takahiroover 16 years ago19 messages
#1Itagaki Takahiro
itagaki.takahiro@oss.ntt.co.jp
1 attachment(s)

Here is a patch to implement the following items in our ToDo list:
* Add CREATE TABLE LIKE ... INCLUDING COMMENTS
* Have CREATE TABLE LIKE copy column storage parameters

The syntax is:
CREATE TABLE clone_table (LIKE template_table INCLUDING COMMENTS)
-- also copy comments on columns.
CREATE TABLE clone_table (LIKE template_table INCLUDING STORAGES)
-- also copy storage parameters on columns.

Also, storage parameters of inherited columns are inherited automatically.

There might be room for improvement:

* Should INCLUDING COMMENTS also copy comments on indexes?
It copies only comments on columns for now.

* Should we have additonal syntax to define storage parameters inline
of CREATE TABLE? For example,
CREATE TABLE tbl (col text STORAGE MAIN);
CREATE TABLE fails if there is a conflicted storage parameter for now.
ERROR: column "col" has a storage parameter conflict
DETAIL: MAIN versus EXTENDED
but there is no way to resolve the confliction unless we modify the
definitions of original tables. Meantime, we can overwrite DEFAULTs
to resolve conflictions by INCLUDING DEFAULTS.

* Should we have "INCLUDING ALL" as an abbreviated form?
Many INCLUDING options in CREATE LIKE seems to be messy:
CREATE TABLE clone_table (LIKE template_table
INCLUDING DEFAULTS
INCLUDING CONSTRAINTS
INCLUDING INDEXES
INCLUDING STORAGES
INCLUDING COMMENTS);

Comments welcome.

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center

Attachments:

create-including-20090907.patchapplication/octet-stream; name=create-including-20090907.patchDownload
diff -cpr --exclude=CVS head/doc/src/sgml/ref/create_table.sgml work/doc/src/sgml/ref/create_table.sgml
*** head/doc/src/sgml/ref/create_table.sgml	2009-07-30 05:56:17.000000000 +0900
--- work/doc/src/sgml/ref/create_table.sgml	2009-09-06 17:17:39.768335000 +0900
*************** PostgreSQL documentation
*** 24,30 ****
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
--- 24,30 ----
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGES | COMMENTS } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
*************** and <replaceable class="PARAMETER">table
*** 230,235 ****
--- 230,239 ----
        will always be chosen for it.
       </para>
  
+      <para>
+       Column storage parameters are also copied from parent tables.
+      </para>
+ 
  <!--
       <para>
        <productname>PostgreSQL</> automatically allows the
*************** and <replaceable class="PARAMETER">table
*** 247,253 ****
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
--- 251,257 ----
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGES | COMMENTS } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
*************** and <replaceable class="PARAMETER">table
*** 281,286 ****
--- 285,302 ----
        specified.
       </para>
       <para>
+       Storage parameters for the copied column definitions will only be
+       copied if <literal>INCLUDING STORAGES</literal> is specified.  The
+       default behavior is to exclude storage paramters, resulting in the
+       copied columns in the new table having type-specific default parameters.
+      </para>
+      <para>
+       Comments for the copied column definitions will only be
+       copied if <literal>INCLUDING COMMENTS</literal> is specified.  The
+       default behavior is to exclude comments, resulting in the
+       copied columns in the new table having no comments.
+      </para>
+      <para>
        Note also that unlike <literal>INHERITS</literal>, copied columns and
        constraints are not merged with similarly named columns and constraints.
        If the same name is specified explicitly or in another
diff -cpr --exclude=CVS head/src/backend/access/common/tupdesc.c work/src/backend/access/common/tupdesc.c
*** head/src/backend/access/common/tupdesc.c	2009-08-03 07:14:51.000000000 +0900
--- work/src/backend/access/common/tupdesc.c	2009-09-06 14:47:00.698569582 +0900
*************** BuildDescForRelation(List *schema)
*** 558,563 ****
--- 558,565 ----
  		has_not_null |= entry->is_not_null;
  		desc->attrs[attnum - 1]->attislocal = entry->is_local;
  		desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
+ 		if (entry->storage)
+ 			desc->attrs[attnum - 1]->attstorage = entry->storage;
  	}
  
  	if (has_not_null)
diff -cpr --exclude=CVS head/src/backend/commands/comment.c work/src/backend/commands/comment.c
*** head/src/backend/commands/comment.c	2009-06-11 23:48:55.000000000 +0900
--- work/src/backend/commands/comment.c	2009-09-06 13:17:56.294992259 +0900
*************** DeleteSharedComments(Oid oid, Oid classo
*** 463,468 ****
--- 463,523 ----
  }
  
  /*
+  * GetComment -- get the comment for a object, or null if not found.
+  */
+ char *
+ GetComment(Oid oid, Oid classoid, int32 subid)
+ {
+ 	Relation	description;
+ 	ScanKeyData skey[3];
+ 	SysScanDesc sd;
+ 	TupleDesc	tupdesc;
+ 	HeapTuple	tuple;
+ 	char	   *comment;
+ 
+ 	/* Use the index to search for a matching old tuple */
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_description_objoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(oid));
+ 	ScanKeyInit(&skey[1],
+ 				Anum_pg_description_classoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(classoid));
+ 	ScanKeyInit(&skey[2],
+ 				Anum_pg_description_objsubid,
+ 				BTEqualStrategyNumber, F_INT4EQ,
+ 				Int32GetDatum(subid));
+ 
+ 	description = heap_open(DescriptionRelationId, AccessShareLock);
+ 	tupdesc = RelationGetDescr(description);
+ 
+ 	sd = systable_beginscan(description, DescriptionObjIndexId, true,
+ 							SnapshotNow, 3, skey);
+ 
+ 	comment  = NULL;
+ 	while ((tuple = systable_getnext(sd)) != NULL)
+ 	{
+ 		Datum	value;
+ 		bool	isnull;
+ 
+ 		/* Found the tuple, get description field */
+ 		value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
+ 		if (!isnull)
+ 			comment = TextDatumGetCString(value);
+ 		break;					/* Assume there can be only one match */
+ 	}
+ 
+ 	systable_endscan(sd);
+ 
+ 	/* Done */
+ 	heap_close(description, AccessShareLock);
+ 
+ 	return comment;
+ }
+ 
+ /*
   * CommentRelation --
   *
   * This routine is used to add/drop a comment from a relation, where
diff -cpr --exclude=CVS head/src/backend/commands/tablecmds.c work/src/backend/commands/tablecmds.c
*** head/src/backend/commands/tablecmds.c	2009-08-24 04:23:41.000000000 +0900
--- work/src/backend/commands/tablecmds.c	2009-09-06 16:25:45.324561796 +0900
***************
*** 39,44 ****
--- 39,45 ----
  #include "catalog/storage.h"
  #include "catalog/toasting.h"
  #include "commands/cluster.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/sequence.h"
  #include "commands/tablecmds.h"
*************** truncate_check_rel(Relation rel)
*** 1098,1103 ****
--- 1099,1117 ----
  	CheckTableNotInUse(rel, "TRUNCATE");
  }
  
+ static const char *
+ storage_name(char c)
+ {
+ 	switch (c)
+ 	{
+ 		case 'p': return "PLAIN";
+ 		case 'm': return "MAIN";
+ 		case 'x': return "EXTENDED";
+ 		case 'e': return "EXTERNAL";
+ 		default: return "???";
+ 	}
+ }
+ 
  /*----------
   * MergeAttributes
   *		Returns new schema given initial schema and superclasses.
*************** MergeAttributes(List *schema, List *supe
*** 1166,1171 ****
--- 1180,1186 ----
  	List	   *constraints = NIL;
  	int			parentsWithOids = 0;
  	bool		have_bogus_defaults = false;
+ 	bool		have_bogus_comments = false;
  	char	   *bogus_marker = "Bogus!";		/* marks conflicting defaults */
  	int			child_attno;
  
*************** MergeAttributes(List *schema, List *supe
*** 1321,1326 ****
--- 1336,1353 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   format_type_be(attribute->atttypid))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = attribute->attstorage;
+ 				else if (def->storage != attribute->attstorage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("inherited column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(attribute->attstorage))));
+ 
  				def->inhcount++;
  				/* Merge of NOT NULL constraints = OR 'em together */
  				def->is_not_null |= attribute->attnotnull;
*************** MergeAttributes(List *schema, List *supe
*** 1342,1347 ****
--- 1369,1375 ----
  				def->raw_default = NULL;
  				def->cooked_default = NULL;
  				def->constraints = NIL;
+ 				def->storage = attribute->attstorage;
  				inhSchema = lappend(inhSchema, def);
  				newattno[parent_attno - 1] = ++child_attno;
  			}
*************** MergeAttributes(List *schema, List *supe
*** 1479,1484 ****
--- 1507,1524 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   TypeNameToString(newdef->typeName))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = newdef->storage;
+ 				else if (newdef->storage != 0 && def->storage != newdef->storage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(newdef->storage))));
+ 
  				/* Mark the column as locally defined */
  				def->is_local = true;
  				/* Merge of NOT NULL constraints = OR 'em together */
*************** MergeAttributes(List *schema, List *supe
*** 1531,1536 ****
--- 1571,1590 ----
  		}
  	}
  
+ 	/* error is conflicting comments */
+ 	if (have_bogus_comments)
+ 	{
+ 		foreach(entry, schema)
+ 		{
+ 			ColumnDef  *def = lfirst(entry);
+ 
+ 			if (def->cooked_default == bogus_marker)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ 				  errmsg("column \"%s\" inherits conflicting comments", def->colname)));
+ 		}
+ 	}
+ 
  	*supOids = parentOids;
  	*supconstr = constraints;
  	*supOidCount = parentsWithOids;
diff -cpr --exclude=CVS head/src/backend/parser/gram.y work/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-08-19 08:40:20.000000000 +0900
--- work/src/backend/parser/gram.y	2009-09-06 15:56:07.469540714 +0900
*************** static TypeName *TableFuncTypeName(List 
*** 455,461 ****
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
--- 455,461 ----
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMENTS COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
*************** static TypeName *TableFuncTypeName(List 
*** 511,517 ****
  	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
  	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
  	SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
! 	STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P
  	SYMMETRIC SYSID SYSTEM_P
  
  	TABLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP
--- 511,517 ----
  	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
  	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
  	SHOW SIMILAR SIMPLE SMALLINT SOME STABLE STANDALONE_P START STATEMENT
! 	STATISTICS STDIN STDOUT STORAGE STORAGES STRICT_P STRIP_P SUBSTRING SUPERUSER_P
  	SYMMETRIC SYSID SYSTEM_P
  
  	TABLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP
*************** TableLikeOption:
*** 2350,2355 ****
--- 2350,2359 ----
  				| EXCLUDING CONSTRAINTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS; }
  				| INCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_INDEXES; }
  				| EXCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES; }
+ 				| INCLUDING STORAGES				{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_STORAGES; }
+ 				| EXCLUDING STORAGES				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_STORAGES; }
+ 				| INCLUDING COMMENTS				{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_COMMENTS; }
+ 				| EXCLUDING COMMENTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_COMMENTS; }
  		;
  
  
*************** unreserved_keyword:
*** 10239,10244 ****
--- 10243,10249 ----
  			| CLOSE
  			| CLUSTER
  			| COMMENT
+ 			| COMMENTS
  			| COMMIT
  			| COMMITTED
  			| CONCURRENTLY
*************** unreserved_keyword:
*** 10416,10421 ****
--- 10421,10427 ----
  			| STDIN
  			| STDOUT
  			| STORAGE
+ 			| STORAGES
  			| STRICT_P
  			| STRIP_P
  			| SUPERUSER_P
diff -cpr --exclude=CVS head/src/backend/parser/parse_utilcmd.c work/src/backend/parser/parse_utilcmd.c
*** head/src/backend/parser/parse_utilcmd.c	2009-07-30 11:45:37.000000000 +0900
--- work/src/backend/parser/parse_utilcmd.c	2009-09-06 16:58:33.718541820 +0900
***************
*** 36,41 ****
--- 36,42 ----
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_opclass.h"
  #include "catalog/pg_type.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/tablecmds.h"
  #include "commands/tablespace.h"
*************** transformInhRelation(ParseState *pstate,
*** 549,554 ****
--- 550,557 ----
  	bool		including_defaults = false;
  	bool		including_constraints = false;
  	bool		including_indexes = false;
+ 	bool		including_storages = false;
+ 	bool		including_comments = false;
  	ListCell   *elem;
  
  	relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock);
*************** transformInhRelation(ParseState *pstate,
*** 595,600 ****
--- 598,615 ----
  			case CREATE_TABLE_LIKE_EXCLUDING_INDEXES:
  				including_indexes = false;
  				break;
+ 			case CREATE_TABLE_LIKE_INCLUDING_STORAGES:
+ 				including_storages = true;
+ 				break;
+ 			case CREATE_TABLE_LIKE_EXCLUDING_STORAGES:
+ 				including_storages = false;
+ 				break;
+ 			case CREATE_TABLE_LIKE_INCLUDING_COMMENTS:
+ 				including_comments = true;
+ 				break;
+ 			case CREATE_TABLE_LIKE_EXCLUDING_COMMENTS:
+ 				including_comments = false;
+ 				break;
  			default:
  				elog(ERROR, "unrecognized CREATE TABLE LIKE option: %d",
  					 option);
*************** transformInhRelation(ParseState *pstate,
*** 668,673 ****
--- 683,712 ----
  
  			def->cooked_default = pstrdup(this_default);
  		}
+ 
+ 		/* Likewise, copy storage if requested */
+ 		if (including_storages)
+ 			def->storage = attribute->attstorage;
+ 
+ 		/* Likewise, copy comment if requested */
+ 		if (including_comments)
+ 		{
+ 			char	   *comment;
+ 
+ 			if ((comment = GetComment(attribute->attrelid, RelationRelationId, attribute->attnum)) != NULL)
+ 			{
+ 				CommentStmt *stmt = makeNode(CommentStmt);
+ 
+ 				stmt->objtype = OBJECT_COLUMN;
+ 				stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ 										   makeString(cxt->relation->relname),
+ 										   makeString(def->colname));
+ 				stmt->objargs = NIL;
+ 				stmt->comment = comment;
+ 
+ 				cxt->alist = lappend(cxt->alist, stmt);
+ 			}
+ 		}
  	}
  
  	/*
diff -cpr --exclude=CVS head/src/include/commands/comment.h work/src/include/commands/comment.h
*** head/src/include/commands/comment.h	2009-06-11 23:49:11.000000000 +0900
--- work/src/include/commands/comment.h	2009-09-06 13:15:24.482240402 +0900
*************** extern void DeleteSharedComments(Oid oid
*** 39,42 ****
--- 39,44 ----
  
  extern void CreateSharedComments(Oid oid, Oid classoid, char *comment);
  
+ extern char *GetComment(Oid oid, Oid classoid, int32 subid);
+ 
  #endif   /* COMMENT_H */
diff -cpr --exclude=CVS head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-08-03 07:14:53.000000000 +0900
--- work/src/include/nodes/parsenodes.h	2009-09-06 16:40:59.379534872 +0900
*************** typedef struct ColumnDef
*** 461,466 ****
--- 461,467 ----
  	int			inhcount;		/* number of times column is inherited */
  	bool		is_local;		/* column has local (non-inherited) def'n */
  	bool		is_not_null;	/* NOT NULL constraint specified? */
+ 	char		storage;		/* storage parameter of column */
  	Node	   *raw_default;	/* default value (untransformed parse tree) */
  	char	   *cooked_default; /* nodeToString representation */
  	List	   *constraints;	/* other constraints on column */
*************** typedef enum CreateStmtLikeOption
*** 483,489 ****
  	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
  	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS,
  	CREATE_TABLE_LIKE_INCLUDING_INDEXES,
! 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES
  } CreateStmtLikeOption;
  
  /*
--- 484,494 ----
  	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
  	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS,
  	CREATE_TABLE_LIKE_INCLUDING_INDEXES,
! 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES,
! 	CREATE_TABLE_LIKE_INCLUDING_STORAGES,
! 	CREATE_TABLE_LIKE_EXCLUDING_STORAGES,
! 	CREATE_TABLE_LIKE_INCLUDING_COMMENTS,
! 	CREATE_TABLE_LIKE_EXCLUDING_COMMENTS
  } CreateStmtLikeOption;
  
  /*
diff -cpr --exclude=CVS head/src/include/parser/kwlist.h work/src/include/parser/kwlist.h
*** head/src/include/parser/kwlist.h	2009-04-06 17:42:53.000000000 +0900
--- work/src/include/parser/kwlist.h	2009-09-06 15:13:57.966535522 +0900
*************** PG_KEYWORD("coalesce", COALESCE, COL_NAM
*** 80,85 ****
--- 80,86 ----
  PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
  PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
  PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
  PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
  PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
  PG_KEYWORD("concurrently", CONCURRENTLY, UNRESERVED_KEYWORD)
*************** PG_KEYWORD("statistics", STATISTICS, UNR
*** 348,353 ****
--- 349,355 ----
  PG_KEYWORD("stdin", STDIN, UNRESERVED_KEYWORD)
  PG_KEYWORD("stdout", STDOUT, UNRESERVED_KEYWORD)
  PG_KEYWORD("storage", STORAGE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("storages", STORAGES, UNRESERVED_KEYWORD)
  PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
diff -cpr --exclude=CVS head/src/test/regress/expected/inherit.out work/src/test/regress/expected/inherit.out
*** head/src/test/regress/expected/inherit.out	2009-08-02 04:59:41.000000000 +0900
--- work/src/test/regress/expected/inherit.out	2009-09-06 16:29:56.346467000 +0900
*************** drop table pp1 cascade;
*** 906,908 ****
--- 906,989 ----
  NOTICE:  drop cascades to 2 other objects
  DETAIL:  drop cascades to table cc1
  drop cascades to table cc2
+ -- including storages and comments
+ CREATE TABLE t1 (a text, b text);
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ CREATE TABLE t3 (a text, c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ CREATE TABLE t12_storages (LIKE t1 INCLUDING STORAGES, LIKE t2 INCLUDING STORAGES);
+ \d+ t12_storages
+             Table "public.t12_storages"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Has OIDs: no
+ 
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+             Table "public.t12_comments"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | extended | A
+  b      | text |           | extended | B
+  c      | text |           | extended | C
+ Has OIDs: no
+ 
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING COMMENTS) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ NOTICE:  merging column "b" with inherited definition
+ \d+ t1_inh
+                Table "public.t1_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | A
+  b      | text |           | extended | B
+ Inherits: t1
+ Has OIDs: no
+ 
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ \d+ t13_inh
+                Table "public.t13_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Inherits: t1,
+           t3
+ Has OIDs: no
+ 
+ CREATE TABLE t13_like (LIKE t3 INCLUDING COMMENTS INCLUDING STORAGES) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ \d+ t13_like
+               Table "public.t13_like"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | A3
+  b      | text |           | extended | 
+  c      | text |           | external | C
+ Inherits: t1
+ Has OIDs: no
+ 
+ CREATE TABLE inh_error1 () INHERITS (t1, t4);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ ERROR:  inherited column "a" has a storage parameter conflict
+ DETAIL:  MAIN versus EXTENDED
+ CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGES) INHERITS (t1);
+ 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_storages, t12_comments, t1_inh, t13_inh, t13_like;
diff -cpr --exclude=CVS head/src/test/regress/sql/inherit.sql work/src/test/regress/sql/inherit.sql
*** head/src/test/regress/sql/inherit.sql	2008-05-10 08:32:05.000000000 +0900
--- work/src/test/regress/sql/inherit.sql	2009-09-06 16:29:22.903535459 +0900
*************** create table cc2(f4 float) inherits(pp1,
*** 276,278 ****
--- 276,312 ----
  alter table pp1 add column a2 int check (a2 > 0);
  \d cc2
  drop table pp1 cascade;
+ 
+ -- including storages and comments
+ CREATE TABLE t1 (a text, b text);
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ 
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ 
+ CREATE TABLE t3 (a text, c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ 
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ 
+ CREATE TABLE t12_storages (LIKE t1 INCLUDING STORAGES, LIKE t2 INCLUDING STORAGES);
+ \d+ t12_storages
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING COMMENTS) INHERITS (t1);
+ \d+ t1_inh
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ \d+ t13_inh
+ CREATE TABLE t13_like (LIKE t3 INCLUDING COMMENTS INCLUDING STORAGES) INHERITS (t1);
+ \d+ t13_like
+ CREATE TABLE inh_error1 () INHERITS (t1, t4);
+ CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGES) INHERITS (t1);
+ 
+ DROP TABLE t1, t2, t3, t4, t12_storages, t12_comments, t1_inh, t13_inh, t13_like;
#2David Fetter
david@fetter.org
In reply to: Itagaki Takahiro (#1)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

On Mon, Sep 07, 2009 at 12:15:21PM +0900, Itagaki Takahiro wrote:

Here is a patch to implement the following items in our ToDo list:
* Add CREATE TABLE LIKE ... INCLUDING COMMENTS
* Have CREATE TABLE LIKE copy column storage parameters

The syntax is:
CREATE TABLE clone_table (LIKE template_table INCLUDING COMMENTS)
-- also copy comments on columns.
CREATE TABLE clone_table (LIKE template_table INCLUDING STORAGES)

This should probably read INCLUDING STORAGE (singular) instead of
STORAGES.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#3Itagaki Takahiro
itagaki.takahiro@oss.ntt.co.jp
In reply to: David Fetter (#2)
1 attachment(s)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

David Fetter <david@fetter.org> wrote:

On Mon, Sep 07, 2009 at 12:15:21PM +0900, Itagaki Takahiro wrote:

Here is a patch to implement the following items in our ToDo list:
* Add CREATE TABLE LIKE ... INCLUDING COMMENTS
* Have CREATE TABLE LIKE copy column storage parameters

The syntax is:
CREATE TABLE clone_table (LIKE template_table INCLUDING STORAGES)

This should probably read INCLUDING STORAGE (singular) instead of
STORAGES.

Thanks. I fixed it to INCLUDING STORAGE.

In addition, I modified INCLUDING COMMENTS to copy comments not only
on columns but also on constraints. However, comments on indexes are
not copied because copied indexes are named in DefineIndex();
We don't know names of new indexes when we build a command list.

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center

Attachments:

create-including-20090908.patchapplication/octet-stream; name=create-including-20090908.patchDownload
diff -cprN head/doc/src/sgml/ref/create_table.sgml create-including/doc/src/sgml/ref/create_table.sgml
*** head/doc/src/sgml/ref/create_table.sgml	2009-07-30 05:56:17.000000000 +0900
--- create-including/doc/src/sgml/ref/create_table.sgml	2009-09-08 14:47:21.235472191 +0900
*************** PostgreSQL documentation
*** 24,30 ****
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
--- 24,30 ----
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
*************** and <replaceable class="PARAMETER">table
*** 230,235 ****
--- 230,239 ----
        will always be chosen for it.
       </para>
  
+      <para>
+       Column storage parameters are also copied from parent tables.
+      </para>
+ 
  <!--
       <para>
        <productname>PostgreSQL</> automatically allows the
*************** and <replaceable class="PARAMETER">table
*** 247,253 ****
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
--- 251,257 ----
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
*************** and <replaceable class="PARAMETER">table
*** 281,286 ****
--- 285,302 ----
        specified.
       </para>
       <para>
+       Storage parameters for the copied column definitions will only be
+       copied if <literal>INCLUDING STORAGE</literal> is specified.  The
+       default behavior is to exclude storage paramters, resulting in the
+       copied columns in the new table having type-specific default parameters.
+      </para>
+      <para>
+       Comments for the copied column and constraint definitions will only be
+       copied if <literal>INCLUDING COMMENTS</literal> is specified.  The
+       default behavior is to exclude comments, resulting in the
+       copied columns and constraints in the new table having no comments.
+      </para>
+      <para>
        Note also that unlike <literal>INHERITS</literal>, copied columns and
        constraints are not merged with similarly named columns and constraints.
        If the same name is specified explicitly or in another
diff -cprN head/src/backend/access/common/tupdesc.c create-including/src/backend/access/common/tupdesc.c
*** head/src/backend/access/common/tupdesc.c	2009-08-03 07:14:51.000000000 +0900
--- create-including/src/backend/access/common/tupdesc.c	2009-09-08 11:04:08.769493826 +0900
*************** BuildDescForRelation(List *schema)
*** 558,563 ****
--- 558,565 ----
  		has_not_null |= entry->is_not_null;
  		desc->attrs[attnum - 1]->attislocal = entry->is_local;
  		desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
+ 		if (entry->storage)
+ 			desc->attrs[attnum - 1]->attstorage = entry->storage;
  	}
  
  	if (has_not_null)
diff -cprN head/src/backend/catalog/pg_constraint.c create-including/src/backend/catalog/pg_constraint.c
*** head/src/backend/catalog/pg_constraint.c	2009-07-28 11:56:29.000000000 +0900
--- create-including/src/backend/catalog/pg_constraint.c	2009-09-08 14:42:34.603475070 +0900
*************** AlterConstraintNamespaces(Oid ownerId, O
*** 702,704 ****
--- 702,766 ----
  
  	heap_close(conRel, RowExclusiveLock);
  }
+ 
+ /*
+  * GetConstraintByName
+  *		Find a constraint with the specified name.
+  */
+ Oid
+ GetConstraintByName(Oid relid, const char *conname)
+ {
+ 	Relation	pg_constraint;
+ 	HeapTuple	tuple;
+ 	SysScanDesc scan;
+ 	ScanKeyData skey[1];
+ 	Oid			conOid = InvalidOid;
+ 
+ 	/*
+ 	 * Fetch the constraint tuple from pg_constraint.  There may be more than
+ 	 * one match, because constraints are not required to have unique names;
+ 	 * if so, error out.
+ 	 */
+ 	pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ, relid);
+ 
+ 	scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
+ 							  SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ 	{
+ 		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ 
+ 		if (strcmp(NameStr(con->conname), conname) == 0)
+ 		{
+ 			if (OidIsValid(conOid))
+ 			{
+ 				char *relname = get_rel_name(relid);
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 				 errmsg("table \"%s\" has multiple constraints named \"%s\"",
+ 					(relname ? relname : "(unknown)"), conname)));
+ 			}
+ 			conOid = HeapTupleGetOid(tuple);
+ 		}
+ 	}
+ 
+ 	systable_endscan(scan);
+ 
+ 	/* If no constraint exists for the relation specified, notify user */
+ 	if (!OidIsValid(conOid))
+ 	{
+ 		char *relname = get_rel_name(relid);
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
+ 						conname, (relname ? relname : "(unknown)"))));
+ 	}
+ 
+ 	heap_close(pg_constraint, AccessShareLock);
+ 
+ 	return conOid;
+ }
diff -cprN head/src/backend/commands/comment.c create-including/src/backend/commands/comment.c
*** head/src/backend/commands/comment.c	2009-06-11 23:48:55.000000000 +0900
--- create-including/src/backend/commands/comment.c	2009-09-08 14:41:57.916520185 +0900
*************** DeleteSharedComments(Oid oid, Oid classo
*** 463,468 ****
--- 463,523 ----
  }
  
  /*
+  * GetComment -- get the comment for a object, or null if not found.
+  */
+ char *
+ GetComment(Oid oid, Oid classoid, int32 subid)
+ {
+ 	Relation	description;
+ 	ScanKeyData skey[3];
+ 	SysScanDesc sd;
+ 	TupleDesc	tupdesc;
+ 	HeapTuple	tuple;
+ 	char	   *comment;
+ 
+ 	/* Use the index to search for a matching old tuple */
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_description_objoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(oid));
+ 	ScanKeyInit(&skey[1],
+ 				Anum_pg_description_classoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(classoid));
+ 	ScanKeyInit(&skey[2],
+ 				Anum_pg_description_objsubid,
+ 				BTEqualStrategyNumber, F_INT4EQ,
+ 				Int32GetDatum(subid));
+ 
+ 	description = heap_open(DescriptionRelationId, AccessShareLock);
+ 	tupdesc = RelationGetDescr(description);
+ 
+ 	sd = systable_beginscan(description, DescriptionObjIndexId, true,
+ 							SnapshotNow, 3, skey);
+ 
+ 	comment  = NULL;
+ 	while ((tuple = systable_getnext(sd)) != NULL)
+ 	{
+ 		Datum	value;
+ 		bool	isnull;
+ 
+ 		/* Found the tuple, get description field */
+ 		value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
+ 		if (!isnull)
+ 			comment = TextDatumGetCString(value);
+ 		break;					/* Assume there can be only one match */
+ 	}
+ 
+ 	systable_endscan(sd);
+ 
+ 	/* Done */
+ 	heap_close(description, AccessShareLock);
+ 
+ 	return comment;
+ }
+ 
+ /*
   * CommentRelation --
   *
   * This routine is used to add/drop a comment from a relation, where
*************** CommentConstraint(List *qualname, char *
*** 1064,1075 ****
  	List	   *relName;
  	char	   *conName;
  	RangeVar   *rel;
! 	Relation	pg_constraint,
! 				relation;
! 	HeapTuple	tuple;
! 	SysScanDesc scan;
! 	ScanKeyData skey[1];
! 	Oid			conOid = InvalidOid;
  
  	/* Separate relname and constraint name */
  	nnames = list_length(qualname);
--- 1119,1126 ----
  	List	   *relName;
  	char	   *conName;
  	RangeVar   *rel;
! 	Relation	relation;
! 	Oid			conOid;
  
  	/* Separate relname and constraint name */
  	nnames = list_length(qualname);
*************** CommentConstraint(List *qualname, char *
*** 1088,1137 ****
  		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
  					   RelationGetRelationName(relation));
  
! 	/*
! 	 * Fetch the constraint tuple from pg_constraint.  There may be more than
! 	 * one match, because constraints are not required to have unique names;
! 	 * if so, error out.
! 	 */
! 	pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
! 
! 	ScanKeyInit(&skey[0],
! 				Anum_pg_constraint_conrelid,
! 				BTEqualStrategyNumber, F_OIDEQ,
! 				ObjectIdGetDatum(RelationGetRelid(relation)));
! 
! 	scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
! 							  SnapshotNow, 1, skey);
! 
! 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
! 	{
! 		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
! 
! 		if (strcmp(NameStr(con->conname), conName) == 0)
! 		{
! 			if (OidIsValid(conOid))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_DUPLICATE_OBJECT),
! 				 errmsg("table \"%s\" has multiple constraints named \"%s\"",
! 						RelationGetRelationName(relation), conName)));
! 			conOid = HeapTupleGetOid(tuple);
! 		}
! 	}
! 
! 	systable_endscan(scan);
! 
! 	/* If no constraint exists for the relation specified, notify user */
! 	if (!OidIsValid(conOid))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
! 						conName, RelationGetRelationName(relation))));
  
  	/* Call CreateComments() to create/drop the comments */
  	CreateComments(conOid, ConstraintRelationId, 0, comment);
  
  	/* Done, but hold lock on relation */
- 	heap_close(pg_constraint, AccessShareLock);
  	heap_close(relation, NoLock);
  }
  
--- 1139,1150 ----
  		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
  					   RelationGetRelationName(relation));
  
! 	conOid = GetConstraintByName(RelationGetRelid(relation), conName);
  
  	/* Call CreateComments() to create/drop the comments */
  	CreateComments(conOid, ConstraintRelationId, 0, comment);
  
  	/* Done, but hold lock on relation */
  	heap_close(relation, NoLock);
  }
  
diff -cprN head/src/backend/commands/tablecmds.c create-including/src/backend/commands/tablecmds.c
*** head/src/backend/commands/tablecmds.c	2009-08-24 04:23:41.000000000 +0900
--- create-including/src/backend/commands/tablecmds.c	2009-09-08 11:04:08.772592787 +0900
***************
*** 39,44 ****
--- 39,45 ----
  #include "catalog/storage.h"
  #include "catalog/toasting.h"
  #include "commands/cluster.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/sequence.h"
  #include "commands/tablecmds.h"
*************** truncate_check_rel(Relation rel)
*** 1098,1103 ****
--- 1099,1117 ----
  	CheckTableNotInUse(rel, "TRUNCATE");
  }
  
+ static const char *
+ storage_name(char c)
+ {
+ 	switch (c)
+ 	{
+ 		case 'p': return "PLAIN";
+ 		case 'm': return "MAIN";
+ 		case 'x': return "EXTENDED";
+ 		case 'e': return "EXTERNAL";
+ 		default: return "???";
+ 	}
+ }
+ 
  /*----------
   * MergeAttributes
   *		Returns new schema given initial schema and superclasses.
*************** MergeAttributes(List *schema, List *supe
*** 1166,1171 ****
--- 1180,1186 ----
  	List	   *constraints = NIL;
  	int			parentsWithOids = 0;
  	bool		have_bogus_defaults = false;
+ 	bool		have_bogus_comments = false;
  	char	   *bogus_marker = "Bogus!";		/* marks conflicting defaults */
  	int			child_attno;
  
*************** MergeAttributes(List *schema, List *supe
*** 1321,1326 ****
--- 1336,1353 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   format_type_be(attribute->atttypid))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = attribute->attstorage;
+ 				else if (def->storage != attribute->attstorage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("inherited column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(attribute->attstorage))));
+ 
  				def->inhcount++;
  				/* Merge of NOT NULL constraints = OR 'em together */
  				def->is_not_null |= attribute->attnotnull;
*************** MergeAttributes(List *schema, List *supe
*** 1342,1347 ****
--- 1369,1375 ----
  				def->raw_default = NULL;
  				def->cooked_default = NULL;
  				def->constraints = NIL;
+ 				def->storage = attribute->attstorage;
  				inhSchema = lappend(inhSchema, def);
  				newattno[parent_attno - 1] = ++child_attno;
  			}
*************** MergeAttributes(List *schema, List *supe
*** 1479,1484 ****
--- 1507,1524 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   TypeNameToString(newdef->typeName))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = newdef->storage;
+ 				else if (newdef->storage != 0 && def->storage != newdef->storage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(newdef->storage))));
+ 
  				/* Mark the column as locally defined */
  				def->is_local = true;
  				/* Merge of NOT NULL constraints = OR 'em together */
*************** MergeAttributes(List *schema, List *supe
*** 1531,1536 ****
--- 1571,1590 ----
  		}
  	}
  
+ 	/* error is conflicting comments */
+ 	if (have_bogus_comments)
+ 	{
+ 		foreach(entry, schema)
+ 		{
+ 			ColumnDef  *def = lfirst(entry);
+ 
+ 			if (def->cooked_default == bogus_marker)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ 				  errmsg("column \"%s\" inherits conflicting comments", def->colname)));
+ 		}
+ 	}
+ 
  	*supOids = parentOids;
  	*supconstr = constraints;
  	*supOidCount = parentsWithOids;
diff -cprN head/src/backend/parser/gram.y create-including/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-08-19 08:40:20.000000000 +0900
--- create-including/src/backend/parser/gram.y	2009-09-08 11:15:23.048574084 +0900
*************** static TypeName *TableFuncTypeName(List 
*** 455,461 ****
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
--- 455,461 ----
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMENTS COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
*************** TableLikeOption:
*** 2350,2355 ****
--- 2350,2359 ----
  				| EXCLUDING CONSTRAINTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS; }
  				| INCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_INDEXES; }
  				| EXCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES; }
+ 				| INCLUDING STORAGE					{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_STORAGE; }
+ 				| EXCLUDING STORAGE					{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_STORAGE; }
+ 				| INCLUDING COMMENTS				{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_COMMENTS; }
+ 				| EXCLUDING COMMENTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_COMMENTS; }
  		;
  
  
*************** unreserved_keyword:
*** 10239,10244 ****
--- 10243,10249 ----
  			| CLOSE
  			| CLUSTER
  			| COMMENT
+ 			| COMMENTS
  			| COMMIT
  			| COMMITTED
  			| CONCURRENTLY
diff -cprN head/src/backend/parser/parse_utilcmd.c create-including/src/backend/parser/parse_utilcmd.c
*** head/src/backend/parser/parse_utilcmd.c	2009-07-30 11:45:37.000000000 +0900
--- create-including/src/backend/parser/parse_utilcmd.c	2009-09-08 14:41:57.856469694 +0900
***************
*** 36,41 ****
--- 36,42 ----
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_opclass.h"
  #include "catalog/pg_type.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/tablecmds.h"
  #include "commands/tablespace.h"
*************** transformInhRelation(ParseState *pstate,
*** 549,555 ****
--- 550,559 ----
  	bool		including_defaults = false;
  	bool		including_constraints = false;
  	bool		including_indexes = false;
+ 	bool		including_storage = false;
+ 	bool		including_comments = false;
  	ListCell   *elem;
+ 	char	   *comment;
  
  	relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock);
  
*************** transformInhRelation(ParseState *pstate,
*** 595,600 ****
--- 599,616 ----
  			case CREATE_TABLE_LIKE_EXCLUDING_INDEXES:
  				including_indexes = false;
  				break;
+ 			case CREATE_TABLE_LIKE_INCLUDING_STORAGE:
+ 				including_storage = true;
+ 				break;
+ 			case CREATE_TABLE_LIKE_EXCLUDING_STORAGE:
+ 				including_storage = false;
+ 				break;
+ 			case CREATE_TABLE_LIKE_INCLUDING_COMMENTS:
+ 				including_comments = true;
+ 				break;
+ 			case CREATE_TABLE_LIKE_EXCLUDING_COMMENTS:
+ 				including_comments = false;
+ 				break;
  			default:
  				elog(ERROR, "unrecognized CREATE TABLE LIKE option: %d",
  					 option);
*************** transformInhRelation(ParseState *pstate,
*** 668,673 ****
--- 684,709 ----
  
  			def->cooked_default = pstrdup(this_default);
  		}
+ 
+ 		/* Likewise, copy storage if requested */
+ 		if (including_storage)
+ 			def->storage = attribute->attstorage;
+ 
+ 		/* Likewise, copy comment if requested */
+ 		if (including_comments && (comment = GetComment(attribute->attrelid,
+ 			RelationRelationId, attribute->attnum)) != NULL)
+ 		{
+ 			CommentStmt *stmt = makeNode(CommentStmt);
+ 
+ 			stmt->objtype = OBJECT_COLUMN;
+ 			stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ 									   makeString(cxt->relation->relname),
+ 									   makeString(def->colname));
+ 			stmt->objargs = NIL;
+ 			stmt->comment = comment;
+ 
+ 			cxt->alist = lappend(cxt->alist, stmt);
+ 		}
  	}
  
  	/*
*************** transformInhRelation(ParseState *pstate,
*** 694,699 ****
--- 730,752 ----
  			n->raw_expr = NULL;
  			n->cooked_expr = nodeToString(ccbin_node);
  			cxt->ckconstraints = lappend(cxt->ckconstraints, n);
+ 
+ 			/* Copy comment on constraint */
+ 			if (including_comments && (comment = GetComment(
+ 				GetConstraintByName(RelationGetRelid(relation), n->conname),
+ 				ConstraintRelationId, 0)) != NULL)
+ 			{
+ 				CommentStmt *stmt = makeNode(CommentStmt);
+ 
+ 				stmt->objtype = OBJECT_CONSTRAINT;
+ 				stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ 										   makeString(cxt->relation->relname),
+ 										   makeString(n->conname));
+ 				stmt->objargs = NIL;
+ 				stmt->comment = comment;
+ 
+ 				cxt->alist = lappend(cxt->alist, stmt);
+ 			}
  		}
  	}
  
*************** transformInhRelation(ParseState *pstate,
*** 719,724 ****
--- 772,779 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			/* XXX: should copy comment on index? */
+ 
  			/* Save it in the inh_indexes list for the time being */
  			cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt);
  
diff -cprN head/src/include/catalog/pg_constraint.h create-including/src/include/catalog/pg_constraint.h
*** head/src/include/catalog/pg_constraint.h	2009-07-28 11:56:31.000000000 +0900
--- create-including/src/include/catalog/pg_constraint.h	2009-09-08 14:41:58.042494586 +0900
*************** extern char *ChooseConstraintName(const 
*** 226,230 ****
--- 226,231 ----
  
  extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
  						  Oid newNspId, bool isType);
+ extern Oid GetConstraintByName(Oid relid, const char *conname);
  
  #endif   /* PG_CONSTRAINT_H */
diff -cprN head/src/include/commands/comment.h create-including/src/include/commands/comment.h
*** head/src/include/commands/comment.h	2009-06-11 23:49:11.000000000 +0900
--- create-including/src/include/commands/comment.h	2009-09-08 11:04:08.777490543 +0900
*************** extern void DeleteSharedComments(Oid oid
*** 39,42 ****
--- 39,44 ----
  
  extern void CreateSharedComments(Oid oid, Oid classoid, char *comment);
  
+ extern char *GetComment(Oid oid, Oid classoid, int32 subid);
+ 
  #endif   /* COMMENT_H */
diff -cprN head/src/include/nodes/parsenodes.h create-including/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-08-03 07:14:53.000000000 +0900
--- create-including/src/include/nodes/parsenodes.h	2009-09-08 11:15:10.997473634 +0900
*************** typedef struct ColumnDef
*** 461,466 ****
--- 461,467 ----
  	int			inhcount;		/* number of times column is inherited */
  	bool		is_local;		/* column has local (non-inherited) def'n */
  	bool		is_not_null;	/* NOT NULL constraint specified? */
+ 	char		storage;		/* storage parameter of column */
  	Node	   *raw_default;	/* default value (untransformed parse tree) */
  	char	   *cooked_default; /* nodeToString representation */
  	List	   *constraints;	/* other constraints on column */
*************** typedef enum CreateStmtLikeOption
*** 483,489 ****
  	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
  	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS,
  	CREATE_TABLE_LIKE_INCLUDING_INDEXES,
! 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES
  } CreateStmtLikeOption;
  
  /*
--- 484,494 ----
  	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
  	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS,
  	CREATE_TABLE_LIKE_INCLUDING_INDEXES,
! 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES,
! 	CREATE_TABLE_LIKE_INCLUDING_STORAGE,
! 	CREATE_TABLE_LIKE_EXCLUDING_STORAGE,
! 	CREATE_TABLE_LIKE_INCLUDING_COMMENTS,
! 	CREATE_TABLE_LIKE_EXCLUDING_COMMENTS
  } CreateStmtLikeOption;
  
  /*
diff -cprN head/src/include/parser/kwlist.h create-including/src/include/parser/kwlist.h
*** head/src/include/parser/kwlist.h	2009-04-06 17:42:53.000000000 +0900
--- create-including/src/include/parser/kwlist.h	2009-09-08 11:14:31.709820694 +0900
*************** PG_KEYWORD("coalesce", COALESCE, COL_NAM
*** 80,85 ****
--- 80,86 ----
  PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
  PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
  PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
  PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
  PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
  PG_KEYWORD("concurrently", CONCURRENTLY, UNRESERVED_KEYWORD)
diff -cprN head/src/test/regress/expected/inherit.out create-including/src/test/regress/expected/inherit.out
*** head/src/test/regress/expected/inherit.out	2009-08-02 04:59:41.000000000 +0900
--- create-including/src/test/regress/expected/inherit.out	2009-09-08 14:43:16.176523000 +0900
*************** drop table pp1 cascade;
*** 906,908 ****
--- 906,1012 ----
  NOTICE:  drop cascades to 2 other objects
  DETAIL:  drop cascades to table cc1
  drop cascades to table cc2
+ -- including storage and comments
+ CREATE TABLE t1 (a text CHECK (length(a) > 2), b text);
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ CREATE TABLE t3 (a text CHECK (length(a) < 5), c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check';
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE);
+ \d+ t12_storage
+              Table "public.t12_storage"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Has OIDs: no
+ 
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+             Table "public.t12_comments"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | extended | A
+  b      | text |           | extended | B
+  c      | text |           | extended | C
+ Has OIDs: no
+ 
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ NOTICE:  merging column "b" with inherited definition
+ NOTICE:  merging constraint "t1_a_check" with inherited definition
+ \d+ t1_inh
+                Table "public.t1_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | A
+  b      | text |           | extended | B
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+ Inherits: t1
+ Has OIDs: no
+ 
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass;
+  description 
+ -------------
+  t1_a_check
+ (1 row)
+ 
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ \d+ t13_inh
+                Table "public.t13_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+     "t3_a_check" CHECK (length(a) < 5)
+ Inherits: t1,
+           t3
+ Has OIDs: no
+ 
+ CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ \d+ t13_like
+               Table "public.t13_like"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | A3
+  b      | text |           | extended | 
+  c      | text |           | external | C
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+     "t3_a_check" CHECK (length(a) < 5)
+ Inherits: t1
+ Has OIDs: no
+ 
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass;
+  description 
+ -------------
+  t3_a_check
+ (1 row)
+ 
+ CREATE TABLE inh_error1 () INHERITS (t1, t4);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ ERROR:  inherited column "a" has a storage parameter conflict
+ DETAIL:  MAIN versus EXTENDED
+ CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
+ 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;
diff -cprN head/src/test/regress/sql/inherit.sql create-including/src/test/regress/sql/inherit.sql
*** head/src/test/regress/sql/inherit.sql	2008-05-10 08:32:05.000000000 +0900
--- create-including/src/test/regress/sql/inherit.sql	2009-09-08 14:35:11.808502204 +0900
*************** create table cc2(f4 float) inherits(pp1,
*** 276,278 ****
--- 276,317 ----
  alter table pp1 add column a2 int check (a2 > 0);
  \d cc2
  drop table pp1 cascade;
+ 
+ -- including storage and comments
+ CREATE TABLE t1 (a text CHECK (length(a) > 2), b text);
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ 
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ 
+ CREATE TABLE t3 (a text CHECK (length(a) < 5), c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check';
+ 
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ 
+ CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE);
+ \d+ t12_storage
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1);
+ \d+ t1_inh
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass;
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ \d+ t13_inh
+ CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1);
+ \d+ t13_like
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass;
+ 
+ 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;
#4Alvaro Herrera
alvherre@commandprompt.com
In reply to: Itagaki Takahiro (#3)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Itagaki Takahiro wrote:

David Fetter <david@fetter.org> wrote:

On Mon, Sep 07, 2009 at 12:15:21PM +0900, Itagaki Takahiro wrote:

Here is a patch to implement the following items in our ToDo list:
* Add CREATE TABLE LIKE ... INCLUDING COMMENTS
* Have CREATE TABLE LIKE copy column storage parameters

The syntax is:
CREATE TABLE clone_table (LIKE template_table INCLUDING STORAGES)

This should probably read INCLUDING STORAGE (singular) instead of
STORAGES.

Thanks. I fixed it to INCLUDING STORAGE.

This INCLUDING STORAGE is supposed to copy reloptions? In that case I
think this is still a misnomer; to me it sounds like it's copying the
underlying storage i.e. data, which would be very surprising. What
about "INCLUDING STORAGE OPTIONS"?

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

#5Itagaki Takahiro
itagaki.takahiro@oss.ntt.co.jp
In reply to: Alvaro Herrera (#4)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Alvaro Herrera <alvherre@commandprompt.com> wrote:

This INCLUDING STORAGE is supposed to copy reloptions?

No. It copies only storage parameters of columns to control TOAST policy.
It might be good to have some features to copy reloptions with convenient
way, but it will be done in another patch.

to me it sounds like it's copying the
underlying storage i.e. data, which would be very surprising. What
about "INCLUDING STORAGE OPTIONS"?

Hmm, but we have the following syntax already:
ALTER TABLE table ALTER COLUMN column SET STORAGE ...
Do you also think it should be "SET STORAGE OPTION ..." ?

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center

#6Brendan Jurd
direvus@gmail.com
In reply to: Itagaki Takahiro (#5)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

2009/9/9 Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp>:

Alvaro Herrera <alvherre@commandprompt.com> wrote:

This INCLUDING STORAGE is supposed to copy reloptions?

No. It copies only storage parameters of columns to control TOAST policy.
It might be good to have some features to copy reloptions with convenient
way, but it will be done in another patch.

to me it sounds like it's copying the
underlying storage i.e. data, which would be very surprising.  What
about "INCLUDING STORAGE OPTIONS"?

It *would* be very surprising. An option to include data would
probably be called "INCLUDING DATA" =)

Hmm, but we have the following syntax already:
   ALTER TABLE table ALTER COLUMN column SET STORAGE ...
Do you also think it should be "SET STORAGE OPTION ..." ?

Personally, I think INCLUDING STORAGE makes as much sense as you can
expect using just one word, and as Itagaki-san points out it
correlates well with the syntax for ALTER COLUMN.

Cheers,
BJ

#7Brendan Jurd
direvus@gmail.com
In reply to: Itagaki Takahiro (#1)
1 attachment(s)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

2009/9/7 Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp>:

Here is a patch to implement the following items in our ToDo list:
 * Add CREATE TABLE LIKE ... INCLUDING COMMENTS
 * Have CREATE TABLE LIKE copy column storage parameters

Hello Itagaki-san,

I am doing an initial review of your patch. I applied the version
labelled 20090908 (applied with minor fuzz to HEAD). It compiled
cleanly and the feature appears to work as advertised.

I did a little bit of copy-editing on my way through (changes
attached) but the patch seems to be in very good shape. The
documentation is clearly worded, although I did add a cross-reference
in the bit about STORAGE. The regression tests seem to give a pretty
good coverage of both the success and failure modes.

In response to the questions you raised in your post:

 * Should INCLUDING COMMENTS also copy comments on indexes?
   It copies only comments on columns for now.

It probably should, but if this is difficult to work in, I don't see
anything wrong with leaving it out of this patch and making it a TODO.

 * Should we have additonal syntax to define storage parameters inline
   of CREATE TABLE? For example,
       CREATE TABLE tbl (col text STORAGE MAIN);
   CREATE TABLE fails if there is a conflicted storage parameter for now.
       ERROR:  column "col" has a storage parameter conflict
       DETAIL:  MAIN versus EXTENDED
   but there is no way to resolve the confliction unless we modify the
   definitions of original tables. Meantime, we can overwrite DEFAULTs
   to resolve conflictions by INCLUDING DEFAULTS.

I think I'm failing to understand why this would be an issue. Why
would the user be specifying columns in the CREATE TABLE statement
that already exist in the table they are cloning?

 * Should we have "INCLUDING ALL" as an abbreviated form?
   Many INCLUDING options in CREATE LIKE seems to be messy:
       CREATE TABLE clone_table (LIKE template_table
           INCLUDING DEFAULTS
           INCLUDING CONSTRAINTS
           INCLUDING INDEXES
           INCLUDING STORAGES
           INCLUDING COMMENTS);

+1 for adding INCLUDING ALL. The grammar should also support
EXCLUDING ALL for symmetry, even though EXCLUDING ALL is the default
behaviour.

However I do think that this should be a separate patch ... add to TODO?

Cheers,
BJ

Attachments:

create-including-20090908-changes.diffapplication/octet-stream; name=create-including-20090908-changes.diffDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e33d73d..636b3e5 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -285,10 +285,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
       specified.
      </para>
      <para>
-      Storage parameters for the copied column definitions will only be
-      copied if <literal>INCLUDING STORAGE</literal> is specified.  The
-      default behavior is to exclude storage paramters, resulting in the
-      copied columns in the new table having type-specific default parameters.
+      Storage parameters for the copied column definitions will only be copied
+      if <literal>INCLUDING STORAGE</literal> is specified.  The default
+      behavior is to exclude storage parameters, resulting in the copied
+      columns in the new table having type-specific default parameters.  For
+      more on storage parameters, see <xref linkend="storage-toast">.
      </para>
      <para>
       Comments for the copied column and constraint definitions will only be
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index 335d226..ce243ea 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -463,7 +463,7 @@ DeleteSharedComments(Oid oid, Oid classoid)
 }
 
 /*
- * GetComment -- get the comment for a object, or null if not found.
+ * GetComment -- get the comment for an object, or null if not found.
  */
 char *
 GetComment(Oid oid, Oid classoid, int32 subid)
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index dded36b..cf77cce 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1571,7 +1571,7 @@ MergeAttributes(List *schema, List *supers, bool istemp,
 		}
 	}
 
-	/* error is conflicting comments */
+	/* Raise an error if we found conflicting comments. */
 	if (have_bogus_comments)
 	{
 		foreach(entry, schema)
#8Itagaki Takahiro
itagaki.takahiro@oss.ntt.co.jp
In reply to: Brendan Jurd (#7)
1 attachment(s)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Brendan Jurd <direvus@gmail.com> wrote:

I am doing an initial review of your patch.

Thank you for reviewing.
I merged your fix and add INCLUDING ALL option to the new patch.
I changed InhRelation.options to be a bitmap of CreateStmtLikeOption.
INCLUDING just adds bits, and EXCLUDING drops bits.

Now this patch adds:
* CREATE TABLE LIKE ... INCLUDING COMMENTS (for columns and constraints)
* CREATE TABLE LIKE ... INCLUDING STORAGE
* CREATE TABLE LIKE ... INCLUDING ALL

I think I'm failing to understand why this would be an issue. Why
would the user be specifying columns in the CREATE TABLE statement
that already exist in the table they are cloning?

Without inline-STORAGE syntax, we cannot resolve conflictions of
storage parameters unless we can define tables without STORAGE
and then re-add options with ALTER TABLE.

There might be ToDo items:
* Make INCLUDING COMMENTS also copy comments on indexes.
* Add syntax to define storage options inline like
CREATE TABLE tbl (col text STORAGE MAIN).

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center

Attachments:

create-including_20090928.patchapplication/octet-stream; name=create-including_20090928.patchDownload
diff -cprN head/doc/src/sgml/ref/create_table.sgml work/doc/src/sgml/ref/create_table.sgml
*** head/doc/src/sgml/ref/create_table.sgml	2009-09-18 14:00:41.000000000 +0900
--- work/doc/src/sgml/ref/create_table.sgml	2009-09-28 11:16:34.150010328 +0900
*************** PostgreSQL documentation
*** 24,30 ****
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
--- 24,30 ----
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY 
*** 230,235 ****
--- 230,239 ----
        will always be chosen for it.
       </para>
  
+      <para>
+       Column storage parameters are also copied from parent tables.
+      </para>
+ 
  <!--
       <para>
        <productname>PostgreSQL</> automatically allows the
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY 
*** 247,253 ****
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
--- 251,257 ----
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY 
*** 281,286 ****
--- 285,307 ----
        specified.
       </para>
       <para>
+       Storage parameters for the copied column definitions will only be copied
+       if <literal>INCLUDING STORAGE</literal> is specified.  The default
+       behavior is to exclude storage parameters, resulting in the copied
+       columns in the new table having type-specific default parameters.  For
+       more on storage parameters, see <xref linkend="storage-toast">.
+      </para>
+      <para>
+       Comments for the copied column and constraint definitions will only be
+       copied if <literal>INCLUDING COMMENTS</literal> is specified.  The
+       default behavior is to exclude comments, resulting in the
+       copied columns and constraints in the new table having no comments.
+      </para>
+      <para>
+       <literal>INCLUDING ALL</literal> is an abbreviated form of
+       <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGES INCLUDING COMMENTS</literal>.
+      </para>
+      <para>
        Note also that unlike <literal>INHERITS</literal>, copied columns and
        constraints are not merged with similarly named columns and constraints.
        If the same name is specified explicitly or in another
diff -cprN head/src/backend/access/common/tupdesc.c work/src/backend/access/common/tupdesc.c
*** head/src/backend/access/common/tupdesc.c	2009-08-03 07:14:51.000000000 +0900
--- work/src/backend/access/common/tupdesc.c	2009-09-28 10:52:37.666113379 +0900
*************** BuildDescForRelation(List *schema)
*** 558,563 ****
--- 558,565 ----
  		has_not_null |= entry->is_not_null;
  		desc->attrs[attnum - 1]->attislocal = entry->is_local;
  		desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
+ 		if (entry->storage)
+ 			desc->attrs[attnum - 1]->attstorage = entry->storage;
  	}
  
  	if (has_not_null)
diff -cprN head/src/backend/catalog/pg_constraint.c work/src/backend/catalog/pg_constraint.c
*** head/src/backend/catalog/pg_constraint.c	2009-07-28 11:56:29.000000000 +0900
--- work/src/backend/catalog/pg_constraint.c	2009-09-28 10:52:37.666113379 +0900
*************** AlterConstraintNamespaces(Oid ownerId, O
*** 702,704 ****
--- 702,766 ----
  
  	heap_close(conRel, RowExclusiveLock);
  }
+ 
+ /*
+  * GetConstraintByName
+  *		Find a constraint with the specified name.
+  */
+ Oid
+ GetConstraintByName(Oid relid, const char *conname)
+ {
+ 	Relation	pg_constraint;
+ 	HeapTuple	tuple;
+ 	SysScanDesc scan;
+ 	ScanKeyData skey[1];
+ 	Oid			conOid = InvalidOid;
+ 
+ 	/*
+ 	 * Fetch the constraint tuple from pg_constraint.  There may be more than
+ 	 * one match, because constraints are not required to have unique names;
+ 	 * if so, error out.
+ 	 */
+ 	pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ, relid);
+ 
+ 	scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
+ 							  SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ 	{
+ 		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ 
+ 		if (strcmp(NameStr(con->conname), conname) == 0)
+ 		{
+ 			if (OidIsValid(conOid))
+ 			{
+ 				char *relname = get_rel_name(relid);
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 				 errmsg("table \"%s\" has multiple constraints named \"%s\"",
+ 					(relname ? relname : "(unknown)"), conname)));
+ 			}
+ 			conOid = HeapTupleGetOid(tuple);
+ 		}
+ 	}
+ 
+ 	systable_endscan(scan);
+ 
+ 	/* If no constraint exists for the relation specified, notify user */
+ 	if (!OidIsValid(conOid))
+ 	{
+ 		char *relname = get_rel_name(relid);
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
+ 						conname, (relname ? relname : "(unknown)"))));
+ 	}
+ 
+ 	heap_close(pg_constraint, AccessShareLock);
+ 
+ 	return conOid;
+ }
diff -cprN head/src/backend/commands/comment.c work/src/backend/commands/comment.c
*** head/src/backend/commands/comment.c	2009-06-11 23:48:55.000000000 +0900
--- work/src/backend/commands/comment.c	2009-09-28 10:52:55.412382289 +0900
*************** DeleteSharedComments(Oid oid, Oid classo
*** 463,468 ****
--- 463,523 ----
  }
  
  /*
+  * GetComment -- get the comment for an object, or null if not found.
+  */
+ char *
+ GetComment(Oid oid, Oid classoid, int32 subid)
+ {
+ 	Relation	description;
+ 	ScanKeyData skey[3];
+ 	SysScanDesc sd;
+ 	TupleDesc	tupdesc;
+ 	HeapTuple	tuple;
+ 	char	   *comment;
+ 
+ 	/* Use the index to search for a matching old tuple */
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_description_objoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(oid));
+ 	ScanKeyInit(&skey[1],
+ 				Anum_pg_description_classoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(classoid));
+ 	ScanKeyInit(&skey[2],
+ 				Anum_pg_description_objsubid,
+ 				BTEqualStrategyNumber, F_INT4EQ,
+ 				Int32GetDatum(subid));
+ 
+ 	description = heap_open(DescriptionRelationId, AccessShareLock);
+ 	tupdesc = RelationGetDescr(description);
+ 
+ 	sd = systable_beginscan(description, DescriptionObjIndexId, true,
+ 							SnapshotNow, 3, skey);
+ 
+ 	comment  = NULL;
+ 	while ((tuple = systable_getnext(sd)) != NULL)
+ 	{
+ 		Datum	value;
+ 		bool	isnull;
+ 
+ 		/* Found the tuple, get description field */
+ 		value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
+ 		if (!isnull)
+ 			comment = TextDatumGetCString(value);
+ 		break;					/* Assume there can be only one match */
+ 	}
+ 
+ 	systable_endscan(sd);
+ 
+ 	/* Done */
+ 	heap_close(description, AccessShareLock);
+ 
+ 	return comment;
+ }
+ 
+ /*
   * CommentRelation --
   *
   * This routine is used to add/drop a comment from a relation, where
*************** CommentConstraint(List *qualname, char *
*** 1064,1075 ****
  	List	   *relName;
  	char	   *conName;
  	RangeVar   *rel;
! 	Relation	pg_constraint,
! 				relation;
! 	HeapTuple	tuple;
! 	SysScanDesc scan;
! 	ScanKeyData skey[1];
! 	Oid			conOid = InvalidOid;
  
  	/* Separate relname and constraint name */
  	nnames = list_length(qualname);
--- 1119,1126 ----
  	List	   *relName;
  	char	   *conName;
  	RangeVar   *rel;
! 	Relation	relation;
! 	Oid			conOid;
  
  	/* Separate relname and constraint name */
  	nnames = list_length(qualname);
*************** CommentConstraint(List *qualname, char *
*** 1088,1137 ****
  		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
  					   RelationGetRelationName(relation));
  
! 	/*
! 	 * Fetch the constraint tuple from pg_constraint.  There may be more than
! 	 * one match, because constraints are not required to have unique names;
! 	 * if so, error out.
! 	 */
! 	pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
! 
! 	ScanKeyInit(&skey[0],
! 				Anum_pg_constraint_conrelid,
! 				BTEqualStrategyNumber, F_OIDEQ,
! 				ObjectIdGetDatum(RelationGetRelid(relation)));
! 
! 	scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
! 							  SnapshotNow, 1, skey);
! 
! 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
! 	{
! 		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
! 
! 		if (strcmp(NameStr(con->conname), conName) == 0)
! 		{
! 			if (OidIsValid(conOid))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_DUPLICATE_OBJECT),
! 				 errmsg("table \"%s\" has multiple constraints named \"%s\"",
! 						RelationGetRelationName(relation), conName)));
! 			conOid = HeapTupleGetOid(tuple);
! 		}
! 	}
! 
! 	systable_endscan(scan);
! 
! 	/* If no constraint exists for the relation specified, notify user */
! 	if (!OidIsValid(conOid))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
! 						conName, RelationGetRelationName(relation))));
  
  	/* Call CreateComments() to create/drop the comments */
  	CreateComments(conOid, ConstraintRelationId, 0, comment);
  
  	/* Done, but hold lock on relation */
- 	heap_close(pg_constraint, AccessShareLock);
  	heap_close(relation, NoLock);
  }
  
--- 1139,1150 ----
  		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
  					   RelationGetRelationName(relation));
  
! 	conOid = GetConstraintByName(RelationGetRelid(relation), conName);
  
  	/* Call CreateComments() to create/drop the comments */
  	CreateComments(conOid, ConstraintRelationId, 0, comment);
  
  	/* Done, but hold lock on relation */
  	heap_close(relation, NoLock);
  }
  
diff -cprN head/src/backend/commands/tablecmds.c work/src/backend/commands/tablecmds.c
*** head/src/backend/commands/tablecmds.c	2009-09-27 07:42:01.000000000 +0900
--- work/src/backend/commands/tablecmds.c	2009-09-28 10:52:55.414056751 +0900
***************
*** 39,44 ****
--- 39,45 ----
  #include "catalog/storage.h"
  #include "catalog/toasting.h"
  #include "commands/cluster.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/sequence.h"
  #include "commands/tablecmds.h"
*************** truncate_check_rel(Relation rel)
*** 1099,1104 ****
--- 1100,1118 ----
  	CheckTableNotInUse(rel, "TRUNCATE");
  }
  
+ static const char *
+ storage_name(char c)
+ {
+ 	switch (c)
+ 	{
+ 		case 'p': return "PLAIN";
+ 		case 'm': return "MAIN";
+ 		case 'x': return "EXTENDED";
+ 		case 'e': return "EXTERNAL";
+ 		default: return "???";
+ 	}
+ }
+ 
  /*----------
   * MergeAttributes
   *		Returns new schema given initial schema and superclasses.
*************** MergeAttributes(List *schema, List *supe
*** 1167,1172 ****
--- 1181,1187 ----
  	List	   *constraints = NIL;
  	int			parentsWithOids = 0;
  	bool		have_bogus_defaults = false;
+ 	bool		have_bogus_comments = false;
  	char	   *bogus_marker = "Bogus!";		/* marks conflicting defaults */
  	int			child_attno;
  
*************** MergeAttributes(List *schema, List *supe
*** 1322,1327 ****
--- 1337,1354 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   format_type_be(attribute->atttypid))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = attribute->attstorage;
+ 				else if (def->storage != attribute->attstorage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("inherited column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(attribute->attstorage))));
+ 
  				def->inhcount++;
  				/* Merge of NOT NULL constraints = OR 'em together */
  				def->is_not_null |= attribute->attnotnull;
*************** MergeAttributes(List *schema, List *supe
*** 1343,1348 ****
--- 1370,1376 ----
  				def->raw_default = NULL;
  				def->cooked_default = NULL;
  				def->constraints = NIL;
+ 				def->storage = attribute->attstorage;
  				inhSchema = lappend(inhSchema, def);
  				newattno[parent_attno - 1] = ++child_attno;
  			}
*************** MergeAttributes(List *schema, List *supe
*** 1480,1485 ****
--- 1508,1525 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   TypeNameToString(newdef->typeName))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = newdef->storage;
+ 				else if (newdef->storage != 0 && def->storage != newdef->storage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(newdef->storage))));
+ 
  				/* Mark the column as locally defined */
  				def->is_local = true;
  				/* Merge of NOT NULL constraints = OR 'em together */
*************** MergeAttributes(List *schema, List *supe
*** 1532,1537 ****
--- 1572,1591 ----
  		}
  	}
  
+ 	/* Raise an error if we found conflicting comments. */
+ 	if (have_bogus_comments)
+ 	{
+ 		foreach(entry, schema)
+ 		{
+ 			ColumnDef  *def = lfirst(entry);
+ 
+ 			if (def->cooked_default == bogus_marker)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ 				  errmsg("column \"%s\" inherits conflicting comments", def->colname)));
+ 		}
+ 	}
+ 
  	*supOids = parentOids;
  	*supconstr = constraints;
  	*supOidCount = parentsWithOids;
diff -cprN head/src/backend/nodes/copyfuncs.c work/src/backend/nodes/copyfuncs.c
*** head/src/backend/nodes/copyfuncs.c	2009-09-23 08:43:37.000000000 +0900
--- work/src/backend/nodes/copyfuncs.c	2009-09-28 11:11:53.099995625 +0900
*************** _copyInhRelation(InhRelation *from)
*** 2416,2422 ****
  	InhRelation *newnode = makeNode(InhRelation);
  
  	COPY_NODE_FIELD(relation);
! 	COPY_NODE_FIELD(options);
  
  	return newnode;
  }
--- 2416,2422 ----
  	InhRelation *newnode = makeNode(InhRelation);
  
  	COPY_NODE_FIELD(relation);
! 	COPY_SCALAR_FIELD(options);
  
  	return newnode;
  }
diff -cprN head/src/backend/nodes/equalfuncs.c work/src/backend/nodes/equalfuncs.c
*** head/src/backend/nodes/equalfuncs.c	2009-09-23 08:43:38.000000000 +0900
--- work/src/backend/nodes/equalfuncs.c	2009-09-28 11:12:33.998113738 +0900
*************** static bool
*** 1087,1093 ****
  _equalInhRelation(InhRelation *a, InhRelation *b)
  {
  	COMPARE_NODE_FIELD(relation);
! 	COMPARE_NODE_FIELD(options);
  
  	return true;
  }
--- 1087,1093 ----
  _equalInhRelation(InhRelation *a, InhRelation *b)
  {
  	COMPARE_NODE_FIELD(relation);
! 	COMPARE_SCALAR_FIELD(options);
  
  	return true;
  }
diff -cprN head/src/backend/parser/gram.y work/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-09-23 08:43:38.000000000 +0900
--- work/src/backend/parser/gram.y	2009-09-28 11:17:17.711010198 +0900
*************** static TypeName *TableFuncTypeName(List 
*** 399,406 ****
  %type <keyword> col_name_keyword reserved_keyword
  
  %type <node>	TableConstraint TableLikeClause
! %type <list>	TableLikeOptionList
! %type <ival>	TableLikeOption
  %type <list>	ColQualList
  %type <node>	ColConstraint ColConstraintElem ConstraintAttr
  %type <ival>	key_actions key_delete key_match key_update key_action
--- 399,405 ----
  %type <keyword> col_name_keyword reserved_keyword
  
  %type <node>	TableConstraint TableLikeClause
! %type <ival>	TableLikeOptionList TableLikeOption
  %type <list>	ColQualList
  %type <node>	ColConstraint ColConstraintElem ConstraintAttr
  %type <ival>	key_actions key_delete key_match key_update key_action
*************** static TypeName *TableFuncTypeName(List 
*** 458,464 ****
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
--- 457,463 ----
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMENTS COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
*************** TableLikeClause:
*** 2392,2408 ****
  		;
  
  TableLikeOptionList:
! 				TableLikeOptionList TableLikeOption	{ $$ = lappend_int($1, $2); }
! 				| /* EMPTY */						{ $$ = NIL; }
  		;
  
  TableLikeOption:
! 				INCLUDING DEFAULTS					{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_DEFAULTS; }
! 				| EXCLUDING DEFAULTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS; }
! 				| INCLUDING CONSTRAINTS				{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS; }
! 				| EXCLUDING CONSTRAINTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS; }
! 				| INCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_INDEXES; }
! 				| EXCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES; }
  		;
  
  
--- 2391,2408 ----
  		;
  
  TableLikeOptionList:
! 				TableLikeOptionList INCLUDING TableLikeOption	{ $$ = $1 | $3; }
! 				| TableLikeOptionList EXCLUDING TableLikeOption	{ $$ = $1 & ~$3; }
! 				| /* EMPTY */						{ $$ = 0; }
  		;
  
  TableLikeOption:
! 				DEFAULTS			{ $$ = CREATE_TABLE_LIKE_DEFAULTS; }
! 				| CONSTRAINTS		{ $$ = CREATE_TABLE_LIKE_CONSTRAINTS; }
! 				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
! 				| STORAGE			{ $$ = CREATE_TABLE_LIKE_STORAGE; }
! 				| COMMENTS			{ $$ = CREATE_TABLE_LIKE_COMMENTS; }
! 				| ALL				{ $$ = CREATE_TABLE_LIKE_ALL; }
  		;
  
  
*************** unreserved_keyword:
*** 10325,10330 ****
--- 10325,10331 ----
  			| CLOSE
  			| CLUSTER
  			| COMMENT
+ 			| COMMENTS
  			| COMMIT
  			| COMMITTED
  			| CONCURRENTLY
diff -cprN head/src/backend/parser/parse_utilcmd.c work/src/backend/parser/parse_utilcmd.c
*** head/src/backend/parser/parse_utilcmd.c	2009-07-30 11:45:37.000000000 +0900
--- work/src/backend/parser/parse_utilcmd.c	2009-09-28 11:04:46.729994997 +0900
***************
*** 36,41 ****
--- 36,42 ----
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_opclass.h"
  #include "catalog/pg_type.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/tablecmds.h"
  #include "commands/tablespace.h"
*************** transformInhRelation(ParseState *pstate,
*** 546,555 ****
  	TupleDesc	tupleDesc;
  	TupleConstr *constr;
  	AclResult	aclresult;
! 	bool		including_defaults = false;
! 	bool		including_constraints = false;
! 	bool		including_indexes = false;
! 	ListCell   *elem;
  
  	relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock);
  
--- 547,553 ----
  	TupleDesc	tupleDesc;
  	TupleConstr *constr;
  	AclResult	aclresult;
! 	char	   *comment;
  
  	relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock);
  
*************** transformInhRelation(ParseState *pstate,
*** 571,606 ****
  	tupleDesc = RelationGetDescr(relation);
  	constr = tupleDesc->constr;
  
- 	foreach(elem, inhRelation->options)
- 	{
- 		int			option = lfirst_int(elem);
- 
- 		switch (option)
- 		{
- 			case CREATE_TABLE_LIKE_INCLUDING_DEFAULTS:
- 				including_defaults = true;
- 				break;
- 			case CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS:
- 				including_defaults = false;
- 				break;
- 			case CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS:
- 				including_constraints = true;
- 				break;
- 			case CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS:
- 				including_constraints = false;
- 				break;
- 			case CREATE_TABLE_LIKE_INCLUDING_INDEXES:
- 				including_indexes = true;
- 				break;
- 			case CREATE_TABLE_LIKE_EXCLUDING_INDEXES:
- 				including_indexes = false;
- 				break;
- 			default:
- 				elog(ERROR, "unrecognized CREATE TABLE LIKE option: %d",
- 					 option);
- 		}
- 	}
- 
  	/*
  	 * Insert the copied attributes into the cxt for the new table definition.
  	 */
--- 569,574 ----
*************** transformInhRelation(ParseState *pstate,
*** 642,648 ****
  		/*
  		 * Copy default, if present and the default has been requested
  		 */
! 		if (attribute->atthasdef && including_defaults)
  		{
  			char	   *this_default = NULL;
  			AttrDefault *attrdef;
--- 610,617 ----
  		/*
  		 * Copy default, if present and the default has been requested
  		 */
! 		if (attribute->atthasdef &&
! 			(inhRelation->options & CREATE_TABLE_LIKE_DEFAULTS))
  		{
  			char	   *this_default = NULL;
  			AttrDefault *attrdef;
*************** transformInhRelation(ParseState *pstate,
*** 668,680 ****
  
  			def->cooked_default = pstrdup(this_default);
  		}
  	}
  
  	/*
  	 * Copy CHECK constraints if requested, being careful to adjust attribute
  	 * numbers
  	 */
! 	if (including_constraints && tupleDesc->constr)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		int			ccnum;
--- 637,670 ----
  
  			def->cooked_default = pstrdup(this_default);
  		}
+ 
+ 		/* Likewise, copy storage if requested */
+ 		if (inhRelation->options & CREATE_TABLE_LIKE_STORAGE)
+ 			def->storage = attribute->attstorage;
+ 
+ 		/* Likewise, copy comment if requested */
+ 		if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) &&
+ 			(comment = GetComment(attribute->attrelid, RelationRelationId,
+ 			attribute->attnum)) != NULL)
+ 		{
+ 			CommentStmt *stmt = makeNode(CommentStmt);
+ 
+ 			stmt->objtype = OBJECT_COLUMN;
+ 			stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ 									   makeString(cxt->relation->relname),
+ 									   makeString(def->colname));
+ 			stmt->objargs = NIL;
+ 			stmt->comment = comment;
+ 
+ 			cxt->alist = lappend(cxt->alist, stmt);
+ 		}
  	}
  
  	/*
  	 * Copy CHECK constraints if requested, being careful to adjust attribute
  	 * numbers
  	 */
! 	if ((inhRelation->options & CREATE_TABLE_LIKE_CONSTRAINTS) && tupleDesc->constr)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		int			ccnum;
*************** transformInhRelation(ParseState *pstate,
*** 694,706 ****
  			n->raw_expr = NULL;
  			n->cooked_expr = nodeToString(ccbin_node);
  			cxt->ckconstraints = lappend(cxt->ckconstraints, n);
  		}
  	}
  
  	/*
  	 * Likewise, copy indexes if requested
  	 */
! 	if (including_indexes && relation->rd_rel->relhasindex)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		List	   *parent_indexes;
--- 684,714 ----
  			n->raw_expr = NULL;
  			n->cooked_expr = nodeToString(ccbin_node);
  			cxt->ckconstraints = lappend(cxt->ckconstraints, n);
+ 
+ 			/* Copy comment on constraint */
+ 			if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) &&
+ 				(comment = GetComment(GetConstraintByName(RelationGetRelid(
+ 				relation), n->conname), ConstraintRelationId, 0)) != NULL)
+ 			{
+ 				CommentStmt *stmt = makeNode(CommentStmt);
+ 
+ 				stmt->objtype = OBJECT_CONSTRAINT;
+ 				stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ 										   makeString(cxt->relation->relname),
+ 										   makeString(n->conname));
+ 				stmt->objargs = NIL;
+ 				stmt->comment = comment;
+ 
+ 				cxt->alist = lappend(cxt->alist, stmt);
+ 			}
  		}
  	}
  
  	/*
  	 * Likewise, copy indexes if requested
  	 */
! 	if ((inhRelation->options & CREATE_TABLE_LIKE_INDEXES) &&
! 		relation->rd_rel->relhasindex)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		List	   *parent_indexes;
*************** transformInhRelation(ParseState *pstate,
*** 719,724 ****
--- 727,734 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			/* XXX: should copy comment on index? */
+ 
  			/* Save it in the inh_indexes list for the time being */
  			cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt);
  
diff -cprN head/src/bin/psql/sql_help.c work/src/bin/psql/sql_help.c
*** head/src/bin/psql/sql_help.c	2009-09-27 17:07:40.000000000 +0900
--- work/src/bin/psql/sql_help.c	2009-09-28 11:13:06.231320101 +0900
***************
*** 3,9 ****
   * *** generated from the DocBook documentation.
   *
   * generated by
!  *     /usr/local/bin/suidperl create_help.pl ../../../doc/src/sgml/ref sql_help
   *
   */
  
--- 3,9 ----
   * *** generated from the DocBook documentation.
   *
   * generated by
!  *     /usr/bin/perl create_help.pl ../../../doc/src/sgml/ref sql_help
   *
   */
  
*************** sql_help_CREATE_TABLE(PQExpBuffer buf)
*** 1279,1285 ****
  					  "CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE %s ( [\n"
  					  "  { %s %s [ DEFAULT %s ] [ %s [ ... ] ]\n"
  					  "    | %s\n"
! 					  "    | LIKE %s [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ] ... }\n"
  					  "    [, ... ]\n"
  					  "] )\n"
  					  "[ INHERITS ( %s [, ... ] ) ]\n"
--- 1279,1285 ----
  					  "CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE %s ( [\n"
  					  "  { %s %s [ DEFAULT %s ] [ %s [ ... ] ]\n"
  					  "    | %s\n"
! 					  "    | LIKE %s [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS } ] ... }\n"
  					  "    [, ... ]\n"
  					  "] )\n"
  					  "[ INHERITS ( %s [, ... ] ) ]\n"
diff -cprN head/src/include/catalog/pg_constraint.h work/src/include/catalog/pg_constraint.h
*** head/src/include/catalog/pg_constraint.h	2009-07-28 11:56:31.000000000 +0900
--- work/src/include/catalog/pg_constraint.h	2009-09-28 10:52:37.673113381 +0900
*************** extern char *ChooseConstraintName(const 
*** 226,230 ****
--- 226,231 ----
  
  extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
  						  Oid newNspId, bool isType);
+ extern Oid GetConstraintByName(Oid relid, const char *conname);
  
  #endif   /* PG_CONSTRAINT_H */
diff -cprN head/src/include/commands/comment.h work/src/include/commands/comment.h
*** head/src/include/commands/comment.h	2009-06-11 23:49:11.000000000 +0900
--- work/src/include/commands/comment.h	2009-09-28 10:52:37.673113381 +0900
*************** extern void DeleteSharedComments(Oid oid
*** 39,42 ****
--- 39,44 ----
  
  extern void CreateSharedComments(Oid oid, Oid classoid, char *comment);
  
+ extern char *GetComment(Oid oid, Oid classoid, int32 subid);
+ 
  #endif   /* COMMENT_H */
diff -cprN head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-09-23 08:43:41.000000000 +0900
--- work/src/include/nodes/parsenodes.h	2009-09-28 10:58:36.806014857 +0900
*************** typedef struct ColumnDef
*** 461,466 ****
--- 461,467 ----
  	int			inhcount;		/* number of times column is inherited */
  	bool		is_local;		/* column has local (non-inherited) def'n */
  	bool		is_not_null;	/* NOT NULL constraint specified? */
+ 	char		storage;		/* storage parameter of column */
  	Node	   *raw_default;	/* default value (untransformed parse tree) */
  	char	   *cooked_default; /* nodeToString representation */
  	List	   *constraints;	/* other constraints on column */
*************** typedef struct InhRelation
*** 473,489 ****
  {
  	NodeTag		type;
  	RangeVar   *relation;
! 	List	   *options;		/* integer List of CreateStmtLikeOption */
  } InhRelation;
  
  typedef enum CreateStmtLikeOption
  {
! 	CREATE_TABLE_LIKE_INCLUDING_DEFAULTS,
! 	CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS,
! 	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
! 	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS,
! 	CREATE_TABLE_LIKE_INCLUDING_INDEXES,
! 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES
  } CreateStmtLikeOption;
  
  /*
--- 474,490 ----
  {
  	NodeTag		type;
  	RangeVar   *relation;
! 	bits32		options;		/* bitmap of CreateStmtLikeOption */
  } InhRelation;
  
  typedef enum CreateStmtLikeOption
  {
! 	CREATE_TABLE_LIKE_DEFAULTS		= 1 << 0,
! 	CREATE_TABLE_LIKE_CONSTRAINTS	= 1 << 1,
! 	CREATE_TABLE_LIKE_INDEXES		= 1 << 2,
! 	CREATE_TABLE_LIKE_STORAGE		= 1 << 3,
! 	CREATE_TABLE_LIKE_COMMENTS		= 1 << 4,
! 	CREATE_TABLE_LIKE_ALL			= 0xFFFFFFFF
  } CreateStmtLikeOption;
  
  /*
diff -cprN head/src/include/parser/kwlist.h work/src/include/parser/kwlist.h
*** head/src/include/parser/kwlist.h	2009-09-23 08:43:41.000000000 +0900
--- work/src/include/parser/kwlist.h	2009-09-28 10:52:37.674113571 +0900
*************** PG_KEYWORD("coalesce", COALESCE, COL_NAM
*** 80,85 ****
--- 80,86 ----
  PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
  PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
  PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
  PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
  PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
  PG_KEYWORD("concurrently", CONCURRENTLY, UNRESERVED_KEYWORD)
diff -cprN head/src/test/regress/expected/inherit.out work/src/test/regress/expected/inherit.out
*** head/src/test/regress/expected/inherit.out	2009-08-02 04:59:41.000000000 +0900
--- work/src/test/regress/expected/inherit.out	2009-09-28 10:52:37.675113379 +0900
*************** drop table pp1 cascade;
*** 906,908 ****
--- 906,1012 ----
  NOTICE:  drop cascades to 2 other objects
  DETAIL:  drop cascades to table cc1
  drop cascades to table cc2
+ -- including storage and comments
+ CREATE TABLE t1 (a text CHECK (length(a) > 2), b text);
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ CREATE TABLE t3 (a text CHECK (length(a) < 5), c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check';
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE);
+ \d+ t12_storage
+              Table "public.t12_storage"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Has OIDs: no
+ 
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+             Table "public.t12_comments"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | extended | A
+  b      | text |           | extended | B
+  c      | text |           | extended | C
+ Has OIDs: no
+ 
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ NOTICE:  merging column "b" with inherited definition
+ NOTICE:  merging constraint "t1_a_check" with inherited definition
+ \d+ t1_inh
+                Table "public.t1_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | A
+  b      | text |           | extended | B
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+ Inherits: t1
+ Has OIDs: no
+ 
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass;
+  description 
+ -------------
+  t1_a_check
+ (1 row)
+ 
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ \d+ t13_inh
+                Table "public.t13_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+     "t3_a_check" CHECK (length(a) < 5)
+ Inherits: t1,
+           t3
+ Has OIDs: no
+ 
+ CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ \d+ t13_like
+               Table "public.t13_like"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | A3
+  b      | text |           | extended | 
+  c      | text |           | external | C
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+     "t3_a_check" CHECK (length(a) < 5)
+ Inherits: t1
+ Has OIDs: no
+ 
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass;
+  description 
+ -------------
+  t3_a_check
+ (1 row)
+ 
+ CREATE TABLE inh_error1 () INHERITS (t1, t4);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ ERROR:  inherited column "a" has a storage parameter conflict
+ DETAIL:  MAIN versus EXTENDED
+ CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
+ 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;
diff -cprN head/src/test/regress/sql/inherit.sql work/src/test/regress/sql/inherit.sql
*** head/src/test/regress/sql/inherit.sql	2008-05-10 08:32:05.000000000 +0900
--- work/src/test/regress/sql/inherit.sql	2009-09-28 10:52:37.675113379 +0900
*************** create table cc2(f4 float) inherits(pp1,
*** 276,278 ****
--- 276,317 ----
  alter table pp1 add column a2 int check (a2 > 0);
  \d cc2
  drop table pp1 cascade;
+ 
+ -- including storage and comments
+ CREATE TABLE t1 (a text CHECK (length(a) > 2), b text);
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ 
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ 
+ CREATE TABLE t3 (a text CHECK (length(a) < 5), c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check';
+ 
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ 
+ CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE);
+ \d+ t12_storage
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1);
+ \d+ t1_inh
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass;
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ \d+ t13_inh
+ CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1);
+ \d+ t13_like
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass;
+ 
+ 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;
#9Brendan Jurd
direvus@gmail.com
In reply to: Itagaki Takahiro (#8)
1 attachment(s)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

2009/9/28 Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp>:

Thank you for reviewing.
I merged your fix and add INCLUDING ALL option to the new patch.
I changed InhRelation.options to be a bitmap of CreateStmtLikeOption.
INCLUDING just adds bits, and EXCLUDING drops bits.

I had two hunks fail trying to apply your new patch to the latest (git) HEAD:

patching file doc/src/sgml/ref/create_table.sgml
patching file src/backend/access/common/tupdesc.c
patching file src/backend/catalog/pg_constraint.c
patching file src/backend/commands/comment.c
patching file src/backend/commands/tablecmds.c
patching file src/backend/nodes/copyfuncs.c
patching file src/backend/nodes/equalfuncs.c
patching file src/backend/parser/gram.y
patching file src/backend/parser/parse_utilcmd.c
patching file src/bin/psql/sql_help.c
Hunk #1 FAILED at 3.
Hunk #2 FAILED at 1279.
2 out of 2 hunks FAILED -- saving rejects to file src/bin/psql/sql_help.c.rej
patching file src/include/catalog/pg_constraint.h
patching file src/include/commands/comment.h
patching file src/include/nodes/parsenodes.h
patching file src/include/parser/kwlist.h
patching file src/test/regress/expected/inherit.out
patching file src/test/regress/sql/inherit.sql

I have attached the rejects file.

Cheers,
BJ

Attachments:

sql_help.c.rejapplication/octet-stream; name=sql_help.c.rejDownload
***************
*** 3,9 ****
   * *** generated from the DocBook documentation.
   *
   * generated by
!  *     /usr/local/bin/suidperl create_help.pl ../../../doc/src/sgml/ref sql_help
   *
   */
  
--- 3,9 ----
   * *** generated from the DocBook documentation.
   *
   * generated by
!  *     /usr/bin/perl create_help.pl ../../../doc/src/sgml/ref sql_help
   *
   */
  
***************
*** 1279,1285 ****
  					  "CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE %s ( [\n"
  					  "  { %s %s [ DEFAULT %s ] [ %s [ ... ] ]\n"
  					  "    | %s\n"
! 					  "    | LIKE %s [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ] ... }\n"
  					  "    [, ... ]\n"
  					  "] )\n"
  					  "[ INHERITS ( %s [, ... ] ) ]\n"
--- 1279,1285 ----
  					  "CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE %s ( [\n"
  					  "  { %s %s [ DEFAULT %s ] [ %s [ ... ] ]\n"
  					  "    | %s\n"
! 					  "    | LIKE %s [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS } ] ... }\n"
  					  "    [, ... ]\n"
  					  "] )\n"
  					  "[ INHERITS ( %s [, ... ] ) ]\n"
#10Itagaki Takahiro
itagaki.takahiro@oss.ntt.co.jp
In reply to: Brendan Jurd (#9)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Brendan Jurd <direvus@gmail.com> wrote:

patching file src/bin/psql/sql_help.c
Hunk #1 FAILED at 3.
Hunk #2 FAILED at 1279.
2 out of 2 hunks FAILED -- saving rejects to file src/bin/psql/sql_help.c.rej

I have attached the rejects file.

Oops, sql_help.c is an automatic generated file. Please ignore the part.

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center

#11Brendan Jurd
direvus@gmail.com
In reply to: Itagaki Takahiro (#10)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

2009/9/28 Itagaki Takahiro <itagaki.takahiro@oss.ntt.co.jp>:

Brendan Jurd <direvus@gmail.com> wrote:

patching file src/bin/psql/sql_help.c
Hunk #1 FAILED at 3.
Hunk #2 FAILED at 1279.
2 out of 2 hunks FAILED -- saving rejects to file src/bin/psql/sql_help.c.rej

Oops, sql_help.c is an automatic generated file. Please ignore the part.

With the sql_help.c changes removed, the patch applied fine and
testing went well.

I noticed only the following in the new documentation in CREATE TABLE:

diff --git a/doc/src/sgml/ref/create_table.sgml
b/doc/src/sgml/ref/create_table.sgml
index 6417007..9ea8a49 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -299,7 +299,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ]
TABLE <replaceable class="PAR
      </para>
      <para>
       <literal>INCLUDING ALL</literal> is an abbreviated form of
-      <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING
INDEXES INCLUDING STORAGES INCLUDING COMMENTS</literal>.
+      <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING
INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>.
      </para>
      <para>
       Note also that unlike <literal>INHERITS</literal>, copied columns and

Aside from the bogus hunks in the patch, and this one typo, the patch
looks to be in excellent shape.

Cheers,
BJ

#12Itagaki Takahiro
itagaki.takahiro@oss.ntt.co.jp
In reply to: Brendan Jurd (#11)
1 attachment(s)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

I removed hunks by sql_help.c and fix a typo in documentation.
An updated patch attached.

Brendan Jurd <direvus@gmail.com> wrote:

With the sql_help.c changes removed, the patch applied fine and
testing went well.

I noticed only the following in the new documentation in CREATE TABLE:
-      <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGES INCLUDING COMMENTS</literal>.
+      <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>.

Aside from the bogus hunks in the patch, and this one typo, the patch
looks to be in excellent shape.

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center

Attachments:

create-including_20090928a.patchapplication/octet-stream; name=create-including_20090928a.patchDownload
diff -cprN head/doc/src/sgml/ref/create_table.sgml work/doc/src/sgml/ref/create_table.sgml
*** head/doc/src/sgml/ref/create_table.sgml	2009-09-18 14:00:41.000000000 +0900
--- work/doc/src/sgml/ref/create_table.sgml	2009-09-28 11:16:34.150010328 +0900
*************** PostgreSQL documentation
*** 24,30 ****
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
--- 24,30 ----
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY 
*** 230,235 ****
--- 230,239 ----
        will always be chosen for it.
       </para>
  
+      <para>
+       Column storage parameters are also copied from parent tables.
+      </para>
+ 
  <!--
       <para>
        <productname>PostgreSQL</> automatically allows the
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY 
*** 247,253 ****
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
--- 251,257 ----
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY 
*** 281,286 ****
--- 285,307 ----
        specified.
       </para>
       <para>
+       Storage parameters for the copied column definitions will only be copied
+       if <literal>INCLUDING STORAGE</literal> is specified.  The default
+       behavior is to exclude storage parameters, resulting in the copied
+       columns in the new table having type-specific default parameters.  For
+       more on storage parameters, see <xref linkend="storage-toast">.
+      </para>
+      <para>
+       Comments for the copied column and constraint definitions will only be
+       copied if <literal>INCLUDING COMMENTS</literal> is specified.  The
+       default behavior is to exclude comments, resulting in the
+       copied columns and constraints in the new table having no comments.
+      </para>
+      <para>
+       <literal>INCLUDING ALL</literal> is an abbreviated form of
+       <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>.
+      </para>
+      <para>
        Note also that unlike <literal>INHERITS</literal>, copied columns and
        constraints are not merged with similarly named columns and constraints.
        If the same name is specified explicitly or in another
diff -cprN head/src/backend/access/common/tupdesc.c work/src/backend/access/common/tupdesc.c
*** head/src/backend/access/common/tupdesc.c	2009-08-03 07:14:51.000000000 +0900
--- work/src/backend/access/common/tupdesc.c	2009-09-28 10:52:37.666113379 +0900
*************** BuildDescForRelation(List *schema)
*** 558,563 ****
--- 558,565 ----
  		has_not_null |= entry->is_not_null;
  		desc->attrs[attnum - 1]->attislocal = entry->is_local;
  		desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
+ 		if (entry->storage)
+ 			desc->attrs[attnum - 1]->attstorage = entry->storage;
  	}
  
  	if (has_not_null)
diff -cprN head/src/backend/catalog/pg_constraint.c work/src/backend/catalog/pg_constraint.c
*** head/src/backend/catalog/pg_constraint.c	2009-07-28 11:56:29.000000000 +0900
--- work/src/backend/catalog/pg_constraint.c	2009-09-28 10:52:37.666113379 +0900
*************** AlterConstraintNamespaces(Oid ownerId, O
*** 702,704 ****
--- 702,766 ----
  
  	heap_close(conRel, RowExclusiveLock);
  }
+ 
+ /*
+  * GetConstraintByName
+  *		Find a constraint with the specified name.
+  */
+ Oid
+ GetConstraintByName(Oid relid, const char *conname)
+ {
+ 	Relation	pg_constraint;
+ 	HeapTuple	tuple;
+ 	SysScanDesc scan;
+ 	ScanKeyData skey[1];
+ 	Oid			conOid = InvalidOid;
+ 
+ 	/*
+ 	 * Fetch the constraint tuple from pg_constraint.  There may be more than
+ 	 * one match, because constraints are not required to have unique names;
+ 	 * if so, error out.
+ 	 */
+ 	pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ, relid);
+ 
+ 	scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
+ 							  SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ 	{
+ 		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ 
+ 		if (strcmp(NameStr(con->conname), conname) == 0)
+ 		{
+ 			if (OidIsValid(conOid))
+ 			{
+ 				char *relname = get_rel_name(relid);
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 				 errmsg("table \"%s\" has multiple constraints named \"%s\"",
+ 					(relname ? relname : "(unknown)"), conname)));
+ 			}
+ 			conOid = HeapTupleGetOid(tuple);
+ 		}
+ 	}
+ 
+ 	systable_endscan(scan);
+ 
+ 	/* If no constraint exists for the relation specified, notify user */
+ 	if (!OidIsValid(conOid))
+ 	{
+ 		char *relname = get_rel_name(relid);
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
+ 						conname, (relname ? relname : "(unknown)"))));
+ 	}
+ 
+ 	heap_close(pg_constraint, AccessShareLock);
+ 
+ 	return conOid;
+ }
diff -cprN head/src/backend/commands/comment.c work/src/backend/commands/comment.c
*** head/src/backend/commands/comment.c	2009-06-11 23:48:55.000000000 +0900
--- work/src/backend/commands/comment.c	2009-09-28 10:52:55.412382289 +0900
*************** DeleteSharedComments(Oid oid, Oid classo
*** 463,468 ****
--- 463,523 ----
  }
  
  /*
+  * GetComment -- get the comment for an object, or null if not found.
+  */
+ char *
+ GetComment(Oid oid, Oid classoid, int32 subid)
+ {
+ 	Relation	description;
+ 	ScanKeyData skey[3];
+ 	SysScanDesc sd;
+ 	TupleDesc	tupdesc;
+ 	HeapTuple	tuple;
+ 	char	   *comment;
+ 
+ 	/* Use the index to search for a matching old tuple */
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_description_objoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(oid));
+ 	ScanKeyInit(&skey[1],
+ 				Anum_pg_description_classoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(classoid));
+ 	ScanKeyInit(&skey[2],
+ 				Anum_pg_description_objsubid,
+ 				BTEqualStrategyNumber, F_INT4EQ,
+ 				Int32GetDatum(subid));
+ 
+ 	description = heap_open(DescriptionRelationId, AccessShareLock);
+ 	tupdesc = RelationGetDescr(description);
+ 
+ 	sd = systable_beginscan(description, DescriptionObjIndexId, true,
+ 							SnapshotNow, 3, skey);
+ 
+ 	comment  = NULL;
+ 	while ((tuple = systable_getnext(sd)) != NULL)
+ 	{
+ 		Datum	value;
+ 		bool	isnull;
+ 
+ 		/* Found the tuple, get description field */
+ 		value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
+ 		if (!isnull)
+ 			comment = TextDatumGetCString(value);
+ 		break;					/* Assume there can be only one match */
+ 	}
+ 
+ 	systable_endscan(sd);
+ 
+ 	/* Done */
+ 	heap_close(description, AccessShareLock);
+ 
+ 	return comment;
+ }
+ 
+ /*
   * CommentRelation --
   *
   * This routine is used to add/drop a comment from a relation, where
*************** CommentConstraint(List *qualname, char *
*** 1064,1075 ****
  	List	   *relName;
  	char	   *conName;
  	RangeVar   *rel;
! 	Relation	pg_constraint,
! 				relation;
! 	HeapTuple	tuple;
! 	SysScanDesc scan;
! 	ScanKeyData skey[1];
! 	Oid			conOid = InvalidOid;
  
  	/* Separate relname and constraint name */
  	nnames = list_length(qualname);
--- 1119,1126 ----
  	List	   *relName;
  	char	   *conName;
  	RangeVar   *rel;
! 	Relation	relation;
! 	Oid			conOid;
  
  	/* Separate relname and constraint name */
  	nnames = list_length(qualname);
*************** CommentConstraint(List *qualname, char *
*** 1088,1137 ****
  		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
  					   RelationGetRelationName(relation));
  
! 	/*
! 	 * Fetch the constraint tuple from pg_constraint.  There may be more than
! 	 * one match, because constraints are not required to have unique names;
! 	 * if so, error out.
! 	 */
! 	pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
! 
! 	ScanKeyInit(&skey[0],
! 				Anum_pg_constraint_conrelid,
! 				BTEqualStrategyNumber, F_OIDEQ,
! 				ObjectIdGetDatum(RelationGetRelid(relation)));
! 
! 	scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
! 							  SnapshotNow, 1, skey);
! 
! 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
! 	{
! 		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
! 
! 		if (strcmp(NameStr(con->conname), conName) == 0)
! 		{
! 			if (OidIsValid(conOid))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_DUPLICATE_OBJECT),
! 				 errmsg("table \"%s\" has multiple constraints named \"%s\"",
! 						RelationGetRelationName(relation), conName)));
! 			conOid = HeapTupleGetOid(tuple);
! 		}
! 	}
! 
! 	systable_endscan(scan);
! 
! 	/* If no constraint exists for the relation specified, notify user */
! 	if (!OidIsValid(conOid))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
! 						conName, RelationGetRelationName(relation))));
  
  	/* Call CreateComments() to create/drop the comments */
  	CreateComments(conOid, ConstraintRelationId, 0, comment);
  
  	/* Done, but hold lock on relation */
- 	heap_close(pg_constraint, AccessShareLock);
  	heap_close(relation, NoLock);
  }
  
--- 1139,1150 ----
  		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
  					   RelationGetRelationName(relation));
  
! 	conOid = GetConstraintByName(RelationGetRelid(relation), conName);
  
  	/* Call CreateComments() to create/drop the comments */
  	CreateComments(conOid, ConstraintRelationId, 0, comment);
  
  	/* Done, but hold lock on relation */
  	heap_close(relation, NoLock);
  }
  
diff -cprN head/src/backend/commands/tablecmds.c work/src/backend/commands/tablecmds.c
*** head/src/backend/commands/tablecmds.c	2009-09-27 07:42:01.000000000 +0900
--- work/src/backend/commands/tablecmds.c	2009-09-28 10:52:55.414056751 +0900
***************
*** 39,44 ****
--- 39,45 ----
  #include "catalog/storage.h"
  #include "catalog/toasting.h"
  #include "commands/cluster.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/sequence.h"
  #include "commands/tablecmds.h"
*************** truncate_check_rel(Relation rel)
*** 1099,1104 ****
--- 1100,1118 ----
  	CheckTableNotInUse(rel, "TRUNCATE");
  }
  
+ static const char *
+ storage_name(char c)
+ {
+ 	switch (c)
+ 	{
+ 		case 'p': return "PLAIN";
+ 		case 'm': return "MAIN";
+ 		case 'x': return "EXTENDED";
+ 		case 'e': return "EXTERNAL";
+ 		default: return "???";
+ 	}
+ }
+ 
  /*----------
   * MergeAttributes
   *		Returns new schema given initial schema and superclasses.
*************** MergeAttributes(List *schema, List *supe
*** 1167,1172 ****
--- 1181,1187 ----
  	List	   *constraints = NIL;
  	int			parentsWithOids = 0;
  	bool		have_bogus_defaults = false;
+ 	bool		have_bogus_comments = false;
  	char	   *bogus_marker = "Bogus!";		/* marks conflicting defaults */
  	int			child_attno;
  
*************** MergeAttributes(List *schema, List *supe
*** 1322,1327 ****
--- 1337,1354 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   format_type_be(attribute->atttypid))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = attribute->attstorage;
+ 				else if (def->storage != attribute->attstorage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("inherited column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(attribute->attstorage))));
+ 
  				def->inhcount++;
  				/* Merge of NOT NULL constraints = OR 'em together */
  				def->is_not_null |= attribute->attnotnull;
*************** MergeAttributes(List *schema, List *supe
*** 1343,1348 ****
--- 1370,1376 ----
  				def->raw_default = NULL;
  				def->cooked_default = NULL;
  				def->constraints = NIL;
+ 				def->storage = attribute->attstorage;
  				inhSchema = lappend(inhSchema, def);
  				newattno[parent_attno - 1] = ++child_attno;
  			}
*************** MergeAttributes(List *schema, List *supe
*** 1480,1485 ****
--- 1508,1525 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   TypeNameToString(newdef->typeName))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = newdef->storage;
+ 				else if (newdef->storage != 0 && def->storage != newdef->storage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(newdef->storage))));
+ 
  				/* Mark the column as locally defined */
  				def->is_local = true;
  				/* Merge of NOT NULL constraints = OR 'em together */
*************** MergeAttributes(List *schema, List *supe
*** 1532,1537 ****
--- 1572,1591 ----
  		}
  	}
  
+ 	/* Raise an error if we found conflicting comments. */
+ 	if (have_bogus_comments)
+ 	{
+ 		foreach(entry, schema)
+ 		{
+ 			ColumnDef  *def = lfirst(entry);
+ 
+ 			if (def->cooked_default == bogus_marker)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ 				  errmsg("column \"%s\" inherits conflicting comments", def->colname)));
+ 		}
+ 	}
+ 
  	*supOids = parentOids;
  	*supconstr = constraints;
  	*supOidCount = parentsWithOids;
diff -cprN head/src/backend/nodes/copyfuncs.c work/src/backend/nodes/copyfuncs.c
*** head/src/backend/nodes/copyfuncs.c	2009-09-23 08:43:37.000000000 +0900
--- work/src/backend/nodes/copyfuncs.c	2009-09-28 11:11:53.099995625 +0900
*************** _copyInhRelation(InhRelation *from)
*** 2416,2422 ****
  	InhRelation *newnode = makeNode(InhRelation);
  
  	COPY_NODE_FIELD(relation);
! 	COPY_NODE_FIELD(options);
  
  	return newnode;
  }
--- 2416,2422 ----
  	InhRelation *newnode = makeNode(InhRelation);
  
  	COPY_NODE_FIELD(relation);
! 	COPY_SCALAR_FIELD(options);
  
  	return newnode;
  }
diff -cprN head/src/backend/nodes/equalfuncs.c work/src/backend/nodes/equalfuncs.c
*** head/src/backend/nodes/equalfuncs.c	2009-09-23 08:43:38.000000000 +0900
--- work/src/backend/nodes/equalfuncs.c	2009-09-28 11:12:33.998113738 +0900
*************** static bool
*** 1087,1093 ****
  _equalInhRelation(InhRelation *a, InhRelation *b)
  {
  	COMPARE_NODE_FIELD(relation);
! 	COMPARE_NODE_FIELD(options);
  
  	return true;
  }
--- 1087,1093 ----
  _equalInhRelation(InhRelation *a, InhRelation *b)
  {
  	COMPARE_NODE_FIELD(relation);
! 	COMPARE_SCALAR_FIELD(options);
  
  	return true;
  }
diff -cprN head/src/backend/parser/gram.y work/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-09-23 08:43:38.000000000 +0900
--- work/src/backend/parser/gram.y	2009-09-28 11:17:17.711010198 +0900
*************** static TypeName *TableFuncTypeName(List 
*** 399,406 ****
  %type <keyword> col_name_keyword reserved_keyword
  
  %type <node>	TableConstraint TableLikeClause
! %type <list>	TableLikeOptionList
! %type <ival>	TableLikeOption
  %type <list>	ColQualList
  %type <node>	ColConstraint ColConstraintElem ConstraintAttr
  %type <ival>	key_actions key_delete key_match key_update key_action
--- 399,405 ----
  %type <keyword> col_name_keyword reserved_keyword
  
  %type <node>	TableConstraint TableLikeClause
! %type <ival>	TableLikeOptionList TableLikeOption
  %type <list>	ColQualList
  %type <node>	ColConstraint ColConstraintElem ConstraintAttr
  %type <ival>	key_actions key_delete key_match key_update key_action
*************** static TypeName *TableFuncTypeName(List 
*** 458,464 ****
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
--- 457,463 ----
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMENTS COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
*************** TableLikeClause:
*** 2392,2408 ****
  		;
  
  TableLikeOptionList:
! 				TableLikeOptionList TableLikeOption	{ $$ = lappend_int($1, $2); }
! 				| /* EMPTY */						{ $$ = NIL; }
  		;
  
  TableLikeOption:
! 				INCLUDING DEFAULTS					{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_DEFAULTS; }
! 				| EXCLUDING DEFAULTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS; }
! 				| INCLUDING CONSTRAINTS				{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS; }
! 				| EXCLUDING CONSTRAINTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS; }
! 				| INCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_INDEXES; }
! 				| EXCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES; }
  		;
  
  
--- 2391,2408 ----
  		;
  
  TableLikeOptionList:
! 				TableLikeOptionList INCLUDING TableLikeOption	{ $$ = $1 | $3; }
! 				| TableLikeOptionList EXCLUDING TableLikeOption	{ $$ = $1 & ~$3; }
! 				| /* EMPTY */						{ $$ = 0; }
  		;
  
  TableLikeOption:
! 				DEFAULTS			{ $$ = CREATE_TABLE_LIKE_DEFAULTS; }
! 				| CONSTRAINTS		{ $$ = CREATE_TABLE_LIKE_CONSTRAINTS; }
! 				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
! 				| STORAGE			{ $$ = CREATE_TABLE_LIKE_STORAGE; }
! 				| COMMENTS			{ $$ = CREATE_TABLE_LIKE_COMMENTS; }
! 				| ALL				{ $$ = CREATE_TABLE_LIKE_ALL; }
  		;
  
  
*************** unreserved_keyword:
*** 10325,10330 ****
--- 10325,10331 ----
  			| CLOSE
  			| CLUSTER
  			| COMMENT
+ 			| COMMENTS
  			| COMMIT
  			| COMMITTED
  			| CONCURRENTLY
diff -cprN head/src/backend/parser/parse_utilcmd.c work/src/backend/parser/parse_utilcmd.c
*** head/src/backend/parser/parse_utilcmd.c	2009-07-30 11:45:37.000000000 +0900
--- work/src/backend/parser/parse_utilcmd.c	2009-09-28 11:04:46.729994997 +0900
***************
*** 36,41 ****
--- 36,42 ----
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_opclass.h"
  #include "catalog/pg_type.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/tablecmds.h"
  #include "commands/tablespace.h"
*************** transformInhRelation(ParseState *pstate,
*** 546,555 ****
  	TupleDesc	tupleDesc;
  	TupleConstr *constr;
  	AclResult	aclresult;
! 	bool		including_defaults = false;
! 	bool		including_constraints = false;
! 	bool		including_indexes = false;
! 	ListCell   *elem;
  
  	relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock);
  
--- 547,553 ----
  	TupleDesc	tupleDesc;
  	TupleConstr *constr;
  	AclResult	aclresult;
! 	char	   *comment;
  
  	relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock);
  
*************** transformInhRelation(ParseState *pstate,
*** 571,606 ****
  	tupleDesc = RelationGetDescr(relation);
  	constr = tupleDesc->constr;
  
- 	foreach(elem, inhRelation->options)
- 	{
- 		int			option = lfirst_int(elem);
- 
- 		switch (option)
- 		{
- 			case CREATE_TABLE_LIKE_INCLUDING_DEFAULTS:
- 				including_defaults = true;
- 				break;
- 			case CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS:
- 				including_defaults = false;
- 				break;
- 			case CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS:
- 				including_constraints = true;
- 				break;
- 			case CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS:
- 				including_constraints = false;
- 				break;
- 			case CREATE_TABLE_LIKE_INCLUDING_INDEXES:
- 				including_indexes = true;
- 				break;
- 			case CREATE_TABLE_LIKE_EXCLUDING_INDEXES:
- 				including_indexes = false;
- 				break;
- 			default:
- 				elog(ERROR, "unrecognized CREATE TABLE LIKE option: %d",
- 					 option);
- 		}
- 	}
- 
  	/*
  	 * Insert the copied attributes into the cxt for the new table definition.
  	 */
--- 569,574 ----
*************** transformInhRelation(ParseState *pstate,
*** 642,648 ****
  		/*
  		 * Copy default, if present and the default has been requested
  		 */
! 		if (attribute->atthasdef && including_defaults)
  		{
  			char	   *this_default = NULL;
  			AttrDefault *attrdef;
--- 610,617 ----
  		/*
  		 * Copy default, if present and the default has been requested
  		 */
! 		if (attribute->atthasdef &&
! 			(inhRelation->options & CREATE_TABLE_LIKE_DEFAULTS))
  		{
  			char	   *this_default = NULL;
  			AttrDefault *attrdef;
*************** transformInhRelation(ParseState *pstate,
*** 668,680 ****
  
  			def->cooked_default = pstrdup(this_default);
  		}
  	}
  
  	/*
  	 * Copy CHECK constraints if requested, being careful to adjust attribute
  	 * numbers
  	 */
! 	if (including_constraints && tupleDesc->constr)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		int			ccnum;
--- 637,670 ----
  
  			def->cooked_default = pstrdup(this_default);
  		}
+ 
+ 		/* Likewise, copy storage if requested */
+ 		if (inhRelation->options & CREATE_TABLE_LIKE_STORAGE)
+ 			def->storage = attribute->attstorage;
+ 
+ 		/* Likewise, copy comment if requested */
+ 		if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) &&
+ 			(comment = GetComment(attribute->attrelid, RelationRelationId,
+ 			attribute->attnum)) != NULL)
+ 		{
+ 			CommentStmt *stmt = makeNode(CommentStmt);
+ 
+ 			stmt->objtype = OBJECT_COLUMN;
+ 			stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ 									   makeString(cxt->relation->relname),
+ 									   makeString(def->colname));
+ 			stmt->objargs = NIL;
+ 			stmt->comment = comment;
+ 
+ 			cxt->alist = lappend(cxt->alist, stmt);
+ 		}
  	}
  
  	/*
  	 * Copy CHECK constraints if requested, being careful to adjust attribute
  	 * numbers
  	 */
! 	if ((inhRelation->options & CREATE_TABLE_LIKE_CONSTRAINTS) && tupleDesc->constr)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		int			ccnum;
*************** transformInhRelation(ParseState *pstate,
*** 694,706 ****
  			n->raw_expr = NULL;
  			n->cooked_expr = nodeToString(ccbin_node);
  			cxt->ckconstraints = lappend(cxt->ckconstraints, n);
  		}
  	}
  
  	/*
  	 * Likewise, copy indexes if requested
  	 */
! 	if (including_indexes && relation->rd_rel->relhasindex)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		List	   *parent_indexes;
--- 684,714 ----
  			n->raw_expr = NULL;
  			n->cooked_expr = nodeToString(ccbin_node);
  			cxt->ckconstraints = lappend(cxt->ckconstraints, n);
+ 
+ 			/* Copy comment on constraint */
+ 			if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) &&
+ 				(comment = GetComment(GetConstraintByName(RelationGetRelid(
+ 				relation), n->conname), ConstraintRelationId, 0)) != NULL)
+ 			{
+ 				CommentStmt *stmt = makeNode(CommentStmt);
+ 
+ 				stmt->objtype = OBJECT_CONSTRAINT;
+ 				stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ 										   makeString(cxt->relation->relname),
+ 										   makeString(n->conname));
+ 				stmt->objargs = NIL;
+ 				stmt->comment = comment;
+ 
+ 				cxt->alist = lappend(cxt->alist, stmt);
+ 			}
  		}
  	}
  
  	/*
  	 * Likewise, copy indexes if requested
  	 */
! 	if ((inhRelation->options & CREATE_TABLE_LIKE_INDEXES) &&
! 		relation->rd_rel->relhasindex)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		List	   *parent_indexes;
*************** transformInhRelation(ParseState *pstate,
*** 719,724 ****
--- 727,734 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			/* XXX: should copy comment on index? */
+ 
  			/* Save it in the inh_indexes list for the time being */
  			cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt);
  
diff -cprN head/src/include/catalog/pg_constraint.h work/src/include/catalog/pg_constraint.h
*** head/src/include/catalog/pg_constraint.h	2009-07-28 11:56:31.000000000 +0900
--- work/src/include/catalog/pg_constraint.h	2009-09-28 10:52:37.673113381 +0900
*************** extern char *ChooseConstraintName(const 
*** 226,230 ****
--- 226,231 ----
  
  extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
  						  Oid newNspId, bool isType);
+ extern Oid GetConstraintByName(Oid relid, const char *conname);
  
  #endif   /* PG_CONSTRAINT_H */
diff -cprN head/src/include/commands/comment.h work/src/include/commands/comment.h
*** head/src/include/commands/comment.h	2009-06-11 23:49:11.000000000 +0900
--- work/src/include/commands/comment.h	2009-09-28 10:52:37.673113381 +0900
*************** extern void DeleteSharedComments(Oid oid
*** 39,42 ****
--- 39,44 ----
  
  extern void CreateSharedComments(Oid oid, Oid classoid, char *comment);
  
+ extern char *GetComment(Oid oid, Oid classoid, int32 subid);
+ 
  #endif   /* COMMENT_H */
diff -cprN head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-09-23 08:43:41.000000000 +0900
--- work/src/include/nodes/parsenodes.h	2009-09-28 10:58:36.806014857 +0900
*************** typedef struct ColumnDef
*** 461,466 ****
--- 461,467 ----
  	int			inhcount;		/* number of times column is inherited */
  	bool		is_local;		/* column has local (non-inherited) def'n */
  	bool		is_not_null;	/* NOT NULL constraint specified? */
+ 	char		storage;		/* storage parameter of column */
  	Node	   *raw_default;	/* default value (untransformed parse tree) */
  	char	   *cooked_default; /* nodeToString representation */
  	List	   *constraints;	/* other constraints on column */
*************** typedef struct InhRelation
*** 473,489 ****
  {
  	NodeTag		type;
  	RangeVar   *relation;
! 	List	   *options;		/* integer List of CreateStmtLikeOption */
  } InhRelation;
  
  typedef enum CreateStmtLikeOption
  {
! 	CREATE_TABLE_LIKE_INCLUDING_DEFAULTS,
! 	CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS,
! 	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
! 	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS,
! 	CREATE_TABLE_LIKE_INCLUDING_INDEXES,
! 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES
  } CreateStmtLikeOption;
  
  /*
--- 474,490 ----
  {
  	NodeTag		type;
  	RangeVar   *relation;
! 	bits32		options;		/* bitmap of CreateStmtLikeOption */
  } InhRelation;
  
  typedef enum CreateStmtLikeOption
  {
! 	CREATE_TABLE_LIKE_DEFAULTS		= 1 << 0,
! 	CREATE_TABLE_LIKE_CONSTRAINTS	= 1 << 1,
! 	CREATE_TABLE_LIKE_INDEXES		= 1 << 2,
! 	CREATE_TABLE_LIKE_STORAGE		= 1 << 3,
! 	CREATE_TABLE_LIKE_COMMENTS		= 1 << 4,
! 	CREATE_TABLE_LIKE_ALL			= 0xFFFFFFFF
  } CreateStmtLikeOption;
  
  /*
diff -cprN head/src/include/parser/kwlist.h work/src/include/parser/kwlist.h
*** head/src/include/parser/kwlist.h	2009-09-23 08:43:41.000000000 +0900
--- work/src/include/parser/kwlist.h	2009-09-28 10:52:37.674113571 +0900
*************** PG_KEYWORD("coalesce", COALESCE, COL_NAM
*** 80,85 ****
--- 80,86 ----
  PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
  PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
  PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
  PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
  PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
  PG_KEYWORD("concurrently", CONCURRENTLY, UNRESERVED_KEYWORD)
diff -cprN head/src/test/regress/expected/inherit.out work/src/test/regress/expected/inherit.out
*** head/src/test/regress/expected/inherit.out	2009-08-02 04:59:41.000000000 +0900
--- work/src/test/regress/expected/inherit.out	2009-09-28 10:52:37.675113379 +0900
*************** drop table pp1 cascade;
*** 906,908 ****
--- 906,1012 ----
  NOTICE:  drop cascades to 2 other objects
  DETAIL:  drop cascades to table cc1
  drop cascades to table cc2
+ -- including storage and comments
+ CREATE TABLE t1 (a text CHECK (length(a) > 2), b text);
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ CREATE TABLE t3 (a text CHECK (length(a) < 5), c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check';
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE);
+ \d+ t12_storage
+              Table "public.t12_storage"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Has OIDs: no
+ 
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+             Table "public.t12_comments"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | extended | A
+  b      | text |           | extended | B
+  c      | text |           | extended | C
+ Has OIDs: no
+ 
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ NOTICE:  merging column "b" with inherited definition
+ NOTICE:  merging constraint "t1_a_check" with inherited definition
+ \d+ t1_inh
+                Table "public.t1_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | A
+  b      | text |           | extended | B
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+ Inherits: t1
+ Has OIDs: no
+ 
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass;
+  description 
+ -------------
+  t1_a_check
+ (1 row)
+ 
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ \d+ t13_inh
+                Table "public.t13_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+     "t3_a_check" CHECK (length(a) < 5)
+ Inherits: t1,
+           t3
+ Has OIDs: no
+ 
+ CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ \d+ t13_like
+               Table "public.t13_like"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text |           | main     | A3
+  b      | text |           | extended | 
+  c      | text |           | external | C
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+     "t3_a_check" CHECK (length(a) < 5)
+ Inherits: t1
+ Has OIDs: no
+ 
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass;
+  description 
+ -------------
+  t3_a_check
+ (1 row)
+ 
+ CREATE TABLE inh_error1 () INHERITS (t1, t4);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ ERROR:  inherited column "a" has a storage parameter conflict
+ DETAIL:  MAIN versus EXTENDED
+ CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
+ 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;
diff -cprN head/src/test/regress/sql/inherit.sql work/src/test/regress/sql/inherit.sql
*** head/src/test/regress/sql/inherit.sql	2008-05-10 08:32:05.000000000 +0900
--- work/src/test/regress/sql/inherit.sql	2009-09-28 10:52:37.675113379 +0900
*************** create table cc2(f4 float) inherits(pp1,
*** 276,278 ****
--- 276,317 ----
  alter table pp1 add column a2 int check (a2 > 0);
  \d cc2
  drop table pp1 cascade;
+ 
+ -- including storage and comments
+ CREATE TABLE t1 (a text CHECK (length(a) > 2), b text);
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ 
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ 
+ CREATE TABLE t3 (a text CHECK (length(a) < 5), c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check';
+ 
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ 
+ CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE);
+ \d+ t12_storage
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1);
+ \d+ t1_inh
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass;
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ \d+ t13_inh
+ CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1);
+ \d+ t13_like
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass;
+ 
+ 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;
#13Alvaro Herrera
alvherre@commandprompt.com
In reply to: Itagaki Takahiro (#12)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Itagaki Takahiro escribi�:

I removed hunks by sql_help.c and fix a typo in documentation.
An updated patch attached.

Hmm, so it works to specify LIKE t1 INCLUDING COMMENTS EXCLUDING COMMENTS?

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

#14Itagaki Takahiro
itagaki.takahiro@oss.ntt.co.jp
In reply to: Alvaro Herrera (#13)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Alvaro Herrera <alvherre@commandprompt.com> wrote:

Hmm, so it works to specify LIKE t1 INCLUDING COMMENTS EXCLUDING COMMENTS?

Only last specifer is applied, which is the same behavior as of now.

EXCLUDING is typically useless because all of the default values are
EXCLUDING, but "INCLUDING ALL EXCLUDING xxx" are meaningful; it copies
all components except xxx.

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center

#15Andrew Dunstan
andrew@dunslane.net
In reply to: Itagaki Takahiro (#12)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Itagaki Takahiro wrote:

I removed hunks by sql_help.c and fix a typo in documentation.
An updated patch attached.

Brendan Jurd <direvus@gmail.com> wrote:

With the sql_help.c changes removed, the patch applied fine and
testing went well.

I noticed only the following in the new documentation in CREATE TABLE:
-      <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGES INCLUDING COMMENTS</literal>.
+      <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>.

Aside from the bogus hunks in the patch, and this one typo, the patch
looks to be in excellent shape.

I'm reviewing this patch with a view to committing it, since the other
patch I was going to look at still seemed to be subject to some
discussion. In general it looks OK, but I'm wondering why we are not
copying comments on cloned indexes. I realize that might involve a bit
more code, but I think I'd feel happier if we cloned all the comments we
reasonably could from the outset. Is it really that hard to do?

cheers

andrew

#16Khee Chin
kheechin@gmail.com
In reply to: Andrew Dunstan (#15)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Recently, I encountered a situation where the docs on (or impl?)
INCLUDING INDEXES and INCLUDING CONSTRAINTS are not clearly defined
for primary keys. Should it be noted in the docs that in this case, we
are referring to the technical implementation of a primary key, i.e. a
unique index and a not null constraint, thus both conditions are
required?

testdb=> CREATE TABLE foo (id int primary key);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index
"foo_pkey" for table "foo"
CREATE TABLE
testdb=> \d foo;
Table "public.foo"
Column | Type | Modifiers
--------+---------+-----------
id | integer | not null
Indexes:
"foo_pkey" PRIMARY KEY, btree (id)

testdb=> CREATE TABLE foo2 (LIKE FOO INCLUDING CONSTRAINTS EXCLUDING INDEXES);
CREATE TABLE
testdb=> \d foo2
Table "public.foo2"
Column | Type | Modifiers
--------+---------+-----------
id | integer | not null

testdb=> CREATE TABLE foo3 (LIKE FOO EXCLUDING CONSTRAINTS INCLUDING INDEXES);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index
"foo3_pkey" for table "foo3"
CREATE TABLE
testdb=> \d foo3;
Table "public.foo3"
Column | Type | Modifiers
--------+---------+-----------
id | integer | not null
Indexes:
"foo3_pkey" PRIMARY KEY, btree (id)

testdb=>

Regards,
Khee Chin.

#17Itagaki Takahiro
itagaki.takahiro@oss.ntt.co.jp
In reply to: Khee Chin (#16)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Khee Chin <kheechin@gmail.com> wrote:

Recently, I encountered a situation where the docs on (or impl?)
INCLUDING INDEXES and INCLUDING CONSTRAINTS are not clearly defined
for primary keys. Should it be noted in the docs that in this case, we
are referring to the technical implementation of a primary key, i.e. a
unique index and a not null constraint, thus both conditions are
required?

It might be a confusable feature, but it should be discussed separated
from this patch. IMO, almost all user will use "INCLUDING ALL"
if the syntax is added by the patch.

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center

#18Itagaki Takahiro
itagaki.takahiro@oss.ntt.co.jp
In reply to: Andrew Dunstan (#15)
1 attachment(s)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Andrew Dunstan <andrew@dunslane.net> wrote:

I'm wondering why we are not
copying comments on cloned indexes. I realize that might involve a bit
more code, but I think I'd feel happier if we cloned all the comments we
reasonably could from the outset. Is it really that hard to do?

I found it is not so difficult as I expected; patch attached. Now it copies
comments on indexes and columns of the indexes on INCLUDING COMMENTS.
Regression test and documentation are also adjusted. Please review around
chooseIndexName() and uses of it.

The codes becomes a bit complex and might be ugly because we will have some
duplicated codes; "pg_expression_%d" hacks and uses of ChooseRelationName()
are spread into index.c, indexcmds.c and parse_utilcmd.c.

Regards,
---
ITAGAKI Takahiro
NTT Open Source Software Center

Attachments:

create-including_20091005.patchapplication/octet-stream; name=create-including_20091005.patchDownload
diff -cprN head/doc/src/sgml/ref/create_table.sgml work/doc/src/sgml/ref/create_table.sgml
*** head/doc/src/sgml/ref/create_table.sgml	2009-09-18 14:00:41.000000000 +0900
--- work/doc/src/sgml/ref/create_table.sgml	2009-10-05 10:00:12.756304704 +0900
*************** PostgreSQL documentation
*** 24,30 ****
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
--- 24,30 ----
  CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable> ( [
    { <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [ DEFAULT <replaceable>default_expr</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
      | <replaceable>table_constraint</replaceable>
!     | LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } ] ... }
      [, ... ]
  ] )
  [ INHERITS ( <replaceable>parent_table</replaceable> [, ... ] ) ]
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY 
*** 230,235 ****
--- 230,239 ----
        will always be chosen for it.
       </para>
  
+      <para>
+       Column storage parameters are also copied from parent tables.
+      </para>
+ 
  <!--
       <para>
        <productname>PostgreSQL</> automatically allows the
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY 
*** 247,253 ****
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
--- 251,257 ----
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LIKE <replaceable>parent_table</replaceable> [ { INCLUDING | EXCLUDING } { DEFAULTS | CONSTRAINTS | INDEXES | STORAGE | COMMENTS | ALL } ]</literal></term>
      <listitem>
       <para>
        The <literal>LIKE</literal> clause specifies a table from which
*************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY 
*** 281,286 ****
--- 285,307 ----
        specified.
       </para>
       <para>
+       Storage parameters for the copied column definitions will only be copied
+       if <literal>INCLUDING STORAGE</literal> is specified.  The default
+       behavior is to exclude storage parameters, resulting in the copied
+       columns in the new table having type-specific default parameters.  For
+       more on storage parameters, see <xref linkend="storage-toast">.
+      </para>
+      <para>
+       Comments for the copied column, constraint, index and columns of index
+ 	  definitions will only be copied if <literal>INCLUDING COMMENTS</literal>
+ 	  is specified. The default behavior is to exclude comments, resulting in
+ 	  the copied columns and constraints in the new table having no comments.
+      </para>
+      <para>
+       <literal>INCLUDING ALL</literal> is an abbreviated form of
+       <literal>INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING INDEXES INCLUDING STORAGE INCLUDING COMMENTS</literal>.
+      </para>
+      <para>
        Note also that unlike <literal>INHERITS</literal>, copied columns and
        constraints are not merged with similarly named columns and constraints.
        If the same name is specified explicitly or in another
diff -cprN head/src/backend/access/common/tupdesc.c work/src/backend/access/common/tupdesc.c
*** head/src/backend/access/common/tupdesc.c	2009-08-03 07:14:51.000000000 +0900
--- work/src/backend/access/common/tupdesc.c	2009-10-05 09:30:43.971316532 +0900
*************** BuildDescForRelation(List *schema)
*** 558,563 ****
--- 558,565 ----
  		has_not_null |= entry->is_not_null;
  		desc->attrs[attnum - 1]->attislocal = entry->is_local;
  		desc->attrs[attnum - 1]->attinhcount = entry->inhcount;
+ 		if (entry->storage)
+ 			desc->attrs[attnum - 1]->attstorage = entry->storage;
  	}
  
  	if (has_not_null)
diff -cprN head/src/backend/catalog/pg_constraint.c work/src/backend/catalog/pg_constraint.c
*** head/src/backend/catalog/pg_constraint.c	2009-07-28 11:56:29.000000000 +0900
--- work/src/backend/catalog/pg_constraint.c	2009-10-05 09:30:43.971316532 +0900
*************** AlterConstraintNamespaces(Oid ownerId, O
*** 702,704 ****
--- 702,766 ----
  
  	heap_close(conRel, RowExclusiveLock);
  }
+ 
+ /*
+  * GetConstraintByName
+  *		Find a constraint with the specified name.
+  */
+ Oid
+ GetConstraintByName(Oid relid, const char *conname)
+ {
+ 	Relation	pg_constraint;
+ 	HeapTuple	tuple;
+ 	SysScanDesc scan;
+ 	ScanKeyData skey[1];
+ 	Oid			conOid = InvalidOid;
+ 
+ 	/*
+ 	 * Fetch the constraint tuple from pg_constraint.  There may be more than
+ 	 * one match, because constraints are not required to have unique names;
+ 	 * if so, error out.
+ 	 */
+ 	pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_constraint_conrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ, relid);
+ 
+ 	scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
+ 							  SnapshotNow, 1, skey);
+ 
+ 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ 	{
+ 		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
+ 
+ 		if (strcmp(NameStr(con->conname), conname) == 0)
+ 		{
+ 			if (OidIsValid(conOid))
+ 			{
+ 				char *relname = get_rel_name(relid);
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DUPLICATE_OBJECT),
+ 				 errmsg("table \"%s\" has multiple constraints named \"%s\"",
+ 					(relname ? relname : "(unknown)"), conname)));
+ 			}
+ 			conOid = HeapTupleGetOid(tuple);
+ 		}
+ 	}
+ 
+ 	systable_endscan(scan);
+ 
+ 	/* If no constraint exists for the relation specified, notify user */
+ 	if (!OidIsValid(conOid))
+ 	{
+ 		char *relname = get_rel_name(relid);
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_OBJECT),
+ 				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
+ 						conname, (relname ? relname : "(unknown)"))));
+ 	}
+ 
+ 	heap_close(pg_constraint, AccessShareLock);
+ 
+ 	return conOid;
+ }
diff -cprN head/src/backend/commands/comment.c work/src/backend/commands/comment.c
*** head/src/backend/commands/comment.c	2009-06-11 23:48:55.000000000 +0900
--- work/src/backend/commands/comment.c	2009-10-05 09:30:43.972761818 +0900
*************** DeleteSharedComments(Oid oid, Oid classo
*** 463,468 ****
--- 463,523 ----
  }
  
  /*
+  * GetComment -- get the comment for an object, or null if not found.
+  */
+ char *
+ GetComment(Oid oid, Oid classoid, int32 subid)
+ {
+ 	Relation	description;
+ 	ScanKeyData skey[3];
+ 	SysScanDesc sd;
+ 	TupleDesc	tupdesc;
+ 	HeapTuple	tuple;
+ 	char	   *comment;
+ 
+ 	/* Use the index to search for a matching old tuple */
+ 
+ 	ScanKeyInit(&skey[0],
+ 				Anum_pg_description_objoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(oid));
+ 	ScanKeyInit(&skey[1],
+ 				Anum_pg_description_classoid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(classoid));
+ 	ScanKeyInit(&skey[2],
+ 				Anum_pg_description_objsubid,
+ 				BTEqualStrategyNumber, F_INT4EQ,
+ 				Int32GetDatum(subid));
+ 
+ 	description = heap_open(DescriptionRelationId, AccessShareLock);
+ 	tupdesc = RelationGetDescr(description);
+ 
+ 	sd = systable_beginscan(description, DescriptionObjIndexId, true,
+ 							SnapshotNow, 3, skey);
+ 
+ 	comment  = NULL;
+ 	while ((tuple = systable_getnext(sd)) != NULL)
+ 	{
+ 		Datum	value;
+ 		bool	isnull;
+ 
+ 		/* Found the tuple, get description field */
+ 		value = heap_getattr(tuple, Anum_pg_description_description, tupdesc, &isnull);
+ 		if (!isnull)
+ 			comment = TextDatumGetCString(value);
+ 		break;					/* Assume there can be only one match */
+ 	}
+ 
+ 	systable_endscan(sd);
+ 
+ 	/* Done */
+ 	heap_close(description, AccessShareLock);
+ 
+ 	return comment;
+ }
+ 
+ /*
   * CommentRelation --
   *
   * This routine is used to add/drop a comment from a relation, where
*************** CommentConstraint(List *qualname, char *
*** 1064,1075 ****
  	List	   *relName;
  	char	   *conName;
  	RangeVar   *rel;
! 	Relation	pg_constraint,
! 				relation;
! 	HeapTuple	tuple;
! 	SysScanDesc scan;
! 	ScanKeyData skey[1];
! 	Oid			conOid = InvalidOid;
  
  	/* Separate relname and constraint name */
  	nnames = list_length(qualname);
--- 1119,1126 ----
  	List	   *relName;
  	char	   *conName;
  	RangeVar   *rel;
! 	Relation	relation;
! 	Oid			conOid;
  
  	/* Separate relname and constraint name */
  	nnames = list_length(qualname);
*************** CommentConstraint(List *qualname, char *
*** 1088,1137 ****
  		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
  					   RelationGetRelationName(relation));
  
! 	/*
! 	 * Fetch the constraint tuple from pg_constraint.  There may be more than
! 	 * one match, because constraints are not required to have unique names;
! 	 * if so, error out.
! 	 */
! 	pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
! 
! 	ScanKeyInit(&skey[0],
! 				Anum_pg_constraint_conrelid,
! 				BTEqualStrategyNumber, F_OIDEQ,
! 				ObjectIdGetDatum(RelationGetRelid(relation)));
! 
! 	scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId, true,
! 							  SnapshotNow, 1, skey);
! 
! 	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
! 	{
! 		Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(tuple);
! 
! 		if (strcmp(NameStr(con->conname), conName) == 0)
! 		{
! 			if (OidIsValid(conOid))
! 				ereport(ERROR,
! 						(errcode(ERRCODE_DUPLICATE_OBJECT),
! 				 errmsg("table \"%s\" has multiple constraints named \"%s\"",
! 						RelationGetRelationName(relation), conName)));
! 			conOid = HeapTupleGetOid(tuple);
! 		}
! 	}
! 
! 	systable_endscan(scan);
! 
! 	/* If no constraint exists for the relation specified, notify user */
! 	if (!OidIsValid(conOid))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_UNDEFINED_OBJECT),
! 				 errmsg("constraint \"%s\" for table \"%s\" does not exist",
! 						conName, RelationGetRelationName(relation))));
  
  	/* Call CreateComments() to create/drop the comments */
  	CreateComments(conOid, ConstraintRelationId, 0, comment);
  
  	/* Done, but hold lock on relation */
- 	heap_close(pg_constraint, AccessShareLock);
  	heap_close(relation, NoLock);
  }
  
--- 1139,1150 ----
  		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
  					   RelationGetRelationName(relation));
  
! 	conOid = GetConstraintByName(RelationGetRelid(relation), conName);
  
  	/* Call CreateComments() to create/drop the comments */
  	CreateComments(conOid, ConstraintRelationId, 0, comment);
  
  	/* Done, but hold lock on relation */
  	heap_close(relation, NoLock);
  }
  
diff -cprN head/src/backend/commands/tablecmds.c work/src/backend/commands/tablecmds.c
*** head/src/backend/commands/tablecmds.c	2009-09-27 07:42:01.000000000 +0900
--- work/src/backend/commands/tablecmds.c	2009-10-05 09:30:43.975503803 +0900
***************
*** 39,44 ****
--- 39,45 ----
  #include "catalog/storage.h"
  #include "catalog/toasting.h"
  #include "commands/cluster.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/sequence.h"
  #include "commands/tablecmds.h"
*************** truncate_check_rel(Relation rel)
*** 1099,1104 ****
--- 1100,1118 ----
  	CheckTableNotInUse(rel, "TRUNCATE");
  }
  
+ static const char *
+ storage_name(char c)
+ {
+ 	switch (c)
+ 	{
+ 		case 'p': return "PLAIN";
+ 		case 'm': return "MAIN";
+ 		case 'x': return "EXTENDED";
+ 		case 'e': return "EXTERNAL";
+ 		default: return "???";
+ 	}
+ }
+ 
  /*----------
   * MergeAttributes
   *		Returns new schema given initial schema and superclasses.
*************** MergeAttributes(List *schema, List *supe
*** 1167,1172 ****
--- 1181,1187 ----
  	List	   *constraints = NIL;
  	int			parentsWithOids = 0;
  	bool		have_bogus_defaults = false;
+ 	bool		have_bogus_comments = false;
  	char	   *bogus_marker = "Bogus!";		/* marks conflicting defaults */
  	int			child_attno;
  
*************** MergeAttributes(List *schema, List *supe
*** 1322,1327 ****
--- 1337,1354 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   format_type_be(attribute->atttypid))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = attribute->attstorage;
+ 				else if (def->storage != attribute->attstorage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("inherited column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(attribute->attstorage))));
+ 
  				def->inhcount++;
  				/* Merge of NOT NULL constraints = OR 'em together */
  				def->is_not_null |= attribute->attnotnull;
*************** MergeAttributes(List *schema, List *supe
*** 1343,1348 ****
--- 1370,1376 ----
  				def->raw_default = NULL;
  				def->cooked_default = NULL;
  				def->constraints = NIL;
+ 				def->storage = attribute->attstorage;
  				inhSchema = lappend(inhSchema, def);
  				newattno[parent_attno - 1] = ++child_attno;
  			}
*************** MergeAttributes(List *schema, List *supe
*** 1480,1485 ****
--- 1508,1525 ----
  							 errdetail("%s versus %s",
  									   TypeNameToString(def->typeName),
  									   TypeNameToString(newdef->typeName))));
+ 
+ 				/* Copy storage parameter */
+ 				if (def->storage == 0)
+ 					def->storage = newdef->storage;
+ 				else if (newdef->storage != 0 && def->storage != newdef->storage)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						errmsg("column \"%s\" has a storage parameter conflict",
+ 							   attributeName),
+ 							   errdetail("%s versus %s", storage_name(def->storage),
+ 										 storage_name(newdef->storage))));
+ 
  				/* Mark the column as locally defined */
  				def->is_local = true;
  				/* Merge of NOT NULL constraints = OR 'em together */
*************** MergeAttributes(List *schema, List *supe
*** 1532,1537 ****
--- 1572,1591 ----
  		}
  	}
  
+ 	/* Raise an error if we found conflicting comments. */
+ 	if (have_bogus_comments)
+ 	{
+ 		foreach(entry, schema)
+ 		{
+ 			ColumnDef  *def = lfirst(entry);
+ 
+ 			if (def->cooked_default == bogus_marker)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_COLUMN_DEFINITION),
+ 				  errmsg("column \"%s\" inherits conflicting comments", def->colname)));
+ 		}
+ 	}
+ 
  	*supOids = parentOids;
  	*supconstr = constraints;
  	*supOidCount = parentsWithOids;
diff -cprN head/src/backend/nodes/copyfuncs.c work/src/backend/nodes/copyfuncs.c
*** head/src/backend/nodes/copyfuncs.c	2009-09-23 08:43:37.000000000 +0900
--- work/src/backend/nodes/copyfuncs.c	2009-10-05 09:30:43.976605826 +0900
*************** _copyInhRelation(InhRelation *from)
*** 2416,2422 ****
  	InhRelation *newnode = makeNode(InhRelation);
  
  	COPY_NODE_FIELD(relation);
! 	COPY_NODE_FIELD(options);
  
  	return newnode;
  }
--- 2416,2422 ----
  	InhRelation *newnode = makeNode(InhRelation);
  
  	COPY_NODE_FIELD(relation);
! 	COPY_SCALAR_FIELD(options);
  
  	return newnode;
  }
diff -cprN head/src/backend/nodes/equalfuncs.c work/src/backend/nodes/equalfuncs.c
*** head/src/backend/nodes/equalfuncs.c	2009-09-23 08:43:38.000000000 +0900
--- work/src/backend/nodes/equalfuncs.c	2009-10-05 09:30:43.977605766 +0900
*************** static bool
*** 1087,1093 ****
  _equalInhRelation(InhRelation *a, InhRelation *b)
  {
  	COMPARE_NODE_FIELD(relation);
! 	COMPARE_NODE_FIELD(options);
  
  	return true;
  }
--- 1087,1093 ----
  _equalInhRelation(InhRelation *a, InhRelation *b)
  {
  	COMPARE_NODE_FIELD(relation);
! 	COMPARE_SCALAR_FIELD(options);
  
  	return true;
  }
diff -cprN head/src/backend/parser/gram.y work/src/backend/parser/gram.y
*** head/src/backend/parser/gram.y	2009-09-23 08:43:38.000000000 +0900
--- work/src/backend/parser/gram.y	2009-10-05 09:30:43.980344371 +0900
*************** static TypeName *TableFuncTypeName(List 
*** 399,406 ****
  %type <keyword> col_name_keyword reserved_keyword
  
  %type <node>	TableConstraint TableLikeClause
! %type <list>	TableLikeOptionList
! %type <ival>	TableLikeOption
  %type <list>	ColQualList
  %type <node>	ColConstraint ColConstraintElem ConstraintAttr
  %type <ival>	key_actions key_delete key_match key_update key_action
--- 399,405 ----
  %type <keyword> col_name_keyword reserved_keyword
  
  %type <node>	TableConstraint TableLikeClause
! %type <ival>	TableLikeOptionList TableLikeOption
  %type <list>	ColQualList
  %type <node>	ColConstraint ColConstraintElem ConstraintAttr
  %type <ival>	key_actions key_delete key_match key_update key_action
*************** static TypeName *TableFuncTypeName(List 
*** 458,464 ****
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
--- 457,463 ----
  
  	CACHE CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
  	CHARACTER CHARACTERISTICS CHECK CHECKPOINT CLASS CLOSE
! 	CLUSTER COALESCE COLLATE COLUMN COMMENT COMMENTS COMMIT
  	COMMITTED CONCURRENTLY CONFIGURATION CONNECTION CONSTRAINT CONSTRAINTS
  	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CREATEDB
  	CREATEROLE CREATEUSER CROSS CSV CURRENT_P
*************** TableLikeClause:
*** 2392,2408 ****
  		;
  
  TableLikeOptionList:
! 				TableLikeOptionList TableLikeOption	{ $$ = lappend_int($1, $2); }
! 				| /* EMPTY */						{ $$ = NIL; }
  		;
  
  TableLikeOption:
! 				INCLUDING DEFAULTS					{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_DEFAULTS; }
! 				| EXCLUDING DEFAULTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS; }
! 				| INCLUDING CONSTRAINTS				{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS; }
! 				| EXCLUDING CONSTRAINTS				{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS; }
! 				| INCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_INCLUDING_INDEXES; }
! 				| EXCLUDING INDEXES					{ $$ = 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES; }
  		;
  
  
--- 2391,2408 ----
  		;
  
  TableLikeOptionList:
! 				TableLikeOptionList INCLUDING TableLikeOption	{ $$ = $1 | $3; }
! 				| TableLikeOptionList EXCLUDING TableLikeOption	{ $$ = $1 & ~$3; }
! 				| /* EMPTY */						{ $$ = 0; }
  		;
  
  TableLikeOption:
! 				DEFAULTS			{ $$ = CREATE_TABLE_LIKE_DEFAULTS; }
! 				| CONSTRAINTS		{ $$ = CREATE_TABLE_LIKE_CONSTRAINTS; }
! 				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
! 				| STORAGE			{ $$ = CREATE_TABLE_LIKE_STORAGE; }
! 				| COMMENTS			{ $$ = CREATE_TABLE_LIKE_COMMENTS; }
! 				| ALL				{ $$ = CREATE_TABLE_LIKE_ALL; }
  		;
  
  
*************** unreserved_keyword:
*** 10325,10330 ****
--- 10325,10331 ----
  			| CLOSE
  			| CLUSTER
  			| COMMENT
+ 			| COMMENTS
  			| COMMIT
  			| COMMITTED
  			| CONCURRENTLY
diff -cprN head/src/backend/parser/parse_utilcmd.c work/src/backend/parser/parse_utilcmd.c
*** head/src/backend/parser/parse_utilcmd.c	2009-07-30 11:45:37.000000000 +0900
--- work/src/backend/parser/parse_utilcmd.c	2009-10-05 11:27:13.744300386 +0900
***************
*** 36,41 ****
--- 36,42 ----
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_opclass.h"
  #include "catalog/pg_type.h"
+ #include "commands/comment.h"
  #include "commands/defrem.h"
  #include "commands/tablecmds.h"
  #include "commands/tablespace.h"
*************** static void transformTableConstraint(Par
*** 101,106 ****
--- 102,108 ----
  						 Constraint *constraint);
  static void transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
  					 InhRelation *inhrelation);
+ static char *chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt);
  static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt,
  						Relation parent_index, AttrNumber *attmap);
  static List *get_opclass(Oid opclass, Oid actual_datatype);
*************** transformInhRelation(ParseState *pstate,
*** 546,555 ****
  	TupleDesc	tupleDesc;
  	TupleConstr *constr;
  	AclResult	aclresult;
! 	bool		including_defaults = false;
! 	bool		including_constraints = false;
! 	bool		including_indexes = false;
! 	ListCell   *elem;
  
  	relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock);
  
--- 548,554 ----
  	TupleDesc	tupleDesc;
  	TupleConstr *constr;
  	AclResult	aclresult;
! 	char	   *comment;
  
  	relation = parserOpenTable(pstate, inhRelation->relation, AccessShareLock);
  
*************** transformInhRelation(ParseState *pstate,
*** 571,606 ****
  	tupleDesc = RelationGetDescr(relation);
  	constr = tupleDesc->constr;
  
- 	foreach(elem, inhRelation->options)
- 	{
- 		int			option = lfirst_int(elem);
- 
- 		switch (option)
- 		{
- 			case CREATE_TABLE_LIKE_INCLUDING_DEFAULTS:
- 				including_defaults = true;
- 				break;
- 			case CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS:
- 				including_defaults = false;
- 				break;
- 			case CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS:
- 				including_constraints = true;
- 				break;
- 			case CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS:
- 				including_constraints = false;
- 				break;
- 			case CREATE_TABLE_LIKE_INCLUDING_INDEXES:
- 				including_indexes = true;
- 				break;
- 			case CREATE_TABLE_LIKE_EXCLUDING_INDEXES:
- 				including_indexes = false;
- 				break;
- 			default:
- 				elog(ERROR, "unrecognized CREATE TABLE LIKE option: %d",
- 					 option);
- 		}
- 	}
- 
  	/*
  	 * Insert the copied attributes into the cxt for the new table definition.
  	 */
--- 570,575 ----
*************** transformInhRelation(ParseState *pstate,
*** 642,648 ****
  		/*
  		 * Copy default, if present and the default has been requested
  		 */
! 		if (attribute->atthasdef && including_defaults)
  		{
  			char	   *this_default = NULL;
  			AttrDefault *attrdef;
--- 611,618 ----
  		/*
  		 * Copy default, if present and the default has been requested
  		 */
! 		if (attribute->atthasdef &&
! 			(inhRelation->options & CREATE_TABLE_LIKE_DEFAULTS))
  		{
  			char	   *this_default = NULL;
  			AttrDefault *attrdef;
*************** transformInhRelation(ParseState *pstate,
*** 668,680 ****
  
  			def->cooked_default = pstrdup(this_default);
  		}
  	}
  
  	/*
  	 * Copy CHECK constraints if requested, being careful to adjust attribute
  	 * numbers
  	 */
! 	if (including_constraints && tupleDesc->constr)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		int			ccnum;
--- 638,671 ----
  
  			def->cooked_default = pstrdup(this_default);
  		}
+ 
+ 		/* Likewise, copy storage if requested */
+ 		if (inhRelation->options & CREATE_TABLE_LIKE_STORAGE)
+ 			def->storage = attribute->attstorage;
+ 
+ 		/* Likewise, copy comment if requested */
+ 		if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) &&
+ 			(comment = GetComment(attribute->attrelid, RelationRelationId,
+ 			attribute->attnum)) != NULL)
+ 		{
+ 			CommentStmt *stmt = makeNode(CommentStmt);
+ 
+ 			stmt->objtype = OBJECT_COLUMN;
+ 			stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ 									   makeString(cxt->relation->relname),
+ 									   makeString(def->colname));
+ 			stmt->objargs = NIL;
+ 			stmt->comment = comment;
+ 
+ 			cxt->alist = lappend(cxt->alist, stmt);
+ 		}
  	}
  
  	/*
  	 * Copy CHECK constraints if requested, being careful to adjust attribute
  	 * numbers
  	 */
! 	if ((inhRelation->options & CREATE_TABLE_LIKE_CONSTRAINTS) && tupleDesc->constr)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		int			ccnum;
*************** transformInhRelation(ParseState *pstate,
*** 694,706 ****
  			n->raw_expr = NULL;
  			n->cooked_expr = nodeToString(ccbin_node);
  			cxt->ckconstraints = lappend(cxt->ckconstraints, n);
  		}
  	}
  
  	/*
  	 * Likewise, copy indexes if requested
  	 */
! 	if (including_indexes && relation->rd_rel->relhasindex)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		List	   *parent_indexes;
--- 685,715 ----
  			n->raw_expr = NULL;
  			n->cooked_expr = nodeToString(ccbin_node);
  			cxt->ckconstraints = lappend(cxt->ckconstraints, n);
+ 
+ 			/* Copy comment on constraint */
+ 			if ((inhRelation->options & CREATE_TABLE_LIKE_COMMENTS) &&
+ 				(comment = GetComment(GetConstraintByName(RelationGetRelid(
+ 				relation), n->conname), ConstraintRelationId, 0)) != NULL)
+ 			{
+ 				CommentStmt *stmt = makeNode(CommentStmt);
+ 
+ 				stmt->objtype = OBJECT_CONSTRAINT;
+ 				stmt->objname = list_make3(makeString(cxt->relation->schemaname),
+ 										   makeString(cxt->relation->relname),
+ 										   makeString(n->conname));
+ 				stmt->objargs = NIL;
+ 				stmt->comment = comment;
+ 
+ 				cxt->alist = lappend(cxt->alist, stmt);
+ 			}
  		}
  	}
  
  	/*
  	 * Likewise, copy indexes if requested
  	 */
! 	if ((inhRelation->options & CREATE_TABLE_LIKE_INDEXES) &&
! 		relation->rd_rel->relhasindex)
  	{
  		AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns);
  		List	   *parent_indexes;
*************** transformInhRelation(ParseState *pstate,
*** 719,724 ****
--- 728,795 ----
  			/* Build CREATE INDEX statement to recreate the parent_index */
  			index_stmt = generateClonedIndexStmt(cxt, parent_index, attmap);
  
+ 			/* Copy comment on index */
+ 			if (inhRelation->options & CREATE_TABLE_LIKE_COMMENTS)
+ 			{
+ 				CommentStmt	   *stmt;
+ 				ListCell	   *lc;
+ 				int				i;
+ 
+ 				comment = GetComment(parent_index_oid, RelationRelationId, 0);
+ 				
+ 				if (comment != NULL)
+ 				{
+ 					/* Assign name for index because CommentStmt requires name. */
+ 					if (index_stmt->idxname == NULL)
+ 						index_stmt->idxname = chooseIndexName(cxt->relation, index_stmt);
+ 
+ 					stmt = makeNode(CommentStmt);
+ 					stmt->objtype = OBJECT_INDEX;
+ 					stmt->objname = list_make2(makeString(cxt->relation->schemaname),
+ 											   makeString(index_stmt->idxname));
+ 					stmt->objargs = NIL;
+ 					stmt->comment = comment;
+ 
+ 					cxt->alist = lappend(cxt->alist, stmt);
+ 				}
+ 
+ 				/* Copy comment on index's columns */
+ 				i = 0;
+ 				foreach(lc, index_stmt->indexParams)
+ 				{
+ 					char	   *attname;
+ 
+ 					i++;
+ 					comment = GetComment(parent_index_oid, RelationRelationId, i);
+ 					if (comment == NULL)
+ 						continue;
+ 
+ 					/* Assign name for index because CommentStmt requires name. */
+ 					if (index_stmt->idxname == NULL)
+ 						index_stmt->idxname = chooseIndexName(cxt->relation, index_stmt);
+ 
+ 					attname = ((IndexElem *) lfirst(lc))->name;
+ 
+ 					/* expression index has a dummy column name */
+ 					if (attname == NULL)
+ 					{
+ 						attname = palloc(NAMEDATALEN);
+ 						sprintf(attname, "pg_expression_%d", i);
+ 					}
+ 
+ 					stmt = makeNode(CommentStmt);
+ 					stmt->objtype = OBJECT_COLUMN;
+ 					stmt->objname = list_make3(
+ 										makeString(cxt->relation->schemaname),
+ 										makeString(index_stmt->idxname),
+ 										makeString(attname));
+ 					stmt->objargs = NIL;
+ 					stmt->comment = comment;
+ 
+ 					cxt->alist = lappend(cxt->alist, stmt);
+ 				}
+ 			}
+ 
  			/* Save it in the inh_indexes list for the time being */
  			cxt->inh_indexes = lappend(cxt->inh_indexes, index_stmt);
  
*************** transformInhRelation(ParseState *pstate,
*** 735,740 ****
--- 806,833 ----
  }
  
  /*
+  * chooseIndexName
+  *
+  * Set name to unnamed index. See also the same logic in DefineIndex.
+  */
+ static char *
+ chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt)
+ {
+ 	Oid	namespaceId;
+ 	
+ 	namespaceId = RangeVarGetCreationNamespace(relation);
+ 	if (index_stmt->primary)
+ 		return ChooseRelationName(relation->relname, NULL, "pkey", namespaceId);
+ 	else
+ 	{
+ 		IndexElem  *iparam = (IndexElem *) linitial(index_stmt->indexParams);
+ 
+ 		return ChooseRelationName(relation->relname, iparam->name,
+ 															"key", namespaceId);
+ 	}
+ }
+ 
+ /*
   * Generate an IndexStmt node using information from an already existing index
   * "source_idx".  Attribute numbers should be adjusted according to attmap.
   */
diff -cprN head/src/include/catalog/pg_constraint.h work/src/include/catalog/pg_constraint.h
*** head/src/include/catalog/pg_constraint.h	2009-07-28 11:56:31.000000000 +0900
--- work/src/include/catalog/pg_constraint.h	2009-10-05 09:30:43.981380980 +0900
*************** extern char *ChooseConstraintName(const 
*** 226,230 ****
--- 226,231 ----
  
  extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
  						  Oid newNspId, bool isType);
+ extern Oid GetConstraintByName(Oid relid, const char *conname);
  
  #endif   /* PG_CONSTRAINT_H */
diff -cprN head/src/include/commands/comment.h work/src/include/commands/comment.h
*** head/src/include/commands/comment.h	2009-06-11 23:49:11.000000000 +0900
--- work/src/include/commands/comment.h	2009-10-05 09:30:43.982605560 +0900
*************** extern void DeleteSharedComments(Oid oid
*** 39,42 ****
--- 39,44 ----
  
  extern void CreateSharedComments(Oid oid, Oid classoid, char *comment);
  
+ extern char *GetComment(Oid oid, Oid classoid, int32 subid);
+ 
  #endif   /* COMMENT_H */
diff -cprN head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h
*** head/src/include/nodes/parsenodes.h	2009-09-23 08:43:41.000000000 +0900
--- work/src/include/nodes/parsenodes.h	2009-10-05 09:30:43.983605501 +0900
*************** typedef struct ColumnDef
*** 461,466 ****
--- 461,467 ----
  	int			inhcount;		/* number of times column is inherited */
  	bool		is_local;		/* column has local (non-inherited) def'n */
  	bool		is_not_null;	/* NOT NULL constraint specified? */
+ 	char		storage;		/* storage parameter of column */
  	Node	   *raw_default;	/* default value (untransformed parse tree) */
  	char	   *cooked_default; /* nodeToString representation */
  	List	   *constraints;	/* other constraints on column */
*************** typedef struct InhRelation
*** 473,489 ****
  {
  	NodeTag		type;
  	RangeVar   *relation;
! 	List	   *options;		/* integer List of CreateStmtLikeOption */
  } InhRelation;
  
  typedef enum CreateStmtLikeOption
  {
! 	CREATE_TABLE_LIKE_INCLUDING_DEFAULTS,
! 	CREATE_TABLE_LIKE_EXCLUDING_DEFAULTS,
! 	CREATE_TABLE_LIKE_INCLUDING_CONSTRAINTS,
! 	CREATE_TABLE_LIKE_EXCLUDING_CONSTRAINTS,
! 	CREATE_TABLE_LIKE_INCLUDING_INDEXES,
! 	CREATE_TABLE_LIKE_EXCLUDING_INDEXES
  } CreateStmtLikeOption;
  
  /*
--- 474,490 ----
  {
  	NodeTag		type;
  	RangeVar   *relation;
! 	bits32		options;		/* bitmap of CreateStmtLikeOption */
  } InhRelation;
  
  typedef enum CreateStmtLikeOption
  {
! 	CREATE_TABLE_LIKE_DEFAULTS		= 1 << 0,
! 	CREATE_TABLE_LIKE_CONSTRAINTS	= 1 << 1,
! 	CREATE_TABLE_LIKE_INDEXES		= 1 << 2,
! 	CREATE_TABLE_LIKE_STORAGE		= 1 << 3,
! 	CREATE_TABLE_LIKE_COMMENTS		= 1 << 4,
! 	CREATE_TABLE_LIKE_ALL			= 0xFFFFFFFF
  } CreateStmtLikeOption;
  
  /*
diff -cprN head/src/include/parser/kwlist.h work/src/include/parser/kwlist.h
*** head/src/include/parser/kwlist.h	2009-09-23 08:43:41.000000000 +0900
--- work/src/include/parser/kwlist.h	2009-10-05 09:30:43.983605501 +0900
*************** PG_KEYWORD("coalesce", COALESCE, COL_NAM
*** 80,85 ****
--- 80,86 ----
  PG_KEYWORD("collate", COLLATE, RESERVED_KEYWORD)
  PG_KEYWORD("column", COLUMN, RESERVED_KEYWORD)
  PG_KEYWORD("comment", COMMENT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("comments", COMMENTS, UNRESERVED_KEYWORD)
  PG_KEYWORD("commit", COMMIT, UNRESERVED_KEYWORD)
  PG_KEYWORD("committed", COMMITTED, UNRESERVED_KEYWORD)
  PG_KEYWORD("concurrently", CONCURRENTLY, UNRESERVED_KEYWORD)
diff -cprN head/src/test/regress/expected/inherit.out work/src/test/regress/expected/inherit.out
*** head/src/test/regress/expected/inherit.out	2009-08-02 04:59:41.000000000 +0900
--- work/src/test/regress/expected/inherit.out	2009-10-05 11:27:36.664486000 +0900
*************** drop table pp1 cascade;
*** 906,908 ****
--- 906,1044 ----
  NOTICE:  drop cascades to 2 other objects
  DETAIL:  drop cascades to table cc1
  drop cascades to table cc2
+ -- including storage and comments
+ CREATE TABLE t1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text);
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1"
+ CREATE INDEX t1_b_key ON t1 (b);
+ CREATE INDEX t1_fnidx ON t1 ((a || b));
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check';
+ COMMENT ON INDEX t1_pkey IS 'index pkey';
+ COMMENT ON INDEX t1_b_key IS 'index b_key';
+ COMMENT ON COLUMN t1_pkey.a IS 'index column pkey.a';
+ COMMENT ON COLUMN t1_fnidx.pg_expression_1 IS 'index column fnidx';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ CREATE TABLE t3 (a text CHECK (length(a) < 5), c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check';
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE);
+ \d+ t12_storage
+              Table "public.t12_storage"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text | not null  | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Has OIDs: no
+ 
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+             Table "public.t12_comments"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text | not null  | extended | A
+  b      | text |           | extended | B
+  c      | text |           | extended | C
+ Has OIDs: no
+ 
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ NOTICE:  merging column "b" with inherited definition
+ NOTICE:  merging constraint "t1_a_check" with inherited definition
+ \d+ t1_inh
+                Table "public.t1_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text | not null  | main     | A
+  b      | text |           | extended | B
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+ Inherits: t1
+ Has OIDs: no
+ 
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass;
+  description 
+ -------------
+  t1_a_check
+ (1 row)
+ 
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ \d+ t13_inh
+                Table "public.t13_inh"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text | not null  | main     | 
+  b      | text |           | extended | 
+  c      | text |           | external | 
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+     "t3_a_check" CHECK (length(a) < 5)
+ Inherits: t1,
+           t3
+ Has OIDs: no
+ 
+ CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1);
+ NOTICE:  merging column "a" with inherited definition
+ \d+ t13_like
+               Table "public.t13_like"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text | not null  | main     | A3
+  b      | text |           | extended | 
+  c      | text |           | external | C
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+     "t3_a_check" CHECK (length(a) < 5)
+ Inherits: t1
+ Has OIDs: no
+ 
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass;
+  description 
+ -------------
+  t3_a_check
+ (1 row)
+ 
+ CREATE TABLE t_all (LIKE t1 INCLUDING ALL);
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t_all_pkey" for table "t_all"
+ \d+ t_all
+                 Table "public.t_all"
+  Column | Type | Modifiers | Storage  | Description 
+ --------+------+-----------+----------+-------------
+  a      | text | not null  | main     | A
+  b      | text |           | extended | B
+ Indexes:
+     "t_all_pkey" PRIMARY KEY, btree (a)
+     "t_all_b_key" btree (b)
+     "t_all_key" btree ((a || b))
+ Check constraints:
+     "t1_a_check" CHECK (length(a) > 2)
+ Has OIDs: no
+ 
+ SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 't_all'::regclass ORDER BY c.relname, objsubid;
+    relname   | objsubid |     description     
+ -------------+----------+---------------------
+  t_all_b_key |        0 | index b_key
+  t_all_key   |        1 | index column fnidx
+  t_all_pkey  |        0 | index pkey
+  t_all_pkey  |        1 | index column pkey.a
+ (4 rows)
+ 
+ CREATE TABLE inh_error1 () INHERITS (t1, t4);
+ NOTICE:  merging multiple inherited definitions of column "a"
+ ERROR:  inherited column "a" has a storage parameter conflict
+ DETAIL:  MAIN versus EXTENDED
+ CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1);
+ 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;
diff -cprN head/src/test/regress/sql/inherit.sql work/src/test/regress/sql/inherit.sql
*** head/src/test/regress/sql/inherit.sql	2008-05-10 08:32:05.000000000 +0900
--- work/src/test/regress/sql/inherit.sql	2009-10-05 11:23:56.616669016 +0900
*************** create table cc2(f4 float) inherits(pp1,
*** 276,278 ****
--- 276,327 ----
  alter table pp1 add column a2 int check (a2 > 0);
  \d cc2
  drop table pp1 cascade;
+ 
+ -- including storage and comments
+ CREATE TABLE t1 (a text CHECK (length(a) > 2) PRIMARY KEY, b text);
+ CREATE INDEX t1_b_key ON t1 (b);
+ CREATE INDEX t1_fnidx ON t1 ((a || b));
+ COMMENT ON COLUMN t1.a IS 'A';
+ COMMENT ON COLUMN t1.b IS 'B';
+ COMMENT ON CONSTRAINT t1_a_check ON t1 IS 't1_a_check';
+ COMMENT ON INDEX t1_pkey IS 'index pkey';
+ COMMENT ON INDEX t1_b_key IS 'index b_key';
+ COMMENT ON COLUMN t1_pkey.a IS 'index column pkey.a';
+ COMMENT ON COLUMN t1_fnidx.pg_expression_1 IS 'index column fnidx';
+ ALTER TABLE t1 ALTER COLUMN a SET STORAGE MAIN;
+ 
+ CREATE TABLE t2 (c text);
+ ALTER TABLE t2 ALTER COLUMN c SET STORAGE EXTERNAL;
+ COMMENT ON COLUMN t2.c IS 'C';
+ 
+ CREATE TABLE t3 (a text CHECK (length(a) < 5), c text);
+ ALTER TABLE t3 ALTER COLUMN c SET STORAGE EXTERNAL;
+ ALTER TABLE t3 ALTER COLUMN a SET STORAGE MAIN;
+ COMMENT ON COLUMN t3.a IS 'A3';
+ COMMENT ON COLUMN t3.c IS 'C';
+ COMMENT ON CONSTRAINT t3_a_check ON t3 IS 't3_a_check';
+ 
+ CREATE TABLE t4 (a text, c text);
+ ALTER TABLE t4 ALTER COLUMN c SET STORAGE EXTERNAL;
+ 
+ CREATE TABLE t12_storage (LIKE t1 INCLUDING STORAGE, LIKE t2 INCLUDING STORAGE);
+ \d+ t12_storage
+ CREATE TABLE t12_comments (LIKE t1 INCLUDING COMMENTS, LIKE t2 INCLUDING COMMENTS);
+ \d+ t12_comments
+ CREATE TABLE t1_inh (LIKE t1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (t1);
+ \d+ t1_inh
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't1_inh'::regclass;
+ CREATE TABLE t13_inh () INHERITS (t1, t3);
+ \d+ t13_inh
+ CREATE TABLE t13_like (LIKE t3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (t1);
+ \d+ t13_like
+ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_constraint'::regclass AND objoid = c.oid AND c.conrelid = 't13_like'::regclass;
+ 
+ CREATE TABLE t_all (LIKE t1 INCLUDING ALL);
+ \d+ t_all
+ SELECT c.relname, objsubid, description FROM pg_description, pg_index i, pg_class c WHERE classoid = 'pg_class'::regclass AND objoid = i.indexrelid AND c.oid = i.indexrelid AND i.indrelid = 't_all'::regclass ORDER BY c.relname, objsubid;
+ 
+ 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;
#19Andrew Dunstan
andrew@dunslane.net
In reply to: Itagaki Takahiro (#18)
Re: CREATE LIKE INCLUDING COMMENTS and STORAGES

Itagaki Takahiro wrote:

Andrew Dunstan <andrew@dunslane.net> wrote:

I'm wondering why we are not
copying comments on cloned indexes. I realize that might involve a bit
more code, but I think I'd feel happier if we cloned all the comments we
reasonably could from the outset. Is it really that hard to do?

I found it is not so difficult as I expected; patch attached. Now it copies
comments on indexes and columns of the indexes on INCLUDING COMMENTS.
Regression test and documentation are also adjusted. Please review around
chooseIndexName() and uses of it.

The codes becomes a bit complex and might be ugly because we will have some
duplicated codes; "pg_expression_%d" hacks and uses of ChooseRelationName()
are spread into index.c, indexcmds.c and parse_utilcmd.c.

I don't think that's a terrible tragedy - you haven't copied huge swags
of code. Committed with slight adjustments for bitrot etc.

cheers

andrew