<!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.103 2009/02/09 20:57:59 alvherre Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.104 2009/02/11 21:11:15 tgl Exp $
PostgreSQL documentation
-->
ENABLE ALWAYS RULE <replaceable class="PARAMETER">rewrite_rule_name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
SET WITHOUT CLUSTER
+ SET WITH OIDS
SET WITHOUT OIDS
SET ( <replaceable class="PARAMETER">storage_parameter</replaceable> = <replaceable class="PARAMETER">value</replaceable> [, ... ] )
RESET ( <replaceable class="PARAMETER">storage_parameter</replaceable> [, ... ] )
<listitem>
<para>
This form adds a new constraint to a table using the same syntax as
- <xref linkend="SQL-CREATETABLE" endterm="SQL-CREATETABLE-TITLE">.
+ <xref linkend="SQL-CREATETABLE" endterm="SQL-CREATETABLE-TITLE">.
</para>
</listitem>
</varlistentry>
The trigger firing mechanism is also affected by the configuration
variable <xref linkend="guc-session-replication-role">. Simply enabled
triggers will fire when the replication role is <quote>origin</>
- (the default) or <quote>local</>. Triggers configured <literal>ENABLE REPLICA</literal>
- will only fire if the session is in <quote>replica</> mode and triggers
- configured <literal>ENABLE ALWAYS</literal> will fire regardless of the current replication
- mode.
+ (the default) or <quote>local</>. Triggers configured as <literal>ENABLE
+ REPLICA</literal> will only fire if the session is in <quote>replica</>
+ mode, and triggers configured as <literal>ENABLE ALWAYS</literal> will
+ fire regardless of the current replication mode.
</para>
</listitem>
</varlistentry>
<term><literal>CLUSTER</literal></term>
<listitem>
<para>
- This form selects the default index for future
+ This form selects the default index for future
<xref linkend="SQL-CLUSTER" endterm="sql-cluster-title">
operations. It does not actually re-cluster the table.
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>SET WITH OIDS</literal></term>
+ <listitem>
+ <para>
+ This form adds an <literal>oid</literal> system column to the
+ table (see <xref linkend="ddl-system-columns">).
+ It does nothing if the table already has OIDs.
+ </para>
+
+ <para>
+ Note that this is not equivalent to <literal>ADD COLUMN oid oid</>;
+ that would add a normal column that happened to be named
+ <literal>oid</>, not a system column.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>SET WITHOUT OIDS</literal></term>
<listitem>
except that it will not complain if there is already no
<literal>oid</literal> column.
</para>
-
- <para>
- Note that there is no variant of <command>ALTER TABLE</command>
- that allows OIDs to be restored to a table once they have been
- removed.
- </para>
</listitem>
</varlistentry>
in the <literal>WITH (<replaceable
class="PARAMETER">storage_parameter</>)</literal> syntax,
<command>ALTER TABLE</> does not treat <literal>OIDS</> as a
- storage parameter.
+ storage parameter. Instead use the <literal>SET WITH OIDS</>
+ and <literal>SET WITHOUT OIDS</> forms to change OID status.
</para>
</note>
</listitem>
moves the data file(s) associated with the table to the new tablespace.
Indexes on the table, if any, are not moved; but they can be moved
separately with additional <literal>SET TABLESPACE</literal> commands.
- See also
+ See also
<xref linkend="SQL-CREATETABLESPACE" endterm="sql-createtablespace-title">.
</para>
</listitem>
Adding a column with a non-null default or changing the type of an
existing column will require the entire table to be rewritten. This
might take a significant amount of time for a large table; and it will
- temporarily require double the disk space.
+ temporarily require double the disk space. Adding or removing a system
+ <literal>oid</> column likewise requires rewriting the entire table.
</para>
<para>
the column, but simply makes it invisible to SQL operations. Subsequent
insert and update operations in the table will store a null value for the
column. Thus, dropping a column is quick but it will not immediately
- reduce the on-disk size of your table, as the space occupied
+ reduce the on-disk size of your table, as the space occupied
by the dropped column is not reclaimed. The space will be
- reclaimed over time as existing rows are updated.
+ reclaimed over time as existing rows are updated. (These statements do
+ not apply when dropping the system <literal>oid</> column; that is done
+ with an immediate rewrite.)
</para>
<para>
</programlisting>
</para>
- <para>
+ <para>
To add a check constraint to a table and all its children:
<programlisting>
ALTER TABLE distributors ADD CONSTRAINT zipchk CHECK (char_length(zipcode) = 5);
</programlisting>
</para>
- <para>
+ <para>
To remove a check constraint from a table and all its children:
<programlisting>
ALTER TABLE distributors DROP CONSTRAINT zipchk;
</programlisting>
</para>
- <para>
+ <para>
To remove a check constraint from a table only:
<programlisting>
ALTER TABLE ONLY distributors DROP CONSTRAINT zipchk;
(The check constraint remains in place for any child tables.)
</para>
- <para>
+ <para>
To add a foreign key constraint to a table:
<programlisting>
ALTER TABLE distributors ADD CONSTRAINT distfk FOREIGN KEY (address) REFERENCES addresses (address) MATCH FULL;
</programlisting>
</para>
- <para>
+ <para>
To add a (multicolumn) unique constraint to a table:
<programlisting>
ALTER TABLE distributors ADD CONSTRAINT dist_id_zipcode_key UNIQUE (dist_id, zipcode);
</programlisting>
</para>
- <para>
+ <para>
To add an automatically named primary key constraint to a table, noting
that a table can only ever have one primary key:
<programlisting>
</programlisting>
</para>
- <para>
+ <para>
To move a table to a different tablespace:
<programlisting>
ALTER TABLE distributors SET TABLESPACE fasttablespace;
</programlisting>
</para>
- <para>
+ <para>
To move a table to a different schema:
<programlisting>
ALTER TABLE myschema.distributors SET SCHEMA yourschema;
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.279 2009/02/02 19:31:38 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.280 2009/02/11 21:11:16 tgl Exp $
*
*-------------------------------------------------------------------------
*/
List *constraints; /* List of NewConstraint */
List *newvals; /* List of NewColumnValue */
bool new_notnull; /* T if we added new NOT NULL constraints */
+ bool new_changeoids; /* T if we added/dropped the OID column */
Oid newTableSpace; /* new tablespace; 0 means no change */
/* Objects to rebuild after completing ALTER TYPE operations */
List *changedConstraintOids; /* OIDs of constraints to rebuild */
static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse,
AlterTableCmd *cmd);
static void ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
- ColumnDef *colDef);
+ ColumnDef *colDef, bool isOid);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
+static void ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
+ AlterTableCmd *cmd);
static void ATExecDropNotNull(Relation rel, const char *colName);
static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName);
Node *newValue);
static void ATExecSetStorage(Relation rel, const char *colName,
Node *newValue);
-static void ATExecDropColumn(Relation rel, const char *colName,
+static void ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
DropBehavior behavior,
bool recurse, bool recursing);
static void ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_AddOids: /* SET WITH OIDS */
+ ATSimplePermissions(rel, false);
+ /* Performs own recursion */
+ if (!rel->rd_rel->relhasoids || recursing)
+ ATPrepAddOids(wqueue, rel, recurse, cmd);
+ pass = AT_PASS_ADD_COL;
+ break;
case AT_DropOids: /* SET WITHOUT OIDS */
ATSimplePermissions(rel, false);
/* Performs own recursion */
{
case AT_AddColumn: /* ADD COLUMN */
case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
- ATExecAddColumn(tab, rel, (ColumnDef *) cmd->def);
+ ATExecAddColumn(tab, rel, (ColumnDef *) cmd->def, false);
break;
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
ATExecColumnDefault(rel, cmd->name, cmd->def);
ATExecSetStorage(rel, cmd->name, cmd->def);
break;
case AT_DropColumn: /* DROP COLUMN */
- ATExecDropColumn(rel, cmd->name, cmd->behavior, false, false);
+ ATExecDropColumn(wqueue, rel, cmd->name,
+ cmd->behavior, false, false);
break;
case AT_DropColumnRecurse: /* DROP COLUMN with recursion */
- ATExecDropColumn(rel, cmd->name, cmd->behavior, true, false);
+ ATExecDropColumn(wqueue, rel, cmd->name,
+ cmd->behavior, true, false);
break;
case AT_AddIndex: /* ADD INDEX */
ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false);
case AT_DropCluster: /* SET WITHOUT CLUSTER */
ATExecDropCluster(rel);
break;
+ case AT_AddOids: /* SET WITH OIDS */
+ /* Use the ADD COLUMN code, unless prep decided to do nothing */
+ if (cmd->def != NULL)
+ ATExecAddColumn(tab, rel, (ColumnDef *) cmd->def, true);
+ break;
case AT_DropOids: /* SET WITHOUT OIDS */
/*
/*
* We only need to rewrite the table if at least one column needs to
- * be recomputed.
+ * be recomputed, or we are adding/removing the OID column.
*/
- if (tab->newvals != NIL)
+ if (tab->newvals != NIL || tab->new_changeoids)
{
/* Build a temporary relation and copy data */
Oid OIDNewHeap;
{
NewColumnValue *ex = lfirst(l);
- needscan = true;
-
ex->exprstate = ExecPrepareExpr((Expr *) ex->expr, estate);
}
needscan = true;
}
- if (needscan)
+ if (newrel || needscan)
{
ExprContext *econtext;
Datum *values;
static void
ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
- ColumnDef *colDef)
+ ColumnDef *colDef, bool isOid)
{
Oid myrelid = RelationGetRelid(rel);
Relation pgclass,
Oid ctypeId;
int32 ctypmod;
- /* Okay if child matches by type */
+ /* Child column must match by type */
ctypeId = typenameTypeId(NULL, colDef->typename, &ctypmod);
if (ctypeId != childatt->atttypid ||
ctypmod != childatt->atttypmod)
errmsg("child table \"%s\" has different type for column \"%s\"",
RelationGetRelationName(rel), colDef->colname)));
+ /* If it's OID, child column must actually be OID */
+ if (isOid && childatt->attnum != ObjectIdAttributeNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("child table \"%s\" has a conflicting \"%s\" column",
+ RelationGetRelationName(rel), colDef->colname)));
+
/* Bump the existing child att's inhcount */
childatt->attinhcount++;
simple_heap_update(attrdesc, &tuple->t_self, tuple);
errmsg("column \"%s\" of relation \"%s\" already exists",
colDef->colname, RelationGetRelationName(rel))));
- newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1;
- if (newattnum > MaxHeapAttributeNumber)
- ereport(ERROR,
- (errcode(ERRCODE_TOO_MANY_COLUMNS),
- errmsg("tables can have at most %d columns",
- MaxHeapAttributeNumber)));
+ /* Determine the new attribute's number */
+ if (isOid)
+ newattnum = ObjectIdAttributeNumber;
+ else
+ {
+ newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1;
+ if (newattnum > MaxHeapAttributeNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_COLUMNS),
+ errmsg("tables can have at most %d columns",
+ MaxHeapAttributeNumber)));
+ }
typeTuple = typenameType(NULL, colDef->typename, &typmod);
tform = (Form_pg_type) GETSTRUCT(typeTuple);
attribute.attrelid = myrelid;
namestrcpy(&(attribute.attname), colDef->colname);
attribute.atttypid = typeOid;
- attribute.attstattarget = -1;
+ attribute.attstattarget = (newattnum > 0) ? -1 : 0;
attribute.attlen = tform->typlen;
attribute.attcacheoff = -1;
attribute.atttypmod = typmod;
heap_close(attrdesc, RowExclusiveLock);
/*
- * Update number of attributes in pg_class tuple
+ * Update pg_class tuple as appropriate
*/
- ((Form_pg_class) GETSTRUCT(reltup))->relnatts = newattnum;
+ if (isOid)
+ ((Form_pg_class) GETSTRUCT(reltup))->relhasoids = true;
+ else
+ ((Form_pg_class) GETSTRUCT(reltup))->relnatts = newattnum;
simple_heap_update(pgclass, &reltup->t_self, reltup);
* defaults, not even for domain-typed columns. And in any case we mustn't
* invoke Phase 3 on a view, since it has no storage.
*/
- if (relkind != RELKIND_VIEW)
+ if (relkind != RELKIND_VIEW && attribute.attnum > 0)
{
defval = (Expr *) build_column_default(rel, attribute.attnum);
/*
* If the new column is NOT NULL, tell Phase 3 it needs to test that.
+ * (Note we don't do this for an OID column. OID will be marked not
+ * null, but since it's filled specially, there's no need to test
+ * anything.)
*/
tab->new_notnull |= colDef->is_not_null;
}
+ /*
+ * If we are adding an OID column, we have to tell Phase 3 to rewrite
+ * the table to fix that.
+ */
+ if (isOid)
+ tab->new_changeoids = true;
+
/*
* Add needed dependency entries for the new column.
*/
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
+/*
+ * ALTER TABLE SET WITH OIDS
+ *
+ * Basically this is an ADD COLUMN for the special OID column. We have
+ * to cons up a ColumnDef node because the ADD COLUMN code needs one.
+ */
+static void
+ATPrepAddOids(List **wqueue, Relation rel, bool recurse, AlterTableCmd *cmd)
+{
+ /* If we're recursing to a child table, the ColumnDef is already set up */
+ if (cmd->def == NULL)
+ {
+ ColumnDef *cdef = makeNode(ColumnDef);
+
+ cdef->colname = pstrdup("oid");
+ cdef->typename = makeTypeNameFromOid(OIDOID, -1);
+ cdef->inhcount = 0;
+ cdef->is_local = true;
+ cdef->is_not_null = true;
+ cmd->def = (Node *) cdef;
+ }
+ ATPrepAddColumn(wqueue, rel, recurse, cmd);
+}
+
/*
* ALTER TABLE ALTER COLUMN DROP NOT NULL
*/
* because we have to decide at runtime whether to recurse or not depending
* on whether attinhcount goes to zero or not. (We can't check this in a
* static pre-pass because it won't handle multiple inheritance situations
- * correctly.) Since DROP COLUMN doesn't need to create any work queue
- * entries for Phase 3, it's okay to recurse internally in this routine
- * without considering the work queue.
+ * correctly.)
*/
static void
-ATExecDropColumn(Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
DropBehavior behavior,
bool recurse, bool recursing)
{
if (childatt->attinhcount == 1 && !childatt->attislocal)
{
/* Time to delete this child column, too */
- ATExecDropColumn(childrel, colName, behavior, true, true);
+ ATExecDropColumn(wqueue, childrel, colName,
+ behavior, true, true);
}
else
{
performDeletion(&object, behavior);
/*
- * If we dropped the OID column, must adjust pg_class.relhasoids
+ * If we dropped the OID column, must adjust pg_class.relhasoids and
+ * tell Phase 3 to physically get rid of the column.
*/
if (attnum == ObjectIdAttributeNumber)
{
Relation class_rel;
Form_pg_class tuple_class;
+ AlteredTableInfo *tab;
class_rel = heap_open(RelationRelationId, RowExclusiveLock);
CatalogUpdateIndexes(class_rel, tuple);
heap_close(class_rel, RowExclusiveLock);
+
+ /* Find or create work queue entry for this table */
+ tab = ATGetQueueEntry(wqueue, rel);
+
+ /* Tell Phase 3 to physically remove the OID column */
+ tab->new_changeoids = true;
}
}
DETAIL: drop cascades to table c1
drop cascades to table gc1
--
--- Test the ALTER TABLE WITHOUT OIDS command
+-- Test the ALTER TABLE SET WITH/WITHOUT OIDS command
--
create table altstartwith (col integer) with oids;
insert into altstartwith values (1);
1
(1 row)
--- Run inheritance tests
+alter table altstartwith set with oids;
+select oid > 0, * from altstartwith;
+ ?column? | col
+----------+-----
+ t | 1
+(1 row)
+
+drop table altstartwith;
+-- Check inheritance cases
create table altwithoid (col integer) with oids;
--- Inherits parents oid column
+-- Inherits parents oid column anyway
create table altinhoid () inherits (altwithoid) without oids;
insert into altinhoid values (1);
select oid > 0, * from altwithoid;
(1 row)
alter table altwithoid set without oids;
-alter table altinhoid set without oids;
select oid > 0, * from altwithoid; -- fails
ERROR: column "oid" does not exist
LINE 1: select oid > 0, * from altwithoid;
1
(1 row)
+alter table altwithoid set with oids;
+select oid > 0, * from altwithoid;
+ ?column? | col
+----------+-----
+ t | 1
+(1 row)
+
+select oid > 0, * from altinhoid;
+ ?column? | col
+----------+-----
+ t | 1
+(1 row)
+
+drop table altwithoid cascade;
+NOTICE: drop cascades to table altinhoid
+create table altwithoid (col integer) without oids;
+-- child can have local oid column
+create table altinhoid () inherits (altwithoid) with oids;
+insert into altinhoid values (1);
+select oid > 0, * from altwithoid; -- fails
+ERROR: column "oid" does not exist
+LINE 1: select oid > 0, * from altwithoid;
+ ^
+select oid > 0, * from altinhoid;
+ ?column? | col
+----------+-----
+ t | 1
+(1 row)
+
+alter table altwithoid set with oids;
+NOTICE: merging definition of column "oid" for child "altinhoid"
+select oid > 0, * from altwithoid;
+ ?column? | col
+----------+-----
+ t | 1
+(1 row)
+
+select oid > 0, * from altinhoid;
+ ?column? | col
+----------+-----
+ t | 1
+(1 row)
+
+-- the child's local definition should remain
+alter table altwithoid set without oids;
+select oid > 0, * from altwithoid; -- fails
+ERROR: column "oid" does not exist
+LINE 1: select oid > 0, * from altwithoid;
+ ^
+select oid > 0, * from altinhoid;
+ ?column? | col
+----------+-----
+ t | 1
+(1 row)
+
+drop table altwithoid cascade;
+NOTICE: drop cascades to table altinhoid
-- test renumbering of child-table columns in inherited operations
create table p1 (f1 int);
create table c1 (f2 text, f3 int) inherits (p1);
drop table p1, p2 cascade;
--
--- Test the ALTER TABLE WITHOUT OIDS command
+-- Test the ALTER TABLE SET WITH/WITHOUT OIDS command
--
create table altstartwith (col integer) with oids;
select oid > 0, * from altstartwith; -- fails
select * from altstartwith;
--- Run inheritance tests
+alter table altstartwith set with oids;
+
+select oid > 0, * from altstartwith;
+
+drop table altstartwith;
+
+-- Check inheritance cases
create table altwithoid (col integer) with oids;
--- Inherits parents oid column
+-- Inherits parents oid column anyway
create table altinhoid () inherits (altwithoid) without oids;
insert into altinhoid values (1);
select oid > 0, * from altinhoid;
alter table altwithoid set without oids;
-alter table altinhoid set without oids;
select oid > 0, * from altwithoid; -- fails
select oid > 0, * from altinhoid; -- fails
select * from altwithoid;
select * from altinhoid;
+alter table altwithoid set with oids;
+
+select oid > 0, * from altwithoid;
+select oid > 0, * from altinhoid;
+
+drop table altwithoid cascade;
+
+create table altwithoid (col integer) without oids;
+
+-- child can have local oid column
+create table altinhoid () inherits (altwithoid) with oids;
+
+insert into altinhoid values (1);
+
+select oid > 0, * from altwithoid; -- fails
+select oid > 0, * from altinhoid;
+
+alter table altwithoid set with oids;
+
+select oid > 0, * from altwithoid;
+select oid > 0, * from altinhoid;
+
+-- the child's local definition should remain
+alter table altwithoid set without oids;
+
+select oid > 0, * from altwithoid; -- fails
+select oid > 0, * from altinhoid;
+
+drop table altwithoid cascade;
+
-- test renumbering of child-table columns in inherited operations
create table p1 (f1 int);