aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/ref/alter_table.sgml175
-rw-r--r--src/backend/bootstrap/bootparse.y10
-rw-r--r--src/backend/catalog/dependency.c95
-rw-r--r--src/backend/catalog/heap.c29
-rw-r--r--src/backend/catalog/index.c27
-rw-r--r--src/backend/commands/cluster.c81
-rw-r--r--src/backend/commands/indexcmds.c246
-rw-r--r--src/backend/commands/tablecmds.c3071
-rw-r--r--src/backend/nodes/copyfuncs.c18
-rw-r--r--src/backend/nodes/equalfuncs.c16
-rw-r--r--src/backend/parser/analyze.c429
-rw-r--r--src/backend/parser/gram.y189
-rw-r--r--src/backend/tcop/utility.c131
-rw-r--r--src/backend/utils/adt/ruleutils.c212
-rw-r--r--src/include/catalog/dependency.h31
-rw-r--r--src/include/catalog/heap.h14
-rw-r--r--src/include/catalog/index.h5
-rw-r--r--src/include/commands/cluster.h6
-rw-r--r--src/include/commands/defrem.h10
-rw-r--r--src/include/commands/tablecmds.h39
-rw-r--r--src/include/nodes/nodes.h3
-rw-r--r--src/include/nodes/parsenodes.h71
-rw-r--r--src/include/parser/analyze.h5
-rw-r--r--src/include/utils/builtins.h4
-rw-r--r--src/test/regress/expected/alter_table.out96
-rw-r--r--src/test/regress/expected/foreign_key.out22
-rw-r--r--src/test/regress/expected/inherit.out9
-rw-r--r--src/test/regress/sql/alter_table.sql59
-rw-r--r--src/test/regress/sql/foreign_key.sql6
-rw-r--r--src/test/regress/sql/inherit.sql7
30 files changed, 3256 insertions, 1860 deletions
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c249ffbc546..6119a150626 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -1,5 +1,5 @@
<!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.68 2004/03/24 09:49:20 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/alter_table.sgml,v 1.69 2004/05/05 04:48:45 tgl Exp $
PostgreSQL documentation
-->
@@ -21,31 +21,26 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
- ADD [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> <replaceable class="PARAMETER">type</replaceable> [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
+ <replaceable class="PARAMETER">action</replaceable> [, ... ]
ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
+ RENAME [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> TO <replaceable class="PARAMETER">new_column</replaceable>
+ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
+ RENAME TO <replaceable class="PARAMETER">new_name</replaceable>
+
+where <replaceable class="PARAMETER">action</replaceable> is one of:
+
+ ADD [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> <replaceable class="PARAMETER">type</replaceable> [ <replaceable class="PARAMETER">column_constraint</replaceable> [ ... ] ]
DROP [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> [ RESTRICT | CASCADE ]
-ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
- ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> { SET DEFAULT <replaceable class="PARAMETER">expression</replaceable> | DROP DEFAULT }
-ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
+ ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> TYPE <replaceable class="PARAMETER">type</replaceable> [ USING <replaceable class="PARAMETER">expression</replaceable> ]
+ ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET DEFAULT <replaceable class="PARAMETER">expression</replaceable>
+ ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> DROP DEFAULT
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> { SET | DROP } NOT NULL
-ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STATISTICS <replaceable class="PARAMETER">integer</replaceable>
-ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
ALTER [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
-ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
- SET WITHOUT OIDS
-ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
- RENAME [ COLUMN ] <replaceable class="PARAMETER">column</replaceable> TO <replaceable
- class="PARAMETER">new_column</replaceable>
-ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
- RENAME TO <replaceable class="PARAMETER">new_name</replaceable>
-ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
ADD <replaceable class="PARAMETER">table_constraint</replaceable>
-ALTER TABLE [ ONLY ] <replaceable class="PARAMETER">name</replaceable> [ * ]
DROP CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> [ RESTRICT | CASCADE ]
-ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
+ SET WITHOUT OIDS
OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
-ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
CLUSTER ON <replaceable class="PARAMETER">index_name</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -82,6 +77,23 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>ALTER COLUMN TYPE</literal></term>
+ <listitem>
+ <para>
+ This form changes the type of a column of a table. Indexes and
+ simple table constraints involving the column will be automatically
+ converted to use the new column type by reparsing the originally
+ supplied expression. The optional <literal>USING</literal>
+ clause specifies how to compute the new column value from the old;
+ if omitted, the default conversion is the same as an assignment
+ cast from old data type to new. A <literal>USING</literal>
+ clause must be provided if there is no implicit or assignment
+ cast from old to new type.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>SET</literal>/<literal>DROP DEFAULT</literal></term>
<listitem>
<para>
@@ -147,53 +159,42 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
</varlistentry>
<varlistentry>
- <term><literal>SET WITHOUT OIDS</literal></term>
+ <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
<listitem>
<para>
- This form removes the <literal>oid</literal> system column from the
- table. This is exactly equivalent to
- <literal>DROP COLUMN oid RESTRICT</literal>,
- 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.
+ This form adds a new constraint to a table using the same syntax as
+ <xref linkend="SQL-CREATETABLE" endterm="SQL-CREATETABLE-TITLE">.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>RENAME</literal></term>
+ <term><literal>DROP CONSTRAINT</literal></term>
<listitem>
<para>
- The <literal>RENAME</literal> forms change the name of a table
- (or an index, sequence, or view) or the name of an individual column in
- a table. There is no effect on the stored data.
+ This form drops constraints on a table.
+ Currently, constraints on tables are not required to have unique
+ names, so there may be more than one constraint matching the specified
+ name. All matching constraints will be dropped.
</para>
</listitem>
</varlistentry>
<varlistentry>
- <term><literal>ADD <replaceable class="PARAMETER">table_constraint</replaceable></literal></term>
+ <term><literal>SET WITHOUT OIDS</literal></term>
<listitem>
<para>
- This form adds a new constraint to a table using the same syntax as
- <xref linkend="SQL-CREATETABLE" endterm="SQL-CREATETABLE-TITLE">.
+ This form removes the <literal>oid</literal> system column from the
+ table. This is exactly equivalent to
+ <literal>DROP COLUMN oid RESTRICT</literal>,
+ except that it will not complain if there is already no
+ <literal>oid</literal> column.
</para>
- </listitem>
- </varlistentry>
- <varlistentry>
- <term><literal>DROP CONSTRAINT</literal></term>
- <listitem>
<para>
- This form drops constraints on a table.
- Currently, constraints on tables are not required to have unique
- names, so there may be more than one constraint matching the specified
- name. All such constraints will be dropped.
+ 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>
@@ -212,16 +213,35 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
<term><literal>CLUSTER</literal></term>
<listitem>
<para>
- This form marks a table for future <xref linkend="SQL-CLUSTER" endterm="sql-cluster-title">
+ This form selects the default controlling index for future <xref linkend="SQL-CLUSTER" endterm="sql-cluster-title">
operations.
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>RENAME</literal></term>
+ <listitem>
+ <para>
+ The <literal>RENAME</literal> forms change the name of a table
+ (or an index, sequence, or view) or the name of an individual column in
+ a table. There is no effect on the stored data.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
<para>
+ All the actions except <literal>RENAME</literal> can be combined into
+ a list of multiple alterations to apply in parallel. For example, it
+ is possible to add several columns and/or alter the type of several
+ columns in a single command. This is particularly useful with large
+ tables, since only one pass over the table need be made.
+ </para>
+
+ <para>
You must own the table to use <command>ALTER TABLE</>; except for
<command>ALTER TABLE OWNER</>, which may only be executed by a superuser.
</para>
@@ -262,7 +282,8 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
<term><replaceable class="PARAMETER">type</replaceable></term>
<listitem>
<para>
- Data type of the new column.
+ Data type of the new column, or new data type for an existing
+ column.
</para>
</listitem>
</varlistentry>
@@ -352,16 +373,27 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
</para>
<para>
- In the current implementation of <literal>ADD COLUMN</literal>,
- default and <literal>NOT NULL</> clauses for the new column are not supported.
- The new column always comes into being with all values null.
- You can use the <literal>SET DEFAULT</literal> form
- of <command>ALTER TABLE</command> to set the default afterward.
- (You may also want to update the already existing rows to the
- new default value, using
- <xref linkend="sql-update" endterm="sql-update-title">.)
- If you want to mark the column non-null, use the <literal>SET NOT NULL</>
- form after you've entered non-null values for the column in all rows.
+ When a column is added with <literal>ADD COLUMN</literal>, all existing
+ rows in the table are initialized with the column's default value
+ (NULL if no <literal>DEFAULT</> clause is specified).
+ </para>
+
+ <para>
+ 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
+ may take a significant amount of time for a large table; and it will
+ temporarily require double the disk space.
+ </para>
+
+ <para>
+ Adding a <literal>CHECK</> or <literal>NOT NULL</> constraint requires
+ scanning the table to verify that existing rows meet the constraint.
+ </para>
+
+ <para>
+ The main reason for providing the option to specify multiple changes
+ in a single <command>ALTER TABLE</> is that multiple table scans or
+ rewrites can thereby be combined into a single pass over the table.
</para>
<para>
@@ -381,9 +413,9 @@ VACUUM FULL table;
</para>
<para>
- If a table has any descendant tables, it is not permitted to add
- or rename a column in the parent table without doing the same to
- the descendants. That is, <command>ALTER TABLE ONLY</command>
+ If a table has any descendant tables, it is not permitted to add,
+ rename, or change the type of a column in the parent table without doing
+ the same to the descendants. That is, <command>ALTER TABLE ONLY</command>
will be rejected. This ensures that the descendants always have
columns matching the parent.
</para>
@@ -428,6 +460,15 @@ ALTER TABLE distributors DROP COLUMN address RESTRICT;
</para>
<para>
+ To change the types of two existing columns in one operation:
+<programlisting>
+ALTER TABLE distributors
+ ALTER COLUMN address TYPE varchar(80),
+ ALTER COLUMN name TYPE varchar(100);
+</programlisting>
+ </para>
+
+ <para>
To rename an existing column:
<programlisting>
ALTER TABLE distributors RENAME COLUMN address TO city;
@@ -493,15 +534,11 @@ ALTER TABLE distributors ADD PRIMARY KEY (dist_id);
<title>Compatibility</title>
<para>
- The <literal>ADD COLUMN</literal> form conforms with the SQL
- standard, with the exception that it does not support defaults and
- not-null constraints, as explained above. The <literal>ALTER
- COLUMN</literal> form is in full conformance.
- </para>
-
- <para>
- The clauses to rename tables, columns, indexes, views, and sequences are
+ The <literal>ADD</literal>, <literal>DROP</>, and <literal>SET DEFAULT</>
+ forms conform with the SQL standard. The other forms are
<productname>PostgreSQL</productname> extensions of the SQL standard.
+ Also, the ability to specify more than one manipulation in a single
+ <command>ALTER TABLE</> command is an extension.
</para>
<para>
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index e6bde26d337..679ef5577fa 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.65 2004/03/23 19:35:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.66 2004/05/05 04:48:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -241,7 +241,9 @@ Boot_DeclareIndexStmt:
LexIDStr($3),
LexIDStr($7),
$9,
- false, false, false, NULL, NIL);
+ NULL, NIL,
+ false, false, false,
+ false, false, true, false);
do_end();
}
;
@@ -255,7 +257,9 @@ Boot_DeclareUniqueIndexStmt:
LexIDStr($4),
LexIDStr($8),
$10,
- true, false, false, NULL, NIL);
+ NULL, NIL,
+ true, false, false,
+ false, false, true, false);
do_end();
}
;
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index e6552b9f473..5f2b2ea28a0 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.34 2003/11/29 19:51:42 pgsql Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.35 2004/05/05 04:48:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -48,25 +48,6 @@
#include "utils/syscache.h"
-/* This enum covers all system catalogs whose OIDs can appear in classid. */
-typedef enum ObjectClasses
-{
- OCLASS_CLASS, /* pg_class */
- OCLASS_PROC, /* pg_proc */
- OCLASS_TYPE, /* pg_type */
- OCLASS_CAST, /* pg_cast */
- OCLASS_CONSTRAINT, /* pg_constraint */
- OCLASS_CONVERSION, /* pg_conversion */
- OCLASS_DEFAULT, /* pg_attrdef */
- OCLASS_LANGUAGE, /* pg_language */
- OCLASS_OPERATOR, /* pg_operator */
- OCLASS_OPCLASS, /* pg_opclass */
- OCLASS_REWRITE, /* pg_rewrite */
- OCLASS_TRIGGER, /* pg_trigger */
- OCLASS_SCHEMA, /* pg_namespace */
- MAX_OCLASS /* MUST BE LAST */
-} ObjectClasses;
-
/* expansible list of ObjectAddresses */
typedef struct
{
@@ -113,7 +94,7 @@ static bool find_expr_references_walker(Node *node,
static void eliminate_duplicate_dependencies(ObjectAddresses *addrs);
static int object_address_comparator(const void *a, const void *b);
static void init_object_addresses(ObjectAddresses *addrs);
-static void add_object_address(ObjectClasses oclass, Oid objectId, int32 subId,
+static void add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
ObjectAddresses *addrs);
static void add_exact_object_address(const ObjectAddress *object,
ObjectAddresses *addrs);
@@ -121,8 +102,6 @@ static bool object_address_present(const ObjectAddress *object,
ObjectAddresses *addrs);
static void term_object_addresses(ObjectAddresses *addrs);
static void init_object_classes(void);
-static ObjectClasses getObjectClass(const ObjectAddress *object);
-static char *getObjectDescription(const ObjectAddress *object);
static void getRelationDescription(StringInfo buffer, Oid relid);
@@ -1238,7 +1217,7 @@ init_object_addresses(ObjectAddresses *addrs)
* by catalog OID.
*/
static void
-add_object_address(ObjectClasses oclass, Oid objectId, int32 subId,
+add_object_address(ObjectClass oclass, Oid objectId, int32 subId,
ObjectAddresses *addrs)
{
ObjectAddress *item;
@@ -1350,7 +1329,7 @@ init_object_classes(void)
* This function is needed just because some of the system catalogs do
* not have hardwired-at-compile-time OIDs.
*/
-static ObjectClasses
+ObjectClass
getObjectClass(const ObjectAddress *object)
{
/* Easy for the bootstrapped catalogs... */
@@ -1435,7 +1414,7 @@ getObjectClass(const ObjectAddress *object)
*
* The result is a palloc'd string.
*/
-static char *
+char *
getObjectDescription(const ObjectAddress *object)
{
StringInfoData buffer;
@@ -1447,18 +1426,18 @@ getObjectDescription(const ObjectAddress *object)
case OCLASS_CLASS:
getRelationDescription(&buffer, object->objectId);
if (object->objectSubId != 0)
- appendStringInfo(&buffer, " column %s",
+ appendStringInfo(&buffer, gettext(" column %s"),
get_relid_attribute_name(object->objectId,
object->objectSubId));
break;
case OCLASS_PROC:
- appendStringInfo(&buffer, "function %s",
+ appendStringInfo(&buffer, gettext("function %s"),
format_procedure(object->objectId));
break;
case OCLASS_TYPE:
- appendStringInfo(&buffer, "type %s",
+ appendStringInfo(&buffer, gettext("type %s"),
format_type_be(object->objectId));
break;
@@ -1488,7 +1467,7 @@ getObjectDescription(const ObjectAddress *object)
castForm = (Form_pg_cast) GETSTRUCT(tup);
- appendStringInfo(&buffer, "cast from %s to %s",
+ appendStringInfo(&buffer, gettext("cast from %s to %s"),
format_type_be(castForm->castsource),
format_type_be(castForm->casttarget));
@@ -1525,13 +1504,13 @@ getObjectDescription(const ObjectAddress *object)
if (OidIsValid(con->conrelid))
{
- appendStringInfo(&buffer, "constraint %s on ",
+ appendStringInfo(&buffer, gettext("constraint %s on "),
NameStr(con->conname));
getRelationDescription(&buffer, con->conrelid);
}
else
{
- appendStringInfo(&buffer, "constraint %s",
+ appendStringInfo(&buffer, gettext("constraint %s"),
NameStr(con->conname));
}
@@ -1550,7 +1529,7 @@ getObjectDescription(const ObjectAddress *object)
if (!HeapTupleIsValid(conTup))
elog(ERROR, "cache lookup failed for conversion %u",
object->objectId);
- appendStringInfo(&buffer, "conversion %s",
+ appendStringInfo(&buffer, gettext("conversion %s"),
NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname));
ReleaseSysCache(conTup);
break;
@@ -1587,7 +1566,7 @@ getObjectDescription(const ObjectAddress *object)
colobject.objectId = attrdef->adrelid;
colobject.objectSubId = attrdef->adnum;
- appendStringInfo(&buffer, "default for %s",
+ appendStringInfo(&buffer, gettext("default for %s"),
getObjectDescription(&colobject));
systable_endscan(adscan);
@@ -1605,14 +1584,14 @@ getObjectDescription(const ObjectAddress *object)
if (!HeapTupleIsValid(langTup))
elog(ERROR, "cache lookup failed for language %u",
object->objectId);
- appendStringInfo(&buffer, "language %s",
+ appendStringInfo(&buffer, gettext("language %s"),
NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname));
ReleaseSysCache(langTup);
break;
}
case OCLASS_OPERATOR:
- appendStringInfo(&buffer, "operator %s",
+ appendStringInfo(&buffer, gettext("operator %s"),
format_operator(object->objectId));
break;
@@ -1632,16 +1611,6 @@ getObjectDescription(const ObjectAddress *object)
object->objectId);
opcForm = (Form_pg_opclass) GETSTRUCT(opcTup);
- /* Qualify the name if not visible in search path */
- if (OpclassIsVisible(object->objectId))
- nspname = NULL;
- else
- nspname = get_namespace_name(opcForm->opcnamespace);
-
- appendStringInfo(&buffer, "operator class %s",
- quote_qualified_identifier(nspname,
- NameStr(opcForm->opcname)));
-
amTup = SearchSysCache(AMOID,
ObjectIdGetDatum(opcForm->opcamid),
0, 0, 0);
@@ -1650,7 +1619,15 @@ getObjectDescription(const ObjectAddress *object)
opcForm->opcamid);
amForm = (Form_pg_am) GETSTRUCT(amTup);
- appendStringInfo(&buffer, " for %s",
+ /* Qualify the name if not visible in search path */
+ if (OpclassIsVisible(object->objectId))
+ nspname = NULL;
+ else
+ nspname = get_namespace_name(opcForm->opcnamespace);
+
+ appendStringInfo(&buffer, gettext("operator class %s for %s"),
+ quote_qualified_identifier(nspname,
+ NameStr(opcForm->opcname)),
NameStr(amForm->amname));
ReleaseSysCache(amTup);
@@ -1684,7 +1661,7 @@ getObjectDescription(const ObjectAddress *object)
rule = (Form_pg_rewrite) GETSTRUCT(tup);
- appendStringInfo(&buffer, "rule %s on ",
+ appendStringInfo(&buffer, gettext("rule %s on "),
NameStr(rule->rulename));
getRelationDescription(&buffer, rule->ev_class);
@@ -1719,7 +1696,7 @@ getObjectDescription(const ObjectAddress *object)
trig = (Form_pg_trigger) GETSTRUCT(tup);
- appendStringInfo(&buffer, "trigger %s on ",
+ appendStringInfo(&buffer, gettext("trigger %s on "),
NameStr(trig->tgname));
getRelationDescription(&buffer, trig->tgrelid);
@@ -1736,7 +1713,7 @@ getObjectDescription(const ObjectAddress *object)
if (!nspname)
elog(ERROR, "cache lookup failed for namespace %u",
object->objectId);
- appendStringInfo(&buffer, "schema %s", nspname);
+ appendStringInfo(&buffer, gettext("schema %s"), nspname);
break;
}
@@ -1780,40 +1757,40 @@ getRelationDescription(StringInfo buffer, Oid relid)
switch (relForm->relkind)
{
case RELKIND_RELATION:
- appendStringInfo(buffer, "table %s",
+ appendStringInfo(buffer, gettext("table %s"),
relname);
break;
case RELKIND_INDEX:
- appendStringInfo(buffer, "index %s",
+ appendStringInfo(buffer, gettext("index %s"),
relname);
break;
case RELKIND_SPECIAL:
- appendStringInfo(buffer, "special system relation %s",
+ appendStringInfo(buffer, gettext("special system relation %s"),
relname);
break;
case RELKIND_SEQUENCE:
- appendStringInfo(buffer, "sequence %s",
+ appendStringInfo(buffer, gettext("sequence %s"),
relname);
break;
case RELKIND_UNCATALOGED:
- appendStringInfo(buffer, "uncataloged table %s",
+ appendStringInfo(buffer, gettext("uncataloged table %s"),
relname);
break;
case RELKIND_TOASTVALUE:
- appendStringInfo(buffer, "toast table %s",
+ appendStringInfo(buffer, gettext("toast table %s"),
relname);
break;
case RELKIND_VIEW:
- appendStringInfo(buffer, "view %s",
+ appendStringInfo(buffer, gettext("view %s"),
relname);
break;
case RELKIND_COMPOSITE_TYPE:
- appendStringInfo(buffer, "composite type %s",
+ appendStringInfo(buffer, gettext("composite type %s"),
relname);
break;
default:
/* shouldn't get here */
- appendStringInfo(buffer, "relation %s",
+ appendStringInfo(buffer, gettext("relation %s"),
relname);
break;
}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 087e2d7f23c..51b7d31772a 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.262 2004/04/01 21:28:43 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.263 2004/05/05 04:48:45 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -72,7 +72,6 @@ static void AddNewRelationType(const char *typeName,
char new_rel_kind,
Oid new_type_oid);
static void RelationRemoveInheritance(Relation relation);
-static void StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin);
static void StoreRelCheck(Relation rel, char *ccname, char *ccbin);
static void StoreConstraints(Relation rel, TupleDesc tupdesc);
static void SetRelationNumChecks(Relation rel, int numchecks);
@@ -1246,7 +1245,7 @@ heap_drop_with_catalog(Oid rid)
* Store a default expression for column attnum of relation rel.
* The expression must be presented as a nodeToString() string.
*/
-static void
+void
StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin)
{
Node *expr;
@@ -1483,16 +1482,20 @@ StoreConstraints(Relation rel, TupleDesc tupdesc)
* will be processed only if they are CONSTR_CHECK type and contain a "raw"
* expression.
*
+ * Returns a list of CookedConstraint nodes that shows the cooked form of
+ * the default and constraint expressions added to the relation.
+ *
* NB: caller should have opened rel with AccessExclusiveLock, and should
* hold that lock till end of transaction. Also, we assume the caller has
* done a CommandCounterIncrement if necessary to make the relation's catalog
* tuples visible.
*/
-void
+List *
AddRelationRawConstraints(Relation rel,
List *rawColDefaults,
List *rawConstraints)
{
+ List *cookedConstraints = NIL;
char *relname = RelationGetRelationName(rel);
TupleDesc tupleDesc;
TupleConstr *oldconstr;
@@ -1504,6 +1507,7 @@ AddRelationRawConstraints(Relation rel,
int constr_name_ctr = 0;
List *listptr;
Node *expr;
+ CookedConstraint *cooked;
/*
* Get info about existing constraints.
@@ -1544,7 +1548,15 @@ AddRelationRawConstraints(Relation rel,
expr = cookDefault(pstate, colDef->raw_default,
atp->atttypid, atp->atttypmod,
NameStr(atp->attname));
+
StoreAttrDefault(rel, colDef->attnum, nodeToString(expr));
+
+ cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
+ cooked->contype = CONSTR_DEFAULT;
+ cooked->name = NULL;
+ cooked->attnum = colDef->attnum;
+ cooked->expr = expr;
+ cookedConstraints = lappend(cookedConstraints, cooked);
}
/*
@@ -1672,6 +1684,13 @@ AddRelationRawConstraints(Relation rel,
StoreRelCheck(rel, ccname, nodeToString(expr));
numchecks++;
+
+ cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
+ cooked->contype = CONSTR_CHECK;
+ cooked->name = ccname;
+ cooked->attnum = 0;
+ cooked->expr = expr;
+ cookedConstraints = lappend(cookedConstraints, cooked);
}
/*
@@ -1682,6 +1701,8 @@ AddRelationRawConstraints(Relation rel,
* (This is critical if we added defaults but not constraints.)
*/
SetRelationNumChecks(rel, numchecks);
+
+ return cookedConstraints;
}
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 86076960a40..5a8d5b22671 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.228 2004/02/15 21:01:39 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.229 2004/05/05 04:48:45 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -470,7 +470,8 @@ index_create(Oid heapRelationId,
Oid *classObjectId,
bool primary,
bool isconstraint,
- bool allow_system_table_mods)
+ bool allow_system_table_mods,
+ bool skip_build)
{
Relation heapRelation;
Relation indexRelation;
@@ -721,21 +722,31 @@ index_create(Oid heapRelationId,
* If this is bootstrap (initdb) time, then we don't actually fill in
* the index yet. We'll be creating more indexes and classes later,
* so we delay filling them in until just before we're done with
- * bootstrapping. Otherwise, we call the routine that constructs the
- * index.
+ * bootstrapping. Similarly, if the caller specified skip_build then
+ * filling the index is delayed till later (ALTER TABLE can save work
+ * in some cases with this). Otherwise, we call the AM routine that
+ * constructs the index.
*
- * In normal processing mode, the heap and index relations are closed by
- * index_build() --- but we continue to hold the ShareLock on the heap
- * and the exclusive lock on the index that we acquired above, until
- * end of transaction.
+ * In normal processing mode, the heap and index relations are closed,
+ * but we continue to hold the ShareLock on the heap and the exclusive
+ * lock on the index that we acquired above, until end of transaction.
*/
if (IsBootstrapProcessingMode())
{
index_register(heapRelationId, indexoid, indexInfo);
/* XXX shouldn't we close the heap and index rels here? */
}
+ else if (skip_build)
+ {
+ /* caller is responsible for filling the index later on */
+ relation_close(indexRelation, NoLock);
+ heap_close(heapRelation, NoLock);
+ }
else
+ {
index_build(heapRelation, indexRelation, indexInfo);
+ /* index_build closes the passed rels */
+ }
return indexoid;
}
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index a10bbbd746c..b4bf08006eb 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.120 2004/03/23 19:35:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.121 2004/05/05 04:48:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -64,11 +64,7 @@ typedef struct
static void cluster_rel(RelToCluster *rv, bool recheck);
-static Oid make_new_heap(Oid OIDOldHeap, const char *NewName);
static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex);
-static List *get_indexattr_list(Relation OldHeap, Oid OldIndex);
-static void rebuild_indexes(Oid OIDOldHeap, List *indexes);
-static void swap_relfilenodes(Oid r1, Oid r2);
static List *get_tables_to_cluster(MemoryContext cluster_context);
@@ -479,7 +475,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid)
/*
* Create the new table that we will fill with correctly-ordered data.
*/
-static Oid
+Oid
make_new_heap(Oid OIDOldHeap, const char *NewName)
{
TupleDesc OldHeapDesc,
@@ -578,7 +574,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex)
* Get the necessary info about the indexes of the relation and
* return a list of IndexAttrs structures.
*/
-static List *
+List *
get_indexattr_list(Relation OldHeap, Oid OldIndex)
{
List *indexes = NIL;
@@ -621,7 +617,7 @@ get_indexattr_list(Relation OldHeap, Oid OldIndex)
* Create new indexes and swap the filenodes with old indexes. Then drop
* the new index (carrying the old index filenode along).
*/
-static void
+void
rebuild_indexes(Oid OIDOldHeap, List *indexes)
{
List *elem;
@@ -646,10 +642,15 @@ rebuild_indexes(Oid OIDOldHeap, List *indexes)
* matter: after the filenode swap the index will keep the
* constraint status of the old index.
*/
- newIndexOID = index_create(OIDOldHeap, newIndexName,
- attrs->indexInfo, attrs->accessMethodOID,
- attrs->classOID, false,
- false, allowSystemTableMods);
+ newIndexOID = index_create(OIDOldHeap,
+ newIndexName,
+ attrs->indexInfo,
+ attrs->accessMethodOID,
+ attrs->classOID,
+ false,
+ false,
+ allowSystemTableMods,
+ false);
CommandCounterIncrement();
/* Swap the filenodes. */
@@ -698,7 +699,7 @@ rebuild_indexes(Oid OIDOldHeap, List *indexes)
* Also swap any TOAST links, so that the toast data moves along with
* the main-table data.
*/
-static void
+void
swap_relfilenodes(Oid r1, Oid r2)
{
Relation relRelation,
@@ -789,9 +790,9 @@ swap_relfilenodes(Oid r1, Oid r2)
* their new owning relations. Otherwise the wrong one will get
* dropped ...
*
- * NOTE: for now, we can assume the new table will have a TOAST table if
- * and only if the old one does. This logic might need work if we get
- * smarter about dropped columns.
+ * NOTE: it is possible that only one table has a toast table; this
+ * can happen in CLUSTER if there were dropped columns in the old table,
+ * and in ALTER TABLE when adding or changing type of columns.
*
* NOTE: at present, a TOAST table's only dependency is the one on its
* owning table. If more are ever created, we'd need to use something
@@ -804,35 +805,43 @@ swap_relfilenodes(Oid r1, Oid r2)
toastobject;
long count;
- if (!(relform1->reltoastrelid && relform2->reltoastrelid))
- elog(ERROR, "expected both swapped tables to have TOAST tables");
-
/* Delete old dependencies */
- count = deleteDependencyRecordsFor(RelOid_pg_class,
- relform1->reltoastrelid);
- if (count != 1)
- elog(ERROR, "expected one dependency record for TOAST table, found %ld",
- count);
- count = deleteDependencyRecordsFor(RelOid_pg_class,
- relform2->reltoastrelid);
- if (count != 1)
- elog(ERROR, "expected one dependency record for TOAST table, found %ld",
- count);
+ if (relform1->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelOid_pg_class,
+ relform1->reltoastrelid);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+ if (relform2->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelOid_pg_class,
+ relform2->reltoastrelid);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
/* Register new dependencies */
baseobject.classId = RelOid_pg_class;
- baseobject.objectId = r1;
baseobject.objectSubId = 0;
toastobject.classId = RelOid_pg_class;
- toastobject.objectId = relform1->reltoastrelid;
toastobject.objectSubId = 0;
- recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
-
- baseobject.objectId = r2;
- toastobject.objectId = relform2->reltoastrelid;
+ if (relform1->reltoastrelid)
+ {
+ baseobject.objectId = r1;
+ toastobject.objectId = relform1->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
+ }
- recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
+ if (relform2->reltoastrelid)
+ {
+ baseobject.objectId = r2;
+ toastobject.objectId = relform2->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
+ }
}
/*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 507bcf50d5f..efcc70c6856 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.117 2003/12/28 21:57:36 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.118 2004/05/05 04:48:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -31,6 +31,7 @@
#include "miscadmin.h"
#include "optimizer/clauses.h"
#include "optimizer/prep.h"
+#include "parser/analyze.h"
#include "parser/parsetree.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
@@ -38,38 +39,62 @@
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+#include "utils/relcache.h"
#include "utils/syscache.h"
/* non-export function prototypes */
static void CheckPredicate(Expr *predicate);
static void ComputeIndexAttrs(IndexInfo *indexInfo, Oid *classOidP,
- List *attList,
- Oid relId,
- char *accessMethodName, Oid accessMethodId);
+ List *attList,
+ Oid relId,
+ char *accessMethodName, Oid accessMethodId,
+ bool isconstraint);
static Oid GetIndexOpClass(List *opclass, Oid attrType,
char *accessMethodName, Oid accessMethodId);
static Oid GetDefaultOpClass(Oid attrType, Oid accessMethodId);
+static char *CreateIndexName(const char *table_name, const char *column_name,
+ const char *label, Oid inamespace);
+static bool relationHasPrimaryKey(Relation rel);
+
/*
* DefineIndex
* Creates a new index.
*
- * 'attributeList' is a list of IndexElem specifying columns and expressions
+ * 'heapRelation': the relation the index will apply to.
+ * 'indexRelationName': the name for the new index, or NULL to indicate
+ * that a nonconflicting default name should be picked.
+ * 'accessMethodName': name of the AM to use.
+ * 'attributeList': a list of IndexElem specifying columns and expressions
* to index on.
- * 'predicate' is the qual specified in the where clause.
- * 'rangetable' is needed to interpret the predicate.
+ * 'predicate': the partial-index condition, or NULL if none.
+ * 'rangetable': needed to interpret the predicate.
+ * 'unique': make the index enforce uniqueness.
+ * 'primary': mark the index as a primary key in the catalogs.
+ * 'isconstraint': index is for a PRIMARY KEY or UNIQUE constraint,
+ * so build a pg_constraint entry for it.
+ * 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
+ * 'check_rights': check for CREATE rights in the namespace. (This should
+ * be true except when ALTER is deleting/recreating an index.)
+ * 'skip_build': make the catalog entries but leave the index file empty;
+ * it will be filled later.
+ * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
*/
void
DefineIndex(RangeVar *heapRelation,
char *indexRelationName,
char *accessMethodName,
List *attributeList,
+ Expr *predicate,
+ List *rangetable,
bool unique,
bool primary,
bool isconstraint,
- Expr *predicate,
- List *rangetable)
+ bool is_alter_table,
+ bool check_rights,
+ bool skip_build,
+ bool quiet)
{
Oid *classObjectId;
Oid accessMethodId;
@@ -111,15 +136,13 @@ DefineIndex(RangeVar *heapRelation,
relationId = RelationGetRelid(rel);
namespaceId = RelationGetNamespace(rel);
- heap_close(rel, NoLock);
-
/*
* Verify we (still) have CREATE rights in the rel's namespace.
* (Presumably we did when the rel was created, but maybe not
- * anymore.) Skip check if bootstrapping, since permissions machinery
- * may not be working yet.
+ * anymore.) Skip check if caller doesn't want it. Also skip check
+ * if bootstrapping, since permissions machinery may not be working yet.
*/
- if (!IsBootstrapProcessingMode())
+ if (check_rights && !IsBootstrapProcessingMode())
{
AclResult aclresult;
@@ -131,6 +154,27 @@ DefineIndex(RangeVar *heapRelation,
}
/*
+ * Select name for index if caller didn't specify
+ */
+ if (indexRelationName == NULL)
+ {
+ if (primary)
+ indexRelationName = CreateIndexName(RelationGetRelationName(rel),
+ NULL,
+ "pkey",
+ namespaceId);
+ else
+ {
+ IndexElem *iparam = (IndexElem *) lfirst(attributeList);
+
+ indexRelationName = CreateIndexName(RelationGetRelationName(rel),
+ iparam->name,
+ "key",
+ namespaceId);
+ }
+ }
+
+ /*
* look up the access method, verify it can handle the requested
* features
*/
@@ -177,13 +221,33 @@ DefineIndex(RangeVar *heapRelation,
CheckPredicate(predicate);
/*
- * Check that all of the attributes in a primary key are marked as not
- * null, otherwise attempt to ALTER TABLE .. SET NOT NULL
+ * Extra checks when creating a PRIMARY KEY index.
*/
if (primary)
{
+ List *cmds;
List *keys;
+ /*
+ * If ALTER TABLE, check that there isn't already a PRIMARY KEY.
+ * In CREATE TABLE, we have faith that the parser rejected multiple
+ * pkey clauses; and CREATE INDEX doesn't have a way to say
+ * PRIMARY KEY, so it's no problem either.
+ */
+ if (is_alter_table &&
+ relationHasPrimaryKey(rel))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("multiple primary keys for table \"%s\" are not allowed",
+ RelationGetRelationName(rel))));
+ }
+
+ /*
+ * Check that all of the attributes in a primary key are marked as not
+ * null, otherwise attempt to ALTER TABLE .. SET NOT NULL
+ */
+ cmds = NIL;
foreach(keys, attributeList)
{
IndexElem *key = (IndexElem *) lfirst(keys);
@@ -203,29 +267,43 @@ DefineIndex(RangeVar *heapRelation,
{
if (!((Form_pg_attribute) GETSTRUCT(atttuple))->attnotnull)
{
- /*
- * Try to make it NOT NULL.
- *
- * XXX: Shouldn't the ALTER TABLE .. SET NOT NULL cascade
- * to child tables? Currently, since the PRIMARY KEY
- * itself doesn't cascade, we don't cascade the
- * notnull constraint either; but this is pretty
- * debatable.
- */
- AlterTableAlterColumnSetNotNull(relationId, false,
- key->name);
+ /* Add a subcommand to make this one NOT NULL */
+ AlterTableCmd *cmd = makeNode(AlterTableCmd);
+
+ cmd->subtype = AT_SetNotNull;
+ cmd->name = key->name;
+
+ cmds = lappend(cmds, cmd);
}
ReleaseSysCache(atttuple);
}
else
{
- /* This shouldn't happen if parser did its job ... */
+ /*
+ * This shouldn't happen during CREATE TABLE, but can
+ * happen during ALTER TABLE. Keep message in sync with
+ * transformIndexConstraints() in parser/analyze.c.
+ */
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" named in key does not exist",
key->name)));
}
}
+
+ /*
+ * XXX: Shouldn't the ALTER TABLE .. SET NOT NULL cascade
+ * to child tables? Currently, since the PRIMARY KEY
+ * itself doesn't cascade, we don't cascade the
+ * notnull constraint(s) either; but this is pretty debatable.
+ *
+ * XXX: possible future improvement: when being called from
+ * ALTER TABLE, it would be more efficient to merge this with
+ * the outer ALTER TABLE, so as to avoid two scans. But that
+ * seems to complicate DefineIndex's API unduly.
+ */
+ if (cmds)
+ AlterTableInternal(relationId, cmds, false);
}
/*
@@ -242,11 +320,26 @@ DefineIndex(RangeVar *heapRelation,
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
ComputeIndexAttrs(indexInfo, classObjectId, attributeList,
- relationId, accessMethodName, accessMethodId);
+ relationId, accessMethodName, accessMethodId,
+ isconstraint);
+
+ heap_close(rel, NoLock);
+
+ /*
+ * Report index creation if appropriate (delay this till after most
+ * of the error checks)
+ */
+ if (isconstraint && !quiet)
+ ereport(NOTICE,
+ (errmsg("%s %s will create implicit index \"%s\" for table \"%s\"",
+ is_alter_table ? "ALTER TABLE / ADD" : "CREATE TABLE /",
+ primary ? "PRIMARY KEY" : "UNIQUE",
+ indexRelationName, RelationGetRelationName(rel))));
index_create(relationId, indexRelationName,
indexInfo, accessMethodId, classObjectId,
- primary, isconstraint, allowSystemTableMods);
+ primary, isconstraint,
+ allowSystemTableMods, skip_build);
/*
* We update the relation's pg_class tuple even if it already has
@@ -303,7 +396,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
List *attList, /* list of IndexElem's */
Oid relId,
char *accessMethodName,
- Oid accessMethodId)
+ Oid accessMethodId,
+ bool isconstraint)
{
List *rest;
int attn = 0;
@@ -325,10 +419,19 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
Assert(attribute->expr == NULL);
atttuple = SearchSysCacheAttName(relId, attribute->name);
if (!HeapTupleIsValid(atttuple))
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" does not exist",
- attribute->name)));
+ {
+ /* difference in error message spellings is historical */
+ if (isconstraint)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" named in key does not exist",
+ attribute->name)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" does not exist",
+ attribute->name)));
+ }
attform = (Form_pg_attribute) GETSTRUCT(atttuple);
indexInfo->ii_KeyAttrNumbers[attn] = attform->attnum;
atttype = attform->atttypid;
@@ -554,6 +657,79 @@ GetDefaultOpClass(Oid attrType, Oid accessMethodId)
}
/*
+ * Select a nonconflicting name for an index.
+ */
+static char *
+CreateIndexName(const char *table_name, const char *column_name,
+ const char *label, Oid inamespace)
+{
+ int pass = 0;
+ char *iname = NULL;
+ char typename[NAMEDATALEN];
+
+ /*
+ * The type name for makeObjectName is label, or labelN if that's
+ * necessary to prevent collision with existing indexes.
+ */
+ strncpy(typename, label, sizeof(typename));
+
+ for (;;)
+ {
+ iname = makeObjectName(table_name, column_name, typename);
+
+ if (!OidIsValid(get_relname_relid(iname, inamespace)))
+ break;
+
+ /* found a conflict, so try a new name component */
+ pfree(iname);
+ snprintf(typename, sizeof(typename), "%s%d", label, ++pass);
+ }
+
+ return iname;
+}
+
+/*
+ * relationHasPrimaryKey -
+ *
+ * See whether an existing relation has a primary key.
+ */
+static bool
+relationHasPrimaryKey(Relation rel)
+{
+ bool result = false;
+ List *indexoidlist,
+ *indexoidscan;
+
+ /*
+ * Get the list of index OIDs for the table from the relcache, and
+ * look up each one in the pg_index syscache until we find one marked
+ * primary key (hopefully there isn't more than one such).
+ */
+ indexoidlist = RelationGetIndexList(rel);
+
+ foreach(indexoidscan, indexoidlist)
+ {
+ Oid indexoid = lfirsto(indexoidscan);
+ HeapTuple indexTuple;
+
+ indexTuple = SearchSysCache(INDEXRELID,
+ ObjectIdGetDatum(indexoid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(indexTuple)) /* should not happen */
+ elog(ERROR, "cache lookup failed for index %u", indexoid);
+ result = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary;
+ ReleaseSysCache(indexTuple);
+ if (result)
+ break;
+ }
+
+ freeList(indexoidlist);
+
+ return result;
+}
+
+
+/*
* RemoveIndex
* Deletes an index.
*/
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a6e3a93d349..a9c307b80c3 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.102 2004/04/01 21:28:44 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.103 2004/05/05 04:48:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -24,27 +24,33 @@
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_constraint.h"
+#include "catalog/pg_depend.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/cluster.h"
+#include "commands/defrem.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
#include "executor/executor.h"
+#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/plancat.h"
#include "optimizer/prep.h"
+#include "parser/analyze.h"
#include "parser/gramparse.h"
+#include "parser/parser.h"
#include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
@@ -76,6 +82,83 @@ typedef struct OnCommitItem
static List *on_commits = NIL;
+/*
+ * State information for ALTER TABLE
+ *
+ * The pending-work queue for an ALTER TABLE is a List of AlteredTableInfo
+ * structs, one for each table modified by the operation (the named table
+ * plus any child tables that are affected). We save lists of subcommands
+ * to apply to this table (possibly modified by parse transformation steps);
+ * these lists will be executed in Phase 2. If a Phase 3 step is needed,
+ * necessary information is stored in the constraints and newvals lists.
+ *
+ * Phase 2 is divided into multiple passes; subcommands are executed in
+ * a pass determined by subcommand type.
+ */
+
+#define AT_PASS_DROP 0 /* DROP (all flavors) */
+#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */
+#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */
+#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */
+#define AT_PASS_COL_ATTRS 4 /* set other column attributes */
+/* We could support a RENAME COLUMN pass here, but not currently used */
+#define AT_PASS_ADD_COL 5 /* ADD COLUMN */
+#define AT_PASS_ADD_INDEX 6 /* ADD indexes */
+#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */
+#define AT_PASS_MISC 8 /* other stuff */
+#define AT_NUM_PASSES 9
+
+typedef struct AlteredTableInfo
+{
+ /* Information saved before any work commences: */
+ Oid relid; /* Relation to work on */
+ TupleDesc oldDesc; /* Pre-modification tuple descriptor */
+ /* Information saved by Phase 1 for Phase 2: */
+ List *subcmds[AT_NUM_PASSES]; /* Lists of AlterTableCmd */
+ /* Information saved by Phases 1/2 for Phase 3: */
+ List *constraints; /* List of NewConstraint */
+ List *newvals; /* List of NewColumnValue */
+ /* Objects to rebuild after completing ALTER TYPE operations */
+ List *changedConstraintOids; /* OIDs of constraints to rebuild */
+ List *changedConstraintDefs; /* string definitions of same */
+ List *changedIndexOids; /* OIDs of indexes to rebuild */
+ List *changedIndexDefs; /* string definitions of same */
+ /* Workspace for ATExecAddConstraint */
+ int constr_name_ctr;
+} AlteredTableInfo;
+
+/* Struct describing one new constraint to check in Phase 3 scan */
+typedef struct NewConstraint
+{
+ char *name; /* Constraint name, or NULL if none */
+ ConstrType contype; /* CHECK, NOT_NULL, or FOREIGN */
+ AttrNumber attnum; /* only relevant for NOT_NULL */
+ Oid refrelid; /* PK rel, if FOREIGN */
+ Node *qual; /* Check expr or FkConstraint struct */
+ List *qualstate; /* Execution state for CHECK */
+} NewConstraint;
+
+/*
+ * Struct describing one new column value that needs to be computed during
+ * Phase 3 copy (this could be either a new column with a non-null default, or
+ * a column that we're changing the type of). Columns without such an entry
+ * are just copied from the old table during ATRewriteTable. Note that the
+ * expr is an expression over *old* table values.
+ */
+typedef struct NewColumnValue
+{
+ AttrNumber attnum; /* which column */
+ Expr *expr; /* expression to compute */
+ ExprState *exprstate; /* execution state */
+} NewColumnValue;
+
+
+/* Used by attribute and relation renaming routines: */
+#define RI_TRIGGER_PK 1 /* is a trigger on the PK relation */
+#define RI_TRIGGER_FK 2 /* is a trigger on the FK relation */
+#define RI_TRIGGER_NONE 0 /* is not an RI trigger function */
+
+
static List *MergeAttributes(List *schema, List *supers, bool istemp,
List **supOids, List **supconstr, int *supOidCount);
static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno);
@@ -83,9 +166,6 @@ static void StoreCatalogInheritance(Oid relationId, List *supers);
static int findAttrByName(const char *attributeName, List *schema);
static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass);
static bool needs_toast_table(Relation rel);
-static void AlterTableAddCheckConstraint(Relation rel, Constraint *constr);
-static void AlterTableAddForeignKeyConstraint(Relation rel,
- FkConstraint *fkconstraint);
static int transformColumnNameList(Oid relId, List *colList,
int16 *attnums, Oid *atttypids);
static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
@@ -100,13 +180,58 @@ static void validateForeignKeyConstraint(FkConstraint *fkconstraint,
static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
Oid constrOid);
static char *fkMatchTypeToString(char match_type);
-
-/* Used by attribute and relation renaming routines: */
-
-#define RI_TRIGGER_PK 1 /* is a trigger on the PK relation */
-#define RI_TRIGGER_FK 2 /* is a trigger on the FK relation */
-#define RI_TRIGGER_NONE 0 /* is not an RI trigger function */
-
+static void ATController(Relation rel, List *cmds, bool recurse);
+static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
+ bool recurse, bool recursing);
+static void ATRewriteCatalogs(List **wqueue);
+static void ATExecCmd(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd);
+static void ATRewriteTables(List **wqueue);
+static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap);
+static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
+static void ATSimplePermissions(Relation rel, bool allowView);
+static void ATSimpleRecursion(List **wqueue, Relation rel,
+ AlterTableCmd *cmd, bool recurse);
+static void ATOneLevelRecursion(List **wqueue, Relation rel,
+ AlterTableCmd *cmd);
+static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse,
+ AlterTableCmd *cmd);
+static void ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
+ ColumnDef *colDef);
+static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
+static void ATExecDropNotNull(Relation rel, const char *colName);
+static void ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
+ const char *colName);
+static void ATExecColumnDefault(Relation rel, const char *colName,
+ Node *newDefault);
+static void ATPrepSetStatistics(Relation rel, const char *colName,
+ Node *flagValue);
+static void ATExecSetStatistics(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,
+ DropBehavior behavior,
+ bool recurse, bool recursing);
+static void ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
+ IndexStmt *stmt, bool is_rebuild);
+static void ATExecAddConstraint(AlteredTableInfo *tab, Relation rel,
+ Node *newConstraint);
+static void ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
+ FkConstraint *fkconstraint);
+static void ATPrepDropConstraint(List **wqueue, Relation rel,
+ bool recurse, AlterTableCmd *cmd);
+static void ATExecDropConstraint(Relation rel, const char *constrName,
+ DropBehavior behavior, bool quiet);
+static void ATPrepAlterColumnType(List **wqueue,
+ AlteredTableInfo *tab, Relation rel,
+ bool recurse, bool recursing,
+ AlterTableCmd *cmd);
+static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
+ const char *colName, TypeName *typename);
+static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab);
+static void ATPostAlterTypeParse(char *cmd, List **wqueue);
+static void ATExecChangeOwner(Oid relationOid, int32 newOwnerSysId);
+static void ATExecClusterOn(Relation rel, const char *indexName);
static int ri_trigger_type(Oid tgfoid);
static void update_ri_trigger_args(Oid relid,
const char *oldname,
@@ -1100,7 +1225,7 @@ renameatt(Oid myrelid,
if (childrelid == myrelid)
continue;
- /* note we need not recurse again! */
+ /* note we need not recurse again */
renameatt(childrelid, oldattname, newattname, false, true);
}
}
@@ -1129,7 +1254,7 @@ renameatt(Oid myrelid,
attform = (Form_pg_attribute) GETSTRUCT(atttup);
attnum = attform->attnum;
- if (attnum < 0)
+ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rename system column \"%s\"",
@@ -1240,8 +1365,7 @@ renameatt(Oid myrelid,
true, false);
}
- relation_close(targetrelation, NoLock); /* close rel but keep
- * lock! */
+ relation_close(targetrelation, NoLock); /* close rel but keep lock */
}
/*
@@ -1559,55 +1683,803 @@ update_ri_trigger_args(Oid relid,
CommandCounterIncrement();
}
+/*
+ * AlterTable
+ * Execute ALTER TABLE, which can be a list of subcommands
+ *
+ * ALTER TABLE is performed in three phases:
+ * 1. Examine subcommands and perform pre-transformation checking.
+ * 2. Update system catalogs.
+ * 3. Scan table(s) to check new constraints, and optionally recopy
+ * the data into new table(s).
+ * Phase 3 is not performed unless one or more of the subcommands requires
+ * it. The intention of this design is to allow multiple independent
+ * updates of the table schema to be performed with only one pass over the
+ * data.
+ *
+ * ATPrepCmd performs phase 1. A "work queue" entry is created for
+ * each table to be affected (there may be multiple affected tables if the
+ * commands traverse a table inheritance hierarchy). Also we do preliminary
+ * validation of the subcommands, including parse transformation of those
+ * expressions that need to be evaluated with respect to the old table
+ * schema.
+ *
+ * ATRewriteCatalogs performs phase 2 for each affected table (note that
+ * phases 2 and 3 do no explicit recursion, since phase 1 already did it).
+ * Certain subcommands need to be performed before others to avoid
+ * unnecessary conflicts; for example, DROP COLUMN should come before
+ * ADD COLUMN. Therefore phase 1 divides the subcommands into multiple
+ * lists, one for each logical "pass" of phase 2.
+ *
+ * ATRewriteTables performs phase 3 for those tables that need it.
+ *
+ * Thanks to the magic of MVCC, an error anywhere along the way rolls back
+ * the whole operation; we don't have to do anything special to clean up.
+ */
+void
+AlterTable(AlterTableStmt *stmt)
+{
+ ATController(relation_openrv(stmt->relation, AccessExclusiveLock),
+ stmt->cmds,
+ interpretInhOption(stmt->relation->inhOpt));
+}
-/* ----------------
- * AlterTableAddColumn
- * (formerly known as PerformAddAttribute)
+/*
+ * AlterTableInternal
*
- * adds an additional attribute to a relation
- * ----------------
+ * ALTER TABLE with target specified by OID
*/
void
-AlterTableAddColumn(Oid myrelid,
- bool recurse,
- ColumnDef *colDef)
+AlterTableInternal(Oid relid, List *cmds, bool recurse)
{
- Relation rel,
- pgclass,
- attrdesc;
- HeapTuple reltup;
- HeapTuple newreltup;
- HeapTuple attributeTuple;
- Form_pg_attribute attribute;
- FormData_pg_attribute attributeD;
+ ATController(relation_open(relid, AccessExclusiveLock),
+ cmds,
+ recurse);
+}
+
+static void
+ATController(Relation rel, List *cmds, bool recurse)
+{
+ List *wqueue = NIL;
+ List *lcmd;
+
+ /* Phase 1: preliminary examination of commands, create work queue */
+ foreach(lcmd, cmds)
+ {
+ AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
+
+ ATPrepCmd(&wqueue, rel, cmd, recurse, false);
+ }
+
+ /* Close the relation, but keep lock until commit */
+ relation_close(rel, NoLock);
+
+ /* Phase 2: update system catalogs */
+ ATRewriteCatalogs(&wqueue);
+
+ /* Phase 3: scan/rewrite tables as needed */
+ ATRewriteTables(&wqueue);
+}
+
+/*
+ * ATPrepCmd
+ *
+ * Traffic cop for ALTER TABLE Phase 1 operations, including simple
+ * recursion and permission checks.
+ *
+ * Caller must have acquired AccessExclusiveLock on relation already.
+ * This lock should be held until commit.
+ */
+static void
+ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
+ bool recurse, bool recursing)
+{
+ AlteredTableInfo *tab;
+ int pass;
+
+ /* Find or create work queue entry for this table */
+ tab = ATGetQueueEntry(wqueue, rel);
+
+ /*
+ * Copy the original subcommand for each table. This avoids conflicts
+ * when different child tables need to make different parse
+ * transformations (for example, the same column may have different
+ * column numbers in different children).
+ */
+ cmd = copyObject(cmd);
+
+ /*
+ * Do permissions checking, recursion to child tables if needed,
+ * and any additional phase-1 processing needed.
+ */
+ switch (cmd->subtype)
+ {
+ case AT_AddColumn: /* ADD COLUMN */
+ ATSimplePermissions(rel, false);
+ /* Performs own recursion */
+ ATPrepAddColumn(wqueue, rel, recurse, cmd);
+ pass = AT_PASS_ADD_COL;
+ break;
+ case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
+ /*
+ * We allow defaults on views so that INSERT into a view can have
+ * default-ish behavior. This works because the rewriter
+ * substitutes default values into INSERTs before it expands
+ * rules.
+ */
+ ATSimplePermissions(rel, true);
+ ATSimpleRecursion(wqueue, rel, cmd, recurse);
+ /* No command-specific prep needed */
+ pass = AT_PASS_ADD_CONSTR;
+ break;
+ case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
+ ATSimplePermissions(rel, false);
+ ATSimpleRecursion(wqueue, rel, cmd, recurse);
+ /* No command-specific prep needed */
+ pass = AT_PASS_DROP;
+ break;
+ case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
+ ATSimplePermissions(rel, false);
+ ATSimpleRecursion(wqueue, rel, cmd, recurse);
+ /* No command-specific prep needed */
+ pass = AT_PASS_ADD_CONSTR;
+ break;
+ case AT_SetStatistics: /* ALTER COLUMN STATISTICS */
+ ATSimpleRecursion(wqueue, rel, cmd, recurse);
+ /* Performs own permission checks */
+ ATPrepSetStatistics(rel, cmd->name, cmd->def);
+ pass = AT_PASS_COL_ATTRS;
+ break;
+ case AT_SetStorage: /* ALTER COLUMN STORAGE */
+ ATSimplePermissions(rel, false);
+ ATSimpleRecursion(wqueue, rel, cmd, recurse);
+ /* No command-specific prep needed */
+ pass = AT_PASS_COL_ATTRS;
+ break;
+ case AT_DropColumn: /* DROP COLUMN */
+ ATSimplePermissions(rel, false);
+ /* Recursion occurs during execution phase */
+ /* No command-specific prep needed except saving recurse flag */
+ if (recurse)
+ cmd->subtype = AT_DropColumnRecurse;
+ pass = AT_PASS_DROP;
+ break;
+ case AT_AddIndex: /* ADD INDEX */
+ ATSimplePermissions(rel, false);
+ /* This command never recurses */
+ /* No command-specific prep needed */
+ pass = AT_PASS_ADD_INDEX;
+ break;
+ case AT_AddConstraint: /* ADD CONSTRAINT */
+ ATSimplePermissions(rel, false);
+ /*
+ * Currently we recurse only for CHECK constraints, never for
+ * foreign-key constraints. UNIQUE/PKEY constraints won't be
+ * seen here.
+ */
+ if (IsA(cmd->def, Constraint))
+ ATSimpleRecursion(wqueue, rel, cmd, recurse);
+ /* No command-specific prep needed */
+ pass = AT_PASS_ADD_CONSTR;
+ break;
+ case AT_DropConstraint: /* DROP CONSTRAINT */
+ ATSimplePermissions(rel, false);
+ /* Performs own recursion */
+ ATPrepDropConstraint(wqueue, rel, recurse, cmd);
+ pass = AT_PASS_DROP;
+ break;
+ case AT_DropConstraintQuietly: /* DROP CONSTRAINT for child */
+ ATSimplePermissions(rel, false);
+ ATSimpleRecursion(wqueue, rel, cmd, recurse);
+ /* No command-specific prep needed */
+ pass = AT_PASS_DROP;
+ break;
+ case AT_AlterColumnType: /* ALTER COLUMN TYPE */
+ ATSimplePermissions(rel, false);
+ /* Performs own recursion */
+ ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd);
+ pass = AT_PASS_ALTER_TYPE;
+ break;
+ case AT_ToastTable: /* CREATE TOAST TABLE */
+ ATSimplePermissions(rel, false);
+ /* This command never recurses */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
+ case AT_ChangeOwner: /* ALTER OWNER */
+ /* check that we are the superuser */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to alter owner")));
+ /* This command never recurses */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
+ case AT_ClusterOn: /* CLUSTER ON */
+ ATSimplePermissions(rel, false);
+ /* This command never recurses */
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
+ case AT_DropOids: /* SET WITHOUT OIDS */
+ ATSimplePermissions(rel, false);
+ /* Performs own recursion */
+ if (rel->rd_rel->relhasoids)
+ {
+ AlterTableCmd *dropCmd = makeNode(AlterTableCmd);
+
+ dropCmd->subtype = AT_DropColumn;
+ dropCmd->name = pstrdup("oid");
+ dropCmd->behavior = cmd->behavior;
+ ATPrepCmd(wqueue, rel, dropCmd, recurse, false);
+ }
+ pass = AT_PASS_DROP;
+ break;
+ default: /* oops */
+ elog(ERROR, "unrecognized alter table type: %d",
+ (int) cmd->subtype);
+ pass = 0; /* keep compiler quiet */
+ break;
+ }
+
+ /* Add the subcommand to the appropriate list for phase 2 */
+ tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd);
+}
+
+/*
+ * ATRewriteCatalogs
+ *
+ * Traffic cop for ALTER TABLE Phase 2 operations. Subcommands are
+ * dispatched in a "safe" execution order (designed to avoid unnecessary
+ * conflicts).
+ */
+static void
+ATRewriteCatalogs(List **wqueue)
+{
+ int pass;
+ List *ltab;
+
+ /*
+ * We process all the tables "in parallel", one pass at a time. This
+ * is needed because we may have to propagate work from one table
+ * to another (specifically, ALTER TYPE on a foreign key's PK has to
+ * dispatch the re-adding of the foreign key constraint to the other
+ * table). Work can only be propagated into later passes, however.
+ */
+ for (pass = 0; pass < AT_NUM_PASSES; pass++)
+ {
+ /* Go through each table that needs to be processed */
+ foreach(ltab, *wqueue)
+ {
+ AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+ List *subcmds = tab->subcmds[pass];
+ Relation rel;
+ List *lcmd;
+
+ if (subcmds == NIL)
+ continue;
+
+ /* Exclusive lock was obtained by phase 1, needn't get it again */
+ rel = relation_open(tab->relid, NoLock);
+
+ foreach(lcmd, subcmds)
+ {
+ ATExecCmd(tab, rel, (AlterTableCmd *) lfirst(lcmd));
+ }
+
+ /*
+ * After the ALTER TYPE pass, do cleanup work (this is not done in
+ * ATExecAlterColumnType since it should be done only once if
+ * multiple columns of a table are altered).
+ */
+ if (pass == AT_PASS_ALTER_TYPE)
+ ATPostAlterTypeCleanup(wqueue, tab);
+
+ relation_close(rel, NoLock);
+ }
+ }
+
+ /*
+ * Do an implicit CREATE TOAST TABLE if we executed any subcommands
+ * that might have added a column or changed column storage.
+ */
+ foreach(ltab, *wqueue)
+ {
+ AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+
+ if (tab->subcmds[AT_PASS_ADD_COL] ||
+ tab->subcmds[AT_PASS_ALTER_TYPE] ||
+ tab->subcmds[AT_PASS_COL_ATTRS])
+ {
+ AlterTableCreateToastTable(tab->relid, true);
+ }
+ }
+}
+
+/*
+ * ATExecCmd: dispatch a subcommand to appropriate execution routine
+ */
+static void
+ATExecCmd(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd)
+{
+ switch (cmd->subtype)
+ {
+ case AT_AddColumn: /* ADD COLUMN */
+ ATExecAddColumn(tab, rel, (ColumnDef *) cmd->def);
+ break;
+ case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
+ ATExecColumnDefault(rel, cmd->name, cmd->def);
+ break;
+ case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
+ ATExecDropNotNull(rel, cmd->name);
+ break;
+ case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
+ ATExecSetNotNull(tab, rel, cmd->name);
+ break;
+ case AT_SetStatistics: /* ALTER COLUMN STATISTICS */
+ ATExecSetStatistics(rel, cmd->name, cmd->def);
+ break;
+ case AT_SetStorage: /* ALTER COLUMN STORAGE */
+ ATExecSetStorage(rel, cmd->name, cmd->def);
+ break;
+ case AT_DropColumn: /* DROP COLUMN */
+ ATExecDropColumn(rel, cmd->name, cmd->behavior, false, false);
+ break;
+ case AT_DropColumnRecurse: /* DROP COLUMN with recursion */
+ ATExecDropColumn(rel, cmd->name, cmd->behavior, true, false);
+ break;
+ case AT_AddIndex: /* ADD INDEX */
+ ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false);
+ break;
+ case AT_ReAddIndex: /* ADD INDEX */
+ ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, true);
+ break;
+ case AT_AddConstraint: /* ADD CONSTRAINT */
+ ATExecAddConstraint(tab, rel, cmd->def);
+ break;
+ case AT_DropConstraint: /* DROP CONSTRAINT */
+ ATExecDropConstraint(rel, cmd->name, cmd->behavior, false);
+ break;
+ case AT_DropConstraintQuietly: /* DROP CONSTRAINT for child */
+ ATExecDropConstraint(rel, cmd->name, cmd->behavior, true);
+ break;
+ case AT_AlterColumnType: /* ALTER COLUMN TYPE */
+ ATExecAlterColumnType(tab, rel, cmd->name, (TypeName *) cmd->def);
+ break;
+ case AT_ToastTable: /* CREATE TOAST TABLE */
+ AlterTableCreateToastTable(RelationGetRelid(rel), false);
+ break;
+ case AT_ChangeOwner: /* ALTER OWNER */
+ /* get_usesysid raises an error if no such user */
+ ATExecChangeOwner(RelationGetRelid(rel), get_usesysid(cmd->name));
+ break;
+ case AT_ClusterOn: /* CLUSTER ON */
+ ATExecClusterOn(rel, cmd->name);
+ break;
+ case AT_DropOids: /* SET WITHOUT OIDS */
+ /*
+ * Nothing to do here; we'll have generated a DropColumn subcommand
+ * to do the real work
+ */
+ break;
+ default: /* oops */
+ elog(ERROR, "unrecognized alter table type: %d",
+ (int) cmd->subtype);
+ break;
+ }
+
+ /*
+ * Bump the command counter to ensure the next subcommand in the sequence
+ * can see the changes so far
+ */
+ CommandCounterIncrement();
+}
+
+/*
+ * ATRewriteTables: ALTER TABLE phase 3
+ */
+static void
+ATRewriteTables(List **wqueue)
+{
+ List *ltab;
+
+ /* Go through each table that needs to be checked or rewritten */
+ foreach(ltab, *wqueue)
+ {
+ AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+
+ /*
+ * We only need to rewrite the table if at least one column needs
+ * to be recomputed.
+ */
+ if (tab->newvals != NIL)
+ {
+ /* Build a temporary relation and copy data */
+ Oid OIDNewHeap;
+ char NewHeapName[NAMEDATALEN];
+ List *indexes;
+ Relation OldHeap;
+ ObjectAddress object;
+
+ /* Save the information about all indexes on the relation. */
+ OldHeap = heap_open(tab->relid, NoLock);
+ indexes = get_indexattr_list(OldHeap, InvalidOid);
+ heap_close(OldHeap, NoLock);
+
+ /*
+ * Create the new heap, using a temporary name in the same
+ * namespace as the existing table. NOTE: there is some risk of
+ * collision with user relnames. Working around this seems more
+ * trouble than it's worth; in particular, we can't create the new
+ * heap in a different namespace from the old, or we will have
+ * problems with the TEMP status of temp tables.
+ */
+ snprintf(NewHeapName, sizeof(NewHeapName),
+ "pg_temp_%u", tab->relid);
+
+ OIDNewHeap = make_new_heap(tab->relid, NewHeapName);
+
+ /*
+ * Copy the heap data into the new table with the desired
+ * modifications, and test the current data within the table
+ * against new constraints generated by ALTER TABLE commands.
+ */
+ ATRewriteTable(tab, OIDNewHeap);
+
+ /* Swap the relfilenodes of the old and new heaps. */
+ swap_relfilenodes(tab->relid, OIDNewHeap);
+
+ CommandCounterIncrement();
+
+ /* Destroy new heap with old filenode */
+ object.classId = RelOid_pg_class;
+ object.objectId = OIDNewHeap;
+ object.objectSubId = 0;
+
+ /*
+ * The new relation is local to our transaction and we know nothing
+ * depends on it, so DROP_RESTRICT should be OK.
+ */
+ performDeletion(&object, DROP_RESTRICT);
+ /* performDeletion does CommandCounterIncrement at end */
+
+ /*
+ * Rebuild each index on the relation. We do not need
+ * CommandCounterIncrement() because rebuild_indexes does it.
+ */
+ rebuild_indexes(tab->relid, indexes);
+ }
+ else
+ {
+ /*
+ * Test the current data within the table against new constraints
+ * generated by ALTER TABLE commands, but don't rebuild data.
+ */
+ ATRewriteTable(tab, InvalidOid);
+ }
+ }
+
+ /*
+ * Foreign key constraints are checked in a final pass, since
+ * (a) it's generally best to examine each one separately, and
+ * (b) it's at least theoretically possible that we have changed
+ * both relations of the foreign key, and we'd better have finished
+ * both rewrites before we try to read the tables.
+ */
+ foreach(ltab, *wqueue)
+ {
+ AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+ Relation rel = NULL;
+ List *lcon;
+
+ foreach(lcon, tab->constraints)
+ {
+ NewConstraint *con = lfirst(lcon);
+
+ if (con->contype == CONSTR_FOREIGN)
+ {
+ FkConstraint *fkconstraint = (FkConstraint *) con->qual;
+ Relation refrel;
+
+ if (rel == NULL)
+ {
+ /* Long since locked, no need for another */
+ rel = heap_open(tab->relid, NoLock);
+ }
+
+ refrel = heap_open(con->refrelid, RowShareLock);
+
+ validateForeignKeyConstraint(fkconstraint, rel, refrel);
+
+ heap_close(refrel, NoLock);
+ }
+ }
+
+ if (rel)
+ heap_close(rel, NoLock);
+ }
+}
+
+/*
+ * ATRewriteTable: scan or rewrite one table
+ *
+ * OIDNewHeap is InvalidOid if we don't need to rewrite
+ */
+static void
+ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
+{
+ Relation oldrel;
+ Relation newrel;
+ TupleDesc oldTupDesc;
+ TupleDesc newTupDesc;
+ bool needscan = false;
int i;
- int minattnum,
- maxatts;
- HeapTuple typeTuple;
- Form_pg_type tform;
- int attndims;
- ObjectAddress myself,
- referenced;
+ List *l;
+ EState *estate;
/*
- * Grab an exclusive lock on the target table, which we will NOT
- * release until end of transaction.
+ * Open the relation(s). We have surely already locked the existing
+ * table.
*/
- rel = heap_open(myrelid, AccessExclusiveLock);
+ oldrel = heap_open(tab->relid, NoLock);
+ oldTupDesc = tab->oldDesc;
+ newTupDesc = RelationGetDescr(oldrel); /* includes all mods */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table",
- RelationGetRelationName(rel))));
+ if (OidIsValid(OIDNewHeap))
+ newrel = heap_open(OIDNewHeap, AccessExclusiveLock);
+ else
+ newrel = NULL;
/*
- * permissions checking. this would normally be done in utility.c,
- * but this particular routine is recursive.
- *
- * normally, only the owner of a class can change its schema.
+ * Generate the constraint and default execution states
*/
- if (!pg_class_ownercheck(myrelid, GetUserId()))
+
+ estate = CreateExecutorState();
+
+ /* Build the needed expression execution states */
+ foreach(l, tab->constraints)
+ {
+ NewConstraint *con = lfirst(l);
+
+ switch (con->contype)
+ {
+ case CONSTR_CHECK:
+ needscan = true;
+ con->qualstate = (List *)
+ ExecPrepareExpr((Expr *) con->qual, estate);
+ break;
+ case CONSTR_FOREIGN:
+ /* Nothing to do here */
+ break;
+ case CONSTR_NOTNULL:
+ needscan = true;
+ break;
+ default:
+ elog(ERROR, "unrecognized constraint type: %d",
+ (int) con->contype);
+ }
+ }
+
+ foreach(l, tab->newvals)
+ {
+ NewColumnValue *ex = lfirst(l);
+
+ needscan = true;
+
+ ex->exprstate = ExecPrepareExpr((Expr *) ex->expr, estate);
+ }
+
+ if (needscan)
+ {
+ ExprContext *econtext;
+ Datum *values;
+ char *nulls;
+ TupleTableSlot *oldslot;
+ TupleTableSlot *newslot;
+ HeapScanDesc scan;
+ HeapTuple tuple;
+
+ econtext = GetPerTupleExprContext(estate);
+
+ /*
+ * Make tuple slots for old and new tuples. Note that even when
+ * the tuples are the same, the tupDescs might not be (consider
+ * ADD COLUMN without a default).
+ */
+ oldslot = MakeTupleTableSlot();
+ ExecSetSlotDescriptor(oldslot, oldTupDesc, false);
+ newslot = MakeTupleTableSlot();
+ ExecSetSlotDescriptor(newslot, newTupDesc, false);
+
+ /* Preallocate values/nulls arrays (+1 in case natts==0) */
+ i = Max(newTupDesc->natts, oldTupDesc->natts);
+ values = (Datum *) palloc(i * sizeof(Datum) + 1);
+ nulls = (char *) palloc(i * sizeof(char) + 1);
+ memset(values, 0, i * sizeof(Datum));
+ memset(nulls, 'n', i * sizeof(char));
+
+ /*
+ * Scan through the rows, generating a new row if needed and then
+ * checking all the constraints.
+ */
+ scan = heap_beginscan(oldrel, SnapshotNow, 0, NULL);
+
+ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ {
+ if (newrel)
+ {
+ /*
+ * Extract data from old tuple. We can force to null any
+ * columns that are deleted according to the new tuple.
+ */
+ int natts = oldTupDesc->natts;
+ bool isNull;
+
+ for (i = 0; i < natts; i++)
+ {
+ if (newTupDesc->attrs[i]->attisdropped)
+ nulls[i] = 'n';
+ else
+ {
+ values[i] = heap_getattr(tuple,
+ i + 1,
+ oldTupDesc,
+ &isNull);
+ if (isNull)
+ nulls[i] = 'n';
+ else
+ nulls[i] = ' ';
+ }
+ }
+
+ /*
+ * Process supplied expressions to replace selected columns.
+ * Expression inputs come from the old tuple.
+ */
+ ExecStoreTuple(tuple, oldslot, InvalidBuffer, false);
+ econtext->ecxt_scantuple = oldslot;
+
+ foreach(l, tab->newvals)
+ {
+ NewColumnValue *ex = lfirst(l);
+
+ values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate,
+ econtext,
+ &isNull,
+ NULL);
+ if (isNull)
+ nulls[ex->attnum - 1] = 'n';
+ else
+ nulls[ex->attnum - 1] = ' ';
+ }
+
+ tuple = heap_formtuple(newTupDesc, values, nulls);
+ }
+
+ /* Now check any constraints on the possibly-changed tuple */
+ ExecStoreTuple(tuple, newslot, InvalidBuffer, false);
+ econtext->ecxt_scantuple = newslot;
+
+ foreach(l, tab->constraints)
+ {
+ NewConstraint *con = lfirst(l);
+
+ switch (con->contype)
+ {
+ case CONSTR_CHECK:
+ if (!ExecQual(con->qualstate, econtext, true))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("check constraint \"%s\" is violated by some row",
+ con->name)));
+ break;
+ case CONSTR_NOTNULL:
+ {
+ Datum d;
+ bool isnull;
+
+ d = heap_getattr(tuple, con->attnum, newTupDesc,
+ &isnull);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NOT_NULL_VIOLATION),
+ errmsg("column \"%s\" contains null values",
+ get_attname(tab->relid,
+ con->attnum))));
+ }
+ break;
+ case CONSTR_FOREIGN:
+ /* Nothing to do here */
+ break;
+ default:
+ elog(ERROR, "unrecognized constraint type: %d",
+ (int) con->contype);
+ }
+ }
+
+ /* Write the tuple out to the new relation */
+ if (newrel)
+ {
+ simple_heap_insert(newrel, tuple);
+
+ heap_freetuple(tuple);
+ }
+
+ ResetExprContext(econtext);
+
+ CHECK_FOR_INTERRUPTS();
+ }
+
+ heap_endscan(scan);
+ }
+
+ FreeExecutorState(estate);
+
+ heap_close(oldrel, NoLock);
+ if (newrel)
+ heap_close(newrel, NoLock);
+}
+
+/*
+ * ATGetQueueEntry: find or create an entry in the ALTER TABLE work queue
+ */
+static AlteredTableInfo *
+ATGetQueueEntry(List **wqueue, Relation rel)
+{
+ Oid relid = RelationGetRelid(rel);
+ AlteredTableInfo *tab;
+ List *ltab;
+
+ foreach(ltab, *wqueue)
+ {
+ tab = (AlteredTableInfo *) lfirst(ltab);
+ if (tab->relid == relid)
+ return tab;
+ }
+
+ /*
+ * Not there, so add it. Note that we make a copy of the relation's
+ * existing descriptor before anything interesting can happen to it.
+ */
+ tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
+ tab->relid = relid;
+ tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel));
+
+ *wqueue = lappend(*wqueue, tab);
+
+ return tab;
+}
+
+/*
+ * ATSimplePermissions
+ *
+ * - Ensure that it is a relation (or possibly a view)
+ * - Ensure this user is the owner
+ * - Ensure that it is not a system table
+ */
+static void
+ATSimplePermissions(Relation rel, bool allowView)
+{
+ if (rel->rd_rel->relkind != RELKIND_RELATION)
+ {
+ if (allowView)
+ {
+ if (rel->rd_rel->relkind != RELKIND_VIEW)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a table or view",
+ RelationGetRelationName(rel))));
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a table",
+ RelationGetRelationName(rel))));
+ }
+
+ /* Permissions checks */
+ if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(rel));
@@ -1616,87 +2488,111 @@ AlterTableAddColumn(Oid myrelid,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
+}
+/*
+ * ATSimpleRecursion
+ *
+ * Simple table recursion sufficient for most ALTER TABLE operations.
+ * All direct and indirect children are processed in an unspecified order.
+ * Note that if a child inherits from the original table via multiple
+ * inheritance paths, it will be visited just once.
+ */
+static void
+ATSimpleRecursion(List **wqueue, Relation rel,
+ AlterTableCmd *cmd, bool recurse)
+{
/*
- * Recurse to add the column to child classes, if requested.
- *
- * any permissions or problems with duplicate attributes will cause the
- * whole transaction to abort, which is what we want -- all or
- * nothing.
+ * Propagate to children if desired. Non-table relations never have
+ * children, so no need to search in that case.
*/
- if (recurse)
+ if (recurse && rel->rd_rel->relkind == RELKIND_RELATION)
{
+ Oid relid = RelationGetRelid(rel);
List *child,
*children;
- ColumnDef *colDefChild = copyObject(colDef);
- /* Child should see column as singly inherited */
- colDefChild->inhcount = 1;
- colDefChild->is_local = false;
-
- /* We only want direct inheritors */
- children = find_inheritance_children(myrelid);
+ /* this routine is actually in the planner */
+ children = find_all_inheritors(relid);
+ /*
+ * find_all_inheritors does the recursive search of the
+ * inheritance hierarchy, so all we have to do is process all of
+ * the relids in the list that it returns.
+ */
foreach(child, children)
{
Oid childrelid = lfirsto(child);
- HeapTuple tuple;
- Form_pg_attribute childatt;
Relation childrel;
- if (childrelid == myrelid)
+ if (childrelid == relid)
continue;
+ childrel = relation_open(childrelid, AccessExclusiveLock);
+ ATPrepCmd(wqueue, childrel, cmd, false, true);
+ relation_close(childrel, NoLock);
+ }
+ }
+}
- childrel = heap_open(childrelid, AccessExclusiveLock);
+/*
+ * ATOneLevelRecursion
+ *
+ * Here, we visit only direct inheritance children. It is expected that
+ * the command's prep routine will recurse again to find indirect children.
+ * When using this technique, a multiply-inheriting child will be visited
+ * multiple times.
+ */
+static void
+ATOneLevelRecursion(List **wqueue, Relation rel,
+ AlterTableCmd *cmd)
+{
+ Oid relid = RelationGetRelid(rel);
+ List *child,
+ *children;
- /* Does child already have a column by this name? */
- attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
- tuple = SearchSysCacheCopyAttName(childrelid, colDef->colname);
- if (!HeapTupleIsValid(tuple))
- {
- /* No, recurse to add it normally */
- heap_close(attrdesc, RowExclusiveLock);
- heap_close(childrel, NoLock);
- AlterTableAddColumn(childrelid, true, colDefChild);
- continue;
- }
- childatt = (Form_pg_attribute) GETSTRUCT(tuple);
+ /* this routine is actually in the planner */
+ children = find_inheritance_children(relid);
- /* Okay if child matches by type */
- if (typenameTypeId(colDef->typename) != childatt->atttypid ||
- colDef->typename->typmod != childatt->atttypmod)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("child table \"%s\" has different type for column \"%s\"",
- get_rel_name(childrelid), colDef->colname)));
+ foreach(child, children)
+ {
+ Oid childrelid = lfirsto(child);
+ Relation childrel;
- /*
- * XXX if we supported NOT NULL or defaults, would need to do
- * more work here to verify child matches
- */
- ereport(NOTICE,
- (errmsg("merging definition of column \"%s\" for child \"%s\"",
- colDef->colname, get_rel_name(childrelid))));
+ childrel = relation_open(childrelid, AccessExclusiveLock);
+ ATPrepCmd(wqueue, childrel, cmd, true, true);
+ relation_close(childrel, NoLock);
+ }
+}
- /* Bump the existing child att's inhcount */
- childatt->attinhcount++;
- simple_heap_update(attrdesc, &tuple->t_self, tuple);
- CatalogUpdateIndexes(attrdesc, tuple);
+/*
+ * ALTER TABLE ADD COLUMN
+ *
+ * Adds an additional attribute to a relation making the assumption that
+ * CHECK, NOT NULL, and FOREIGN KEY constraints will be removed from the
+ * AT_AddColumn AlterTableCmd by analyze.c and added as independent
+ * AlterTableCmd's.
+ */
+static void
+ATPrepAddColumn(List **wqueue, Relation rel, bool recurse,
+ AlterTableCmd *cmd)
+{
+ /*
+ * Recurse to add the column to child classes, if requested.
+ *
+ * We must recurse one level at a time, so that multiply-inheriting
+ * children are visited the right number of times and end up with the
+ * right attinhcount.
+ */
+ if (recurse)
+ {
+ AlterTableCmd *childCmd = copyObject(cmd);
+ ColumnDef *colDefChild = (ColumnDef *) childCmd->def;
- /*
- * Propagate any new CHECK constraints into the child table
- * and its descendants
- */
- if (colDef->constraints != NIL)
- {
- CommandCounterIncrement();
- AlterTableAddConstraint(childrelid, true, colDef->constraints);
- }
+ /* Child should see column as singly inherited */
+ colDefChild->inhcount = 1;
+ colDefChild->is_local = false;
- heap_freetuple(tuple);
- heap_close(attrdesc, RowExclusiveLock);
- heap_close(childrel, NoLock);
- }
+ ATOneLevelRecursion(wqueue, rel, childCmd);
}
else
{
@@ -1704,42 +2600,77 @@ AlterTableAddColumn(Oid myrelid,
* If we are told not to recurse, there had better not be any
* child tables; else the addition would put them out of step.
*/
- if (find_inheritance_children(myrelid) != NIL)
+ if (find_inheritance_children(RelationGetRelid(rel)) != NIL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("column must be added to child tables too")));
}
+}
+
+static void
+ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
+ ColumnDef *colDef)
+{
+ Oid myrelid = RelationGetRelid(rel);
+ Relation pgclass,
+ attrdesc;
+ HeapTuple reltup;
+ HeapTuple attributeTuple;
+ Form_pg_attribute attribute;
+ FormData_pg_attribute attributeD;
+ int i;
+ int minattnum,
+ maxatts;
+ HeapTuple typeTuple;
+ Form_pg_type tform;
+ Expr *defval;
+
+ attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
/*
- * OK, get on with it...
- *
- * Implementation restrictions: because we don't touch the table rows,
- * the new column values will initially appear to be NULLs. (This
- * happens because the heap tuple access routines always check for
- * attnum > # of attributes in tuple, and return NULL if so.)
- * Therefore we can't support a DEFAULT value in SQL92-compliant
- * fashion, and we also can't allow a NOT NULL constraint.
- *
- * We do allow CHECK constraints, even though these theoretically could
- * fail for NULL rows (eg, CHECK (newcol IS NOT NULL)).
+ * Are we adding the column to a recursion child? If so, check whether
+ * to merge with an existing definition for the column.
*/
- if (colDef->raw_default || colDef->cooked_default)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("adding columns with defaults is not implemented"),
- errhint("Add the column, then use ALTER TABLE SET DEFAULT.")));
+ if (colDef->inhcount > 0)
+ {
+ HeapTuple tuple;
- if (colDef->is_not_null)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("adding NOT NULL columns is not implemented"),
- errhint("Add the column, then use ALTER TABLE SET NOT NULL.")));
+ /* Does child already have a column by this name? */
+ tuple = SearchSysCacheCopyAttName(myrelid, colDef->colname);
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_attribute childatt = (Form_pg_attribute) GETSTRUCT(tuple);
+
+ /* Okay if child matches by type */
+ if (typenameTypeId(colDef->typename) != childatt->atttypid ||
+ colDef->typename->typmod != childatt->atttypmod)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("child table \"%s\" has different type for column \"%s\"",
+ RelationGetRelationName(rel), colDef->colname)));
+
+ /* Bump the existing child att's inhcount */
+ childatt->attinhcount++;
+ simple_heap_update(attrdesc, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(attrdesc, tuple);
+
+ heap_freetuple(tuple);
+
+ /* Inform the user about the merge */
+ ereport(NOTICE,
+ (errmsg("merging definition of column \"%s\" for child \"%s\"",
+ colDef->colname, RelationGetRelationName(rel))));
+
+ heap_close(attrdesc, RowExclusiveLock);
+ return;
+ }
+ }
pgclass = heap_openr(RelationRelationName, RowExclusiveLock);
- reltup = SearchSysCache(RELOID,
- ObjectIdGetDatum(myrelid),
- 0, 0, 0);
+ reltup = SearchSysCacheCopy(RELOID,
+ ObjectIdGetDatum(myrelid),
+ 0, 0, 0);
if (!HeapTupleIsValid(reltup))
elog(ERROR, "cache lookup failed for relation %u", myrelid);
@@ -1766,13 +2697,6 @@ AlterTableAddColumn(Oid myrelid,
MaxHeapAttributeNumber)));
i = minattnum + 1;
- attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
-
- if (colDef->typename->arrayBounds)
- attndims = length(colDef->typename->arrayBounds);
- else
- attndims = 0;
-
typeTuple = typenameType(colDef->typename);
tform = (Form_pg_type) GETSTRUCT(typeTuple);
@@ -1795,12 +2719,11 @@ AlterTableAddColumn(Oid myrelid,
attribute->atttypmod = colDef->typename->typmod;
attribute->attnum = i;
attribute->attbyval = tform->typbyval;
- attribute->attndims = attndims;
+ attribute->attndims = length(colDef->typename->arrayBounds);
attribute->attstorage = tform->typstorage;
attribute->attalign = tform->typalign;
attribute->attnotnull = colDef->is_not_null;
- attribute->atthasdef = (colDef->raw_default != NULL ||
- colDef->cooked_default != NULL);
+ attribute->atthasdef = false;
attribute->attisdropped = false;
attribute->attislocal = colDef->is_local;
attribute->attinhcount = colDef->inhcount;
@@ -1817,132 +2740,120 @@ AlterTableAddColumn(Oid myrelid,
/*
* Update number of attributes in pg_class tuple
*/
- newreltup = heap_copytuple(reltup);
-
- ((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts;
+ ((Form_pg_class) GETSTRUCT(reltup))->relnatts = maxatts;
- simple_heap_update(pgclass, &newreltup->t_self, newreltup);
+ simple_heap_update(pgclass, &reltup->t_self, reltup);
/* keep catalog indexes current */
- CatalogUpdateIndexes(pgclass, newreltup);
+ CatalogUpdateIndexes(pgclass, reltup);
- heap_freetuple(newreltup);
- ReleaseSysCache(reltup);
+ heap_freetuple(reltup);
heap_close(pgclass, RowExclusiveLock);
- heap_close(rel, NoLock); /* close rel but keep lock! */
+ /* Make the attribute's catalog entry visible */
+ CommandCounterIncrement();
/*
- * Add datatype dependency for the new column.
+ * Store the DEFAULT, if any, in the catalogs
*/
- myself.classId = RelOid_pg_class;
- myself.objectId = myrelid;
- myself.objectSubId = i;
- referenced.classId = RelOid_pg_type;
- referenced.objectId = attribute->atttypid;
- referenced.objectSubId = 0;
- recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ if (colDef->raw_default)
+ {
+ RawColumnDefault *rawEnt;
- /*
- * Make our catalog updates visible for subsequent steps.
- */
- CommandCounterIncrement();
+ rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
+ rawEnt->attnum = attribute->attnum;
+ rawEnt->raw_default = copyObject(colDef->raw_default);
+
+ /*
+ * This function is intended for CREATE TABLE, so it processes a
+ * _list_ of defaults, but we just do one.
+ */
+ AddRelationRawConstraints(rel, makeList1(rawEnt), NIL);
+
+ /* Make the additional catalog changes visible */
+ CommandCounterIncrement();
+ }
/*
- * Add any CHECK constraints attached to the new column.
+ * Tell Phase 3 to fill in the default expression, if there is one.
+ *
+ * If there is no default, Phase 3 doesn't have to do anything,
+ * because that effectively means that the default is NULL. The
+ * heap tuple access routines always check for attnum > # of attributes
+ * in tuple, and return NULL if so, so without any modification of
+ * the tuple data we will get the effect of NULL values in the new
+ * column.
*
- * To do this we must re-open the rel so that its new attr list gets
- * loaded into the relcache.
+ * Note: we use build_column_default, and not just the cooked default
+ * returned by AddRelationRawConstraints, so that the right thing happens
+ * when a datatype's default applies.
*/
- if (colDef->constraints != NIL)
+ defval = (Expr *) build_column_default(rel, attribute->attnum);
+ if (defval)
{
- rel = heap_open(myrelid, AccessExclusiveLock);
- AddRelationRawConstraints(rel, NIL, colDef->constraints);
- heap_close(rel, NoLock);
+ NewColumnValue *newval;
+
+ newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
+ newval->attnum = attribute->attnum;
+ newval->expr = defval;
+
+ tab->newvals = lappend(tab->newvals, newval);
}
/*
- * Automatically create the secondary relation for TOAST if it
- * formerly had no such but now has toastable attributes.
+ * Add datatype dependency for the new column.
*/
- AlterTableCreateToastTable(myrelid, true);
+ add_column_datatype_dependency(myrelid, i, attribute->atttypid);
+}
+
+/*
+ * Install a column's dependency on its datatype.
+ */
+static void
+add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
+{
+ ObjectAddress myself,
+ referenced;
+
+ myself.classId = RelOid_pg_class;
+ myself.objectId = relid;
+ myself.objectSubId = attnum;
+ referenced.classId = RelOid_pg_type;
+ referenced.objectId = typid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/*
* ALTER TABLE ALTER COLUMN DROP NOT NULL
*/
-void
-AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse,
- const char *colName)
+static void
+ATExecDropNotNull(Relation rel, const char *colName)
{
- Relation rel;
HeapTuple tuple;
AttrNumber attnum;
Relation attr_rel;
List *indexoidlist;
List *indexoidscan;
- rel = heap_open(myrelid, AccessExclusiveLock);
-
- if (rel->rd_rel->relkind != RELKIND_RELATION)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table",
- RelationGetRelationName(rel))));
-
- /* Permissions checks */
- if (!pg_class_ownercheck(myrelid, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
-
- if (!allowSystemTableMods && IsSystemRelation(rel))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: \"%s\" is a system catalog",
- RelationGetRelationName(rel))));
-
/*
- * Propagate to children if desired
+ * lookup the attribute
*/
- if (recurse)
- {
- List *child,
- *children;
-
- /* this routine is actually in the planner */
- children = find_all_inheritors(myrelid);
-
- /*
- * find_all_inheritors does the recursive search of the
- * inheritance hierarchy, so all we have to do is process all of
- * the relids in the list that it returns.
- */
- foreach(child, children)
- {
- Oid childrelid = lfirsto(child);
-
- if (childrelid == myrelid)
- continue;
- AlterTableAlterColumnDropNotNull(childrelid,
- false, colName);
- }
- }
+ attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
- /* now do the thing on this relation */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
- /*
- * get the number of the attribute
- */
- attnum = get_attnum(myrelid, colName);
- if (attnum == InvalidAttrNumber)
+ if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- colName, RelationGetRelationName(rel))));
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ colName, RelationGetRelationName(rel))));
+
+ attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
/* Prevent them from altering a system attribute */
- if (attnum < 0)
+ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
@@ -1992,221 +2903,92 @@ AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse,
freeList(indexoidlist);
/*
- * Okay, actually perform the catalog change
+ * Okay, actually perform the catalog change ... if needed
*/
- attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
-
- tuple = SearchSysCacheCopyAttName(myrelid, colName);
- if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
- elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
- colName, myrelid);
-
- ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
+ if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
+ {
+ ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
- simple_heap_update(attr_rel, &tuple->t_self, tuple);
+ simple_heap_update(attr_rel, &tuple->t_self, tuple);
- /* keep the system catalog indexes current */
- CatalogUpdateIndexes(attr_rel, tuple);
+ /* keep the system catalog indexes current */
+ CatalogUpdateIndexes(attr_rel, tuple);
+ }
heap_close(attr_rel, RowExclusiveLock);
-
- heap_close(rel, NoLock);
}
/*
* ALTER TABLE ALTER COLUMN SET NOT NULL
*/
-void
-AlterTableAlterColumnSetNotNull(Oid myrelid, bool recurse,
- const char *colName)
+static void
+ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
+ const char *colName)
{
- Relation rel;
HeapTuple tuple;
AttrNumber attnum;
Relation attr_rel;
- HeapScanDesc scan;
- TupleDesc tupdesc;
-
- rel = heap_open(myrelid, AccessExclusiveLock);
-
- if (rel->rd_rel->relkind != RELKIND_RELATION)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table",
- RelationGetRelationName(rel))));
-
- /* Permissions checks */
- if (!pg_class_ownercheck(myrelid, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
-
- if (!allowSystemTableMods && IsSystemRelation(rel))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: \"%s\" is a system catalog",
- RelationGetRelationName(rel))));
+ NewConstraint *newcon;
/*
- * Propagate to children if desired
+ * lookup the attribute
*/
- if (recurse)
- {
- List *child,
- *children;
-
- /* this routine is actually in the planner */
- children = find_all_inheritors(myrelid);
-
- /*
- * find_all_inheritors does the recursive search of the
- * inheritance hierarchy, so all we have to do is process all of
- * the relids in the list that it returns.
- */
- foreach(child, children)
- {
- Oid childrelid = lfirsto(child);
-
- if (childrelid == myrelid)
- continue;
- AlterTableAlterColumnSetNotNull(childrelid,
- false, colName);
- }
- }
+ attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
- /* now do the thing on this relation */
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
- /*
- * get the number of the attribute
- */
- attnum = get_attnum(myrelid, colName);
- if (attnum == InvalidAttrNumber)
+ if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column \"%s\" of relation \"%s\" does not exist",
- colName, RelationGetRelationName(rel))));
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ colName, RelationGetRelationName(rel))));
+
+ attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
/* Prevent them from altering a system attribute */
- if (attnum < 0)
+ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
/*
- * Perform a scan to ensure that there are no NULL values already in
- * the relation
+ * Okay, actually perform the catalog change ... if needed
*/
- tupdesc = RelationGetDescr(rel);
-
- scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
-
- while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+ if (! ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
{
- Datum d;
- bool isnull;
-
- d = heap_getattr(tuple, attnum, tupdesc, &isnull);
-
- if (isnull)
- ereport(ERROR,
- (errcode(ERRCODE_NOT_NULL_VIOLATION),
- errmsg("column \"%s\" contains null values",
- colName)));
- }
+ ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
- heap_endscan(scan);
+ simple_heap_update(attr_rel, &tuple->t_self, tuple);
- /*
- * Okay, actually perform the catalog change
- */
- attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
+ /* keep the system catalog indexes current */
+ CatalogUpdateIndexes(attr_rel, tuple);
- tuple = SearchSysCacheCopyAttName(myrelid, colName);
- if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
- elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
- colName, myrelid);
+ /* Tell Phase 3 to test the constraint */
+ newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ newcon->contype = CONSTR_NOTNULL;
+ newcon->attnum = attnum;
+ newcon->name = "NOT NULL";
- ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
-
- simple_heap_update(attr_rel, &tuple->t_self, tuple);
-
- /* keep the system catalog indexes current */
- CatalogUpdateIndexes(attr_rel, tuple);
+ tab->constraints = lappend(tab->constraints, newcon);
+ }
heap_close(attr_rel, RowExclusiveLock);
-
- heap_close(rel, NoLock);
}
/*
* ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
*/
-void
-AlterTableAlterColumnDefault(Oid myrelid, bool recurse,
- const char *colName,
- Node *newDefault)
+static void
+ATExecColumnDefault(Relation rel, const char *colName,
+ Node *newDefault)
{
- Relation rel;
AttrNumber attnum;
- rel = heap_open(myrelid, AccessExclusiveLock);
-
- /*
- * We allow defaults on views so that INSERT into a view can have
- * default-ish behavior. This works because the rewriter substitutes
- * default values into INSERTs before it expands rules.
- */
- if (rel->rd_rel->relkind != RELKIND_RELATION &&
- rel->rd_rel->relkind != RELKIND_VIEW)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table or view",
- RelationGetRelationName(rel))));
-
- /* Permissions checks */
- if (!pg_class_ownercheck(myrelid, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
-
- if (!allowSystemTableMods && IsSystemRelation(rel))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: \"%s\" is a system catalog",
- RelationGetRelationName(rel))));
-
- /*
- * Propagate to children if desired
- */
- if (recurse)
- {
- List *child,
- *children;
-
- /* this routine is actually in the planner */
- children = find_all_inheritors(myrelid);
-
- /*
- * find_all_inheritors does the recursive search of the
- * inheritance hierarchy, so all we have to do is process all of
- * the relids in the list that it returns.
- */
- foreach(child, children)
- {
- Oid childrelid = lfirsto(child);
-
- if (childrelid == myrelid)
- continue;
- AlterTableAlterColumnDefault(childrelid,
- false, colName, newDefault);
- }
- }
-
- /* now do the thing on this relation */
-
/*
* get the number of the attribute
*/
- attnum = get_attnum(myrelid, colName);
+ attnum = get_attnum(RelationGetRelid(rel), colName);
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -2214,7 +2996,7 @@ AlterTableAlterColumnDefault(Oid myrelid, bool recurse,
colName, RelationGetRelationName(rel))));
/* Prevent them from altering a system attribute */
- if (attnum < 0)
+ if (attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
@@ -2225,7 +3007,7 @@ AlterTableAlterColumnDefault(Oid myrelid, bool recurse,
* safety, but at present we do not expect anything to depend on the
* default.
*/
- RemoveAttrDefault(myrelid, attnum, DROP_RESTRICT, false);
+ RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, false);
if (newDefault)
{
@@ -2242,141 +3024,67 @@ AlterTableAlterColumnDefault(Oid myrelid, bool recurse,
*/
AddRelationRawConstraints(rel, makeList1(rawEnt), NIL);
}
-
- heap_close(rel, NoLock);
}
/*
- * ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE
+ * ALTER TABLE ALTER COLUMN SET STATISTICS
*/
-void
-AlterTableAlterColumnFlags(Oid myrelid, bool recurse,
- const char *colName,
- Node *flagValue, const char *flagType)
+static void
+ATPrepSetStatistics(Relation rel, const char *colName, Node *flagValue)
{
- Relation rel;
- int newtarget = 1;
- char newstorage = 'p';
- Relation attrelation;
- HeapTuple tuple;
- Form_pg_attribute attrtuple;
-
- rel = relation_open(myrelid, AccessExclusiveLock);
-
/*
- * Allow index for statistics case only
+ * We do our own permission checking because (a) we want to allow
+ * SET STATISTICS on indexes (for expressional index columns), and
+ * (b) we want to allow SET STATISTICS on system catalogs without
+ * requiring allowSystemTableMods to be turned on.
*/
- if (rel->rd_rel->relkind != RELKIND_RELATION)
- {
- if (rel->rd_rel->relkind != RELKIND_INDEX || *flagType != 'S')
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table",
- RelationGetRelationName(rel))));
- }
+ if (rel->rd_rel->relkind != RELKIND_RELATION &&
+ rel->rd_rel->relkind != RELKIND_INDEX)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a table or index",
+ RelationGetRelationName(rel))));
/* Permissions checks */
- if (!pg_class_ownercheck(myrelid, GetUserId()))
+ if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(rel));
+}
- /*
- * we allow statistics case for system tables
- */
- if (*flagType != 'S' && !allowSystemTableMods && IsSystemRelation(rel))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: \"%s\" is a system catalog",
- RelationGetRelationName(rel))));
+static void
+ATExecSetStatistics(Relation rel, const char *colName, Node *newValue)
+{
+ int newtarget;
+ Relation attrelation;
+ HeapTuple tuple;
+ Form_pg_attribute attrtuple;
+
+ Assert(IsA(newValue, Integer));
+ newtarget = intVal(newValue);
/*
- * Check the supplied parameters before anything else
+ * Limit target to a sane range
*/
- if (*flagType == 'S')
- {
- /* STATISTICS */
- Assert(IsA(flagValue, Integer));
- newtarget = intVal(flagValue);
-
- /*
- * Limit target to a sane range
- */
- if (newtarget < -1)
- {
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("statistics target %d is too low",
- newtarget)));
- }
- else if (newtarget > 1000)
- {
- newtarget = 1000;
- ereport(WARNING,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("lowering statistics target to %d",
- newtarget)));
- }
- }
- else if (*flagType == 'M')
- {
- /* STORAGE */
- char *storagemode;
-
- Assert(IsA(flagValue, String));
- storagemode = strVal(flagValue);
-
- if (strcasecmp(storagemode, "plain") == 0)
- newstorage = 'p';
- else if (strcasecmp(storagemode, "external") == 0)
- newstorage = 'e';
- else if (strcasecmp(storagemode, "extended") == 0)
- newstorage = 'x';
- else if (strcasecmp(storagemode, "main") == 0)
- newstorage = 'm';
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid storage type \"%s\"",
- storagemode)));
- }
- else
+ if (newtarget < -1)
{
- elog(ERROR, "unrecognized alter-column type flag: %c",
- (int) *flagType);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistics target %d is too low",
+ newtarget)));
}
-
- /*
- * Propagate to children if desired
- */
- if (recurse && rel->rd_rel->relkind == RELKIND_RELATION)
+ else if (newtarget > 1000)
{
- List *child,
- *children;
-
- /* this routine is actually in the planner */
- children = find_all_inheritors(myrelid);
-
- /*
- * find_all_inheritors does the recursive search of the
- * inheritance hierarchy, so all we have to do is process all of
- * the relids in the list that it returns.
- */
- foreach(child, children)
- {
- Oid childrelid = lfirsto(child);
-
- if (childrelid == myrelid)
- continue;
- AlterTableAlterColumnFlags(childrelid,
- false, colName, flagValue, flagType);
- }
+ newtarget = 1000;
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("lowering statistics target to %d",
+ newtarget)));
}
- /* now do the thing on this relation */
-
attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
- tuple = SearchSysCacheCopyAttName(myrelid, colName);
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
+
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -2384,31 +3092,13 @@ AlterTableAlterColumnFlags(Oid myrelid, bool recurse,
colName, RelationGetRelationName(rel))));
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
- if (attrtuple->attnum < 0)
+ if (attrtuple->attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot alter system column \"%s\"",
colName)));
- /*
- * Now change the appropriate field
- */
- if (*flagType == 'S')
- attrtuple->attstattarget = newtarget;
- else if (*flagType == 'M')
- {
- /*
- * safety check: do not allow toasted storage modes unless column
- * datatype is TOAST-aware.
- */
- if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid))
- attrtuple->attstorage = newstorage;
- else
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("column data type %s can only have storage PLAIN",
- format_type_be(attrtuple->atttypid))));
- }
+ attrtuple->attstattarget = newtarget;
simple_heap_update(attrelation, &tuple->t_self, tuple);
@@ -2418,85 +3108,110 @@ AlterTableAlterColumnFlags(Oid myrelid, bool recurse,
heap_freetuple(tuple);
heap_close(attrelation, RowExclusiveLock);
-
- heap_close(rel, NoLock); /* close rel, but keep lock! */
}
/*
- * ALTER TABLE SET WITH/WITHOUT OIDS
+ * ALTER TABLE ALTER COLUMN SET STORAGE
*/
-void
-AlterTableAlterOids(Oid myrelid, bool setOid, bool recurse,
- DropBehavior behavior)
+static void
+ATExecSetStorage(Relation rel, const char *colName, Node *newValue)
{
- Relation rel;
-
- rel = heap_open(myrelid, AccessExclusiveLock);
+ char *storagemode;
+ char newstorage;
+ Relation attrelation;
+ HeapTuple tuple;
+ Form_pg_attribute attrtuple;
- /*
- * check to see if we actually need to change anything
- */
- if (rel->rd_rel->relhasoids == setOid)
+ Assert(IsA(newValue, String));
+ storagemode = strVal(newValue);
+
+ if (strcasecmp(storagemode, "plain") == 0)
+ newstorage = 'p';
+ else if (strcasecmp(storagemode, "external") == 0)
+ newstorage = 'e';
+ else if (strcasecmp(storagemode, "extended") == 0)
+ newstorage = 'x';
+ else if (strcasecmp(storagemode, "main") == 0)
+ newstorage = 'm';
+ else
{
- heap_close(rel, NoLock); /* close rel, but keep lock! */
- return;
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid storage type \"%s\"",
+ storagemode)));
+ newstorage = 0; /* keep compiler quiet */
}
- if (setOid)
- {
- /*
- * TODO: Generate the now required OID pg_attribute entry, and
- * modify physical rows to have OIDs.
- */
+ attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
+
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ colName, RelationGetRelationName(rel))));
+ attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
+
+ if (attrtuple->attnum <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("ALTER TABLE WITH OIDS is not yet implemented")));
- }
+ errmsg("cannot alter system column \"%s\"",
+ colName)));
+
+ /*
+ * safety check: do not allow toasted storage modes unless column
+ * datatype is TOAST-aware.
+ */
+ if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid))
+ attrtuple->attstorage = newstorage;
else
- {
- heap_close(rel, NoLock); /* close rel, but keep lock! */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("column data type %s can only have storage PLAIN",
+ format_type_be(attrtuple->atttypid))));
- AlterTableDropColumn(myrelid, recurse, false, "oid", behavior);
- }
+ simple_heap_update(attrelation, &tuple->t_self, tuple);
+
+ /* keep system catalog indexes current */
+ CatalogUpdateIndexes(attrelation, tuple);
+
+ heap_freetuple(tuple);
+
+ heap_close(attrelation, RowExclusiveLock);
}
+
/*
* ALTER TABLE DROP COLUMN
+ *
+ * DROP COLUMN cannot use the normal ALTER TABLE recursion mechanism,
+ * 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.
*/
-void
-AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing,
- const char *colName,
- DropBehavior behavior)
+static void
+ATExecDropColumn(Relation rel, const char *colName,
+ DropBehavior behavior,
+ bool recurse, bool recursing)
{
- Relation rel;
- AttrNumber attnum;
HeapTuple tuple;
Form_pg_attribute targetatt;
+ AttrNumber attnum;
+ List *children;
ObjectAddress object;
- rel = heap_open(myrelid, AccessExclusiveLock);
-
- if (rel->rd_rel->relkind != RELKIND_RELATION)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table",
- RelationGetRelationName(rel))));
-
- /* Permissions checks */
- if (!pg_class_ownercheck(myrelid, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
-
- if (!allowSystemTableMods && IsSystemRelation(rel))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: \"%s\" is a system catalog",
- RelationGetRelationName(rel))));
+ /* At top level, permission check was done in ATPrepCmd, else do it */
+ if (recursing)
+ ATSimplePermissions(rel, false);
/*
* get the number of the attribute
*/
- tuple = SearchSysCacheAttName(myrelid, colName);
+ tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
@@ -2523,65 +3238,16 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing,
ReleaseSysCache(tuple);
/*
- * If we are asked to drop ONLY in this table (no recursion), we need
- * to mark the inheritors' attribute as locally defined rather than
- * inherited.
- */
- if (!recurse && !recursing)
- {
- Relation attr_rel;
- List *child,
- *children;
-
- /* We only want direct inheritors in this case */
- children = find_inheritance_children(myrelid);
-
- attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
- foreach(child, children)
- {
- Oid childrelid = lfirsto(child);
- Relation childrel;
- Form_pg_attribute childatt;
-
- childrel = heap_open(childrelid, AccessExclusiveLock);
-
- tuple = SearchSysCacheCopyAttName(childrelid, colName);
- if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
- elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
- colName, childrelid);
- childatt = (Form_pg_attribute) GETSTRUCT(tuple);
-
- if (childatt->attinhcount <= 0) /* shouldn't happen */
- elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
- childrelid, colName);
- childatt->attinhcount--;
- childatt->attislocal = true;
-
- simple_heap_update(attr_rel, &tuple->t_self, tuple);
-
- /* keep the system catalog indexes current */
- CatalogUpdateIndexes(attr_rel, tuple);
-
- heap_freetuple(tuple);
-
- heap_close(childrel, NoLock);
- }
- heap_close(attr_rel, RowExclusiveLock);
- }
-
- /*
- * Propagate to children if desired. Unlike most other ALTER
+ * Propagate to children as appropriate. Unlike most other ALTER
* routines, we have to do this one level of recursion at a time; we
* can't use find_all_inheritors to do it in one pass.
*/
- if (recurse)
+ children = find_inheritance_children(RelationGetRelid(rel));
+
+ if (children)
{
Relation attr_rel;
- List *child,
- *children;
-
- /* We only want direct inheritors in this case */
- children = find_inheritance_children(myrelid);
+ List *child;
attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
foreach(child, children)
@@ -2590,9 +3256,6 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing,
Relation childrel;
Form_pg_attribute childatt;
- if (childrelid == myrelid)
- continue;
-
childrel = heap_open(childrelid, AccessExclusiveLock);
tuple = SearchSysCacheCopyAttName(childrelid, colName);
@@ -2605,20 +3268,49 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing,
elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
childrelid, colName);
- if (childatt->attinhcount == 1 && !childatt->attislocal)
+ if (recurse)
{
- /* Time to delete this child column, too */
- AlterTableDropColumn(childrelid, true, true, colName, behavior);
+ /*
+ * If the child column has other definition sources, just
+ * decrement its inheritance count; if not, recurse to delete
+ * it.
+ */
+ if (childatt->attinhcount == 1 && !childatt->attislocal)
+ {
+ /* Time to delete this child column, too */
+ ATExecDropColumn(childrel, colName, behavior, true, true);
+ }
+ else
+ {
+ /* Child column must survive my deletion */
+ childatt->attinhcount--;
+
+ simple_heap_update(attr_rel, &tuple->t_self, tuple);
+
+ /* keep the system catalog indexes current */
+ CatalogUpdateIndexes(attr_rel, tuple);
+
+ /* Make update visible */
+ CommandCounterIncrement();
+ }
}
else
{
- /* Child column must survive my deletion */
+ /*
+ * If we were told to drop ONLY in this table (no recursion),
+ * we need to mark the inheritors' attribute as locally
+ * defined rather than inherited.
+ */
childatt->attinhcount--;
+ childatt->attislocal = true;
simple_heap_update(attr_rel, &tuple->t_self, tuple);
/* keep the system catalog indexes current */
CatalogUpdateIndexes(attr_rel, tuple);
+
+ /* Make update visible */
+ CommandCounterIncrement();
}
heap_freetuple(tuple);
@@ -2632,7 +3324,7 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing,
* Perform the actual column deletion
*/
object.classId = RelOid_pg_class;
- object.objectId = myrelid;
+ object.objectId = RelationGetRelid(rel);
object.objectSubId = attnum;
performDeletion(&object, behavior);
@@ -2648,10 +3340,11 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing,
class_rel = heap_openr(RelationRelationName, RowExclusiveLock);
tuple = SearchSysCacheCopy(RELOID,
- ObjectIdGetDatum(myrelid),
+ ObjectIdGetDatum(RelationGetRelid(rel)),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
- elog(ERROR, "cache lookup failed for relation %u", myrelid);
+ elog(ERROR, "cache lookup failed for relation %u",
+ RelationGetRelid(rel));
tuple_class = (Form_pg_class) GETSTRUCT(tuple);
tuple_class->relhasoids = false;
@@ -2662,298 +3355,149 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing,
heap_close(class_rel, RowExclusiveLock);
}
-
- heap_close(rel, NoLock); /* close rel, but keep lock! */
}
+/*
+ * ALTER TABLE ADD INDEX
+ *
+ * There is no such command in the grammar, but the parser converts UNIQUE
+ * and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets us
+ * schedule creation of the index at the appropriate time during ALTER.
+ */
+static void
+ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
+ IndexStmt *stmt, bool is_rebuild)
+{
+ bool check_rights;
+ bool skip_build;
+ bool quiet;
+
+ Assert(IsA(stmt, IndexStmt));
+
+ /* suppress schema rights check when rebuilding existing index */
+ check_rights = !is_rebuild;
+ /* skip index build if phase 3 will have to rewrite table anyway */
+ skip_build = (tab->newvals != NIL);
+ /* suppress notices when rebuilding existing index */
+ quiet = is_rebuild;
+
+ DefineIndex(stmt->relation, /* relation */
+ stmt->idxname, /* index name */
+ stmt->accessMethod, /* am name */
+ stmt->indexParams, /* parameters */
+ (Expr *) stmt->whereClause,
+ stmt->rangetable,
+ stmt->unique,
+ stmt->primary,
+ stmt->isconstraint,
+ true, /* is_alter_table */
+ check_rights,
+ skip_build,
+ quiet);
+}
/*
* ALTER TABLE ADD CONSTRAINT
*/
-void
-AlterTableAddConstraint(Oid myrelid, bool recurse,
- List *newConstraints)
+static void
+ATExecAddConstraint(AlteredTableInfo *tab, Relation rel, Node *newConstraint)
{
- Relation rel;
- List *listptr;
- int counter = 0;
-
- /*
- * Grab an exclusive lock on the target table, which we will NOT
- * release until end of transaction.
- */
- rel = heap_open(myrelid, AccessExclusiveLock);
-
- if (rel->rd_rel->relkind != RELKIND_RELATION)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table",
- RelationGetRelationName(rel))));
-
- /* Permissions checks */
- if (!pg_class_ownercheck(myrelid, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
-
- if (!allowSystemTableMods && IsSystemRelation(rel))
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: \"%s\" is a system catalog",
- RelationGetRelationName(rel))));
-
- if (recurse)
+ switch (nodeTag(newConstraint))
{
- List *child,
- *children;
-
- /* this routine is actually in the planner */
- children = find_all_inheritors(myrelid);
-
- /*
- * find_all_inheritors does the recursive search of the
- * inheritance hierarchy, so all we have to do is process all of
- * the relids in the list that it returns.
- */
- foreach(child, children)
+ case T_Constraint:
{
- Oid childrelid = lfirsto(child);
+ Constraint *constr = (Constraint *) newConstraint;
- if (childrelid == myrelid)
- continue;
- AlterTableAddConstraint(childrelid, false, newConstraints);
- }
- }
-
- foreach(listptr, newConstraints)
- {
- /*
- * copy is because we may destructively alter the node below by
- * inserting a generated name; this name is not necessarily
- * correct for children or parents.
- */
- Node *newConstraint = copyObject(lfirst(listptr));
-
- switch (nodeTag(newConstraint))
- {
- case T_Constraint:
- {
- Constraint *constr = (Constraint *) newConstraint;
-
- /*
- * Assign or validate constraint name
- */
- if (constr->name)
- {
- if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
- RelationGetRelid(rel),
- RelationGetNamespace(rel),
- constr->name))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("constraint \"%s\" for relation \"%s\" already exists",
- constr->name,
- RelationGetRelationName(rel))));
- }
- else
- constr->name = GenerateConstraintName(CONSTRAINT_RELATION,
- RelationGetRelid(rel),
- RelationGetNamespace(rel),
- &counter);
-
- /*
- * Currently, we only expect to see CONSTR_CHECK nodes
- * arriving here (see the preprocessing done in
- * parser/analyze.c). Use a switch anyway to make it
- * easier to add more code later.
- */
- switch (constr->contype)
- {
- case CONSTR_CHECK:
- AlterTableAddCheckConstraint(rel, constr);
- break;
- default:
- elog(ERROR, "unrecognized constraint type: %d",
- (int) constr->contype);
- }
- break;
- }
- case T_FkConstraint:
+ /*
+ * Currently, we only expect to see CONSTR_CHECK nodes
+ * arriving here (see the preprocessing done in
+ * parser/analyze.c). Use a switch anyway to make it
+ * easier to add more code later.
+ */
+ switch (constr->contype)
+ {
+ case CONSTR_CHECK:
{
- FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
+ List *newcons;
+ List *lcon;
/*
- * Assign or validate constraint name
+ * Call AddRelationRawConstraints to do the work.
+ * It returns a list of cooked constraints.
*/
- if (fkconstraint->constr_name)
- {
- if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
- RelationGetRelid(rel),
- RelationGetNamespace(rel),
- fkconstraint->constr_name))
- ereport(ERROR,
- (errcode(ERRCODE_DUPLICATE_OBJECT),
- errmsg("constraint \"%s\" for relation \"%s\" already exists",
- fkconstraint->constr_name,
- RelationGetRelationName(rel))));
+ newcons = AddRelationRawConstraints(rel, NIL,
+ makeList1(constr));
+ /* Add each constraint to Phase 3's queue */
+ foreach(lcon, newcons)
+ {
+ CookedConstraint *ccon = (CookedConstraint *) lfirst(lcon);
+ NewConstraint *newcon;
+
+ newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ newcon->name = ccon->name;
+ newcon->contype = ccon->contype;
+ newcon->attnum = ccon->attnum;
+ /* ExecQual wants implicit-AND format */
+ newcon->qual = (Node *)
+ make_ands_implicit((Expr *) ccon->expr);
+
+ tab->constraints = lappend(tab->constraints,
+ newcon);
}
- else
- fkconstraint->constr_name = GenerateConstraintName(CONSTRAINT_RELATION,
- RelationGetRelid(rel),
- RelationGetNamespace(rel),
- &counter);
-
- AlterTableAddForeignKeyConstraint(rel, fkconstraint);
-
break;
}
- default:
- elog(ERROR, "unrecognized node type: %d",
- (int) nodeTag(newConstraint));
+ default:
+ elog(ERROR, "unrecognized constraint type: %d",
+ (int) constr->contype);
+ }
+ break;
}
+ case T_FkConstraint:
+ {
+ FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
- /* If we have multiple constraints to make, bump CC between 'em */
- if (lnext(listptr))
- CommandCounterIncrement();
- }
-
- /* Close rel, but keep lock till commit */
- heap_close(rel, NoLock);
-}
-
-/*
- * Add a check constraint to a single table
- *
- * Subroutine for AlterTableAddConstraint. Must already hold exclusive
- * lock on the rel, and have done appropriate validity/permissions checks
- * for it.
- */
-static void
-AlterTableAddCheckConstraint(Relation rel, Constraint *constr)
-{
- ParseState *pstate;
- bool successful = true;
- HeapScanDesc scan;
- EState *estate;
- ExprContext *econtext;
- TupleTableSlot *slot;
- HeapTuple tuple;
- RangeTblEntry *rte;
- List *qual;
- List *qualstate;
- Node *expr;
-
- /*
- * We need to make a parse state and range table to allow us to do
- * transformExpr()
- */
- pstate = make_parsestate(NULL);
- rte = addRangeTableEntryForRelation(pstate,
- RelationGetRelid(rel),
- makeAlias(RelationGetRelationName(rel), NIL),
- false,
- true);
- addRTEtoQuery(pstate, rte, true, true);
-
- /*
- * Convert the A_EXPR in raw_expr into an EXPR
- */
- expr = transformExpr(pstate, constr->raw_expr);
-
- /*
- * Make sure it yields a boolean result.
- */
- expr = coerce_to_boolean(pstate, expr, "CHECK");
-
- /*
- * Make sure no outside relations are referred to.
- */
- if (length(pstate->p_rtable) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("check constraint may only reference relation \"%s\"",
- RelationGetRelationName(rel))));
-
- /*
- * No subplans or aggregates, either...
- */
- if (pstate->p_hasSubLinks)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot use subquery in check constraint")));
- if (pstate->p_hasAggs)
- ereport(ERROR,
- (errcode(ERRCODE_GROUPING_ERROR),
- errmsg("cannot use aggregate function in check constraint")));
-
- /*
- * Might as well try to reduce any constant expressions, so as to
- * minimize overhead while testing the constraint at each row.
- *
- * Note that the stored form of the constraint will NOT be const-folded.
- */
- expr = eval_const_expressions(expr);
-
- /* Needs to be in implicit-ANDs form for ExecQual */
- qual = make_ands_implicit((Expr *) expr);
-
- /* Need an EState to run ExecQual */
- estate = CreateExecutorState();
- econtext = GetPerTupleExprContext(estate);
-
- /* build execution state for qual */
- qualstate = (List *) ExecPrepareExpr((Expr *) qual, estate);
-
- /* Make tuple slot to hold tuples */
- slot = MakeTupleTableSlot();
- ExecSetSlotDescriptor(slot, RelationGetDescr(rel), false);
-
- /* Arrange for econtext's scan tuple to be the tuple under test */
- econtext->ecxt_scantuple = slot;
+ /*
+ * Assign or validate constraint name
+ */
+ if (fkconstraint->constr_name)
+ {
+ if (ConstraintNameIsUsed(CONSTRAINT_RELATION,
+ RelationGetRelid(rel),
+ RelationGetNamespace(rel),
+ fkconstraint->constr_name))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("constraint \"%s\" for relation \"%s\" already exists",
+ fkconstraint->constr_name,
+ RelationGetRelationName(rel))));
+ }
+ else
+ fkconstraint->constr_name =
+ GenerateConstraintName(CONSTRAINT_RELATION,
+ RelationGetRelid(rel),
+ RelationGetNamespace(rel),
+ &tab->constr_name_ctr);
- /*
- * Scan through the rows now, checking the expression at each row.
- */
- scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
+ ATAddForeignKeyConstraint(tab, rel, fkconstraint);
- while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
- {
- ExecStoreTuple(tuple, slot, InvalidBuffer, false);
- if (!ExecQual(qualstate, econtext, true))
- {
- successful = false;
break;
}
- ResetExprContext(econtext);
+ default:
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(newConstraint));
}
-
- heap_endscan(scan);
-
- pfree(slot);
- FreeExecutorState(estate);
-
- if (!successful)
- ereport(ERROR,
- (errcode(ERRCODE_CHECK_VIOLATION),
- errmsg("check constraint \"%s\" is violated by some row",
- constr->name)));
-
- /*
- * Call AddRelationRawConstraints to do the real adding -- It
- * duplicates some of the above, but does not check the validity of
- * the constraint against tuples already in the table.
- */
- AddRelationRawConstraints(rel, NIL, makeList1(constr));
}
/*
* Add a foreign-key constraint to a single table
*
- * Subroutine for AlterTableAddConstraint. Must already hold exclusive
+ * Subroutine for ATExecAddConstraint. Must already hold exclusive
* lock on the rel, and have done appropriate validity/permissions checks
* for it.
*/
static void
-AlterTableAddForeignKeyConstraint(Relation rel, FkConstraint *fkconstraint)
+ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
+ FkConstraint *fkconstraint)
{
Relation pkrel;
AclResult aclresult;
@@ -3124,11 +3668,21 @@ AlterTableAddForeignKeyConstraint(Relation rel, FkConstraint *fkconstraint)
}
/*
- * Check that the constraint is satisfied by existing rows (we can
- * skip this during table creation).
+ * Tell Phase 3 to check that the constraint is satisfied by existing rows
+ * (we can skip this during table creation).
*/
if (!fkconstraint->skip_validation)
- validateForeignKeyConstraint(fkconstraint, rel, pkrel);
+ {
+ NewConstraint *newcon;
+
+ newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+ newcon->name = fkconstraint->constr_name;
+ newcon->contype = CONSTR_FOREIGN;
+ newcon->refrelid = RelationGetRelid(pkrel);
+ newcon->qual = (Node *) fkconstraint;
+
+ tab->constraints = lappend(tab->constraints, newcon);
+ }
/*
* Record the FK constraint in pg_constraint.
@@ -3728,94 +4282,626 @@ fkMatchTypeToString(char match_type)
/*
* ALTER TABLE DROP CONSTRAINT
*/
-void
-AlterTableDropConstraint(Oid myrelid, bool recurse,
- const char *constrName,
- DropBehavior behavior)
+static void
+ATPrepDropConstraint(List **wqueue, Relation rel,
+ bool recurse, AlterTableCmd *cmd)
{
- Relation rel;
- int deleted = 0;
-
/*
- * Acquire an exclusive lock on the target relation for the duration
- * of the operation.
+ * We don't want errors or noise from child tables, so we have to pass
+ * down a modified command.
*/
- rel = heap_open(myrelid, AccessExclusiveLock);
+ if (recurse)
+ {
+ AlterTableCmd *childCmd = copyObject(cmd);
- /* Disallow DROP CONSTRAINT on views, indexes, sequences, etc */
- if (rel->rd_rel->relkind != RELKIND_RELATION)
+ childCmd->subtype = AT_DropConstraintQuietly;
+ ATSimpleRecursion(wqueue, rel, childCmd, recurse);
+ }
+}
+
+static void
+ATExecDropConstraint(Relation rel, const char *constrName,
+ DropBehavior behavior, bool quiet)
+{
+ int deleted;
+
+ deleted = RemoveRelConstraints(rel, constrName, behavior);
+
+ if (!quiet)
+ {
+ /* If zero constraints deleted, complain */
+ if (deleted == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("constraint \"%s\" does not exist",
+ constrName)));
+ /* Otherwise if more than one constraint deleted, notify */
+ else if (deleted > 1)
+ ereport(NOTICE,
+ (errmsg("multiple constraints named \"%s\" were dropped",
+ constrName)));
+ }
+}
+
+/*
+ * ALTER COLUMN TYPE
+ */
+static void
+ATPrepAlterColumnType(List **wqueue,
+ AlteredTableInfo *tab, Relation rel,
+ bool recurse, bool recursing,
+ AlterTableCmd *cmd)
+{
+ char *colName = cmd->name;
+ TypeName *typename = (TypeName *) cmd->def;
+ HeapTuple tuple;
+ Form_pg_attribute attTup;
+ AttrNumber attnum;
+ Oid targettype;
+ Node *transform;
+ NewColumnValue *newval;
+ ParseState *pstate = make_parsestate(NULL);
+
+ /* lookup the attribute so we can check inheritance status */
+ tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
+ if (!HeapTupleIsValid(tuple))
ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table",
- RelationGetRelationName(rel))));
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ colName, RelationGetRelationName(rel))));
+ attTup = (Form_pg_attribute) GETSTRUCT(tuple);
+ attnum = attTup->attnum;
- /* Permissions checks */
- if (!pg_class_ownercheck(myrelid, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
+ /* Can't alter a system attribute */
+ if (attnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter system column \"%s\"",
+ colName)));
- if (!allowSystemTableMods && IsSystemRelation(rel))
+ /* Don't alter inherited columns */
+ if (attTup->attinhcount > 0 && !recursing)
ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("permission denied: \"%s\" is a system catalog",
- RelationGetRelationName(rel))));
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot alter inherited column \"%s\"",
+ colName)));
+
+ /* Look up the target type */
+ targettype = LookupTypeName(typename);
+ if (!OidIsValid(targettype))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" does not exist",
+ TypeNameToString(typename))));
+
+ /* make sure datatype is legal for a column */
+ CheckAttributeType(colName, targettype);
/*
- * Process child tables if requested.
+ * Set up an expression to transform the old data value to the new type.
+ * If a USING option was given, transform and use that expression, else
+ * just take the old value and try to coerce it. We do this first so
+ * that type incompatibility can be detected before we waste effort,
+ * and because we need the expression to be parsed against the original
+ * table rowtype.
+ */
+ if (cmd->transform)
+ {
+ RangeTblEntry *rte;
+
+ /* Expression must be able to access vars of old table */
+ rte = addRangeTableEntryForRelation(pstate,
+ RelationGetRelid(rel),
+ makeAlias(RelationGetRelationName(rel), NIL),
+ false,
+ true);
+ addRTEtoQuery(pstate, rte, false, true);
+
+ transform = transformExpr(pstate, cmd->transform);
+
+ /* It can't return a set */
+ if (expression_returns_set(transform))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("transform expression must not return a set")));
+
+ /* No subplans or aggregates, either... */
+ if (pstate->p_hasSubLinks)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot use subquery in transform expression")));
+ if (pstate->p_hasAggs)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("cannot use aggregate function in transform expression")));
+ }
+ else
+ {
+ transform = (Node *) makeVar(1, attnum,
+ attTup->atttypid, attTup->atttypmod,
+ 0);
+ }
+
+ transform = coerce_to_target_type(pstate,
+ transform, exprType(transform),
+ targettype, typename->typmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST);
+ if (transform == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("column \"%s\" cannot be cast to type \"%s\"",
+ colName, TypeNameToString(typename))));
+
+ /*
+ * Add a work queue item to make ATRewriteTable update the column
+ * contents.
+ */
+ newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
+ newval->attnum = attnum;
+ newval->expr = (Expr *) transform;
+
+ tab->newvals = lappend(tab->newvals, newval);
+
+ ReleaseSysCache(tuple);
+
+ /*
+ * The recursion case is handled by ATSimpleRecursion. However,
+ * if we are told not to recurse, there had better not be any
+ * child tables; else the alter would put them out of step.
*/
if (recurse)
+ ATSimpleRecursion(wqueue, rel, cmd, recurse);
+ else if (!recursing &&
+ find_inheritance_children(RelationGetRelid(rel)) != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("type of inherited column \"%s\" must be changed in child tables too",
+ colName)));
+}
+
+static void
+ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
+ const char *colName, TypeName *typename)
+{
+ HeapTuple heapTup;
+ Form_pg_attribute attTup;
+ AttrNumber attnum;
+ HeapTuple typeTuple;
+ Form_pg_type tform;
+ Oid targettype;
+ Node *defaultexpr;
+ Relation attrelation;
+ Relation depRel;
+ ScanKeyData key[3];
+ SysScanDesc scan;
+ HeapTuple depTup;
+
+ attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
+
+ /* Look up the target column */
+ heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
+ if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ colName, RelationGetRelationName(rel))));
+ attTup = (Form_pg_attribute) GETSTRUCT(heapTup);
+ attnum = attTup->attnum;
+
+ /* Check for multiple ALTER TYPE on same column --- can't cope */
+ if (attTup->atttypid != tab->oldDesc->attrs[attnum-1]->atttypid ||
+ attTup->atttypmod != tab->oldDesc->attrs[attnum-1]->atttypmod)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter type of column \"%s\" twice",
+ colName)));
+
+ /* Look up the target type (should not fail, since prep found it) */
+ typeTuple = typenameType(typename);
+ tform = (Form_pg_type) GETSTRUCT(typeTuple);
+ targettype = HeapTupleGetOid(typeTuple);
+
+ /*
+ * If there is a default expression for the column, get it and ensure
+ * we can coerce it to the new datatype. (We must do this before
+ * changing the column type, because build_column_default itself will
+ * try to coerce, and will not issue the error message we want if it
+ * fails.)
+ */
+ if (attTup->atthasdef)
{
- List *child,
- *children;
+ defaultexpr = build_column_default(rel, attnum);
+ Assert(defaultexpr);
+ defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */
+ defaultexpr, exprType(defaultexpr),
+ targettype, typename->typmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST);
+ if (defaultexpr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("default for column \"%s\" cannot be cast to type \"%s\"",
+ colName, TypeNameToString(typename))));
+ }
+ else
+ defaultexpr = NULL;
- /* This routine is actually in the planner */
- children = find_all_inheritors(myrelid);
+ /*
+ * Find everything that depends on the column (constraints, indexes, etc),
+ * and record enough information to let us recreate the objects.
+ *
+ * The actual recreation does not happen here, but only after we have
+ * performed all the individual ALTER TYPE operations. We have to save
+ * the info before executing ALTER TYPE, though, else the deparser will
+ * get confused.
+ *
+ * There could be multiple entries for the same object, so we must check
+ * to ensure we process each one only once. Note: we assume that an index
+ * that implements a constraint will not show a direct dependency on the
+ * column.
+ */
+ depRel = heap_openr(DependRelationName, RowExclusiveLock);
+
+ ScanKeyInit(&key[0],
+ Anum_pg_depend_refclassid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelOid_pg_class));
+ ScanKeyInit(&key[1],
+ Anum_pg_depend_refobjid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ ScanKeyInit(&key[2],
+ Anum_pg_depend_refobjsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum((int32) attnum));
+
+ scan = systable_beginscan(depRel, DependReferenceIndex, true,
+ SnapshotNow, 3, key);
+
+ while (HeapTupleIsValid(depTup = systable_getnext(scan)))
+ {
+ Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
+ ObjectAddress foundObject;
- /*
- * find_all_inheritors does the recursive search of the
- * inheritance hierarchy, so all we have to do is process all of
- * the relids in the list that it returns.
- */
- foreach(child, children)
+ /* We don't expect any PIN dependencies on columns */
+ if (foundDep->deptype == DEPENDENCY_PIN)
+ elog(ERROR, "cannot alter type of a pinned column");
+
+ foundObject.classId = foundDep->classid;
+ foundObject.objectId = foundDep->objid;
+ foundObject.objectSubId = foundDep->objsubid;
+
+ switch (getObjectClass(&foundObject))
{
- Oid childrelid = lfirsto(child);
- Relation inhrel;
+ case OCLASS_CLASS:
+ {
+ char relKind = get_rel_relkind(foundObject.objectId);
- if (childrelid == myrelid)
- continue;
- inhrel = heap_open(childrelid, AccessExclusiveLock);
- /* do NOT count child constraints in deleted. */
- RemoveRelConstraints(inhrel, constrName, behavior);
- heap_close(inhrel, NoLock);
+ if (relKind == RELKIND_INDEX)
+ {
+ Assert(foundObject.objectSubId == 0);
+ if (!oidMember(foundObject.objectId, tab->changedIndexOids))
+ {
+ tab->changedIndexOids = lappendo(tab->changedIndexOids,
+ foundObject.objectId);
+ tab->changedIndexDefs = lappend(tab->changedIndexDefs,
+ pg_get_indexdef_string(foundObject.objectId));
+ }
+ }
+ else if (relKind == RELKIND_SEQUENCE)
+ {
+ /*
+ * This must be a SERIAL column's sequence. We need not
+ * do anything to it.
+ */
+ Assert(foundObject.objectSubId == 0);
+ }
+ else
+ {
+ /* Not expecting any other direct dependencies... */
+ elog(ERROR, "unexpected object depending on column: %s",
+ getObjectDescription(&foundObject));
+ }
+ break;
+ }
+
+ case OCLASS_CONSTRAINT:
+ Assert(foundObject.objectSubId == 0);
+ if (!oidMember(foundObject.objectId, tab->changedConstraintOids))
+ {
+ tab->changedConstraintOids = lappendo(tab->changedConstraintOids,
+ foundObject.objectId);
+ tab->changedConstraintDefs = lappend(tab->changedConstraintDefs,
+ pg_get_constraintdef_string(foundObject.objectId));
+ }
+ break;
+
+ case OCLASS_REWRITE:
+ /* XXX someday see if we can cope with revising views */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot alter type of a column used by a view or rule"),
+ errdetail("%s depends on column \"%s\"",
+ getObjectDescription(&foundObject),
+ colName)));
+ break;
+
+ case OCLASS_DEFAULT:
+ /*
+ * Ignore the column's default expression, since we will fix
+ * it below.
+ */
+ Assert(defaultexpr);
+ break;
+
+ case OCLASS_PROC:
+ case OCLASS_TYPE:
+ case OCLASS_CAST:
+ case OCLASS_CONVERSION:
+ case OCLASS_LANGUAGE:
+ case OCLASS_OPERATOR:
+ case OCLASS_OPCLASS:
+ case OCLASS_TRIGGER:
+ case OCLASS_SCHEMA:
+ /*
+ * We don't expect any of these sorts of objects to depend
+ * on a column.
+ */
+ elog(ERROR, "unexpected object depending on column: %s",
+ getObjectDescription(&foundObject));
+ break;
+
+ default:
+ elog(ERROR, "unrecognized object class: %u",
+ foundObject.classId);
}
}
+ systable_endscan(scan);
+
/*
- * Now do the thing on this relation.
+ * Now scan for dependencies of this column on other things. The only
+ * thing we should find is the dependency on the column datatype,
+ * which we want to remove.
*/
- deleted += RemoveRelConstraints(rel, constrName, behavior);
+ ScanKeyInit(&key[0],
+ Anum_pg_depend_classid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelOid_pg_class));
+ ScanKeyInit(&key[1],
+ Anum_pg_depend_objid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(rel)));
+ ScanKeyInit(&key[2],
+ Anum_pg_depend_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum((int32) attnum));
+
+ scan = systable_beginscan(depRel, DependDependerIndex, true,
+ SnapshotNow, 3, key);
+
+ while (HeapTupleIsValid(depTup = systable_getnext(scan)))
+ {
+ Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(depTup);
- /* Close the target relation */
- heap_close(rel, NoLock);
+ if (foundDep->deptype != DEPENDENCY_NORMAL)
+ elog(ERROR, "found unexpected dependency type '%c'",
+ foundDep->deptype);
+ if (foundDep->classid != RelOid_pg_type ||
+ foundDep->objid != attTup->atttypid)
+ elog(ERROR, "found unexpected dependency for column");
- /* If zero constraints deleted, complain */
- if (deleted == 0)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("constraint \"%s\" does not exist",
- constrName)));
- /* Otherwise if more than one constraint deleted, notify */
- else if (deleted > 1)
- ereport(NOTICE,
- (errmsg("multiple constraints named \"%s\" were dropped",
- constrName)));
+ simple_heap_delete(depRel, &depTup->t_self);
+ }
+
+ systable_endscan(scan);
+
+ heap_close(depRel, RowExclusiveLock);
+
+ /*
+ * Here we go --- change the recorded column type. (Note heapTup is
+ * a copy of the syscache entry, so okay to scribble on.)
+ */
+ attTup->atttypid = targettype;
+ attTup->atttypmod = typename->typmod;
+ attTup->attndims = length(typename->arrayBounds);
+ attTup->attlen = tform->typlen;
+ attTup->attbyval = tform->typbyval;
+ attTup->attalign = tform->typalign;
+ attTup->attstorage = tform->typstorage;
+
+ ReleaseSysCache(typeTuple);
+
+ simple_heap_update(attrelation, &heapTup->t_self, heapTup);
+
+ /* keep system catalog indexes current */
+ CatalogUpdateIndexes(attrelation, heapTup);
+
+ heap_close(attrelation, RowExclusiveLock);
+
+ /* Install dependency on new datatype */
+ add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
+
+ /* Drop any pg_statistic entry for the column, since it's now wrong type */
+ RemoveStatistics(rel, attnum);
+
+ /*
+ * Update the default, if present, by brute force --- remove and re-add
+ * the default. Probably unsafe to take shortcuts, since the new version
+ * may well have additional dependencies. (It's okay to do this now,
+ * rather than after other ALTER TYPE commands, since the default won't
+ * depend on other column types.)
+ */
+ if (defaultexpr)
+ {
+ /* Must make new row visible since it will be updated again */
+ CommandCounterIncrement();
+
+ /*
+ * We use RESTRICT here for safety, but at present we do not expect
+ * anything to depend on the default.
+ */
+ RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true);
+
+ StoreAttrDefault(rel, attnum, nodeToString(defaultexpr));
+ }
+
+ /* Cleanup */
+ heap_freetuple(heapTup);
}
/*
+ * Cleanup after we've finished all the ALTER TYPE operations for a
+ * particular relation. We have to drop and recreate all the indexes
+ * and constraints that depend on the altered columns.
+ */
+static void
+ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab)
+{
+ ObjectAddress obj;
+ List *l;
+
+ /*
+ * Re-parse the index and constraint definitions, and attach them to
+ * the appropriate work queue entries. We do this before dropping
+ * because in the case of a FOREIGN KEY constraint, we might not yet
+ * have exclusive lock on the table the constraint is attached to,
+ * and we need to get that before dropping. It's safe because the
+ * parser won't actually look at the catalogs to detect the existing
+ * entry.
+ */
+ foreach(l, tab->changedIndexDefs)
+ {
+ ATPostAlterTypeParse((char *) lfirst(l), wqueue);
+ }
+ foreach(l, tab->changedConstraintDefs)
+ {
+ ATPostAlterTypeParse((char *) lfirst(l), wqueue);
+ }
+
+ /*
+ * Now we can drop the existing constraints and indexes --- constraints
+ * first, since some of them might depend on the indexes. It should be
+ * okay to use DROP_RESTRICT here, since nothing else should be depending
+ * on these objects.
+ */
+ if (tab->changedConstraintOids)
+ obj.classId = get_system_catalog_relid(ConstraintRelationName);
+ foreach(l, tab->changedConstraintOids)
+ {
+ obj.objectId = lfirsto(l);
+ obj.objectSubId = 0;
+ performDeletion(&obj, DROP_RESTRICT);
+ }
+
+ obj.classId = RelOid_pg_class;
+ foreach(l, tab->changedIndexOids)
+ {
+ obj.objectId = lfirsto(l);
+ obj.objectSubId = 0;
+ performDeletion(&obj, DROP_RESTRICT);
+ }
+
+ /*
+ * The objects will get recreated during subsequent passes over the
+ * work queue.
+ */
+}
+
+static void
+ATPostAlterTypeParse(char *cmd, List **wqueue)
+{
+ List *raw_parsetree_list;
+ List *querytree_list;
+ List *list_item;
+
+ /*
+ * We expect that we only have to do raw parsing and parse analysis, not
+ * any rule rewriting, since these will all be utility statements.
+ */
+ raw_parsetree_list = raw_parser(cmd);
+ querytree_list = NIL;
+ foreach(list_item, raw_parsetree_list)
+ {
+ Node *parsetree = (Node *) lfirst(list_item);
+
+ querytree_list = nconc(querytree_list,
+ parse_analyze(parsetree, NULL, 0));
+ }
+
+ /*
+ * Attach each generated command to the proper place in the work queue.
+ * Note this could result in creation of entirely new work-queue entries.
+ */
+ foreach(list_item, querytree_list)
+ {
+ Query *query = (Query *) lfirst(list_item);
+ Relation rel;
+ AlteredTableInfo *tab;
+
+ Assert(IsA(query, Query));
+ Assert(query->commandType == CMD_UTILITY);
+ switch (nodeTag(query->utilityStmt))
+ {
+ case T_IndexStmt:
+ {
+ IndexStmt *stmt = (IndexStmt *) query->utilityStmt;
+ AlterTableCmd *newcmd;
+
+ rel = relation_openrv(stmt->relation, AccessExclusiveLock);
+ tab = ATGetQueueEntry(wqueue, rel);
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_ReAddIndex;
+ newcmd->def = (Node *) stmt;
+ tab->subcmds[AT_PASS_OLD_INDEX] =
+ lappend(tab->subcmds[AT_PASS_OLD_INDEX], newcmd);
+ relation_close(rel, NoLock);
+ break;
+ }
+ case T_AlterTableStmt:
+ {
+ AlterTableStmt *stmt = (AlterTableStmt *) query->utilityStmt;
+ List *lcmd;
+
+ rel = relation_openrv(stmt->relation, AccessExclusiveLock);
+ tab = ATGetQueueEntry(wqueue, rel);
+ foreach(lcmd, stmt->cmds)
+ {
+ AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
+
+ switch (cmd->subtype)
+ {
+ case AT_AddIndex:
+ cmd->subtype = AT_ReAddIndex;
+ tab->subcmds[AT_PASS_OLD_INDEX] =
+ lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
+ break;
+ case AT_AddConstraint:
+ tab->subcmds[AT_PASS_OLD_CONSTR] =
+ lappend(tab->subcmds[AT_PASS_OLD_CONSTR], cmd);
+ break;
+ default:
+ elog(ERROR, "unexpected statement type: %d",
+ (int) cmd->subtype);
+ }
+ }
+ relation_close(rel, NoLock);
+ break;
+ }
+ default:
+ elog(ERROR, "unexpected statement type: %d",
+ (int) nodeTag(query->utilityStmt));
+ }
+ }
+}
+
+
+/*
* ALTER TABLE OWNER
*/
-void
-AlterTableOwner(Oid relationOid, int32 newOwnerSysId)
+static void
+ATExecChangeOwner(Oid relationOid, int32 newOwnerSysId)
{
Relation target_rel;
Relation class_rel;
@@ -3879,7 +4965,7 @@ AlterTableOwner(Oid relationOid, int32 newOwnerSysId)
/* For each index, recursively change its ownership */
foreach(i, index_oid_list)
- AlterTableOwner(lfirsto(i), newOwnerSysId);
+ ATExecChangeOwner(lfirsto(i), newOwnerSysId);
freeList(index_oid_list);
}
@@ -3888,7 +4974,7 @@ AlterTableOwner(Oid relationOid, int32 newOwnerSysId)
{
/* If it has a toast table, recurse to change its ownership */
if (tuple_class->reltoastrelid != InvalidOid)
- AlterTableOwner(tuple_class->reltoastrelid, newOwnerSysId);
+ ATExecChangeOwner(tuple_class->reltoastrelid, newOwnerSysId);
}
heap_freetuple(tuple);
@@ -3901,25 +4987,22 @@ AlterTableOwner(Oid relationOid, int32 newOwnerSysId)
*
* The only thing we have to do is to change the indisclustered bits.
*/
-void
-AlterTableClusterOn(Oid relOid, const char *indexName)
+static void
+ATExecClusterOn(Relation rel, const char *indexName)
{
- Relation rel,
- pg_index;
+ Relation pg_index;
List *index;
Oid indexOid;
HeapTuple indexTuple;
Form_pg_index indexForm;
- rel = heap_open(relOid, AccessExclusiveLock);
-
indexOid = get_relname_relid(indexName, rel->rd_rel->relnamespace);
if (!OidIsValid(indexOid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("index \"%s\" for table \"%s\" does not exist",
- indexName, NameStr(rel->rd_rel->relname))));
+ indexName, RelationGetRelationName(rel))));
indexTuple = SearchSysCache(INDEXRELID,
ObjectIdGetDatum(indexOid),
@@ -3935,10 +5018,11 @@ AlterTableClusterOn(Oid relOid, const char *indexName)
if (indexForm->indisclustered)
{
ReleaseSysCache(indexTuple);
- heap_close(rel, NoLock);
return;
}
+ ReleaseSysCache(indexTuple);
+
pg_index = heap_openr(IndexRelationName, RowExclusiveLock);
/*
@@ -3977,14 +5061,15 @@ AlterTableClusterOn(Oid relOid, const char *indexName)
}
heap_close(pg_index, RowExclusiveLock);
-
- ReleaseSysCache(indexTuple);
-
- heap_close(rel, NoLock); /* close rel, but keep lock till commit */
}
/*
* ALTER TABLE CREATE TOAST TABLE
+ *
+ * Note: this is also invoked from outside this module; in such cases we
+ * expect the caller to have verified that the relation is a table and we
+ * have all the right permissions. Callers expect this function
+ * to end with CommandCounterIncrement if it makes any changes.
*/
void
AlterTableCreateToastTable(Oid relOid, bool silent)
@@ -4005,21 +5090,11 @@ AlterTableCreateToastTable(Oid relOid, bool silent)
/*
* Grab an exclusive lock on the target table, which we will NOT
- * release until end of transaction.
+ * release until end of transaction. (This is probably redundant
+ * in all present uses...)
*/
rel = heap_open(relOid, AccessExclusiveLock);
- if (rel->rd_rel->relkind != RELKIND_RELATION)
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table",
- RelationGetRelationName(rel))));
-
- /* Permissions checks */
- if (!pg_class_ownercheck(relOid, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
-
/*
* Toast table is shared if and only if its parent is.
*
@@ -4149,7 +5224,7 @@ AlterTableCreateToastTable(Oid relOid, bool silent)
toast_idxid = index_create(toast_relid, toast_idxname, indexInfo,
BTREE_AM_OID, classObjectId,
- true, false, true);
+ true, false, true, false);
/*
* Update toast rel's pg_class entry to show that it has an index. The
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c7d6193280e..1466be98cbb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.279 2004/03/17 20:48:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.280 2004/05/05 04:48:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1674,10 +1674,21 @@ _copyAlterTableStmt(AlterTableStmt *from)
{
AlterTableStmt *newnode = makeNode(AlterTableStmt);
- COPY_SCALAR_FIELD(subtype);
COPY_NODE_FIELD(relation);
+ COPY_NODE_FIELD(cmds);
+
+ return newnode;
+}
+
+static AlterTableCmd *
+_copyAlterTableCmd(AlterTableCmd *from)
+{
+ AlterTableCmd *newnode = makeNode(AlterTableCmd);
+
+ COPY_SCALAR_FIELD(subtype);
COPY_STRING_FIELD(name);
COPY_NODE_FIELD(def);
+ COPY_NODE_FIELD(transform);
COPY_SCALAR_FIELD(behavior);
return newnode;
@@ -2773,6 +2784,9 @@ copyObject(void *from)
case T_AlterTableStmt:
retval = _copyAlterTableStmt(from);
break;
+ case T_AlterTableCmd:
+ retval = _copyAlterTableCmd(from);
+ break;
case T_AlterDomainStmt:
retval = _copyAlterDomainStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 900d98dc8c0..eab30d122c2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.218 2004/03/17 20:48:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.219 2004/05/05 04:48:45 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -710,10 +710,19 @@ _equalSetOperationStmt(SetOperationStmt *a, SetOperationStmt *b)
static bool
_equalAlterTableStmt(AlterTableStmt *a, AlterTableStmt *b)
{
- COMPARE_SCALAR_FIELD(subtype);
COMPARE_NODE_FIELD(relation);
+ COMPARE_NODE_FIELD(cmds);
+
+ return true;
+}
+
+static bool
+_equalAlterTableCmd(AlterTableCmd *a, AlterTableCmd *b)
+{
+ COMPARE_SCALAR_FIELD(subtype);
COMPARE_STRING_FIELD(name);
COMPARE_NODE_FIELD(def);
+ COMPARE_NODE_FIELD(transform);
COMPARE_SCALAR_FIELD(behavior);
return true;
@@ -1846,6 +1855,9 @@ equal(void *a, void *b)
case T_AlterTableStmt:
retval = _equalAlterTableStmt(a, b);
break;
+ case T_AlterTableCmd:
+ retval = _equalAlterTableCmd(a, b);
+ break;
case T_AlterDomainStmt:
retval = _equalAlterDomainStmt(a, b);
break;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 2330bf18d43..4a251b63de4 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.298 2004/04/02 21:05:32 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.299 2004/05/05 04:48:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -77,7 +77,7 @@ typedef struct
RangeVar *relation; /* relation to create */
List *inhRelations; /* relations to inherit from */
bool hasoids; /* does relation have an OID column? */
- Oid relOid; /* OID of table, if ALTER TABLE case */
+ bool isalter; /* true if altering existing table */
List *columns; /* ColumnDef items */
List *ckconstraints; /* CHECK constraints */
List *fkconstraints; /* FOREIGN KEY constraints */
@@ -131,18 +131,17 @@ static void transformIndexConstraints(ParseState *pstate,
CreateStmtContext *cxt);
static void transformFKConstraints(ParseState *pstate,
CreateStmtContext *cxt,
+ bool skipValidation,
bool isAddConstraint);
static void applyColumnNames(List *dst, List *src);
static List *getSetColTypes(ParseState *pstate, Node *node);
static void transformForUpdate(Query *qry, List *forUpdate);
static void transformConstraintAttrs(List *constraintList);
static void transformColumnType(ParseState *pstate, ColumnDef *column);
-static bool relationHasPrimaryKey(Oid relationOid);
static void release_pstate_resources(ParseState *pstate);
static FromExpr *makeFromExpr(List *fromlist, Node *quals);
static bool check_parameter_resolution_walker(Node *node,
check_parameter_resolution_context *context);
-static char *makeObjectName(char *name1, char *name2, char *typename);
/*
@@ -346,7 +345,8 @@ transformStmt(ParseState *pstate, Node *parseTree,
break;
case T_AlterTableStmt:
- result = transformAlterTableStmt(pstate, (AlterTableStmt *) parseTree,
+ result = transformAlterTableStmt(pstate,
+ (AlterTableStmt *) parseTree,
extras_before, extras_after);
break;
@@ -733,8 +733,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
* from the truncated characters. Currently it seems best to keep it simple,
* so that the generated names are easily predictable by a person.
*/
-static char *
-makeObjectName(char *name1, char *name2, char *typename)
+char *
+makeObjectName(const char *name1, const char *name2, const char *typename)
{
char *name;
int overhead = 0; /* chars needed for type and underscores */
@@ -795,48 +795,6 @@ makeObjectName(char *name1, char *name2, char *typename)
return name;
}
-static char *
-CreateIndexName(char *table_name, char *column_name,
- char *label, List *indices)
-{
- int pass = 0;
- char *iname = NULL;
- List *ilist;
- char typename[NAMEDATALEN];
-
- /*
- * The type name for makeObjectName is label, or labelN if that's
- * necessary to prevent collisions among multiple indexes for the same
- * table. Note there is no check for collisions with already-existing
- * indexes, only among the indexes we're about to create now; this
- * ought to be improved someday.
- */
- strncpy(typename, label, sizeof(typename));
-
- for (;;)
- {
- iname = makeObjectName(table_name, column_name, typename);
-
- foreach(ilist, indices)
- {
- IndexStmt *index = lfirst(ilist);
-
- if (index->idxname != NULL &&
- strcmp(iname, index->idxname) == 0)
- break;
- }
- /* ran through entire list? then no name conflict found so done */
- if (ilist == NIL)
- break;
-
- /* found a conflict, so try a new name component */
- pfree(iname);
- snprintf(typename, sizeof(typename), "%s%d", label, ++pass);
- }
-
- return iname;
-}
-
/*
* transformCreateStmt -
* transforms the "create table" statement
@@ -857,7 +815,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
cxt.stmtType = "CREATE TABLE";
cxt.relation = stmt->relation;
cxt.inhRelations = stmt->inhRelations;
- cxt.relOid = InvalidOid;
+ cxt.isalter = false;
cxt.columns = NIL;
cxt.ckconstraints = NIL;
cxt.fkconstraints = NIL;
@@ -914,7 +872,7 @@ transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
/*
* Postprocess foreign-key constraints.
*/
- transformFKConstraints(pstate, &cxt, false);
+ transformFKConstraints(pstate, &cxt, true, false);
/*
* Output results.
@@ -1326,24 +1284,23 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
index->primary = (constraint->contype == CONSTR_PRIMARY);
if (index->primary)
{
- /* In ALTER TABLE case, a primary index might already exist */
- if (cxt->pkey != NULL ||
- (OidIsValid(cxt->relOid) &&
- relationHasPrimaryKey(cxt->relOid)))
+ if (cxt->pkey != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("multiple primary keys for table \"%s\" are not allowed",
cxt->relation->relname)));
cxt->pkey = index;
+ /*
+ * In ALTER TABLE case, a primary index might already exist,
+ * but DefineIndex will check for it.
+ */
}
index->isconstraint = true;
if (constraint->name != NULL)
index->idxname = pstrdup(constraint->name);
- else if (constraint->contype == CONSTR_PRIMARY)
- index->idxname = makeObjectName(cxt->relation->relname, NULL, "pkey");
else
- index->idxname = NULL; /* will set it later */
+ index->idxname = NULL; /* DefineIndex will choose name */
index->relation = cxt->relation;
index->accessMethod = DEFAULT_INDEX_TYPE;
@@ -1431,25 +1388,14 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
break;
}
}
- else if (OidIsValid(cxt->relOid))
- {
- /* ALTER TABLE case: does column already exist? */
- HeapTuple atttuple;
-
- atttuple = SearchSysCacheAttName(cxt->relOid, key);
- if (HeapTupleIsValid(atttuple))
- {
- found = true;
- /*
- * If it's not already NOT NULL, leave it to
- * DefineIndex to fix later.
- */
- ReleaseSysCache(atttuple);
- }
- }
-
- if (!found)
+ /*
+ * In the ALTER TABLE case, don't complain about index keys
+ * not created in the command; they may well exist already.
+ * DefineIndex will complain about them if not, and will also
+ * take care of marking them NOT NULL.
+ */
+ if (!found && !cxt->isalter)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" named in key does not exist",
@@ -1537,51 +1483,36 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
indexlist = lnext(indexlist);
}
-
- /*
- * Finally, select unique names for all not-previously-named indices,
- * and display NOTICE messages.
- *
- * XXX in ALTER TABLE case, we fail to consider name collisions against
- * pre-existing indexes.
- */
- foreach(indexlist, cxt->alist)
- {
- index = lfirst(indexlist);
-
- if (index->idxname == NULL && index->indexParams != NIL)
- {
- iparam = (IndexElem *) lfirst(index->indexParams);
- /* we should never see an expression item here */
- Assert(iparam->expr == NULL);
- index->idxname = CreateIndexName(cxt->relation->relname,
- iparam->name,
- "key",
- cxt->alist);
- }
- if (index->idxname == NULL) /* should not happen */
- elog(ERROR, "failed to make implicit index name");
-
- ereport(NOTICE,
- (errmsg("%s / %s%s will create implicit index \"%s\" for table \"%s\"",
- cxt->stmtType,
- (strcmp(cxt->stmtType, "ALTER TABLE") == 0) ? "ADD " : "",
- (index->primary ? "PRIMARY KEY" : "UNIQUE"),
- index->idxname, cxt->relation->relname)));
- }
}
static void
transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
- bool isAddConstraint)
+ bool skipValidation, bool isAddConstraint)
{
+ List *fkclist;
+
if (cxt->fkconstraints == NIL)
return;
/*
- * For ALTER TABLE ADD CONSTRAINT, nothing to do. For CREATE TABLE or
- * ALTER TABLE ADD COLUMN, gin up an ALTER TABLE ADD CONSTRAINT
- * command to execute after the basic command is complete.
+ * If CREATE TABLE or adding a column with NULL default, we can safely
+ * skip validation of the constraint.
+ */
+ if (skipValidation)
+ {
+ foreach(fkclist, cxt->fkconstraints)
+ {
+ FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist);
+
+ fkconstraint->skip_validation = true;
+ }
+ }
+
+ /*
+ * For CREATE TABLE or ALTER TABLE ADD COLUMN, gin up an ALTER TABLE
+ * ADD CONSTRAINT command to execute after the basic command is complete.
+ * (If called from ADD CONSTRAINT, that routine will add the FK constraints
+ * to its own subcommand list.)
*
* Note: the ADD CONSTRAINT command must also execute after any index
* creation commands. Thus, this should run after
@@ -1591,22 +1522,22 @@ transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
if (!isAddConstraint)
{
AlterTableStmt *alterstmt = makeNode(AlterTableStmt);
- List *fkclist;
- alterstmt->subtype = 'c'; /* preprocessed add constraint */
alterstmt->relation = cxt->relation;
- alterstmt->name = NULL;
- alterstmt->def = (Node *) cxt->fkconstraints;
+ alterstmt->cmds = NIL;
- /* Don't need to scan the table contents in this case */
foreach(fkclist, cxt->fkconstraints)
{
FkConstraint *fkconstraint = (FkConstraint *) lfirst(fkclist);
+ AlterTableCmd *altercmd = makeNode(AlterTableCmd);
- fkconstraint->skip_validation = true;
+ altercmd->subtype = AT_ProcessedConstraint;
+ altercmd->name = NULL;
+ altercmd->def = (Node *) fkconstraint;
+ alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
}
- cxt->alist = lappend(cxt->alist, (Node *) alterstmt);
+ cxt->alist = lappend(cxt->alist, alterstmt);
}
}
@@ -2554,111 +2485,158 @@ static Query *
transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
List **extras_before, List **extras_after)
{
- Relation rel;
CreateStmtContext cxt;
Query *qry;
+ List *lcmd,
+ *l;
+ List *newcmds = NIL;
+ bool skipValidation = true;
+ AlterTableCmd *newcmd;
+
+ cxt.stmtType = "ALTER TABLE";
+ cxt.relation = stmt->relation;
+ cxt.inhRelations = NIL;
+ cxt.isalter = true;
+ cxt.hasoids = false; /* need not be right */
+ cxt.columns = NIL;
+ cxt.ckconstraints = NIL;
+ cxt.fkconstraints = NIL;
+ cxt.ixconstraints = NIL;
+ cxt.blist = NIL;
+ cxt.alist = NIL;
+ cxt.pkey = NULL;
/*
* The only subtypes that currently require parse transformation
- * handling are 'A'dd column and Add 'C'onstraint. These largely
+ * handling are ADD COLUMN and ADD CONSTRAINT. These largely
* re-use code from CREATE TABLE.
- *
- * If we need to do any parse transformation, get exclusive lock on the
- * relation to make sure it won't change before we execute the
- * command.
*/
- switch (stmt->subtype)
+ foreach(lcmd, stmt->cmds)
{
- case 'A':
- rel = heap_openrv(stmt->relation, AccessExclusiveLock);
-
- cxt.stmtType = "ALTER TABLE";
- cxt.relation = stmt->relation;
- cxt.inhRelations = NIL;
- cxt.relOid = RelationGetRelid(rel);
- cxt.hasoids = SearchSysCacheExists(ATTNUM,
- ObjectIdGetDatum(cxt.relOid),
- Int16GetDatum(ObjectIdAttributeNumber),
- 0, 0);
- cxt.columns = NIL;
- cxt.ckconstraints = NIL;
- cxt.fkconstraints = NIL;
- cxt.ixconstraints = NIL;
- cxt.blist = NIL;
- cxt.alist = NIL;
- cxt.pkey = NULL;
-
- Assert(IsA(stmt->def, ColumnDef));
- transformColumnDefinition(pstate, &cxt,
- (ColumnDef *) stmt->def);
-
- transformIndexConstraints(pstate, &cxt);
- transformFKConstraints(pstate, &cxt, false);
-
- ((ColumnDef *) stmt->def)->constraints = cxt.ckconstraints;
- *extras_before = nconc(*extras_before, cxt.blist);
- *extras_after = nconc(cxt.alist, *extras_after);
-
- heap_close(rel, NoLock); /* close rel, keep lock */
- break;
+ AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);
- case 'C':
- rel = heap_openrv(stmt->relation, AccessExclusiveLock);
-
- cxt.stmtType = "ALTER TABLE";
- cxt.relation = stmt->relation;
- cxt.inhRelations = NIL;
- cxt.relOid = RelationGetRelid(rel);
- cxt.hasoids = SearchSysCacheExists(ATTNUM,
- ObjectIdGetDatum(cxt.relOid),
- Int16GetDatum(ObjectIdAttributeNumber),
- 0, 0);
- cxt.columns = NIL;
- cxt.ckconstraints = NIL;
- cxt.fkconstraints = NIL;
- cxt.ixconstraints = NIL;
- cxt.blist = NIL;
- cxt.alist = NIL;
- cxt.pkey = NULL;
-
- if (IsA(stmt->def, Constraint))
- transformTableConstraint(pstate, &cxt,
- (Constraint *) stmt->def);
- else if (IsA(stmt->def, FkConstraint))
- cxt.fkconstraints = lappend(cxt.fkconstraints, stmt->def);
- else
- elog(ERROR, "unrecognized node type: %d",
- (int) nodeTag(stmt->def));
+ switch (cmd->subtype)
+ {
+ case AT_AddColumn:
+ {
+ ColumnDef *def = (ColumnDef *) cmd->def;
- transformIndexConstraints(pstate, &cxt);
- transformFKConstraints(pstate, &cxt, true);
+ Assert(IsA(cmd->def, ColumnDef));
+ transformColumnDefinition(pstate, &cxt,
+ (ColumnDef *) cmd->def);
- Assert(cxt.columns == NIL);
- /* fkconstraints should be put into my own stmt in this case */
- stmt->def = (Node *) nconc(cxt.ckconstraints, cxt.fkconstraints);
- *extras_before = nconc(*extras_before, cxt.blist);
- *extras_after = nconc(cxt.alist, *extras_after);
+ /*
+ * If the column has a non-null default, we can't skip
+ * validation of foreign keys.
+ */
+ if (((ColumnDef *) cmd->def)->raw_default != NULL)
+ skipValidation = false;
- heap_close(rel, NoLock); /* close rel, keep lock */
- break;
+ newcmds = lappend(newcmds, cmd);
- case 'c':
+ /*
+ * Convert an ADD COLUMN ... NOT NULL constraint to a separate
+ * command
+ */
+ if (def->is_not_null)
+ {
+ /* Remove NOT NULL from AddColumn */
+ def->is_not_null = false;
+
+ /* Add as a separate AlterTableCmd */
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_SetNotNull;
+ newcmd->name = pstrdup(def->colname);
+ newcmds = lappend(newcmds, newcmd);
+ }
- /*
- * Already-transformed ADD CONSTRAINT, so just make it look
- * like the standard case.
- */
- stmt->subtype = 'C';
- break;
+ /*
+ * All constraints are processed in other ways.
+ * Remove the original list
+ */
+ def->constraints = NIL;
- default:
- break;
+ break;
+ }
+ case AT_AddConstraint:
+ /* The original AddConstraint cmd node doesn't go to newcmds */
+
+ if (IsA(cmd->def, Constraint))
+ transformTableConstraint(pstate, &cxt,
+ (Constraint *) cmd->def);
+ else if (IsA(cmd->def, FkConstraint))
+ {
+ cxt.fkconstraints = lappend(cxt.fkconstraints, cmd->def);
+ skipValidation = false;
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(cmd->def));
+ break;
+
+ case AT_ProcessedConstraint:
+
+ /*
+ * Already-transformed ADD CONSTRAINT, so just make it look
+ * like the standard case.
+ */
+ cmd->subtype = AT_AddConstraint;
+ newcmds = lappend(newcmds, cmd);
+ break;
+
+ default:
+ newcmds = lappend(newcmds, cmd);
+ break;
+ }
}
+ /* Postprocess index and FK constraints */
+ transformIndexConstraints(pstate, &cxt);
+
+ transformFKConstraints(pstate, &cxt, skipValidation, true);
+
+ /*
+ * Push any index-creation commands into the ALTER, so that
+ * they can be scheduled nicely by tablecmds.c.
+ */
+ foreach(l, cxt.alist)
+ {
+ Node *idxstmt = (Node *) lfirst(l);
+
+ Assert(IsA(idxstmt, IndexStmt));
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_AddIndex;
+ newcmd->def = idxstmt;
+ newcmds = lappend(newcmds, newcmd);
+ }
+ cxt.alist = NIL;
+
+ /* Append any CHECK or FK constraints to the commands list */
+ foreach(l, cxt.ckconstraints)
+ {
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_AddConstraint;
+ newcmd->def = (Node *) lfirst(l);
+ newcmds = lappend(newcmds, newcmd);
+ }
+ foreach(l, cxt.fkconstraints)
+ {
+ newcmd = makeNode(AlterTableCmd);
+ newcmd->subtype = AT_AddConstraint;
+ newcmd->def = (Node *) lfirst(l);
+ newcmds = lappend(newcmds, newcmd);
+ }
+
+ /* Update statement's commands list */
+ stmt->cmds = newcmds;
+
qry = makeNode(Query);
qry->commandType = CMD_UTILITY;
qry->utilityStmt = (Node *) stmt;
+ *extras_before = nconc(*extras_before, cxt.blist);
+ *extras_after = nconc(cxt.alist, *extras_after);
+
return qry;
}
@@ -2947,51 +2925,6 @@ transformForUpdate(Query *qry, List *forUpdate)
/*
- * relationHasPrimaryKey -
- *
- * See whether an existing relation has a primary key.
- */
-static bool
-relationHasPrimaryKey(Oid relationOid)
-{
- bool result = false;
- Relation rel;
- List *indexoidlist,
- *indexoidscan;
-
- rel = heap_open(relationOid, AccessShareLock);
-
- /*
- * Get the list of index OIDs for the table from the relcache, and
- * look up each one in the pg_index syscache until we find one marked
- * primary key (hopefully there isn't more than one such).
- */
- indexoidlist = RelationGetIndexList(rel);
-
- foreach(indexoidscan, indexoidlist)
- {
- Oid indexoid = lfirsto(indexoidscan);
- HeapTuple indexTuple;
-
- indexTuple = SearchSysCache(INDEXRELID,
- ObjectIdGetDatum(indexoid),
- 0, 0, 0);
- if (!HeapTupleIsValid(indexTuple)) /* should not happen */
- elog(ERROR, "cache lookup failed for index %u", indexoid);
- result = ((Form_pg_index) GETSTRUCT(indexTuple))->indisprimary;
- ReleaseSysCache(indexTuple);
- if (result)
- break;
- }
-
- freeList(indexoidlist);
-
- heap_close(rel, AccessShareLock);
-
- return result;
-}
-
-/*
* Preprocess a list of column constraint clauses
* to attach constraint attributes to their primary constraint nodes
* and detect inconsistent/misplaced constraint attributes.
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index de8597ed9ef..64825893bc1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.452 2004/04/21 00:34:18 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.453 2004/05/05 04:48:46 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -158,9 +158,12 @@ static void doNegateFloat(Value *v);
%type <node> select_no_parens select_with_parens select_clause
simple_select
-%type <node> alter_column_default opclass_item
+%type <node> alter_column_default opclass_item alter_using
%type <ival> add_drop
+%type <node> alter_table_cmd
+%type <list> alter_table_cmds
+
%type <dbehavior> opt_drop_behavior
%type <list> createdb_opt_list copy_opt_list
@@ -199,7 +202,7 @@ static void doNegateFloat(Value *v);
%type <range> qualified_name OptConstrFromTable
-%type <str> all_Op MathOp opt_name SpecialRuleRelation
+%type <str> all_Op MathOp SpecialRuleRelation
%type <str> iso_level opt_encoding
%type <node> grantee
@@ -1127,127 +1130,139 @@ CheckPointStmt:
*****************************************************************************/
AlterTableStmt:
- /* ALTER TABLE <relation> ADD [COLUMN] <coldef> */
- ALTER TABLE relation_expr ADD opt_column columnDef
+ ALTER TABLE relation_expr alter_table_cmds
{
AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'A';
n->relation = $3;
- n->def = $6;
+ n->cmds = $4;
+ $$ = (Node *)n;
+ }
+ ;
+
+alter_table_cmds:
+ alter_table_cmd { $$ = makeList1($1); }
+ | alter_table_cmds ',' alter_table_cmd { $$ = lappend($1, $3); }
+ ;
+
+alter_table_cmd:
+ /* ALTER TABLE <relation> ADD [COLUMN] <coldef> */
+ ADD opt_column columnDef
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_AddColumn;
+ n->def = $3;
$$ = (Node *)n;
}
/* ALTER TABLE <relation> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
- | ALTER TABLE relation_expr ALTER opt_column ColId alter_column_default
+ | ALTER opt_column ColId alter_column_default
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'T';
- n->relation = $3;
- n->name = $6;
- n->def = $7;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ColumnDefault;
+ n->name = $3;
+ n->def = $4;
$$ = (Node *)n;
}
/* ALTER TABLE <relation> ALTER [COLUMN] <colname> DROP NOT NULL */
- | ALTER TABLE relation_expr ALTER opt_column ColId DROP NOT NULL_P
+ | ALTER opt_column ColId DROP NOT NULL_P
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'N';
- n->relation = $3;
- n->name = $6;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropNotNull;
+ n->name = $3;
$$ = (Node *)n;
}
/* ALTER TABLE <relation> ALTER [COLUMN] <colname> SET NOT NULL */
- | ALTER TABLE relation_expr ALTER opt_column ColId SET NOT NULL_P
+ | ALTER opt_column ColId SET NOT NULL_P
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'n';
- n->relation = $3;
- n->name = $6;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_SetNotNull;
+ n->name = $3;
$$ = (Node *)n;
}
/* ALTER TABLE <relation> ALTER [COLUMN] <colname> SET STATISTICS <IntegerOnly> */
- | ALTER TABLE relation_expr ALTER opt_column ColId SET STATISTICS IntegerOnly
+ | ALTER opt_column ColId SET STATISTICS IntegerOnly
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'S';
- n->relation = $3;
- n->name = $6;
- n->def = (Node *) $9;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_SetStatistics;
+ n->name = $3;
+ n->def = (Node *) $6;
$$ = (Node *)n;
}
/* ALTER TABLE <relation> ALTER [COLUMN] <colname> SET STORAGE <storagemode> */
- | ALTER TABLE relation_expr ALTER opt_column ColId
- SET STORAGE ColId
+ | ALTER opt_column ColId SET STORAGE ColId
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'M';
- n->relation = $3;
- n->name = $6;
- n->def = (Node *) makeString($9);
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_SetStorage;
+ n->name = $3;
+ n->def = (Node *) makeString($6);
$$ = (Node *)n;
}
/* ALTER TABLE <relation> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
- | ALTER TABLE relation_expr DROP opt_column ColId opt_drop_behavior
+ | DROP opt_column ColId opt_drop_behavior
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'D';
- n->relation = $3;
- n->name = $6;
- n->behavior = $7;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropColumn;
+ n->name = $3;
+ n->behavior = $4;
+ $$ = (Node *)n;
+ }
+ /*
+ * ALTER TABLE <relation> ALTER [COLUMN] <colname> TYPE <typename>
+ * [ USING <expression> ]
+ */
+ | ALTER opt_column ColId TYPE_P Typename alter_using
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_AlterColumnType;
+ n->name = $3;
+ n->def = (Node *) $5;
+ n->transform = $6;
$$ = (Node *)n;
}
/* ALTER TABLE <relation> ADD CONSTRAINT ... */
- | ALTER TABLE relation_expr ADD TableConstraint
+ | ADD TableConstraint
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'C';
- n->relation = $3;
- n->def = $5;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_AddConstraint;
+ n->def = $2;
$$ = (Node *)n;
}
/* ALTER TABLE <relation> DROP CONSTRAINT <name> [RESTRICT|CASCADE] */
- | ALTER TABLE relation_expr DROP CONSTRAINT name opt_drop_behavior
+ | DROP CONSTRAINT name opt_drop_behavior
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'X';
- n->relation = $3;
- n->name = $6;
- n->behavior = $7;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropConstraint;
+ n->name = $3;
+ n->behavior = $4;
$$ = (Node *)n;
}
/* ALTER TABLE <relation> SET WITHOUT OIDS */
- | ALTER TABLE relation_expr SET WITHOUT OIDS
+ | SET WITHOUT OIDS
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->relation = $3;
- n->subtype = 'o';
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DropOids;
$$ = (Node *)n;
}
- /* ALTER TABLE <name> CREATE TOAST TABLE */
- | ALTER TABLE qualified_name CREATE TOAST TABLE
+ /* ALTER TABLE <name> CREATE TOAST TABLE -- ONLY */
+ | CREATE TOAST TABLE
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'E';
- $3->inhOpt = INH_NO;
- n->relation = $3;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ToastTable;
$$ = (Node *)n;
}
/* ALTER TABLE <name> OWNER TO UserId */
- | ALTER TABLE qualified_name OWNER TO UserId
+ | OWNER TO UserId
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'U';
- $3->inhOpt = INH_NO;
- n->relation = $3;
- n->name = $6;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ChangeOwner;
+ n->name = $3;
$$ = (Node *)n;
}
/* ALTER TABLE <name> CLUSTER ON <indexname> */
- | ALTER TABLE qualified_name CLUSTER ON name
+ | CLUSTER ON name
{
- AlterTableStmt *n = makeNode(AlterTableStmt);
- n->subtype = 'L';
- n->relation = $3;
- n->name = $6;
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_ClusterOn;
+ n->name = $3;
$$ = (Node *)n;
}
;
@@ -1261,7 +1276,7 @@ alter_column_default:
else
$$ = $3;
}
- | DROP DEFAULT { $$ = NULL; }
+ | DROP DEFAULT { $$ = NULL; }
;
opt_drop_behavior:
@@ -1270,6 +1285,10 @@ opt_drop_behavior:
| /* EMPTY */ { $$ = DROP_RESTRICT; /* default */ }
;
+alter_using:
+ USING a_expr { $$ = $2; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
/*****************************************************************************
*
@@ -3525,16 +3544,22 @@ RenameStmt: ALTER AGGREGATE func_name '(' aggr_argtype ')' RENAME TO name
n->newname = $6;
$$ = (Node *)n;
}
- | ALTER TABLE relation_expr RENAME opt_column opt_name TO name
+ | ALTER TABLE relation_expr RENAME TO name
{
RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_TABLE;
+ n->relation = $3;
+ n->subname = NULL;
+ n->newname = $6;
+ $$ = (Node *)n;
+ }
+ | ALTER TABLE relation_expr RENAME opt_column name TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_COLUMN;
n->relation = $3;
n->subname = $6;
n->newname = $8;
- if ($6 == NULL)
- n->renameType = OBJECT_TABLE;
- else
- n->renameType = OBJECT_COLUMN;
$$ = (Node *)n;
}
| ALTER TRIGGER name ON relation_expr RENAME TO name
@@ -3556,10 +3581,6 @@ RenameStmt: ALTER AGGREGATE func_name '(' aggr_argtype ')' RENAME TO name
}
;
-opt_name: name { $$ = $1; }
- | /*EMPTY*/ { $$ = NULL; }
- ;
-
opt_column: COLUMN { $$ = COLUMN; }
| /*EMPTY*/ { $$ = 0; }
;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 98c63fd0e64..68e9f846725 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.213 2004/04/22 02:58:20 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.214 2004/05/05 04:48:46 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -43,7 +43,6 @@
#include "commands/view.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
-#include "parser/parse_clause.h"
#include "parser/parse_expr.h"
#include "parser/parse_type.h"
#include "rewrite/rewriteDefine.h"
@@ -497,126 +496,8 @@ ProcessUtility(Node *parsetree,
ExecRenameStmt((RenameStmt *) parsetree);
break;
- /* various Alter Table forms */
-
case T_AlterTableStmt:
- {
- AlterTableStmt *stmt = (AlterTableStmt *) parsetree;
- Oid relid;
-
- relid = RangeVarGetRelid(stmt->relation, false);
-
- /*
- * Some or all of these functions are recursive to cover
- * inherited things, so permission checks are done there.
- */
- switch (stmt->subtype)
- {
- case 'A': /* ADD COLUMN */
-
- /*
- * Recursively add column to table and, if
- * requested, to descendants
- */
- AlterTableAddColumn(relid,
- interpretInhOption(stmt->relation->inhOpt),
- (ColumnDef *) stmt->def);
- break;
- case 'T': /* ALTER COLUMN DEFAULT */
-
- /*
- * Recursively alter column default for table and,
- * if requested, for descendants
- */
- AlterTableAlterColumnDefault(relid,
- interpretInhOption(stmt->relation->inhOpt),
- stmt->name,
- stmt->def);
- break;
- case 'N': /* ALTER COLUMN DROP NOT NULL */
- AlterTableAlterColumnDropNotNull(relid,
- interpretInhOption(stmt->relation->inhOpt),
- stmt->name);
- break;
- case 'n': /* ALTER COLUMN SET NOT NULL */
- AlterTableAlterColumnSetNotNull(relid,
- interpretInhOption(stmt->relation->inhOpt),
- stmt->name);
- break;
- case 'S': /* ALTER COLUMN STATISTICS */
- case 'M': /* ALTER COLUMN STORAGE */
-
- /*
- * Recursively alter column statistics for table
- * and, if requested, for descendants
- */
- AlterTableAlterColumnFlags(relid,
- interpretInhOption(stmt->relation->inhOpt),
- stmt->name,
- stmt->def,
- &(stmt->subtype));
- break;
- case 'D': /* DROP COLUMN */
-
- /*
- * Recursively drop column from table and, if
- * requested, from descendants
- */
- AlterTableDropColumn(relid,
- interpretInhOption(stmt->relation->inhOpt),
- false,
- stmt->name,
- stmt->behavior);
- break;
- case 'C': /* ADD CONSTRAINT */
-
- /*
- * Recursively add constraint to table and, if
- * requested, to descendants
- */
- AlterTableAddConstraint(relid,
- interpretInhOption(stmt->relation->inhOpt),
- (List *) stmt->def);
- break;
- case 'X': /* DROP CONSTRAINT */
-
- /*
- * Recursively drop constraint from table and, if
- * requested, from descendants
- */
- AlterTableDropConstraint(relid,
- interpretInhOption(stmt->relation->inhOpt),
- stmt->name,
- stmt->behavior);
- break;
- case 'E': /* CREATE TOAST TABLE */
- AlterTableCreateToastTable(relid, false);
- break;
- case 'U': /* ALTER OWNER */
- /* check that we are the superuser */
- if (!superuser())
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser to alter owner")));
- /* get_usesysid raises an error if no such user */
- AlterTableOwner(relid,
- get_usesysid(stmt->name));
- break;
- case 'L': /* CLUSTER ON */
- AlterTableClusterOn(relid, stmt->name);
- break;
- case 'o': /* SET WITHOUT OIDS */
- AlterTableAlterOids(relid,
- false,
- interpretInhOption(stmt->relation->inhOpt),
- DROP_RESTRICT);
- break;
- default: /* oops */
- elog(ERROR, "unrecognized alter table type: %d",
- (int) stmt->subtype);
- break;
- }
- }
+ AlterTable((AlterTableStmt *) parsetree);
break;
case T_AlterDomainStmt:
@@ -736,11 +617,15 @@ ProcessUtility(Node *parsetree,
stmt->idxname, /* index name */
stmt->accessMethod, /* am name */
stmt->indexParams, /* parameters */
+ (Expr *) stmt->whereClause,
+ stmt->rangetable,
stmt->unique,
stmt->primary,
stmt->isconstraint,
- (Expr *) stmt->whereClause,
- stmt->rangetable);
+ false, /* is_alter_table */
+ true, /* check_rights */
+ false, /* skip_build */
+ false); /* quiet */
}
break;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3960152f387..412aa252d62 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
* back to source text
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.163 2004/03/17 20:48:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.164 2004/05/05 04:48:46 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@@ -149,15 +149,16 @@ static char *query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_c
static char *deparse_expression_pretty(Node *expr, List *dpcontext,
bool forceprefix, bool showimplicit,
int prettyFlags, int startIndent);
-static text *pg_do_getviewdef(Oid viewoid, int prettyFlags);
+static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
-static Datum pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
-static Datum pg_get_indexdef_worker(Oid indexrelid, int colno,
- int prettyFlags);
-static Datum pg_get_constraintdef_worker(Oid constraintId, int prettyFlags);
-static Datum pg_get_expr_worker(text *expr, Oid relid, char *relname,
- int prettyFlags);
+static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
+static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
+ int prettyFlags);
+static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
+ int prettyFlags);
+static char *pg_get_expr_worker(text *expr, Oid relid, char *relname,
+ int prettyFlags);
static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
int prettyFlags);
static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
@@ -208,6 +209,7 @@ static char *generate_relation_name(Oid relid);
static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes);
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static void print_operator_name(StringInfo buf, List *opname);
+static text *string_to_text(char *str);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -223,7 +225,7 @@ pg_get_ruledef(PG_FUNCTION_ARGS)
{
Oid ruleoid = PG_GETARG_OID(0);
- return pg_get_ruledef_worker(ruleoid, 0);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, 0)));
}
@@ -235,21 +237,24 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS)
int prettyFlags;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
- return pg_get_ruledef_worker(ruleoid, prettyFlags);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, prettyFlags)));
}
-static Datum
+static char *
pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
{
- text *ruledef;
Datum args[1];
char nulls[1];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
StringInfoData buf;
- int len;
+
+ /*
+ * Do this first so that string is alloc'd in outer context not SPI's.
+ */
+ initStringInfo(&buf);
/*
* Connect to SPI manager
@@ -283,39 +288,24 @@ pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid);
if (SPI_processed != 1)
+ appendStringInfo(&buf, "-");
+ else
{
- if (SPI_finish() != SPI_OK_FINISH)
- elog(ERROR, "SPI_finish failed");
- ruledef = palloc(VARHDRSZ + 1);
- VARATT_SIZEP(ruledef) = VARHDRSZ + 1;
- VARDATA(ruledef)[0] = '-';
- PG_RETURN_TEXT_P(ruledef);
+ /*
+ * Get the rules definition and put it into executors memory
+ */
+ ruletup = SPI_tuptable->vals[0];
+ rulettc = SPI_tuptable->tupdesc;
+ make_ruledef(&buf, ruletup, rulettc, prettyFlags);
}
- ruletup = SPI_tuptable->vals[0];
- rulettc = SPI_tuptable->tupdesc;
-
- /*
- * Get the rules definition and put it into executors memory
- */
- initStringInfo(&buf);
- make_ruledef(&buf, ruletup, rulettc, prettyFlags);
- len = buf.len + VARHDRSZ;
- ruledef = SPI_palloc(len);
- VARATT_SIZEP(ruledef) = len;
- memcpy(VARDATA(ruledef), buf.data, buf.len);
- pfree(buf.data);
-
/*
* Disconnect from SPI manager
*/
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
- /*
- * Easy - isn't it?
- */
- PG_RETURN_TEXT_P(ruledef);
+ return buf.data;
}
@@ -329,10 +319,8 @@ pg_get_viewdef(PG_FUNCTION_ARGS)
{
/* By OID */
Oid viewoid = PG_GETARG_OID(0);
- text *ruledef;
- ruledef = pg_do_getviewdef(viewoid, 0);
- PG_RETURN_TEXT_P(ruledef);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0)));
}
@@ -342,12 +330,10 @@ pg_get_viewdef_ext(PG_FUNCTION_ARGS)
/* By OID */
Oid viewoid = PG_GETARG_OID(0);
bool pretty = PG_GETARG_BOOL(1);
- text *ruledef;
int prettyFlags;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
- ruledef = pg_do_getviewdef(viewoid, prettyFlags);
- PG_RETURN_TEXT_P(ruledef);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags)));
}
Datum
@@ -357,14 +343,12 @@ pg_get_viewdef_name(PG_FUNCTION_ARGS)
text *viewname = PG_GETARG_TEXT_P(0);
RangeVar *viewrel;
Oid viewoid;
- text *ruledef;
viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname,
"get_viewdef"));
viewoid = RangeVarGetRelid(viewrel, false);
- ruledef = pg_do_getviewdef(viewoid, 0);
- PG_RETURN_TEXT_P(ruledef);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0)));
}
@@ -377,31 +361,32 @@ pg_get_viewdef_name_ext(PG_FUNCTION_ARGS)
int prettyFlags;
RangeVar *viewrel;
Oid viewoid;
- text *ruledef;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname,
"get_viewdef"));
viewoid = RangeVarGetRelid(viewrel, false);
- ruledef = pg_do_getviewdef(viewoid, prettyFlags);
- PG_RETURN_TEXT_P(ruledef);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags)));
}
/*
* Common code for by-OID and by-name variants of pg_get_viewdef
*/
-static text *
-pg_do_getviewdef(Oid viewoid, int prettyFlags)
+static char *
+pg_get_viewdef_worker(Oid viewoid, int prettyFlags)
{
- text *ruledef;
Datum args[2];
char nulls[2];
int spirc;
HeapTuple ruletup;
TupleDesc rulettc;
StringInfoData buf;
- int len;
+
+ /*
+ * Do this first so that string is alloc'd in outer context not SPI's.
+ */
+ initStringInfo(&buf);
/*
* Connect to SPI manager
@@ -437,7 +422,6 @@ pg_do_getviewdef(Oid viewoid, int prettyFlags)
spirc = SPI_execp(plan_getviewrule, args, nulls, 2);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid);
- initStringInfo(&buf);
if (SPI_processed != 1)
appendStringInfo(&buf, "Not a view");
else
@@ -449,11 +433,6 @@ pg_do_getviewdef(Oid viewoid, int prettyFlags)
rulettc = SPI_tuptable->tupdesc;
make_viewdef(&buf, ruletup, rulettc, prettyFlags);
}
- len = buf.len + VARHDRSZ;
- ruledef = SPI_palloc(len);
- VARATT_SIZEP(ruledef) = len;
- memcpy(VARDATA(ruledef), buf.data, buf.len);
- pfree(buf.data);
/*
* Disconnect from SPI manager
@@ -461,7 +440,7 @@ pg_do_getviewdef(Oid viewoid, int prettyFlags)
if (SPI_finish() != SPI_OK_FINISH)
elog(ERROR, "SPI_finish failed");
- return ruledef;
+ return buf.data;
}
/* ----------
@@ -472,10 +451,8 @@ Datum
pg_get_triggerdef(PG_FUNCTION_ARGS)
{
Oid trigid = PG_GETARG_OID(0);
- text *trigdef;
HeapTuple ht_trig;
Form_pg_trigger trigrec;
- int len;
StringInfoData buf;
Relation tgrel;
ScanKeyData skey[1];
@@ -597,21 +574,12 @@ pg_get_triggerdef(PG_FUNCTION_ARGS)
/* We deliberately do not put semi-colon at end */
appendStringInfo(&buf, ")");
- /*
- * Create the result as a TEXT datum, and free working data
- */
- len = buf.len + VARHDRSZ;
- trigdef = (text *) palloc(len);
- VARATT_SIZEP(trigdef) = len;
- memcpy(VARDATA(trigdef), buf.data, buf.len);
-
- pfree(buf.data);
-
+ /* Clean up */
systable_endscan(tgscan);
heap_close(tgrel, AccessShareLock);
- PG_RETURN_TEXT_P(trigdef);
+ PG_RETURN_TEXT_P(string_to_text(buf.data));
}
/* ----------
@@ -627,7 +595,7 @@ pg_get_indexdef(PG_FUNCTION_ARGS)
{
Oid indexrelid = PG_GETARG_OID(0);
- return pg_get_indexdef_worker(indexrelid, 0, 0);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, 0, 0)));
}
Datum
@@ -639,13 +607,19 @@ pg_get_indexdef_ext(PG_FUNCTION_ARGS)
int prettyFlags;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
- return pg_get_indexdef_worker(indexrelid, colno, prettyFlags);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, colno, prettyFlags)));
+}
+
+/* Internal version that returns a palloc'd C string */
+char *
+pg_get_indexdef_string(Oid indexrelid)
+{
+ return pg_get_indexdef_worker(indexrelid, 0, 0);
}
-static Datum
+static char *
pg_get_indexdef_worker(Oid indexrelid, int colno, int prettyFlags)
{
- text *indexdef;
HeapTuple ht_idx;
HeapTuple ht_idxrel;
HeapTuple ht_am;
@@ -655,7 +629,6 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, int prettyFlags)
List *indexprs;
List *context;
Oid indrelid;
- int len;
int keyno;
Oid keycoltype;
StringInfoData buf;
@@ -817,21 +790,12 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, int prettyFlags)
}
}
- /*
- * Create the result as a TEXT datum, and free working data
- */
- len = buf.len + VARHDRSZ;
- indexdef = (text *) palloc(len);
- VARATT_SIZEP(indexdef) = len;
- memcpy(VARDATA(indexdef), buf.data, buf.len);
-
- pfree(buf.data);
-
+ /* Clean up */
ReleaseSysCache(ht_idx);
ReleaseSysCache(ht_idxrel);
ReleaseSysCache(ht_am);
- PG_RETURN_TEXT_P(indexdef);
+ return buf.data;
}
@@ -846,7 +810,8 @@ pg_get_constraintdef(PG_FUNCTION_ARGS)
{
Oid constraintId = PG_GETARG_OID(0);
- return pg_get_constraintdef_worker(constraintId, 0);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId,
+ false, 0)));
}
Datum
@@ -857,16 +822,22 @@ pg_get_constraintdef_ext(PG_FUNCTION_ARGS)
int prettyFlags;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
- return pg_get_constraintdef_worker(constraintId, prettyFlags);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId,
+ false, prettyFlags)));
}
+/* Internal version that returns a palloc'd C string */
+char *
+pg_get_constraintdef_string(Oid constraintId)
+{
+ return pg_get_constraintdef_worker(constraintId, true, 0);
+}
-static Datum
-pg_get_constraintdef_worker(Oid constraintId, int prettyFlags)
+static char *
+pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
+ int prettyFlags)
{
- text *result;
StringInfoData buf;
- int len;
Relation conDesc;
SysScanDesc conscan;
ScanKeyData skey[1];
@@ -894,6 +865,13 @@ pg_get_constraintdef_worker(Oid constraintId, int prettyFlags)
initStringInfo(&buf);
+ if (fullCommand && OidIsValid(conForm->conrelid))
+ {
+ appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ",
+ generate_relation_name(conForm->conrelid),
+ quote_identifier(NameStr(conForm->conname)));
+ }
+
switch (conForm->contype)
{
case CONSTRAINT_FOREIGN:
@@ -1087,18 +1065,11 @@ pg_get_constraintdef_worker(Oid constraintId, int prettyFlags)
break;
}
- /* Record the results */
- len = buf.len + VARHDRSZ;
- result = (text *) palloc(len);
- VARATT_SIZEP(result) = len;
- memcpy(VARDATA(result), buf.data, buf.len);
-
/* Cleanup */
- pfree(buf.data);
systable_endscan(conscan);
heap_close(conDesc, AccessShareLock);
- PG_RETURN_TEXT_P(result);
+ return buf.data;
}
@@ -1157,7 +1128,7 @@ pg_get_expr(PG_FUNCTION_ARGS)
if (relname == NULL)
PG_RETURN_NULL(); /* should we raise an error? */
- return pg_get_expr_worker(expr, relid, relname, 0);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_expr_worker(expr, relid, relname, 0)));
}
Datum
@@ -1176,13 +1147,12 @@ pg_get_expr_ext(PG_FUNCTION_ARGS)
if (relname == NULL)
PG_RETURN_NULL(); /* should we raise an error? */
- return pg_get_expr_worker(expr, relid, relname, prettyFlags);
+ PG_RETURN_TEXT_P(string_to_text(pg_get_expr_worker(expr, relid, relname, prettyFlags)));
}
-static Datum
+static char *
pg_get_expr_worker(text *expr, Oid relid, char *relname, int prettyFlags)
{
- text *result;
Node *node;
List *context;
char *exprstr;
@@ -1200,11 +1170,7 @@ pg_get_expr_worker(text *expr, Oid relid, char *relname, int prettyFlags)
str = deparse_expression_pretty(node, context, false, false,
prettyFlags, 0);
- /* Pass the result back as TEXT */
- result = DatumGetTextP(DirectFunctionCall1(textin,
- CStringGetDatum(str)));
-
- PG_RETURN_TEXT_P(result);
+ return str;
}
@@ -4364,3 +4330,25 @@ print_operator_name(StringInfo buf, List *opname)
appendStringInfo(buf, "%s)", strVal(lfirst(opname)));
}
}
+
+/*
+ * Given a C string, produce a TEXT datum.
+ *
+ * We assume that the input was palloc'd and may be freed.
+ */
+static text *
+string_to_text(char *str)
+{
+ text *result;
+ int slen = strlen(str);
+ int tlen;
+
+ tlen = slen + VARHDRSZ;
+ result = (text *) palloc(tlen);
+ VARATT_SIZEP(result) = tlen;
+ memcpy(VARDATA(result), str, slen);
+
+ pfree(str);
+
+ return result;
+}
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 583851c7493..0e230d7e862 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.11 2003/11/29 22:40:58 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/dependency.h,v 1.12 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -17,7 +17,7 @@
#include "nodes/parsenodes.h" /* for DropBehavior */
-/*
+/*----------
* Precise semantics of a dependency relationship are specified by the
* DependencyType code (which is stored in a "char" field in pg_depend,
* so we assign ASCII-code values to the enumeration members).
@@ -56,6 +56,7 @@
* contain zeroes.
*
* Other dependency flavors may be needed in future.
+ *----------
*/
typedef enum DependencyType
@@ -79,6 +80,28 @@ typedef struct ObjectAddress
} ObjectAddress;
+/*
+ * This enum covers all system catalogs whose OIDs can appear in classId.
+ */
+typedef enum ObjectClass
+{
+ OCLASS_CLASS, /* pg_class */
+ OCLASS_PROC, /* pg_proc */
+ OCLASS_TYPE, /* pg_type */
+ OCLASS_CAST, /* pg_cast */
+ OCLASS_CONSTRAINT, /* pg_constraint */
+ OCLASS_CONVERSION, /* pg_conversion */
+ OCLASS_DEFAULT, /* pg_attrdef */
+ OCLASS_LANGUAGE, /* pg_language */
+ OCLASS_OPERATOR, /* pg_operator */
+ OCLASS_OPCLASS, /* pg_opclass */
+ OCLASS_REWRITE, /* pg_rewrite */
+ OCLASS_TRIGGER, /* pg_trigger */
+ OCLASS_SCHEMA, /* pg_namespace */
+ MAX_OCLASS /* MUST BE LAST */
+} ObjectClass;
+
+
/* in dependency.c */
extern void performDeletion(const ObjectAddress *object,
@@ -96,6 +119,10 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
DependencyType behavior,
DependencyType self_behavior);
+extern ObjectClass getObjectClass(const ObjectAddress *object);
+
+extern char *getObjectDescription(const ObjectAddress *object);
+
/* in pg_depend.c */
extern void recordDependencyOn(const ObjectAddress *depender,
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index f635ef1bd09..2913d53e521 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/heap.h,v 1.65 2004/03/23 19:35:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/heap.h,v 1.66 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -27,6 +27,14 @@ typedef struct RawColumnDefault
* tree) */
} RawColumnDefault;
+typedef struct CookedConstraint
+{
+ ConstrType contype; /* CONSTR_DEFAULT or CONSTR_CHECK */
+ char *name; /* name, or NULL if none */
+ AttrNumber attnum; /* which attr (only for DEFAULT) */
+ Node *expr; /* transformed default or check expr */
+} CookedConstraint;
+
extern Relation heap_create(const char *relname,
Oid relnamespace,
TupleDesc tupDesc,
@@ -52,10 +60,12 @@ extern void heap_truncate(Oid rid);
extern void heap_truncate_check_FKs(Relation rel);
-extern void AddRelationRawConstraints(Relation rel,
+extern List *AddRelationRawConstraints(Relation rel,
List *rawColDefaults,
List *rawConstraints);
+extern void StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin);
+
extern Node *cookDefault(ParseState *pstate,
Node *raw_default,
Oid atttypid,
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index da210c483ae..13c367c9022 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.54 2003/11/29 22:40:58 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.55 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -37,7 +37,8 @@ extern Oid index_create(Oid heapRelationId,
Oid *classObjectId,
bool primary,
bool isconstraint,
- bool allow_system_table_mods);
+ bool allow_system_table_mods,
+ bool skip_build);
extern void index_drop(Oid indexId);
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 72295803abb..5474fdd5490 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.20 2003/11/29 22:40:59 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.21 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -20,5 +20,9 @@
extern void cluster(ClusterStmt *stmt);
extern void rebuild_relation(Relation OldHeap, Oid indexOid);
+extern Oid make_new_heap(Oid OIDOldHeap, const char *NewName);
+extern List *get_indexattr_list(Relation OldHeap, Oid OldIndex);
+extern void rebuild_indexes(Oid OIDOldHeap, List *indexes);
+extern void swap_relfilenodes(Oid r1, Oid r2);
#endif /* CLUSTER_H */
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 00f5fa1a480..78fe4ab9071 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.54 2004/02/21 00:34:53 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.55 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -22,11 +22,15 @@ extern void DefineIndex(RangeVar *heapRelation,
char *indexRelationName,
char *accessMethodName,
List *attributeList,
+ Expr *predicate,
+ List *rangetable,
bool unique,
bool primary,
bool isconstraint,
- Expr *predicate,
- List *rangetable);
+ bool is_alter_table,
+ bool check_rights,
+ bool skip_build,
+ bool quiet);
extern void RemoveIndex(RangeVar *relation, DropBehavior behavior);
extern void ReindexIndex(RangeVar *indexRelation, bool force);
extern void ReindexTable(RangeVar *relation, bool force);
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 889b4dfa626..f9f03c1bd03 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.15 2004/03/23 19:35:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/tablecmds.h,v 1.16 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -16,46 +16,17 @@
#include "nodes/parsenodes.h"
-extern void AlterTableAddColumn(Oid myrelid, bool recurse, ColumnDef *colDef);
-extern void AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse,
- const char *colName);
-
-extern void AlterTableAlterColumnSetNotNull(Oid myrelid, bool recurse,
- const char *colName);
-
-extern void AlterTableAlterColumnDefault(Oid myrelid, bool recurse,
- const char *colName,
- Node *newDefault);
-
-extern void AlterTableAlterColumnFlags(Oid myrelid, bool recurse,
- const char *colName,
- Node *flagValue, const char *flagType);
-
-extern void AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing,
- const char *colName,
- DropBehavior behavior);
+extern Oid DefineRelation(CreateStmt *stmt, char relkind);
-extern void AlterTableAddConstraint(Oid myrelid, bool recurse,
- List *newConstraints);
+extern void RemoveRelation(const RangeVar *relation, DropBehavior behavior);
-extern void AlterTableDropConstraint(Oid myrelid, bool recurse,
- const char *constrName,
- DropBehavior behavior);
+extern void AlterTable(AlterTableStmt *stmt);
-extern void AlterTableClusterOn(Oid relOid, const char *indexName);
+extern void AlterTableInternal(Oid relid, List *cmds, bool recurse);
extern void AlterTableCreateToastTable(Oid relOid, bool silent);
-extern void AlterTableOwner(Oid relationOid, int32 newOwnerSysId);
-
-extern void AlterTableAlterOids(Oid myrelid, bool setOid, bool recurse,
- DropBehavior behavior);
-
-extern Oid DefineRelation(CreateStmt *stmt, char relkind);
-
-extern void RemoveRelation(const RangeVar *relation, DropBehavior behavior);
-
extern void TruncateRelation(const RangeVar *relation);
extern void renameatt(Oid myrelid,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d5d4f0832a7..a776607ec66 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.152 2004/04/01 21:28:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.153 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -200,6 +200,7 @@ typedef enum NodeTag
T_UpdateStmt,
T_SelectStmt,
T_AlterTableStmt,
+ T_AlterTableCmd,
T_AlterDomainStmt,
T_SetOperationStmt,
T_GrantStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ab505939bc3..8eceb6e59cb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.254 2004/03/11 01:47:41 ishii Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.255 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -762,44 +762,58 @@ typedef enum DropBehavior
/* ----------------------
* Alter Table
- *
- * The fields are used in different ways by the different variants of
- * this command.
* ----------------------
*/
typedef struct AlterTableStmt
{
NodeTag type;
- char subtype; /*------------
- * A = add column
- * T = alter column default
- * N = alter column drop not null
- * n = alter column set not null
- * S = alter column statistics
- * M = alter column storage
- * D = drop column
- * C = add constraint
- * c = pre-processed add constraint
- * (local in parser/analyze.c)
- * X = drop constraint
- * E = create toast table
- * U = change owner
- * L = CLUSTER ON
- * o = DROP OIDS
- *------------
- */
RangeVar *relation; /* table to work on */
+ List *cmds; /* list of subcommands */
+} AlterTableStmt;
+
+typedef enum AlterTableType
+{
+ AT_AddColumn, /* add column */
+ AT_ColumnDefault, /* alter column default */
+ AT_DropNotNull, /* alter column drop not null */
+ AT_SetNotNull, /* alter column set not null */
+ AT_SetStatistics, /* alter column statistics */
+ AT_SetStorage, /* alter column storage */
+ AT_DropColumn, /* drop column */
+ AT_DropColumnRecurse, /* internal to commands/tablecmds.c */
+ AT_AddIndex, /* add index */
+ AT_ReAddIndex, /* internal to commands/tablecmds.c */
+ AT_AddConstraint, /* add constraint */
+ AT_ProcessedConstraint, /* pre-processed add constraint
+ * (local in parser/analyze.c) */
+ AT_DropConstraint, /* drop constraint */
+ AT_DropConstraintQuietly, /* drop constraint, no error/warning
+ * (local in commands/tablecmds.c) */
+ AT_AlterColumnType, /* alter column type */
+ AT_ToastTable, /* create toast table */
+ AT_ChangeOwner, /* change owner */
+ AT_ClusterOn, /* CLUSTER ON */
+ AT_DropOids /* SET WITHOUT OIDS */
+} AlterTableType;
+
+typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */
+{
+ NodeTag type;
+ AlterTableType subtype; /* Type of table alteration to apply */
char *name; /* column or constraint name to act on, or
* new owner */
- Node *def; /* definition of new column or constraint */
+ Node *def; /* definition of new column, column type,
+ * index, or constraint */
+ Node *transform; /* transformation expr for ALTER TYPE */
DropBehavior behavior; /* RESTRICT or CASCADE for DROP cases */
-} AlterTableStmt;
+} AlterTableCmd;
+
/* ----------------------
* Alter Domain
*
* The fields are used in different ways by the different variants of
- * this command. Subtypes should match AlterTable subtypes where possible.
+ * this command.
* ----------------------
*/
typedef struct AlterDomainStmt
@@ -814,7 +828,7 @@ typedef struct AlterDomainStmt
* U = change owner
*------------
*/
- List *typename; /* table to work on */
+ List *typename; /* domain to work on */
char *name; /* column or constraint name to act on, or
* new owner */
Node *def; /* definition of default or constraint */
@@ -922,6 +936,8 @@ typedef struct CreateStmt
* Definitions for plain (non-FOREIGN KEY) constraints in CreateStmt
*
* XXX probably these ought to be unified with FkConstraints at some point?
+ * To this end we include CONSTR_FOREIGN in the ConstrType enum, even though
+ * the parser does not generate it.
*
* For constraints that use expressions (CONSTR_DEFAULT, CONSTR_CHECK)
* we may have the expression in either "raw" form (an untransformed
@@ -944,6 +960,7 @@ typedef enum ConstrType /* types of constraints */
CONSTR_NOTNULL,
CONSTR_DEFAULT,
CONSTR_CHECK,
+ CONSTR_FOREIGN,
CONSTR_PRIMARY,
CONSTR_UNIQUE,
CONSTR_ATTR_DEFERRABLE, /* attributes for previous constraint node */
@@ -1291,7 +1308,7 @@ typedef struct FetchStmt
typedef struct IndexStmt
{
NodeTag type;
- char *idxname; /* name of the index */
+ char *idxname; /* name of new index, or NULL for default */
RangeVar *relation; /* relation to build index on */
char *accessMethod; /* name of access method (eg. btree) */
List *indexParams; /* a list of IndexElem */
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index e3cf88b4fd7..76c77025ece 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.25 2004/01/23 02:13:12 neilc Exp $
+ * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.26 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -22,6 +22,9 @@ extern List *parse_analyze_varparams(Node *parseTree, Oid **paramTypes,
extern List *parse_sub_analyze(Node *parseTree, ParseState *parentParseState);
extern List *analyzeCreateSchemaStmt(CreateSchemaStmt *stmt);
+extern char *makeObjectName(const char *name1, const char *name2,
+ const char *typename);
+
extern void CheckSelectForUpdate(Query *qry);
#endif /* ANALYZE_H */
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 02e6cbca49d..59fb0a9a853 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.236 2004/04/01 21:28:46 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.237 2004/05/05 04:48:47 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -463,9 +463,11 @@ extern Datum pg_get_viewdef_name(PG_FUNCTION_ARGS);
extern Datum pg_get_viewdef_name_ext(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
extern Datum pg_get_indexdef_ext(PG_FUNCTION_ARGS);
+extern char *pg_get_indexdef_string(Oid indexrelid);
extern Datum pg_get_triggerdef(PG_FUNCTION_ARGS);
extern Datum pg_get_constraintdef(PG_FUNCTION_ARGS);
extern Datum pg_get_constraintdef_ext(PG_FUNCTION_ARGS);
+extern char *pg_get_constraintdef_string(Oid constraintId);
extern Datum pg_get_userbyid(PG_FUNCTION_ARGS);
extern Datum pg_get_expr(PG_FUNCTION_ARGS);
extern Datum pg_get_expr_ext(PG_FUNCTION_ARGS);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 089651c7278..feb08520326 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -7,7 +7,7 @@ COMMENT ON TABLE tmp_wrong IS 'table comment';
ERROR: relation "tmp_wrong" does not exist
COMMENT ON TABLE tmp IS 'table comment';
COMMENT ON TABLE tmp IS NULL;
-ALTER TABLE tmp ADD COLUMN a int4;
+ALTER TABLE tmp ADD COLUMN a int4 default 3;
ALTER TABLE tmp ADD COLUMN b name;
ALTER TABLE tmp ADD COLUMN c text;
ALTER TABLE tmp ADD COLUMN d float8;
@@ -419,7 +419,6 @@ create table atacc1 ( test int );
insert into atacc1 (test) values (NULL);
-- add a primary key (fails)
alter table atacc1 add constraint atacc_test1 primary key (test);
-NOTICE: ALTER TABLE / ADD PRIMARY KEY will create implicit index "atacc_test1" for table "atacc1"
ERROR: column "test" contains null values
insert into atacc1 (test) values (3);
drop table atacc1;
@@ -1143,3 +1142,96 @@ select f3,max(f1) from foo group by f3;
zz | qq
(1 row)
+-- Simple tests for alter table column type
+alter table foo alter f1 TYPE integer; -- fails
+ERROR: column "f1" cannot be cast to type "pg_catalog.int4"
+alter table foo alter f1 TYPE varchar(10);
+create table anothertab (atcol1 serial8, atcol2 boolean,
+ constraint anothertab_chk check (atcol1 <= 3));
+NOTICE: CREATE TABLE will create implicit sequence "anothertab_atcol1_seq" for "serial" column "anothertab.atcol1"
+insert into anothertab (atcol1, atcol2) values (default, true);
+insert into anothertab (atcol1, atcol2) values (default, false);
+select * from anothertab;
+ atcol1 | atcol2
+--------+--------
+ 1 | t
+ 2 | f
+(2 rows)
+
+alter table anothertab alter column atcol1 type boolean; -- fails
+ERROR: column "atcol1" cannot be cast to type "pg_catalog.bool"
+alter table anothertab alter column atcol1 type integer;
+select * from anothertab;
+ atcol1 | atcol2
+--------+--------
+ 1 | t
+ 2 | f
+(2 rows)
+
+insert into anothertab (atcol1, atcol2) values (45, null); -- fails
+ERROR: new row for relation "anothertab" violates check constraint "anothertab_chk"
+insert into anothertab (atcol1, atcol2) values (default, null);
+select * from anothertab;
+ atcol1 | atcol2
+--------+--------
+ 1 | t
+ 2 | f
+ 3 |
+(3 rows)
+
+alter table anothertab alter column atcol2 type text
+ using case when atcol2 is true then 'IT WAS TRUE'
+ when atcol2 is false then 'IT WAS FALSE'
+ else 'IT WAS NULL!' end;
+select * from anothertab;
+ atcol1 | atcol2
+--------+--------------
+ 1 | IT WAS TRUE
+ 2 | IT WAS FALSE
+ 3 | IT WAS NULL!
+(3 rows)
+
+alter table anothertab alter column atcol1 type boolean
+ using case when atcol1 % 2 = 0 then true else false end; -- fails
+ERROR: default for column "atcol1" cannot be cast to type "pg_catalog.bool"
+alter table anothertab alter column atcol1 drop default;
+alter table anothertab alter column atcol1 type boolean
+ using case when atcol1 % 2 = 0 then true else false end; -- fails
+ERROR: operator does not exist: boolean <= integer
+HINT: No operator matches the given name and argument type(s). You may need to add explicit type casts.
+alter table anothertab drop constraint anothertab_chk;
+alter table anothertab alter column atcol1 type boolean
+ using case when atcol1 % 2 = 0 then true else false end;
+select * from anothertab;
+ atcol1 | atcol2
+--------+--------------
+ f | IT WAS TRUE
+ t | IT WAS FALSE
+ f | IT WAS NULL!
+(3 rows)
+
+drop table anothertab;
+create table another (f1 int, f2 text);
+insert into another values(1, 'one');
+insert into another values(2, 'two');
+insert into another values(3, 'three');
+select * from another;
+ f1 | f2
+----+-------
+ 1 | one
+ 2 | two
+ 3 | three
+(3 rows)
+
+alter table another
+ alter f1 type text using f2 || ' more',
+ alter f2 type bigint using f1 * 10;
+select * from another;
+ f1 | f2
+------------+----
+ one more | 10
+ two more | 20
+ three more | 30
+(3 rows)
+
+drop table another;
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index bd4bccfc9d4..5aae3720d30 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -145,6 +145,28 @@ SELECT * FROM FKTABLE;
| | 8
(5 rows)
+-- Try altering the column type where foreign keys are involved
+ALTER TABLE PKTABLE ALTER COLUMN ptest1 TYPE bigint;
+ALTER TABLE FKTABLE ALTER COLUMN ftest1 TYPE bigint;
+SELECT * FROM PKTABLE;
+ ptest1 | ptest2 | ptest3
+--------+--------+---------
+ 1 | 3 | Test1-2
+ 3 | 6 | Test3
+ 4 | 8 | Test4
+ 1 | 4 | Test2
+(4 rows)
+
+SELECT * FROM FKTABLE;
+ ftest1 | ftest2 | ftest3
+--------+--------+--------
+ 1 | 3 | 5
+ 3 | 6 | 12
+ | | 0
+ | | 4
+ | | 8
+(5 rows)
+
DROP TABLE PKTABLE CASCADE;
NOTICE: drop cascades to constraint constrname on table fktable
DROP TABLE FKTABLE;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index f1890595ea0..e1205a9b001 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -613,3 +613,12 @@ SELECT * FROM inhf; /* Single entry with value 'text' */
text
(1 row)
+-- Test changing the type of inherited columns
+insert into d values('test','one','two','three');
+alter table a alter column aa type integer using bit_length(aa);
+select * from d;
+ aa | bb | cc | dd
+----+-----+-----+-------
+ 32 | one | two | three
+(1 row)
+
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index dcd968a0b65..6077788583c 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -9,7 +9,7 @@ COMMENT ON TABLE tmp_wrong IS 'table comment';
COMMENT ON TABLE tmp IS 'table comment';
COMMENT ON TABLE tmp IS NULL;
-ALTER TABLE tmp ADD COLUMN a int4;
+ALTER TABLE tmp ADD COLUMN a int4 default 3;
ALTER TABLE tmp ADD COLUMN b name;
@@ -918,3 +918,60 @@ select * from foo;
update foo set f3 = 'zz';
select * from foo;
select f3,max(f1) from foo group by f3;
+
+-- Simple tests for alter table column type
+alter table foo alter f1 TYPE integer; -- fails
+alter table foo alter f1 TYPE varchar(10);
+
+create table anothertab (atcol1 serial8, atcol2 boolean,
+ constraint anothertab_chk check (atcol1 <= 3));
+
+insert into anothertab (atcol1, atcol2) values (default, true);
+insert into anothertab (atcol1, atcol2) values (default, false);
+select * from anothertab;
+
+alter table anothertab alter column atcol1 type boolean; -- fails
+alter table anothertab alter column atcol1 type integer;
+
+select * from anothertab;
+
+insert into anothertab (atcol1, atcol2) values (45, null); -- fails
+insert into anothertab (atcol1, atcol2) values (default, null);
+
+select * from anothertab;
+
+alter table anothertab alter column atcol2 type text
+ using case when atcol2 is true then 'IT WAS TRUE'
+ when atcol2 is false then 'IT WAS FALSE'
+ else 'IT WAS NULL!' end;
+
+select * from anothertab;
+alter table anothertab alter column atcol1 type boolean
+ using case when atcol1 % 2 = 0 then true else false end; -- fails
+alter table anothertab alter column atcol1 drop default;
+alter table anothertab alter column atcol1 type boolean
+ using case when atcol1 % 2 = 0 then true else false end; -- fails
+alter table anothertab drop constraint anothertab_chk;
+
+alter table anothertab alter column atcol1 type boolean
+ using case when atcol1 % 2 = 0 then true else false end;
+
+select * from anothertab;
+
+drop table anothertab;
+
+create table another (f1 int, f2 text);
+
+insert into another values(1, 'one');
+insert into another values(2, 'two');
+insert into another values(3, 'three');
+
+select * from another;
+
+alter table another
+ alter f1 type text using f2 || ' more',
+ alter f2 type bigint using f1 * 10;
+
+select * from another;
+
+drop table another;
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index 34fb787680d..ad1274c7f84 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -97,6 +97,12 @@ UPDATE PKTABLE SET ptest1=1 WHERE ptest1=2;
-- Check FKTABLE for update of matched row
SELECT * FROM FKTABLE;
+-- Try altering the column type where foreign keys are involved
+ALTER TABLE PKTABLE ALTER COLUMN ptest1 TYPE bigint;
+ALTER TABLE FKTABLE ALTER COLUMN ftest1 TYPE bigint;
+SELECT * FROM PKTABLE;
+SELECT * FROM FKTABLE;
+
DROP TABLE PKTABLE CASCADE;
DROP TABLE FKTABLE;
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 57f18673bfa..7bfe6cb7f2e 100644
--- a/src/test/regress/sql/inherit.sql
+++ b/src/test/regress/sql/inherit.sql
@@ -142,3 +142,10 @@ CREATE TABLE inhf (LIKE inhx, LIKE inhx); /* Throw error */
CREATE TABLE inhf (LIKE inhx INCLUDING DEFAULTS);
INSERT INTO inhf DEFAULT VALUES;
SELECT * FROM inhf; /* Single entry with value 'text' */
+
+-- Test changing the type of inherited columns
+insert into d values('test','one','two','three');
+
+alter table a alter column aa type integer using bit_length(aa);
+
+select * from d;