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