diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c194862..4e02438 100644
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
***************
*** 63,68 **** ALTER TABLE name
--- 63,70 ----
RESET ( storage_parameter [, ... ] )
INHERIT parent_table
NO INHERIT parent_table
+ OF type_name
+ NOT OF
OWNER TO new_owner
SET TABLESPACE new_tablespace
***************
*** 491,496 **** ALTER TABLE name
--- 493,522 ----
+ OF type_name
+
+
+ This form links the table to a composite type as though CREATE
+ TABLE OF> had formed it. The table's list of column names and types
+ must precisely match that of the composite type; the presence of
+ an oid> system column is permitted to differ. The table must
+ not inherit from any other table. These restrictions ensure
+ that CREATE TABLE OF> would permit an equivalent table
+ definition.
+
+
+
+
+
+ NOT OF
+
+
+ This form dissociates a typed table from its type.
+
+
+
+
+
OWNER
diff --git a/src/backend/commands/tablecindex bd18db3..0d657a3 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 81,86 ****
--- 81,87 ----
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
+ #include "utils/typcache.h"
/*
***************
*** 357,362 **** static void ATExecEnableDisableRule(Relation rel, char *rulename,
--- 358,366 ----
static void ATPrepAddInherit(Relation child_rel);
static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
+ static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
+ static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
+ static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
static void ATExecGenericOptions(Relation rel, List *options);
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
***************
*** 2679,2684 **** AlterTableGetLockLevel(List *cmds)
--- 2683,2698 ----
break;
/*
+ * These subcommands affect implicit row type conversion. They have
+ * affects similar to CREATE/DROP CAST on queries. We don't provide
+ * for invalidating parse trees as a result of such changes. Do
+ * avoid concurrent pg_class updates, though.
+ */
+ case AT_AddOf:
+ case AT_DropOf:
+ cmd_lockmode = ShareUpdateExclusiveLock;
+
+ /*
* These subcommands affect general strategies for performance and maintenance,
* though don't change the semantic results from normal data reads and writes.
* Delaying an ALTER TABLE behind currently active writes only delays the point
***************
*** 2935,2947 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_EnableAlwaysRule:
case AT_EnableReplicaRule:
case AT_DisableRule:
- ATSimplePermissions(rel, ATT_TABLE);
- /* These commands never recurse */
- /* No command-specific prep needed */
- pass = AT_PASS_MISC;
- break;
case AT_DropInherit: /* NO INHERIT */
ATSimplePermissions(rel, ATT_TABLE);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
--- 2949,2959 ----
case AT_EnableAlwaysRule:
case AT_EnableReplicaRule:
case AT_DisableRule:
case AT_DropInherit: /* NO INHERIT */
+ case AT_AddOf: /* OF */
+ case AT_DropOf: /* NOT OF */
ATSimplePermissions(rel, ATT_TABLE);
+ /* These commands never recurse */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
***************
*** 3210,3215 **** ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
--- 3222,3233 ----
case AT_DropInherit:
ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
break;
+ case AT_AddOf:
+ ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
+ break;
+ case AT_DropOf:
+ ATExecDropOf(rel, lockmode);
+ break;
case AT_GenericOptions:
ATExecGenericOptions(rel, (List *) cmd->def);
break;
***************
*** 8333,8340 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyData key[3];
HeapTuple inheritsTuple,
attributeTuple,
! constraintTuple,
! depTuple;
List *connames;
bool found = false;
--- 8351,8357 ----
ScanKeyData key[3];
HeapTuple inheritsTuple,
attributeTuple,
! constraintTuple;
List *connames;
bool found = false;
***************
*** 8500,8510 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
! /*
! * Drop the dependency
! *
! * There's no convenient way to do this, so go trawling through pg_depend
! */
catalogRelation = heap_open(DependRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
--- 8517,8545 ----
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
! drop_parent_dependency(RelationGetRelid(rel),
! RelationRelationId,
! RelationGetRelid(parent_rel));
!
! /* keep our lock on the parent relation until commit */
! heap_close(parent_rel, NoLock);
! }
!
! /*
! * Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE
! * INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or
! * heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will
! * be TypeRelationId). There's no convenient way to do this, so go trawling
! * through pg_depend.
! */
! static void
! drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid)
! {
! Relation catalogRelation;
! SysScanDesc scan;
! ScanKeyData key[3];
! HeapTuple depTuple;
!
catalogRelation = heap_open(DependRelationId, RowExclusiveLock);
ScanKeyInit(&key[0],
***************
*** 8514,8520 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
ScanKeyInit(&key[1],
Anum_pg_depend_objid,
BTEqualStrategyNumber, F_OIDEQ,
! ObjectIdGetDatum(RelationGetRelid(rel)));
ScanKeyInit(&key[2],
Anum_pg_depend_objsubid,
BTEqualStrategyNumber, F_INT4EQ,
--- 8549,8555 ----
ScanKeyInit(&key[1],
Anum_pg_depend_objid,
BTEqualStrategyNumber, F_OIDEQ,
! ObjectIdGetDatum(relid));
ScanKeyInit(&key[2],
Anum_pg_depend_objsubid,
BTEqualStrategyNumber, F_INT4EQ,
***************
*** 8527,8534 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
{
Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple);
! if (dep->refclassid == RelationRelationId &&
! dep->refobjid == RelationGetRelid(parent_rel) &&
dep->refobjsubid == 0 &&
dep->deptype == DEPENDENCY_NORMAL)
simple_heap_delete(catalogRelation, &depTuple->t_self);
--- 8562,8569 ----
{
Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple);
! if (dep->refclassid == refclassid &&
! dep->refobjid == refobjid &&
dep->refobjsubid == 0 &&
dep->deptype == DEPENDENCY_NORMAL)
simple_heap_delete(catalogRelation, &depTuple->t_self);
***************
*** 8536,8544 **** ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
! /* keep our lock on the parent relation until commit */
! heap_close(parent_rel, NoLock);
}
/*
--- 8571,8794 ----
systable_endscan(scan);
heap_close(catalogRelation, RowExclusiveLock);
+ }
! /*
! * ALTER TABLE OF
! *
! * Attach a table to a composite type, as though it had been created with CREATE
! * TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The
! * subject table must not have inheritance parents. These restrictions ensure
! * that you cannot create a configuration impossible with CREATE TABLE OF alone.
! */
! static void
! ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
! {
! Oid relid = RelationGetRelid(rel),
! cycle_relid;
! Type typetuple;
! Form_pg_type typ;
! Oid typeid;
! Relation inheritsRelation,
! relationRelation;
! SysScanDesc scan;
! ScanKeyData key;
! AttrNumber table_attno,
! type_attno;
! TupleDesc typeTupleDesc,
! tableTupleDesc;
! ObjectAddress tableobj,
! typeobj;
! HeapTuple classtuple;
!
! /* Validate the type. */
! typetuple = typenameType(NULL, ofTypename, NULL);
! typ = (Form_pg_type) GETSTRUCT(typetuple);
! typeid = HeapTupleGetOid(typetuple);
!
! if (typ->typtype != TYPTYPE_COMPOSITE)
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("type %s is not a composite type",
! format_type_be(typeid))));
!
! /* Fail if the table has any inheritance parents. */
! inheritsRelation = heap_open(InheritsRelationId, RowExclusiveLock);
! ScanKeyInit(&key,
! Anum_pg_inherits_inhrelid,
! BTEqualStrategyNumber, F_OIDEQ,
! ObjectIdGetDatum(relid));
! scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
! true, SnapshotNow, 1, &key);
! if (HeapTupleIsValid(systable_getnext(scan)))
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("typed tables cannot inherit")));
! systable_endscan(scan);
! heap_close(inheritsRelation, RowExclusiveLock);
!
! /*
! * Forbid reloftype cycles. This is subject to the same race condition as
! * the comparable code in ATExecAddInherit(); see discussion there.
! *
! * As a side effect, open and lock the type relations all the way up the
! * chain, keeping those locks until end of transaction. This prevents a
! * concurrent column change from invalidating our later analysis. We don't
! * normally take this sort of precaution for row types. Since we're opening
! * them anyway, we may as well do so.
! */
! cycle_relid = typ->typrelid;
! for (;;)
! {
! Relation typeRelation;
! Oid cycle_typeid;
! HeapTuple tup;
!
! /* Get the next reloftype from the current reloftype's relation. */
! typeRelation = relation_open(cycle_relid, AccessShareLock);
! cycle_typeid = typeRelation->rd_rel->reloftype;
! relation_close(typeRelation, NoLock);
!
! /* Might be finished. */
! if (!OidIsValid(cycle_typeid)) /* end of the chain */
! break;
! if (cycle_typeid == rel->rd_rel->reltype) /* cycle detected */
! ereport(ERROR,
! (errcode(ERRCODE_DUPLICATE_TABLE),
! errmsg("circular typed table relationship not allowed")));
!
! /* Continue up the chain: find the next typrelid. */
! tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(cycle_typeid));
! if (!HeapTupleIsValid(tup)) /* should not happen */
! elog(ERROR, "cache lookup failed for type %u", cycle_typeid);
! cycle_relid = ((Form_pg_type) GETSTRUCT(tup))->typrelid;
! ReleaseSysCache(tup);
! }
!
! /*
! * Check the tuple descriptors for compatibility. Unlike inheritance, we
! * require that the order also match. However, attnotnull need not match.
! * Also unlike inheritance, we do not require matching relhasoids.
! */
! typeTupleDesc = lookup_rowtype_tupdesc(typeid, -1);
! tableTupleDesc = RelationGetDescr(rel);
! table_attno = 1;
! for (type_attno = 1; type_attno <= typeTupleDesc->natts; type_attno++)
! {
! Form_pg_attribute type_attr,
! table_attr;
! const char *type_attname,
! *table_attname;
!
! /* Get the next non-dropped type attribute. */
! type_attr = typeTupleDesc->attrs[type_attno - 1];
! if (type_attr->attisdropped)
! continue;
! type_attname = NameStr(type_attr->attname);
!
! /* Get the next non-dropped table attribute. */
! do
! {
! if (table_attno > tableTupleDesc->natts)
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("table is missing column \"%s\"",
! type_attname)));
! table_attr = tableTupleDesc->attrs[table_attno++ - 1];
! } while (table_attr->attisdropped);
! table_attname = NameStr(table_attr->attname);
!
! /* Compare name. */
! if (strncmp(table_attname, type_attname, NAMEDATALEN) != 0)
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("table has column \"%s\" where type requires \"%s\"",
! table_attname, type_attname)));
!
! /* Compare type. */
! if (table_attr->atttypid != type_attr->atttypid ||
! table_attr->atttypmod != type_attr->atttypmod ||
! table_attr->attcollation != type_attr->attcollation)
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("table \"%s\" has different type for column \"%s\"",
! RelationGetRelationName(rel), type_attname)));
! }
! DecrTupleDescRefCount(typeTupleDesc);
!
! /* Any remaining columns at the end of the table had better be dropped. */
! for (; table_attno <= tableTupleDesc->natts; table_attno++)
! {
! Form_pg_attribute table_attr = tableTupleDesc->attrs[table_attno - 1];
! if (!table_attr->attisdropped)
! ereport(ERROR,
! (errcode(ERRCODE_DATATYPE_MISMATCH),
! errmsg("table has extra column \"%s\"",
! NameStr(table_attr->attname))));
! }
!
! /* If the table was already typed, drop the existing dependency. */
! if (rel->rd_rel->reloftype)
! drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype);
!
! /* Record a dependency on the new type. */
! tableobj.classId = RelationRelationId;
! tableobj.objectId = relid;
! tableobj.objectSubId = 0;
! typeobj.classId = TypeRelationId;
! typeobj.objectId = typeid;
! typeobj.objectSubId = 0;
! recordDependencyOn(&tableobj, &typeobj, DEPENDENCY_NORMAL);
!
! /* Update pg_class.reloftype */
! relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
! classtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
! if (!HeapTupleIsValid(classtuple))
! elog(ERROR, "cache lookup failed for relation %u", relid);
! ((Form_pg_class) GETSTRUCT(classtuple))->reloftype = typeid;
! simple_heap_update(relationRelation, &classtuple->t_self, classtuple);
! heap_freetuple(classtuple);
! heap_close(relationRelation, RowExclusiveLock);
!
! ReleaseSysCache(typetuple);
! }
!
! /*
! * ALTER TABLE NOT OF
! *
! * Detach a typed table from its originating type. Just clear reloftype and
! * remove the dependency.
! */
! static void
! ATExecDropOf(Relation rel, LOCKMODE lockmode)
! {
! Oid relid = RelationGetRelid(rel);
! Relation relationRelation;
! HeapTuple tuple;
!
! if (!OidIsValid(rel->rd_rel->reloftype))
! ereport(ERROR,
! (errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a typed table",
! RelationGetRelationName(rel))));
!
! /*
! * We don't bother to check ownership of the row type --- ownership of the
! * child is presumed enough rights. No particular lock required on the
! * row type, either.
! */
!
! drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype);
!
! /* Clear pg_class.reloftype */
! relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
! tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
! if (!HeapTupleIsValid(tuple))
! elog(ERROR, "cache lookup failed for relation %u", relid);
! ((Form_pg_class) GETSTRUCT(tuple))->reloftype = InvalidOid;
! simple_heap_update(relationRelation, &tuple->t_self, tuple);
! heap_freetuple(tuple);
! heap_close(relationRelation, RowExclusiveLock);
}
/*
diff --git a/src/backend/parser/gram.y index a22ab66..1e4f8f6 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 1933,1938 **** alter_table_cmd:
--- 1933,1955 ----
n->def = (Node *) $3;
$$ = (Node *)n;
}
+ /* ALTER TABLE OF */
+ | OF any_name
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ TypeName *def = makeTypeNameFromNameList($2);
+ def->location = @2;
+ n->subtype = AT_AddOf;
+ n->def = (Node *) def;
+ $$ = (Node *)n;
+ }
+ /* ALTER TABLE NOT OF */
+ | NOT OF
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropOf;
+ $$ = (Node *)n;
+ }
/* ALTER TABLE OWNER TO RoleId */
| OWNER TO RoleId
{
diff --git a/src/include/nodes/pindex d9eac76..e28c189 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1218,1223 **** typedef enum AlterTableType
--- 1218,1225 ----
AT_DisableRule, /* DISABLE RULE name */
AT_AddInherit, /* INHERIT parent */
AT_DropInherit, /* NO INHERIT parent */
+ AT_AddOf, /* OF */
+ AT_DropOf, /* NOT OF */
AT_GenericOptions, /* OPTIONS (...) */
} AlterTableType;
diff --git a/src/test/regress/expecteindex d7d1b64..b7b0b8b 100644
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
***************
*** 1929,1931 **** Typed table of type: test_type2
--- 1929,1970 ----
CREATE TYPE test_type_empty AS ();
DROP TYPE test_type_empty;
+ --
+ -- typed tables: OF / NOT OF
+ --
+ CREATE TYPE tt_t0 AS (z inet, x int, y numeric(8,2));
+ ALTER TYPE tt_t0 DROP ATTRIBUTE z;
+ CREATE TABLE tt0 (x int NOT NULL, y numeric(8,2)); -- OK
+ CREATE TABLE tt1 (x int, y bigint); -- wrong base type
+ CREATE TABLE tt2 (x int, y numeric(9,2)); -- wrong typmod
+ CREATE TABLE tt3 (y numeric(8,2), x int); -- wrong column order
+ CREATE TABLE tt4 (x int); -- too few columns
+ CREATE TABLE tt5 (x int, y numeric(8,2), z int); -- too few columns
+ CREATE TABLE tt6 () INHERITS (tt0); -- can't have a parent
+ CREATE TABLE tt7 (x int, q text, y numeric(8,2)) WITH OIDS;
+ ALTER TABLE tt7 DROP q; -- OK
+ ALTER TABLE tt0 OF tt_t0;
+ ALTER TABLE tt1 OF tt_t0;
+ ERROR: table "tt1" has different type for column "y"
+ ALTER TABLE tt2 OF tt_t0;
+ ERROR: table "tt2" has different type for column "y"
+ ALTER TABLE tt3 OF tt_t0;
+ ERROR: table has column "y" where type requires "x"
+ ALTER TABLE tt4 OF tt_t0;
+ ERROR: table is missing column "y"
+ ALTER TABLE tt5 OF tt_t0;
+ ERROR: table has extra column "z"
+ ALTER TABLE tt6 OF tt_t0;
+ ERROR: typed tables cannot inherit
+ ALTER TABLE tt7 OF tt_t0;
+ ALTER TABLE tt7 OF tt0; -- reassign an already-typed table
+ ALTER TABLE tt0 OF tt7; -- no cycles allowed, though
+ ERROR: circular typed table relationship not allowed
+ ALTER TABLE tt7 NOT OF;
+ \d tt7
+ Table "public.tt7"
+ Column | Type | Modifiers
+ --------+--------------+-----------
+ x | integer |
+ y | numeric(8,2) |
+
diff --git a/src/test/regress/sql/alter_table.sqindex 749584d..bd9dab1 100644
*** a/src/test/regress/sql/alter_table.sql
--- b/src/test/regress/sql/alter_table.sql
***************
*** 1359,1361 **** ALTER TYPE test_type2 RENAME ATTRIBUTE a TO aa CASCADE;
--- 1359,1392 ----
CREATE TYPE test_type_empty AS ();
DROP TYPE test_type_empty;
+
+ --
+ -- typed tables: OF / NOT OF
+ --
+
+ CREATE TYPE tt_t0 AS (z inet, x int, y numeric(8,2));
+ ALTER TYPE tt_t0 DROP ATTRIBUTE z;
+ CREATE TABLE tt0 (x int NOT NULL, y numeric(8,2)); -- OK
+ CREATE TABLE tt1 (x int, y bigint); -- wrong base type
+ CREATE TABLE tt2 (x int, y numeric(9,2)); -- wrong typmod
+ CREATE TABLE tt3 (y numeric(8,2), x int); -- wrong column order
+ CREATE TABLE tt4 (x int); -- too few columns
+ CREATE TABLE tt5 (x int, y numeric(8,2), z int); -- too few columns
+ CREATE TABLE tt6 () INHERITS (tt0); -- can't have a parent
+ CREATE TABLE tt7 (x int, q text, y numeric(8,2)) WITH OIDS;
+ ALTER TABLE tt7 DROP q; -- OK
+
+ ALTER TABLE tt0 OF tt_t0;
+ ALTER TABLE tt1 OF tt_t0;
+ ALTER TABLE tt2 OF tt_t0;
+ ALTER TABLE tt3 OF tt_t0;
+ ALTER TABLE tt4 OF tt_t0;
+ ALTER TABLE tt5 OF tt_t0;
+ ALTER TABLE tt6 OF tt_t0;
+ ALTER TABLE tt7 OF tt_t0;
+
+ ALTER TABLE tt7 OF tt0; -- reassign an already-typed table
+ ALTER TABLE tt0 OF tt7; -- no cycles allowed, though
+
+ ALTER TABLE tt7 NOT OF;
+ \d tt7