Review: Typed Table

Started by Hitoshi Haradaalmost 16 years ago10 messages
#1Hitoshi Harada
umi.tanuki@gmail.com

I reviewed this patch today.

Overview:
Almost everything is OK. Applied with few hunks (in psql/describle.c 2
lines offset), compiled without warnings, passed regression tests. The
results of advertised queries are as expected. Coding style is of
course satisfied. Since this is utility changes performance does not
go down.

* in namespace.c
I didn't see why this file has been changed.
case 0:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper qualified name (zero-length name list)")));
break;
in DeconstructQualifiedName().

* Crash with wrong column name
regression=# create type persons_type as (name text, bdate date);
CREATE TYPE
regression=# create table persons of persons_type (myname with options
not null);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Succeeded.

* Conflict between transactions
I'm not sure if this is related with the patch but I met this situation;

A: regression=# create type persons_type as (name text, bdate date);
A: CREATE TYPE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (LOCK)
A: regression=# rollback;
A: ROLLBACK
B: CREATE TABLE

B: regression=# drop table persons;
B: DROP TABLE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (NO LOCK)
B: CREATE TABLE

A: regression=# commit;
A: COMMIT

B: regression=# select 'persons_type'::regtype;
B: ERROR: type "persons_type" does not exist
B: LINE 1: select 'persons_type'::regtype;

I have at all no idea why the second create table doesn't lock.

* Comment needed in pg_dump.h
Please add comment on reloftype of struct _tableInfo

* Consistency between add/drop and rename
Typed table can rename its column but can NOT add/drop column. Is this
what the spec requires? IMHO, if it allows rename, do so for add/drop
and if do not allow add/drop, do not so rename.

I read SQL standard about typed table for few minutes but did not find
any problems. It mentions about "reference column" as the patch
document says and I, too, think our oid mechanism will do for that as
the doc says.

Regards,

--
Hitoshi Harada

#2Peter Eisentraut
peter_e@gmx.net
In reply to: Hitoshi Harada (#1)
1 attachment(s)
Re: Review: Typed Table

On tis, 2010-01-19 at 01:01 +0900, Hitoshi Harada wrote:

I reviewed this patch today.

Thank you for this very thorough and helpful review. Comments below and
a new patch attached.

* in namespace.c
I didn't see why this file has been changed.
case 0:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper qualified name (zero-length name list)")));
break;
in DeconstructQualifiedName().

Yeah, that doesn't really belong here. I ran into this problem early in
the hacking that an accidental zero-length list was reported as "too
many dotted names". But the final patch doesn't use any zero-length
dotted lists anymore. :-) The error message could still be clarified,
but this can be addressed separately if desired. I took it out for now.

* Crash with wrong column name
regression=# create type persons_type as (name text, bdate date);
CREATE TYPE
regression=# create table persons of persons_type (myname with options
not null);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Succeeded.

Fixed.

* Conflict between transactions
I'm not sure if this is related with the patch but I met this situation;

A: regression=# create type persons_type as (name text, bdate date);
A: CREATE TYPE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (LOCK)
A: regression=# rollback;
A: ROLLBACK
B: CREATE TABLE

B: regression=# drop table persons;
B: DROP TABLE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (NO LOCK)
B: CREATE TABLE

A: regression=# commit;
A: COMMIT

B: regression=# select 'persons_type'::regtype;
B: ERROR: type "persons_type" does not exist
B: LINE 1: select 'persons_type'::regtype;

I have at all no idea why the second create table doesn't lock.

Well, if you try the same thing with CREATE FUNCTION foo() RETURNS
persons_type AS $$ blah $$ LANGUAGE plpythonu; or some similar cases,
there is also no lock. You will notice that (some/many?) DDL statements
actually behave very poorly against concurrent other DDL. Against that
background, however, the real question is why the first case *does*
lock. I don't know.

* Comment needed in pg_dump.h
Please add comment on reloftype of struct _tableInfo

Fixed.

* Consistency between add/drop and rename
Typed table can rename its column but can NOT add/drop column. Is this
what the spec requires? IMHO, if it allows rename, do so for add/drop
and if do not allow add/drop, do not so rename.

I added a prohibition against renaming.

I have a follow-up patch that I haven't been able to finish that adds
ALTER TYPE stuff to do add/dropping/renaming on the type. I will submit
it once we get this patch finalized and I find some time.

Attachments:

typed-tables.patchtext/x-patch; charset=UTF-8; name=typed-tables.patchDownload
diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml
index 97e95ed..37c6543 100644
--- a/doc/src/sgml/information_schema.sgml
+++ b/doc/src/sgml/information_schema.sgml
@@ -4750,19 +4750,29 @@ ORDER BY c.ordinal_position;
      <row>
       <entry><literal>user_defined_type_catalog</literal></entry>
       <entry><type>sql_identifier</type></entry>
-      <entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
+      <entry>
+       If the table is a typed table, the name of the database that
+       contains the underlying data type (always the current
+       database), else null.
+      </entry>
      </row>
 
      <row>
       <entry><literal>user_defined_type_schema</literal></entry>
       <entry><type>sql_identifier</type></entry>
-      <entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
+      <entry>
+       If the table is a typed table, the name of the schema that
+       contains the underlying data type, else null.
+      </entry>
      </row>
 
      <row>
       <entry><literal>user_defined_type_name</literal></entry>
       <entry><type>sql_identifier</type></entry>
-      <entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
+      <entry>
+       If the table is a typed table, the name of the underlying data
+       type, else null.
+      </entry>
      </row>
 
      <row>
@@ -4778,7 +4788,7 @@ ORDER BY c.ordinal_position;
      <row>
       <entry><literal>is_typed</literal></entry>
       <entry><type>yes_or_no</type></entry>
-      <entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
+      <entry><literal>YES</literal> if the table is a typed table, <literal>NO</literal> if not</entry>
      </row>
 
      <row>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 43764f1..28eb52e 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -32,6 +32,16 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
 
+CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PARAMETER">table_name</replaceable>
+    OF <replaceable class="PARAMETER">type_name</replaceable> [ (
+  { <replaceable class="PARAMETER">column_name</replaceable> WITH OPTIONS [ DEFAULT <replaceable>default_expr</replaceable> ] [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+    | <replaceable>table_constraint</replaceable> }
+    [, ... ]
+) ]
+[ WITH ( <replaceable class="PARAMETER">storage_parameter</replaceable> [= <replaceable class="PARAMETER">value</replaceable>] [, ... ] ) | WITH OIDS | WITHOUT OIDS ]
+[ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
+[ TABLESPACE <replaceable class="PARAMETER">tablespace</replaceable> ]
+
 <phrase>where <replaceable class="PARAMETER">column_constraint</replaceable> is:</phrase>
 
 [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
@@ -154,6 +164,27 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE <replaceable class="PAR
    </varlistentry>
 
    <varlistentry>
+    <term><literal>OF <replaceable class="PARAMETER">type_name</replaceable></literal></term>
+    <listitem>
+     <para>
+      Creates a <firstterm>typed table</firstterm>, which takes its
+      structure from the specified composite type (name optionally
+      schema-qualified).  A typed table is tied to its type; for
+      example the table will be dropped if the type is dropped
+      (with <literal>DROP TYPE ... CASCADE</literal>).
+     </para>
+
+     <para>
+      When a typed table is created, then the data types of the
+      columns are determined by the underlying composite type and are
+      not specified by the <literal>CREATE TABLE</literal> command.
+      But the <literal>CREATE TABLE</literal> command can add defaults
+      and constraints to the table and can specify storage parameters.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><replaceable class="PARAMETER">column_name</replaceable></term>
     <listitem>
      <para>
@@ -1182,6 +1213,17 @@ CREATE TABLE cinemas (
 </programlisting>
   </para>
 
+  <para>
+   Create a composite type and a typed table:
+<programlisting>
+CREATE TYPE employee_type AS (name text, salary numeric);
+
+CREATE TABLE employees OF employee_type (
+    PRIMARY KEY (name),
+    salary WITH OPTIONS DEFAULT 1000
+);
+</programlisting>
+  </para>
  </refsect1>
 
  <refsect1 id="SQL-CREATETABLE-compatibility">
@@ -1331,6 +1373,19 @@ CREATE TABLE cinemas (
     and <literal>USING INDEX TABLESPACE</literal> are extensions.
    </para>
   </refsect2>
+
+  <refsect2>
+   <title>Typed Tables</title>
+
+   <para>
+    Typed tables implement a subset of the SQL standard.  According to
+    the standard, a typed table has columns corresponding to the
+    underlying composite type as well as one other column that is
+    the <quote>self-referencing column</quote>.  PostgreSQL does not
+    support these self-referencing columns explicitly, but the same
+    effect can be had using the OID feature.
+   </para>
+  </refsect2>
  </refsect1>
 
 
@@ -1341,6 +1396,7 @@ CREATE TABLE cinemas (
    <member><xref linkend="sql-altertable" endterm="sql-altertable-title"></member>
    <member><xref linkend="sql-droptable" endterm="sql-droptable-title"></member>
    <member><xref linkend="sql-createtablespace" endterm="sql-createtablespace-title"></member>
+   <member><xref linkend="sql-createtype" endterm="sql-createtype-title"></member>
   </simplelist>
  </refsect1>
 </refentry>
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index b37ee39..7ec96de 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -217,6 +217,7 @@ Boot_CreateStmt:
 													  $5 ? GLOBALTABLESPACE_OID : 0,
 													  $3,
 													  $7,
+													  InvalidOid,
 													  BOOTSTRAP_SUPERUSERID,
 													  tupdesc,
 													  NIL,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8951486..946625a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -72,7 +72,9 @@
 
 static void AddNewRelationTuple(Relation pg_class_desc,
 					Relation new_rel_desc,
-					Oid new_rel_oid, Oid new_type_oid,
+					Oid new_rel_oid,
+					Oid new_type_oid,
+					Oid reloftype,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
@@ -669,6 +671,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relname - 1] = NameGetDatum(&rd_rel->relname);
 	values[Anum_pg_class_relnamespace - 1] = ObjectIdGetDatum(rd_rel->relnamespace);
 	values[Anum_pg_class_reltype - 1] = ObjectIdGetDatum(rd_rel->reltype);
+	values[Anum_pg_class_reloftype - 1] = ObjectIdGetDatum(rd_rel->reloftype);
 	values[Anum_pg_class_relowner - 1] = ObjectIdGetDatum(rd_rel->relowner);
 	values[Anum_pg_class_relam - 1] = ObjectIdGetDatum(rd_rel->relam);
 	values[Anum_pg_class_relfilenode - 1] = ObjectIdGetDatum(rd_rel->relfilenode);
@@ -727,6 +730,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Relation new_rel_desc,
 					Oid new_rel_oid,
 					Oid new_type_oid,
+					Oid reloftype,
 					Oid relowner,
 					char relkind,
 					Datum relacl,
@@ -785,6 +789,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
+	new_rel_reltup->reloftype = reloftype;
 	new_rel_reltup->relkind = relkind;
 
 	new_rel_desc->rd_att->tdtypeid = new_type_oid;
@@ -876,6 +881,7 @@ heap_create_with_catalog(const char *relname,
 						 Oid reltablespace,
 						 Oid relid,
 						 Oid reltypeid,
+						 Oid reloftypeid,
 						 Oid ownerid,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
@@ -1097,6 +1103,7 @@ heap_create_with_catalog(const char *relname,
 						new_rel_desc,
 						relid,
 						new_type_oid,
+						reloftypeid,
 						ownerid,
 						relkind,
 						PointerGetDatum(relacl),
@@ -1139,6 +1146,14 @@ heap_create_with_catalog(const char *relname,
 
 		recordDependencyOnOwner(RelationRelationId, relid, ownerid);
 
+		if (reloftypeid)
+		{
+			referenced.classId = TypeRelationId;
+			referenced.objectId = reloftypeid;
+			referenced.objectSubId = 0;
+			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+		}
+
 		if (relacl != NULL)
 		{
 			int			nnewmembers;
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index cf788c9..5aa45de 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -1799,25 +1799,25 @@ CREATE VIEW tables AS
            CAST(null AS sql_identifier) AS self_referencing_column_name,
            CAST(null AS character_data) AS reference_generation,
 
-           CAST(null AS sql_identifier) AS user_defined_type_catalog,
-           CAST(null AS sql_identifier) AS user_defined_type_schema,
-           CAST(null AS sql_identifier) AS user_defined_type_name,
+           CAST(CASE WHEN t.typname IS NOT NULL THEN current_database() ELSE null END AS sql_identifier) AS user_defined_type_catalog,
+           CAST(nt.nspname AS sql_identifier) AS user_defined_type_schema,
+           CAST(t.typname AS sql_identifier) AS user_defined_type_name,
 
            CAST(CASE WHEN c.relkind = 'r'
                           OR (c.relkind = 'v'
                               AND EXISTS (SELECT 1 FROM pg_rewrite WHERE ev_class = c.oid AND ev_type = '3' AND is_instead))
                 THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_insertable_into,
 
-           CAST('NO' AS yes_or_no) AS is_typed,
+           CAST(CASE WHEN t.typname IS NOT NULL THEN 'YES' ELSE 'NO' END AS yes_or_no) AS is_typed,
            CAST(
              CASE WHEN nc.oid = pg_my_temp_schema() THEN 'PRESERVE' -- FIXME
                   ELSE null END
              AS character_data) AS commit_action
 
-    FROM pg_namespace nc, pg_class c
+    FROM pg_namespace nc JOIN pg_class c ON (nc.oid = c.relnamespace)
+           LEFT JOIN (pg_type t JOIN pg_namespace nt ON (t.typnamespace = nt.oid)) ON (c.reloftype = t.oid)
 
-    WHERE c.relnamespace = nc.oid
-          AND c.relkind IN ('r', 'v')
+    WHERE c.relkind IN ('r', 'v')
           AND (NOT pg_is_other_temp_schema(nc.oid))
           AND (pg_has_role(c.relowner, 'USAGE')
                OR has_table_privilege(c.oid, 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER')
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index e0fac3d..4666771 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -203,6 +203,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptio
 										   rel->rd_rel->reltablespace,
 										   toastOid,
 										   toast_typid,
+										   InvalidOid,
 										   rel->rd_rel->relowner,
 										   tupdesc,
 										   NIL,
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index cab7092..b3a1af7 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -720,6 +720,7 @@ make_new_heap(Oid OIDOldHeap, const char *NewName, Oid NewTableSpace)
 										  NewTableSpace,
 										  InvalidOid,
 										  InvalidOid,
+										  InvalidOid,
 										  OldHeap->rd_rel->relowner,
 										  tupdesc,
 										  NIL,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2db97dd..224e0a6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -361,6 +361,7 @@ DefineRelation(CreateStmt *stmt, char relkind)
 	ListCell   *listptr;
 	AttrNumber	attnum;
 	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+	Oid			ofTypeId;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -443,6 +444,11 @@ DefineRelation(CreateStmt *stmt, char relkind)
 
 	(void) heap_reloptions(relkind, reloptions, true);
 
+	if (stmt->ofTypename)
+		ofTypeId = typenameTypeId(NULL, stmt->ofTypename, NULL);
+	else
+		ofTypeId = InvalidOid;
+
 	/*
 	 * Look up inheritance ancestors and generate relation schema, including
 	 * inherited attributes.
@@ -521,6 +527,7 @@ DefineRelation(CreateStmt *stmt, char relkind)
 										  tablespaceId,
 										  InvalidOid,
 										  InvalidOid,
+										  ofTypeId,
 										  GetUserId(),
 										  descriptor,
 										  list_concat(cookedDefaults,
@@ -1230,17 +1237,39 @@ MergeAttributes(List *schema, List *supers, bool istemp,
 	foreach(entry, schema)
 	{
 		ColumnDef  *coldef = lfirst(entry);
-		ListCell   *rest;
+		ListCell   *rest = lnext(entry);
+		ListCell   *prev = entry;
+
+		if (coldef->typeName == NULL)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" does not exist",
+							coldef->colname)));
 
-		for_each_cell(rest, lnext(entry))
+		while (rest != NULL)
 		{
 			ColumnDef  *restdef = lfirst(rest);
+			ListCell   *next = lnext(rest); /* need to save it in case we delete it */
 
 			if (strcmp(coldef->colname, restdef->colname) == 0)
+			{
+				if (coldef->is_from_type)
+				{
+					coldef->is_not_null = restdef->is_not_null;
+					coldef->raw_default = restdef->raw_default;
+					coldef->cooked_default = restdef->cooked_default;
+					coldef->constraints = restdef->constraints;
+					coldef->is_from_type = false;
+					list_delete_cell(schema, rest, prev);
+				}
+				else
 				ereport(ERROR,
 						(errcode(ERRCODE_DUPLICATE_COLUMN),
 						 errmsg("column \"%s\" specified more than once",
 								coldef->colname)));
+			}
+			prev = rest;
+			rest = next;
 		}
 	}
 
@@ -1921,6 +1950,11 @@ renameatt(Oid myrelid,
 	 */
 	targetrelation = relation_open(myrelid, AccessExclusiveLock);
 
+	if (targetrelation->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot rename column of typed table")));
+
 	/*
 	 * permissions checking.  this would normally be done in utility.c, but
 	 * this particular routine is recursive.
@@ -3585,6 +3619,11 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
 	Form_pg_type tform;
 	Expr	   *defval;
 
+	if (rel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot add column to typed table")));
+
 	attrdesc = heap_open(AttributeRelationId, RowExclusiveLock);
 
 	/*
@@ -4306,6 +4345,11 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 	List	   *children;
 	ObjectAddress object;
 
+	if (rel->rd_rel->reloftype)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("cannot drop column from typed table")));
+
 	/* At top level, permission check was done in ATPrepCmd, else do it */
 	if (recursing)
 		ATSimplePermissions(rel, false);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 351dbb7..5eeb132 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2162,6 +2162,7 @@ OpenIntoRel(QueryDesc *queryDesc)
 											  tablespaceId,
 											  InvalidOid,
 											  InvalidOid,
+											  InvalidOid,
 											  GetUserId(),
 											  tupdesc,
 											  NIL,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0faa05d..d1bbf2d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2507,6 +2507,7 @@ _copyCreateStmt(CreateStmt *from)
 	COPY_NODE_FIELD(relation);
 	COPY_NODE_FIELD(tableElts);
 	COPY_NODE_FIELD(inhRelations);
+	COPY_NODE_FIELD(ofTypename);
 	COPY_NODE_FIELD(constraints);
 	COPY_NODE_FIELD(options);
 	COPY_SCALAR_FIELD(oncommit);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 62bf3b1..3adeacd 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1099,6 +1099,7 @@ _equalCreateStmt(CreateStmt *a, CreateStmt *b)
 	COMPARE_NODE_FIELD(relation);
 	COMPARE_NODE_FIELD(tableElts);
 	COMPARE_NODE_FIELD(inhRelations);
+	COMPARE_NODE_FIELD(ofTypename);
 	COMPARE_NODE_FIELD(constraints);
 	COMPARE_NODE_FIELD(options);
 	COMPARE_SCALAR_FIELD(oncommit);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d7c62ed..dca39df 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1784,6 +1784,7 @@ _outCreateStmt(StringInfo str, CreateStmt *node)
 	WRITE_NODE_FIELD(relation);
 	WRITE_NODE_FIELD(tableElts);
 	WRITE_NODE_FIELD(inhRelations);
+	WRITE_NODE_FIELD(ofTypename);
 	WRITE_NODE_FIELD(constraints);
 	WRITE_NODE_FIELD(options);
 	WRITE_ENUM_FIELD(oncommit, OnCommitAction);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3d3ae79..318c2fa 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -277,6 +277,7 @@ static TypeName *TableFuncTypeName(List *columns);
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
+				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
 				OptWith opt_distinct opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
@@ -347,8 +348,8 @@ static TypeName *TableFuncTypeName(List *columns);
 
 %type <vsetstmt> set_rest SetResetClause
 
-%type <node>	TableElement ConstraintElem TableFuncElement
-%type <node>	columnDef
+%type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
+%type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem
 %type <node>	def_arg columnElem where_clause where_or_current_clause
 				a_expr b_expr c_expr func_expr AexprConst indirection_el
@@ -2203,21 +2204,19 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					n->tablespacename = $11;
 					$$ = (Node *)n;
 				}
-		| CREATE OptTemp TABLE qualified_name OF qualified_name
-			'(' OptTableElementList ')' OptWith OnCommitOption OptTableSpace
+		| CREATE OptTemp TABLE qualified_name OF any_name
+			OptTypedTableElementList OptWith OnCommitOption OptTableSpace
 				{
-					/* SQL99 CREATE TABLE OF <UDT> (cols) seems to be satisfied
-					 * by our inheritance capabilities. Let's try it...
-					 */
 					CreateStmt *n = makeNode(CreateStmt);
 					$4->istemp = $2;
 					n->relation = $4;
-					n->tableElts = $8;
-					n->inhRelations = list_make1($6);
+					n->tableElts = $7;
+					n->ofTypename = makeTypeNameFromNameList($6);
+					n->ofTypename->location = @6;
 					n->constraints = NIL;
-					n->options = $10;
-					n->oncommit = $11;
-					n->tablespacename = $12;
+					n->options = $8;
+					n->oncommit = $9;
+					n->tablespacename = $10;
 					$$ = (Node *)n;
 				}
 		;
@@ -2243,6 +2242,11 @@ OptTableElementList:
 			| /*EMPTY*/							{ $$ = NIL; }
 		;
 
+OptTypedTableElementList:
+			'(' TypedTableElementList ')'		{ $$ = $2; }
+			| /*EMPTY*/							{ $$ = NIL; }
+		;
+
 TableElementList:
 			TableElement
 				{
@@ -2254,12 +2258,28 @@ TableElementList:
 				}
 		;
 
+TypedTableElementList:
+			TypedTableElement
+				{
+					$$ = list_make1($1);
+				}
+			| TypedTableElementList ',' TypedTableElement
+				{
+					$$ = lappend($1, $3);
+				}
+		;
+
 TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
 		;
 
+TypedTableElement:
+			columnOptions						{ $$ = $1; }
+			| TableConstraint					{ $$ = $1; }
+		;
+
 columnDef:	ColId Typename ColQualList
 				{
 					ColumnDef *n = makeNode(ColumnDef);
@@ -2271,6 +2291,16 @@ columnDef:	ColId Typename ColQualList
 				}
 		;
 
+columnOptions:	ColId WITH OPTIONS ColQualList
+				{
+					ColumnDef *n = makeNode(ColumnDef);
+					n->colname = $1;
+					n->constraints = $4;
+					n->is_local = true;
+					$$ = (Node *)n;
+				}
+		;
+
 ColQualList:
 			ColQualList ColConstraint				{ $$ = lappend($1, $2); }
 			| /*EMPTY*/								{ $$ = NIL; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b2ba8f8..ff9453d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -58,6 +58,7 @@
 #include "utils/lsyscache.h"
 #include "utils/relcache.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 /* State shared by transformCreateStmt and its subroutines */
@@ -104,6 +105,8 @@ static void transformTableConstraint(ParseState *pstate,
 						 Constraint *constraint);
 static void transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
 					 InhRelation *inhrelation);
+static void transformOfType(ParseState *pstate, CreateStmtContext *cxt,
+					 TypeName *ofTypename);
 static char *chooseIndexName(const RangeVar *relation, IndexStmt *index_stmt);
 static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt,
 						Relation parent_index, AttrNumber *attmap);
@@ -183,6 +186,11 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.pkey = NULL;
 	cxt.hasoids = interpretOidsOption(stmt->options);
 
+	Assert(!stmt->ofTypename || !stmt->inhRelations); /* grammar enforces */
+
+	if (stmt->ofTypename)
+		transformOfType(pstate, &cxt, stmt->ofTypename);
+
 	/*
 	 * Run through each primary element in the table creation clause. Separate
 	 * column defs from constraints, and do preliminary analysis.
@@ -266,8 +274,9 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
 
 	/* Check for SERIAL pseudo-types */
 	is_serial = false;
-	if (list_length(column->typeName->names) == 1 &&
-		!column->typeName->pct_type)
+	if (column->typeName
+		&& list_length(column->typeName->names) == 1
+		&& !column->typeName->pct_type)
 	{
 		char	   *typname = strVal(linitial(column->typeName->names));
 
@@ -299,7 +308,8 @@ transformColumnDefinition(ParseState *pstate, CreateStmtContext *cxt,
 	}
 
 	/* Do necessary work on the column type declaration */
-	transformColumnType(pstate, column);
+	if (column->typeName)
+		transformColumnType(pstate, column);
 
 	/* Special actions for SERIAL pseudo-types */
 	if (is_serial)
@@ -787,6 +797,46 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
 	heap_close(relation, NoLock);
 }
 
+static void
+transformOfType(ParseState *pstate, CreateStmtContext *cxt, TypeName *ofTypename)
+{
+	HeapTuple	tuple;
+	Form_pg_type typ;
+	TupleDesc	tupdesc;
+	int			i;
+	Oid			ofTypeId;
+
+	AssertArg(ofTypename);
+
+	tuple = typenameType(NULL, ofTypename, NULL);
+	typ = (Form_pg_type) GETSTRUCT(tuple);
+	ofTypeId = HeapTupleGetOid(tuple);
+	ofTypename->typeOid = ofTypeId; /* cached for later */
+
+	if (typ->typtype != TYPTYPE_COMPOSITE)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("type %s is not a composite type",
+						format_type_be(ofTypeId))));
+
+	tupdesc = lookup_rowtype_tupdesc(ofTypeId, -1);
+	for (i = 0; i < tupdesc->natts; i++)
+	{
+		ColumnDef *n = makeNode(ColumnDef);
+		Form_pg_attribute attr = tupdesc->attrs[i];
+
+		n->colname = NameStr(attr->attname);
+		n->typeName = makeTypeNameFromOid(attr->atttypid, attr->atttypmod);
+		n->constraints = NULL;
+		n->is_local = true;
+		n->is_from_type = true;
+		cxt->columns = lappend(cxt->columns, n);
+	}
+	DecrTupleDescRefCount(tupdesc);
+
+	ReleaseSysCache(tuple);
+}
+
 /*
  * chooseIndexName
  *
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b872c19..2db9e0f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3441,6 +3441,7 @@ getTables(int *numTables)
 	int			i_reltablespace;
 	int			i_reloptions;
 	int			i_toastreloptions;
+	int			i_reloftype;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema("pg_catalog");
@@ -3465,7 +3466,7 @@ getTables(int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (g_fout->remoteVersion >= 80400)
+	if (g_fout->remoteVersion >= 80500)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -3478,6 +3479,40 @@ getTables(int *numTables)
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
 						  "c.relfrozenxid, "
+						  "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+						  "d.refobjid AS owning_tab, "
+						  "d.refobjsubid AS owning_col, "
+						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+						"array_to_string(c.reloptions, ', ') AS reloptions, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "FROM pg_class c "
+						  "LEFT JOIN pg_depend d ON "
+						  "(c.relkind = '%c' AND "
+						  "d.classid = c.tableoid AND d.objid = c.oid AND "
+						  "d.objsubid = 0 AND "
+						  "d.refclassid = c.tableoid AND d.deptype = 'a') "
+					   "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+						  "WHERE c.relkind in ('%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE);
+	}
+	else if (g_fout->remoteVersion >= 80400)
+	{
+		/*
+		 * Left join to pick up dependency info linking sequences to their
+		 * owning column, if any (note this dependency is AUTO as of 8.2)
+		 */
+		appendPQExpBuffer(query,
+						  "SELECT c.tableoid, c.oid, c.relname, "
+						  "c.relacl, c.relkind, c.relnamespace, "
+						  "(%s c.relowner) AS rolname, "
+						  "c.relchecks, c.relhastriggers, "
+						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "c.relfrozenxid, "
+						  "NULL AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
@@ -3510,6 +3545,7 @@ getTables(int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
 						  "relfrozenxid, "
+						  "NULL AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
@@ -3541,6 +3577,7 @@ getTables(int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
 						  "0 AS relfrozenxid, "
+						  "NULL AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
@@ -3572,6 +3609,7 @@ getTables(int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
 						  "0 AS relfrozenxid, "
+						  "NULL AS reloftype, "
 						  "d.refobjid AS owning_tab, "
 						  "d.refobjsubid AS owning_col, "
 						  "NULL AS reltablespace, "
@@ -3599,6 +3637,7 @@ getTables(int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
 						  "0 AS relfrozenxid, "
+						  "NULL AS reloftype, "
 						  "NULL::oid AS owning_tab, "
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
@@ -3621,6 +3660,7 @@ getTables(int *numTables)
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
 						  "0 AS relfrozenxid, "
+						  "NULL AS reloftype, "
 						  "NULL::oid AS owning_tab, "
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
@@ -3653,6 +3693,7 @@ getTables(int *numTables)
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
 						  "0 as relfrozenxid, "
+						  "NULL AS reloftype, "
 						  "NULL::oid AS owning_tab, "
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
@@ -3702,6 +3743,7 @@ getTables(int *numTables)
 	i_reltablespace = PQfnumber(res, "reltablespace");
 	i_reloptions = PQfnumber(res, "reloptions");
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
+	i_reloftype = PQfnumber(res, "reloftype");
 
 	if (lockWaitTimeout && g_fout->remoteVersion >= 70300)
 	{
@@ -3735,6 +3777,10 @@ getTables(int *numTables)
 		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
 		tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
 		tblinfo[i].frozenxid = atooid(PQgetvalue(res, i, i_relfrozenxid));
+		if (PQgetisnull(res, i, i_reloftype))
+			tblinfo[i].reloftype = NULL;
+		else
+			tblinfo[i].reloftype = strdup(PQgetvalue(res, i, i_reloftype));
 		tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
 		if (PQgetisnull(res, i, i_owning_tab))
 		{
@@ -10552,8 +10598,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		if (binary_upgrade)
 			binary_upgrade_set_relfilenodes(q, tbinfo->dobj.catId.oid, false);
 
-		appendPQExpBuffer(q, "CREATE TABLE %s (",
+		appendPQExpBuffer(q, "CREATE TABLE %s",
 						  fmtId(tbinfo->dobj.name));
+		if (tbinfo->reloftype)
+			appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
 		actual_atts = 0;
 		for (j = 0; j < tbinfo->numatts; j++)
 		{
@@ -10564,8 +10612,28 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			if ((!tbinfo->inhAttrs[j] && !tbinfo->attisdropped[j]) ||
 				binary_upgrade)
 			{
+				/*
+				 * Default value --- suppress if inherited (except in
+				 * binary-upgrade case, where we're not doing normal
+				 * inheritance) or if it's to be printed separately.
+				 */
+				bool has_default = (tbinfo->attrdefs[j] != NULL
+									&& (!tbinfo->inhAttrDef[j] || binary_upgrade)
+									&& !tbinfo->attrdefs[j]->separate);
+				/*
+				 * Not Null constraint --- suppress if inherited, except
+				 * in binary-upgrade case.
+				 */
+				bool has_notnull =  (tbinfo->notnull[j]
+									 &&	(!tbinfo->inhNotNull[j] || binary_upgrade));
+
+				if (tbinfo->reloftype && !has_default && !has_notnull)
+					continue;
+
 				/* Format properly if not first attr */
-				if (actual_atts > 0)
+				if (actual_atts == 0)
+					appendPQExpBuffer(q, " (");
+				else
 					appendPQExpBuffer(q, ",");
 				appendPQExpBuffer(q, "\n    ");
 				actual_atts++;
@@ -10587,7 +10655,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 				}
 
 				/* Attribute type */
-				if (g_fout->remoteVersion >= 70100)
+				if (tbinfo->reloftype)
+				{
+					appendPQExpBuffer(q, "WITH OPTIONS");
+				}
+				else if (g_fout->remoteVersion >= 70100)
 				{
 					appendPQExpBuffer(q, "%s",
 									  tbinfo->atttypnames[j]);
@@ -10600,23 +10672,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 												   tbinfo->atttypmod[j]));
 				}
 
-				/*
-				 * Default value --- suppress if inherited (except in
-				 * binary-upgrade case, where we're not doing normal
-				 * inheritance) or if it's to be printed separately.
-				 */
-				if (tbinfo->attrdefs[j] != NULL &&
-					(!tbinfo->inhAttrDef[j] || binary_upgrade) &&
-					!tbinfo->attrdefs[j]->separate)
+				if (has_default)
 					appendPQExpBuffer(q, " DEFAULT %s",
 									  tbinfo->attrdefs[j]->adef_expr);
 
-				/*
-				 * Not Null constraint --- suppress if inherited, except
-				 * in binary-upgrade case.
-				 */
-				if (tbinfo->notnull[j] &&
-					(!tbinfo->inhNotNull[j] || binary_upgrade))
+				if (has_notnull)
 					appendPQExpBuffer(q, " NOT NULL");
 			}
 		}
@@ -10631,7 +10691,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			if (constr->separate || !constr->conislocal)
 				continue;
 
-			if (actual_atts > 0)
+			if (actual_atts == 0)
+				appendPQExpBuffer(q, " (\n    ");
+			else
 				appendPQExpBuffer(q, ",\n    ");
 
 			appendPQExpBuffer(q, "CONSTRAINT %s ",
@@ -10641,7 +10703,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			actual_atts++;
 		}
 
-		appendPQExpBuffer(q, "\n)");
+		if (actual_atts)
+			appendPQExpBuffer(q, "\n)");
 
 		if (numParents > 0 && !binary_upgrade)
 		{
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f193bdd..1e65fac 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -229,6 +229,7 @@ typedef struct _tableInfo
 	bool		hasoids;		/* does it have OIDs? */
 	uint32		frozenxid;		/* for restore frozen xid */
 	int			ncheck;			/* # of CHECK expressions */
+	char	   *reloftype;		/* underlying type for typed table */
 	/* these two are set only if table is a sequence owned by a column: */
 	Oid			owning_tab;		/* OID of table owning sequence */
 	int			owning_col;		/* attr # of column owning sequence */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index d3f3d0d..5f7db9b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1108,6 +1108,7 @@ describeOneTableDetails(const char *schemaname,
 		bool		hasexclusion;
 		Oid			tablespace;
 		char	   *reloptions;
+		char	   *reloftype;
 	}			tableinfo;
 	bool		show_modifiers = false;
 	bool		retval;
@@ -1127,7 +1128,8 @@ describeOneTableDetails(const char *schemaname,
 		printfPQExpBuffer(&buf,
 			  "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
 						  "c.relhastriggers, c.relhasoids, "
-						  "%s, c.reltablespace, c.relhasexclusion\n"
+						  "%s, c.reltablespace, c.relhasexclusion, "
+						  "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::text END\n"
 						  "FROM pg_catalog.pg_class c\n "
 		   "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n"
 						  "WHERE c.oid = '%s'\n",
@@ -1207,6 +1209,8 @@ describeOneTableDetails(const char *schemaname,
 		atooid(PQgetvalue(res, 0, 7)) : 0;
 	tableinfo.hasexclusion = (pset.sversion >= 80500) ?
 		strcmp(PQgetvalue(res, 0, 8), "t") == 0 : false;
+	tableinfo.reloftype = (pset.sversion >= 80500 && strcmp(PQgetvalue(res, 0, 9), "") != 0) ?
+		strdup(PQgetvalue(res, 0, 9)) : 0;
 	PQclear(res);
 	res = NULL;
 
@@ -2031,6 +2035,13 @@ describeOneTableDetails(const char *schemaname,
 		}
 		PQclear(result);
 
+		/* Table type */
+		if (tableinfo.reloftype)
+		{
+			printfPQExpBuffer(&buf, _("Typed table of type: %s"), tableinfo.reloftype);
+			printTableAddFooter(&cont, buf.data);
+		}
+
 		/* OIDs and options */
 		if (verbose)
 		{
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 30a5033..c499ce4 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201001222
+#define CATALOG_VERSION_NO	201001241
 
 #endif
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 775f0f7..8b1ac22 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -48,6 +48,7 @@ extern Oid heap_create_with_catalog(const char *relname,
 						 Oid reltablespace,
 						 Oid relid,
 						 Oid reltypeid,
+						 Oid reloftypeid,
 						 Oid ownerid,
 						 TupleDesc tupdesc,
 						 List *cooked_constraints,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index c5b15f5..8e43daa 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -33,7 +33,8 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 {
 	NameData	relname;		/* class name */
 	Oid			relnamespace;	/* OID of namespace containing this class */
-	Oid			reltype;		/* OID of associated entry in pg_type */
+	Oid			reltype;		/* OID of entry in pg_type for table's implicit row type */
+	Oid			reloftype;		/* OID of entry in pg_type for underlying composite type */
 	Oid			relowner;		/* class owner */
 	Oid			relam;			/* index access method; 0 if not an index */
 	Oid			relfilenode;	/* identifier of physical storage file */
@@ -88,33 +89,34 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					26
+#define Natts_pg_class					27
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
-#define Anum_pg_class_relowner			4
-#define Anum_pg_class_relam				5
-#define Anum_pg_class_relfilenode		6
-#define Anum_pg_class_reltablespace		7
-#define Anum_pg_class_relpages			8
-#define Anum_pg_class_reltuples			9
-#define Anum_pg_class_reltoastrelid		10
-#define Anum_pg_class_reltoastidxid		11
-#define Anum_pg_class_relhasindex		12
-#define Anum_pg_class_relisshared		13
-#define Anum_pg_class_relistemp			14
-#define Anum_pg_class_relkind			15
-#define Anum_pg_class_relnatts			16
-#define Anum_pg_class_relchecks			17
-#define Anum_pg_class_relhasoids		18
-#define Anum_pg_class_relhaspkey		19
-#define Anum_pg_class_relhasexclusion	20
-#define Anum_pg_class_relhasrules		21
-#define Anum_pg_class_relhastriggers	22
-#define Anum_pg_class_relhassubclass	23
-#define Anum_pg_class_relfrozenxid		24
-#define Anum_pg_class_relacl			25
-#define Anum_pg_class_reloptions		26
+#define Anum_pg_class_reloftype			4
+#define Anum_pg_class_relowner			5
+#define Anum_pg_class_relam				6
+#define Anum_pg_class_relfilenode		7
+#define Anum_pg_class_reltablespace		8
+#define Anum_pg_class_relpages			9
+#define Anum_pg_class_reltuples			10
+#define Anum_pg_class_reltoastrelid		11
+#define Anum_pg_class_reltoastidxid		12
+#define Anum_pg_class_relhasindex		13
+#define Anum_pg_class_relisshared		14
+#define Anum_pg_class_relistemp			15
+#define Anum_pg_class_relkind			16
+#define Anum_pg_class_relnatts			17
+#define Anum_pg_class_relchecks			18
+#define Anum_pg_class_relhasoids		19
+#define Anum_pg_class_relhaspkey		20
+#define Anum_pg_class_relhasexclusion	21
+#define Anum_pg_class_relhasrules		22
+#define Anum_pg_class_relhastriggers	23
+#define Anum_pg_class_relhassubclass	24
+#define Anum_pg_class_relfrozenxid		25
+#define Anum_pg_class_relacl			26
+#define Anum_pg_class_reloptions		27
 
 /* ----------------
  *		initial contents of pg_class
@@ -126,13 +128,13 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 1247 0 0 0 0 0 f f f r 28 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 1249 0 0 0 0 0 f f f r 19 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 t f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 1255 0 0 0 0 0 f f f r 25 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f f r 26 0 t f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 1259 0 0 0 0 0 f f f r 27 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 
 #define		  RELKIND_INDEX			  'i'		/* secondary index */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0a150f7..1893f7b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -463,6 +463,7 @@ typedef struct ColumnDef
 	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? */
+	bool		is_from_type;	/* column definition came from table type */
 	char		storage;		/* attstorage setting, or 0 for default */
 	Node	   *raw_default;	/* default value (untransformed parse tree) */
 	Node	   *cooked_default; /* default value (transformed expr tree) */
@@ -1356,6 +1357,7 @@ typedef struct CreateStmt
 	List	   *tableElts;		/* column definitions (list of ColumnDef) */
 	List	   *inhRelations;	/* relations to inherit from (list of
 								 * inhRelation) */
+	TypeName   *ofTypename;		/* OF typename */
 	List	   *constraints;	/* constraints (list of Constraint nodes) */
 	List	   *options;		/* options from WITH clause */
 	OnCommitAction oncommit;	/* what do we do at COMMIT? */
diff --git a/src/test/regress/expected/typed_table.out b/src/test/regress/expected/typed_table.out
new file mode 100644
index 0000000..e92cdf6
--- /dev/null
+++ b/src/test/regress/expected/typed_table.out
@@ -0,0 +1,85 @@
+CREATE TABLE ttable1 OF nothing;
+ERROR:  type "nothing" does not exist
+CREATE TYPE person_type AS (id int, name text);
+CREATE TABLE persons OF person_type;
+SELECT * FROM persons;
+ id | name 
+----+------
+(0 rows)
+
+\d persons
+    Table "public.persons"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ id     | integer | 
+ name   | text    | 
+Typed table of type: person_type
+
+CREATE FUNCTION get_all_persons() RETURNS SETOF person_type
+LANGUAGE SQL
+AS $$
+    SELECT * FROM persons;
+$$;
+SELECT * FROM get_all_persons();
+ id | name 
+----+------
+(0 rows)
+
+ALTER TABLE persons ADD COLUMN comment text;
+ERROR:  cannot add column to typed table
+ALTER TABLE persons DROP COLUMN name;
+ERROR:  cannot drop column from typed table
+ALTER TABLE persons RENAME COLUMN id TO num;
+ERROR:  cannot rename column of typed table
+CREATE TABLE personsx OF person_type (myname WITH OPTIONS NOT NULL); -- error
+ERROR:  column "myname" does not exist
+CREATE TABLE persons2 OF person_type (
+    id WITH OPTIONS PRIMARY KEY,
+    UNIQUE (name)
+);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "persons2_pkey" for table "persons2"
+NOTICE:  CREATE TABLE / UNIQUE will create implicit index "persons2_name_key" for table "persons2"
+\d persons2
+   Table "public.persons2"
+ Column |  Type   | Modifiers 
+--------+---------+-----------
+ id     | integer | not null
+ name   | text    | 
+Indexes:
+    "persons2_pkey" PRIMARY KEY, btree (id)
+    "persons2_name_key" UNIQUE, btree (name)
+Typed table of type: person_type
+
+CREATE TABLE persons3 OF person_type (
+    PRIMARY KEY (id),
+    name WITH OPTIONS DEFAULT ''
+);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "persons3_pkey" for table "persons3"
+\d persons3
+       Table "public.persons3"
+ Column |  Type   |    Modifiers     
+--------+---------+------------------
+ id     | integer | not null
+ name   | text    | default ''::text
+Indexes:
+    "persons3_pkey" PRIMARY KEY, btree (id)
+Typed table of type: person_type
+
+CREATE TABLE persons4 OF person_type (
+    name WITH OPTIONS NOT NULL,
+    name WITH OPTIONS DEFAULT ''  -- error, specified more than once
+);
+ERROR:  column "name" specified more than once
+DROP TYPE person_type RESTRICT;
+ERROR:  cannot drop type person_type because other objects depend on it
+DETAIL:  table persons depends on type person_type
+function get_all_persons() depends on type person_type
+table persons2 depends on type person_type
+table persons3 depends on type person_type
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP TYPE person_type CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table persons
+drop cascades to function get_all_persons()
+drop cascades to table persons2
+drop cascades to table persons3
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 3605898..2cf9f35 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -52,7 +52,7 @@ test: copy copyselect
 # ----------
 # Another group of parallel tests
 # ----------
-test: constraints triggers create_misc create_aggregate create_operator inherit vacuum drop_if_exists create_cast
+test: constraints triggers create_misc create_aggregate create_operator inherit typed_table vacuum drop_if_exists create_cast
 
 # Depends on the above
 test: create_index create_view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index dc97b54..0c2b5ce 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -60,6 +60,7 @@ test: create_operator
 test: create_index
 test: drop_if_exists
 test: inherit
+test: typed_table
 test: vacuum
 test: create_view
 test: sanity_check
diff --git a/src/test/regress/sql/typed_table.sql b/src/test/regress/sql/typed_table.sql
new file mode 100644
index 0000000..4e81f1d
--- /dev/null
+++ b/src/test/regress/sql/typed_table.sql
@@ -0,0 +1,42 @@
+CREATE TABLE ttable1 OF nothing;
+
+CREATE TYPE person_type AS (id int, name text);
+CREATE TABLE persons OF person_type;
+SELECT * FROM persons;
+\d persons
+
+CREATE FUNCTION get_all_persons() RETURNS SETOF person_type
+LANGUAGE SQL
+AS $$
+    SELECT * FROM persons;
+$$;
+
+SELECT * FROM get_all_persons();
+
+ALTER TABLE persons ADD COLUMN comment text;
+ALTER TABLE persons DROP COLUMN name;
+ALTER TABLE persons RENAME COLUMN id TO num;
+
+CREATE TABLE personsx OF person_type (myname WITH OPTIONS NOT NULL); -- error
+
+CREATE TABLE persons2 OF person_type (
+    id WITH OPTIONS PRIMARY KEY,
+    UNIQUE (name)
+);
+
+\d persons2
+
+CREATE TABLE persons3 OF person_type (
+    PRIMARY KEY (id),
+    name WITH OPTIONS DEFAULT ''
+);
+
+\d persons3
+
+CREATE TABLE persons4 OF person_type (
+    name WITH OPTIONS NOT NULL,
+    name WITH OPTIONS DEFAULT ''  -- error, specified more than once
+);
+
+DROP TYPE person_type RESTRICT;
+DROP TYPE person_type CASCADE;
#3Hitoshi Harada
umi.tanuki@gmail.com
In reply to: Peter Eisentraut (#2)
Re: Review: Typed Table

2010/1/26 Peter Eisentraut <peter_e@gmx.net>:

On tis, 2010-01-19 at 01:01 +0900, Hitoshi Harada wrote:

I reviewed this patch today.

Thank you for this very thorough and helpful review.  Comments below and
a new patch attached.

OK, I confirmed all the issues relevant to the patch were fixed. I'm
not so familiar with transaction detail, so I leave it as a known
issue.

I found ereport() in MergeAttributes() should be indented but except
for that there's no issue. So I think I've done my review.

I have a follow-up patch that I haven't been able to finish that adds
ALTER TYPE stuff to do add/dropping/renaming on the type.  I will submit
it once we get this patch finalized and I find some time.

I'll look at it when it's ready.

Regards,

--
Hitoshi Harada

#4Peter Eisentraut
peter_e@gmx.net
In reply to: Peter Eisentraut (#2)
Re: Review: Typed Table

Everyone,

We could use some help. Anyone's got an idea what could be causing the
behavior described below?

Show quoted text

On mån, 2010-01-25 at 21:45 +0200, Peter Eisentraut wrote:

On tis, 2010-01-19 at 01:01 +0900, Hitoshi Harada wrote:

* Conflict between transactions
I'm not sure if this is related with the patch but I met this situation;

A: regression=# create type persons_type as (name text, bdate date);
A: CREATE TYPE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (LOCK)
A: regression=# rollback;
A: ROLLBACK
B: CREATE TABLE

B: regression=# drop table persons;
B: DROP TABLE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (NO LOCK)
B: CREATE TABLE

A: regression=# commit;
A: COMMIT

B: regression=# select 'persons_type'::regtype;
B: ERROR: type "persons_type" does not exist
B: LINE 1: select 'persons_type'::regtype;

I have at all no idea why the second create table doesn't lock.

Well, if you try the same thing with CREATE FUNCTION foo() RETURNS
persons_type AS $$ blah $$ LANGUAGE plpythonu; or some similar cases,
there is also no lock. You will notice that (some/many?) DDL statements
actually behave very poorly against concurrent other DDL. Against that
background, however, the real question is why the first case *does*
lock. I don't know.

#5Alvaro Herrera
alvherre@commandprompt.com
In reply to: Peter Eisentraut (#4)
Re: Review: Typed Table

Peter Eisentraut escribi�:

Everyone,

We could use some help. Anyone's got an idea what could be causing the
behavior described below?

I wonder if the problem is that you're missing a recheck on the type's
existence after you've grabbed the lock on it, similar to what
shdepLockAndCheckObject does. Maybe the second attempt to create the
table doesn't block because pg_depends contents are different? It seems
very strange.

On m�n, 2010-01-25 at 21:45 +0200, Peter Eisentraut wrote:

On tis, 2010-01-19 at 01:01 +0900, Hitoshi Harada wrote:

* Conflict between transactions
I'm not sure if this is related with the patch but I met this situation;

A: regression=# create type persons_type as (name text, bdate date);
A: CREATE TYPE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (LOCK)
A: regression=# rollback;
A: ROLLBACK
B: CREATE TABLE

B: regression=# drop table persons;
B: DROP TABLE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (NO LOCK)
B: CREATE TABLE

A: regression=# commit;
A: COMMIT

B: regression=# select 'persons_type'::regtype;
B: ERROR: type "persons_type" does not exist
B: LINE 1: select 'persons_type'::regtype;

I have at all no idea why the second create table doesn't lock.

--
Alvaro Herrera http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.

#6Heikki Linnakangas
heikki.linnakangas@enterprisedb.com
In reply to: Peter Eisentraut (#4)
Re: Review: Typed Table

Peter Eisentraut wrote:

Everyone,

We could use some help. Anyone's got an idea what could be causing the
behavior described below?

On mån, 2010-01-25 at 21:45 +0200, Peter Eisentraut wrote:

On tis, 2010-01-19 at 01:01 +0900, Hitoshi Harada wrote:

* Conflict between transactions
I'm not sure if this is related with the patch but I met this situation;

A: regression=# create type persons_type as (name text, bdate date);
A: CREATE TYPE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (LOCK)
A: regression=# rollback;
A: ROLLBACK
B: CREATE TABLE

B: regression=# drop table persons;
B: DROP TABLE

A: regression=# begin;
A: BEGIN

A: regression=# drop type persons_type;
A: DROP TYPE

B: regression=# create table persons of persons_type; (NO LOCK)
B: CREATE TABLE

A: regression=# commit;
A: COMMIT

B: regression=# select 'persons_type'::regtype;
B: ERROR: type "persons_type" does not exist
B: LINE 1: select 'persons_type'::regtype;

I have at all no idea why the second create table doesn't lock.

Well, if you try the same thing with CREATE FUNCTION foo() RETURNS
persons_type AS $$ blah $$ LANGUAGE plpythonu; or some similar cases,
there is also no lock. You will notice that (some/many?) DDL statements
actually behave very poorly against concurrent other DDL. Against that
background, however, the real question is why the first case *does*
lock. I don't know.

Types are cached in typcache. At the first CREATE TABLE, the type is not
in cache, and lookup_type_cache() (by the call to
lookup_rowtype_tupdesc() in transformOfType()) calls relation_open()
which blocks. On the second call, however, it's already in the cache,
and relation_open is not called.

ISTM you should explicitly grab a lock on the of-type at some point, to
make sure it doesn't get dropped while you're busy creating the table.
How do we protect against that for the types used in columns? For
example, if you do "CREATE TABLE (foo mytype)", and someone tries to
"drop mytype" simultaneously?

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#6)
Re: Review: Typed Table

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

ISTM you should explicitly grab a lock on the of-type at some point, to
make sure it doesn't get dropped while you're busy creating the table.
How do we protect against that for the types used in columns?

We don't. There is no concept of a lock on a type.

For scalar types this is more or less irrelevant anyway, since a scalar
has no substructure that can be altered in any interesting way. I'm not
sure how hard we ought to work on making composites behave differently.
I think it's as likely to cause problems as solve them.

regards, tom lane

#8Peter Eisentraut
peter_e@gmx.net
In reply to: Tom Lane (#7)
Re: Review: Typed Table

On tor, 2010-01-28 at 10:34 -0500, Tom Lane wrote:

Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:

ISTM you should explicitly grab a lock on the of-type at some point, to
make sure it doesn't get dropped while you're busy creating the table.
How do we protect against that for the types used in columns?

We don't. There is no concept of a lock on a type.

For scalar types this is more or less irrelevant anyway, since a scalar
has no substructure that can be altered in any interesting way. I'm not
sure how hard we ought to work on making composites behave differently.
I think it's as likely to cause problems as solve them.

The right thing would probably be SELECT FOR SHARE on the pg_type row,
but I don't see that sort of thing used anywhere else in system catalog
changes.

#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#8)
Re: Review: Typed Table

Peter Eisentraut <peter_e@gmx.net> writes:

The right thing would probably be SELECT FOR SHARE on the pg_type row,
but I don't see that sort of thing used anywhere else in system catalog
changes.

If we were to do it the right thing would just be to define a locktag
for type OIDs and add appropriate locking calls all over the system.
But that would be a large, invasive change that seems far outside the
scope of this patch, and certainly much beyond what can get done for
9.0.

(Actually, if memory serves there is some notion of locking on arbitrary
catalog objects already in the DROP code, so there probably is a
suitable locktag defined already. But getting ALTER and generic type
references to play along is still a major project, and I'm not convinced
about the cost/benefit ...)

regards, tom lane

#10Peter Eisentraut
peter_e@gmx.net
In reply to: Hitoshi Harada (#3)
Re: Review: Typed Table

On tor, 2010-01-28 at 00:43 +0900, Hitoshi Harada wrote:

OK, I confirmed all the issues relevant to the patch were fixed. I'm
not so familiar with transaction detail, so I leave it as a known
issue.

I have applied this now, because it appeared that the locking issue is a
known more general problem.