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

Lines changed: 26 additions & 0 deletions
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

Lines changed: 260 additions & 17 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy