Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 68739ba

Browse files
committed
Allow ALTER TABLE name {OF type | NOT OF}.
This syntax allows a standalone table to be made into a typed table, or a typed table to be made standalone. This is possibly a mildly useful feature in its own right, but the real motivation for this change is that we need it to make pg_upgrade work with typed tables. This doesn't actually fix that problem, but it's necessary infrastructure. Noah Misch
1 parent 520bcd9 commit 68739ba

File tree

8 files changed

+376
-38
lines changed

8 files changed

+376
-38
lines changed

doc/src/sgml/ref/alter_table.sgml

+26
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
6363
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
6464
INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
6565
NO INHERIT <replaceable class="PARAMETER">parent_table</replaceable>
66+
OF <replaceable class="PARAMETER">type_name</replaceable>
67+
NOT OF
6668
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
6769
SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
6870

@@ -490,6 +492,30 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
490492
</listitem>
491493
</varlistentry>
492494

495+
<varlistentry>
496+
<term><literal>OF <replaceable class="PARAMETER">type_name</replaceable></literal></term>
497+
<listitem>
498+
<para>
499+
This form links the table to a composite type as though <command>CREATE
500+
TABLE OF</> had formed it. The table's list of column names and types
501+
must precisely match that of the composite type; the presence of
502+
an <literal>oid</> system column is permitted to differ. The table must
503+
not inherit from any other table. These restrictions ensure
504+
that <command>CREATE TABLE OF</> would permit an equivalent table
505+
definition.
506+
</para>
507+
</listitem>
508+
</varlistentry>
509+
510+
<varlistentry>
511+
<term><literal>NOT OF</literal></term>
512+
<listitem>
513+
<para>
514+
This form dissociates a typed table from its type.
515+
</para>
516+
</listitem>
517+
</varlistentry>
518+
493519
<varlistentry>
494520
<term><literal>OWNER</literal></term>
495521
<listitem>

src/backend/commands/tablecmds.c

+260-17
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
#include "utils/snapmgr.h"
8282
#include "utils/syscache.h"
8383
#include "utils/tqual.h"
84+
#include "utils/typcache.h"
8485

8586

8687
/*
@@ -357,6 +358,9 @@ static void ATExecEnableDisableRule(Relation rel, char *rulename,
357358
static void ATPrepAddInherit(Relation child_rel);
358359
static void ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode);
359360
static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode);
361+
static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
362+
static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
363+
static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
360364
static void ATExecGenericOptions(Relation rel, List *options);
361365

362366
static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2683,6 +2687,16 @@ AlterTableGetLockLevel(List *cmds)
26832687
cmd_lockmode = ShareUpdateExclusiveLock;
26842688
break;
26852689

2690+
/*
2691+
* These subcommands affect implicit row type conversion. They
2692+
* have affects similar to CREATE/DROP CAST on queries. We
2693+
* don't provide for invalidating parse trees as a result of
2694+
* such changes. Do avoid concurrent pg_class updates, though.
2695+
*/
2696+
case AT_AddOf:
2697+
case AT_DropOf:
2698+
cmd_lockmode = ShareUpdateExclusiveLock;
2699+
26862700
/*
26872701
* These subcommands affect general strategies for performance
26882702
* and maintenance, though don't change the semantic results
@@ -2942,13 +2956,11 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
29422956
case AT_EnableAlwaysRule:
29432957
case AT_EnableReplicaRule:
29442958
case AT_DisableRule:
2945-
ATSimplePermissions(rel, ATT_TABLE);
2946-
/* These commands never recurse */
2947-
/* No command-specific prep needed */
2948-
pass = AT_PASS_MISC;
2949-
break;
29502959
case AT_DropInherit: /* NO INHERIT */
2960+
case AT_AddOf: /* OF */
2961+
case AT_DropOf: /* NOT OF */
29512962
ATSimplePermissions(rel, ATT_TABLE);
2963+
/* These commands never recurse */
29522964
/* No command-specific prep needed */
29532965
pass = AT_PASS_MISC;
29542966
break;
@@ -3211,6 +3223,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
32113223
case AT_DropInherit:
32123224
ATExecDropInherit(rel, (RangeVar *) cmd->def, lockmode);
32133225
break;
3226+
case AT_AddOf:
3227+
ATExecAddOf(rel, (TypeName *) cmd->def, lockmode);
3228+
break;
3229+
case AT_DropOf:
3230+
ATExecDropOf(rel, lockmode);
3231+
break;
32143232
case AT_GenericOptions:
32153233
ATExecGenericOptions(rel, (List *) cmd->def);
32163234
break;
@@ -4045,6 +4063,42 @@ find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior be
40454063
}
40464064

40474065

4066+
/*
4067+
* check_of_type
4068+
*
4069+
* Check whether a type is suitable for CREATE TABLE OF/ALTER TABLE OF. If it
4070+
* isn't suitable, throw an error. Currently, we require that the type
4071+
* originated with CREATE TABLE AS. We could support any row type, but doing so
4072+
* would require handling a number of extra corner cases in the DDL commands.
4073+
*/
4074+
void
4075+
check_of_type(HeapTuple typetuple)
4076+
{
4077+
Form_pg_type typ = (Form_pg_type) GETSTRUCT(typetuple);
4078+
bool typeOk = false;
4079+
4080+
if (typ->typtype == TYPTYPE_COMPOSITE)
4081+
{
4082+
Relation typeRelation;
4083+
4084+
Assert(OidIsValid(typ->typrelid));
4085+
typeRelation = relation_open(typ->typrelid, AccessShareLock);
4086+
typeOk = (typeRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE);
4087+
/*
4088+
* Close the parent rel, but keep our AccessShareLock on it until xact
4089+
* commit. That will prevent someone else from deleting or ALTERing
4090+
* the type before the typed table creation/conversion commits.
4091+
*/
4092+
relation_close(typeRelation, NoLock);
4093+
}
4094+
if (!typeOk)
4095+
ereport(ERROR,
4096+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
4097+
errmsg("type %s is not a composite type",
4098+
format_type_be(HeapTupleGetOid(typetuple)))));
4099+
}
4100+
4101+
40484102
/*
40494103
* ALTER TABLE ADD COLUMN
40504104
*
@@ -8355,8 +8409,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
83558409
ScanKeyData key[3];
83568410
HeapTuple inheritsTuple,
83578411
attributeTuple,
8358-
constraintTuple,
8359-
depTuple;
8412+
constraintTuple;
83608413
List *connames;
83618414
bool found = false;
83628415

@@ -8522,11 +8575,29 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
85228575
systable_endscan(scan);
85238576
heap_close(catalogRelation, RowExclusiveLock);
85248577

8525-
/*
8526-
* Drop the dependency
8527-
*
8528-
* There's no convenient way to do this, so go trawling through pg_depend
8529-
*/
8578+
drop_parent_dependency(RelationGetRelid(rel),
8579+
RelationRelationId,
8580+
RelationGetRelid(parent_rel));
8581+
8582+
/* keep our lock on the parent relation until commit */
8583+
heap_close(parent_rel, NoLock);
8584+
}
8585+
8586+
/*
8587+
* Drop the dependency created by StoreCatalogInheritance1 (CREATE TABLE
8588+
* INHERITS/ALTER TABLE INHERIT -- refclassid will be RelationRelationId) or
8589+
* heap_create_with_catalog (CREATE TABLE OF/ALTER TABLE OF -- refclassid will
8590+
* be TypeRelationId). There's no convenient way to do this, so go trawling
8591+
* through pg_depend.
8592+
*/
8593+
static void
8594+
drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid)
8595+
{
8596+
Relation catalogRelation;
8597+
SysScanDesc scan;
8598+
ScanKeyData key[3];
8599+
HeapTuple depTuple;
8600+
85308601
catalogRelation = heap_open(DependRelationId, RowExclusiveLock);
85318602

85328603
ScanKeyInit(&key[0],
@@ -8536,7 +8607,7 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
85368607
ScanKeyInit(&key[1],
85378608
Anum_pg_depend_objid,
85388609
BTEqualStrategyNumber, F_OIDEQ,
8539-
ObjectIdGetDatum(RelationGetRelid(rel)));
8610+
ObjectIdGetDatum(relid));
85408611
ScanKeyInit(&key[2],
85418612
Anum_pg_depend_objsubid,
85428613
BTEqualStrategyNumber, F_INT4EQ,
@@ -8549,18 +8620,190 @@ ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
85498620
{
85508621
Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(depTuple);
85518622

8552-
if (dep->refclassid == RelationRelationId &&
8553-
dep->refobjid == RelationGetRelid(parent_rel) &&
8623+
if (dep->refclassid == refclassid &&
8624+
dep->refobjid == refobjid &&
85548625
dep->refobjsubid == 0 &&
85558626
dep->deptype == DEPENDENCY_NORMAL)
85568627
simple_heap_delete(catalogRelation, &depTuple->t_self);
85578628
}
85588629

85598630
systable_endscan(scan);
85608631
heap_close(catalogRelation, RowExclusiveLock);
8632+
}
85618633

8562-
/* keep our lock on the parent relation until commit */
8563-
heap_close(parent_rel, NoLock);
8634+
/*
8635+
* ALTER TABLE OF
8636+
*
8637+
* Attach a table to a composite type, as though it had been created with CREATE
8638+
* TABLE OF. All attname, atttypid, atttypmod and attcollation must match. The
8639+
* subject table must not have inheritance parents. These restrictions ensure
8640+
* that you cannot create a configuration impossible with CREATE TABLE OF alone.
8641+
*/
8642+
static void
8643+
ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
8644+
{
8645+
Oid relid = RelationGetRelid(rel);
8646+
Type typetuple;
8647+
Form_pg_type typ;
8648+
Oid typeid;
8649+
Relation inheritsRelation,
8650+
relationRelation;
8651+
SysScanDesc scan;
8652+
ScanKeyData key;
8653+
AttrNumber table_attno,
8654+
type_attno;
8655+
TupleDesc typeTupleDesc,
8656+
tableTupleDesc;
8657+
ObjectAddress tableobj,
8658+
typeobj;
8659+
HeapTuple classtuple;
8660+
8661+
/* Validate the type. */
8662+
typetuple = typenameType(NULL, ofTypename, NULL);
8663+
check_of_type(typetuple);
8664+
typ = (Form_pg_type) GETSTRUCT(typetuple);
8665+
typeid = HeapTupleGetOid(typetuple);
8666+
8667+
/* Fail if the table has any inheritance parents. */
8668+
inheritsRelation = heap_open(InheritsRelationId, AccessShareLock);
8669+
ScanKeyInit(&key,
8670+
Anum_pg_inherits_inhrelid,
8671+
BTEqualStrategyNumber, F_OIDEQ,
8672+
ObjectIdGetDatum(relid));
8673+
scan = systable_beginscan(inheritsRelation, InheritsRelidSeqnoIndexId,
8674+
true, SnapshotNow, 1, &key);
8675+
if (HeapTupleIsValid(systable_getnext(scan)))
8676+
ereport(ERROR,
8677+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
8678+
errmsg("typed tables cannot inherit")));
8679+
systable_endscan(scan);
8680+
heap_close(inheritsRelation, AccessShareLock);
8681+
8682+
/*
8683+
* Check the tuple descriptors for compatibility. Unlike inheritance, we
8684+
* require that the order also match. However, attnotnull need not match.
8685+
* Also unlike inheritance, we do not require matching relhasoids.
8686+
*/
8687+
typeTupleDesc = lookup_rowtype_tupdesc(typeid, -1);
8688+
tableTupleDesc = RelationGetDescr(rel);
8689+
table_attno = 1;
8690+
for (type_attno = 1; type_attno <= typeTupleDesc->natts; type_attno++)
8691+
{
8692+
Form_pg_attribute type_attr,
8693+
table_attr;
8694+
const char *type_attname,
8695+
*table_attname;
8696+
8697+
/* Get the next non-dropped type attribute. */
8698+
type_attr = typeTupleDesc->attrs[type_attno - 1];
8699+
if (type_attr->attisdropped)
8700+
continue;
8701+
type_attname = NameStr(type_attr->attname);
8702+
8703+
/* Get the next non-dropped table attribute. */
8704+
do
8705+
{
8706+
if (table_attno > tableTupleDesc->natts)
8707+
ereport(ERROR,
8708+
(errcode(ERRCODE_DATATYPE_MISMATCH),
8709+
errmsg("table is missing column \"%s\"",
8710+
type_attname)));
8711+
table_attr = tableTupleDesc->attrs[table_attno++ - 1];
8712+
} while (table_attr->attisdropped);
8713+
table_attname = NameStr(table_attr->attname);
8714+
8715+
/* Compare name. */
8716+
if (strncmp(table_attname, type_attname, NAMEDATALEN) != 0)
8717+
ereport(ERROR,
8718+
(errcode(ERRCODE_DATATYPE_MISMATCH),
8719+
errmsg("table has column \"%s\" where type requires \"%s\"",
8720+
table_attname, type_attname)));
8721+
8722+
/* Compare type. */
8723+
if (table_attr->atttypid != type_attr->atttypid ||
8724+
table_attr->atttypmod != type_attr->atttypmod ||
8725+
table_attr->attcollation != type_attr->attcollation)
8726+
ereport(ERROR,
8727+
(errcode(ERRCODE_DATATYPE_MISMATCH),
8728+
errmsg("table \"%s\" has different type for column \"%s\"",
8729+
RelationGetRelationName(rel), type_attname)));
8730+
}
8731+
DecrTupleDescRefCount(typeTupleDesc);
8732+
8733+
/* Any remaining columns at the end of the table had better be dropped. */
8734+
for (; table_attno <= tableTupleDesc->natts; table_attno++)
8735+
{
8736+
Form_pg_attribute table_attr = tableTupleDesc->attrs[table_attno - 1];
8737+
if (!table_attr->attisdropped)
8738+
ereport(ERROR,
8739+
(errcode(ERRCODE_DATATYPE_MISMATCH),
8740+
errmsg("table has extra column \"%s\"",
8741+
NameStr(table_attr->attname))));
8742+
}
8743+
8744+
/* If the table was already typed, drop the existing dependency. */
8745+
if (rel->rd_rel->reloftype)
8746+
drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype);
8747+
8748+
/* Record a dependency on the new type. */
8749+
tableobj.classId = RelationRelationId;
8750+
tableobj.objectId = relid;
8751+
tableobj.objectSubId = 0;
8752+
typeobj.classId = TypeRelationId;
8753+
typeobj.objectId = typeid;
8754+
typeobj.objectSubId = 0;
8755+
recordDependencyOn(&tableobj, &typeobj, DEPENDENCY_NORMAL);
8756+
8757+
/* Update pg_class.reloftype */
8758+
relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
8759+
classtuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
8760+
if (!HeapTupleIsValid(classtuple))
8761+
elog(ERROR, "cache lookup failed for relation %u", relid);
8762+
((Form_pg_class) GETSTRUCT(classtuple))->reloftype = typeid;
8763+
simple_heap_update(relationRelation, &classtuple->t_self, classtuple);
8764+
CatalogUpdateIndexes(relationRelation, classtuple);
8765+
heap_freetuple(classtuple);
8766+
heap_close(relationRelation, RowExclusiveLock);
8767+
8768+
ReleaseSysCache(typetuple);
8769+
}
8770+
8771+
/*
8772+
* ALTER TABLE NOT OF
8773+
*
8774+
* Detach a typed table from its originating type. Just clear reloftype and
8775+
* remove the dependency.
8776+
*/
8777+
static void
8778+
ATExecDropOf(Relation rel, LOCKMODE lockmode)
8779+
{
8780+
Oid relid = RelationGetRelid(rel);
8781+
Relation relationRelation;
8782+
HeapTuple tuple;
8783+
8784+
if (!OidIsValid(rel->rd_rel->reloftype))
8785+
ereport(ERROR,
8786+
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
8787+
errmsg("\"%s\" is not a typed table",
8788+
RelationGetRelationName(rel))));
8789+
8790+
/*
8791+
* We don't bother to check ownership of the type --- ownership of the table
8792+
* is presumed enough rights. No lock required on the type, either.
8793+
*/
8794+
8795+
drop_parent_dependency(relid, TypeRelationId, rel->rd_rel->reloftype);
8796+
8797+
/* Clear pg_class.reloftype */
8798+
relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
8799+
tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
8800+
if (!HeapTupleIsValid(tuple))
8801+
elog(ERROR, "cache lookup failed for relation %u", relid);
8802+
((Form_pg_class) GETSTRUCT(tuple))->reloftype = InvalidOid;
8803+
simple_heap_update(relationRelation, &tuple->t_self, tuple);
8804+
CatalogUpdateIndexes(relationRelation, tuple);
8805+
heap_freetuple(tuple);
8806+
heap_close(relationRelation, RowExclusiveLock);
85648807
}
85658808

85668809
/*

0 commit comments

Comments
 (0)