diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c249ffbc546f269eaa3081c3680a75f88e755586..6119a150626c1123a1c0bd41440699c1b7c0384d 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>
@@ -81,6 +76,23 @@ ALTER TABLE <replaceable class="PARAMETER">name</replaceable>
     </listitem>
    </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>
@@ -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,15 +213,34 @@ 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.
@@ -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>
@@ -427,6 +459,15 @@ ALTER TABLE distributors DROP COLUMN address RESTRICT;
 </programlisting>
   </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>
@@ -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 e6bde26d33726422705d52fd448a9deb82f21b62..679ef5577fab6751b6d9a9fab75c61bafc7ad45c 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 e6552b9f4736940f849203612f7eac269f695903..5f2b2ea28a070b816449d3858861d820a9004de4 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 087e2d7f23cd312fbe3951ba98b6556ff0047619..51b7d31772aff88853ca9a49564809a55dc8262d 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 86076960a4096d88825bc2d256660211c09e2cc6..5a8d5b226718dac320f8b82069ecfcb1ef42c0cd 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 a10bbbd746cb258c27be34fd7a5c4d62a15bef18..b4bf08006ebb31cdee59ba08441c0856b7aa4559 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 507bcf50d5fb3d85600e5c50bb66b728d38e57cd..efcc70c68560cbabb704feaedd71a29760e17b0c 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;
 
@@ -130,6 +153,27 @@ DefineIndex(RangeVar *heapRelation,
 						   get_namespace_name(namespaceId));
 	}
 
+	/*
+	 * 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;
@@ -553,6 +656,79 @@ GetDefaultOpClass(Oid attrType, Oid accessMethodId)
 	return InvalidOid;
 }
 
+/*
+ * 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 a6e3a93d349512634f89184c89ec2d4484c14472..a9c307b80c30f86ebc66e28bc9e9a4c3781caec5 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,340 +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;
-	int			i;
-	int			minattnum,
-				maxatts;
-	HeapTuple	typeTuple;
-	Form_pg_type tform;
-	int			attndims;
-	ObjectAddress myself,
-				referenced;
+	ATController(relation_open(relid, AccessExclusiveLock),
+				 cmds,
+				 recurse);
+}
 
-	/*
-	 * Grab an exclusive lock on the target table, which we will NOT
-	 * release until end of transaction.
-	 */
-	rel = heap_open(myrelid, AccessExclusiveLock);
+static void
+ATController(Relation rel, List *cmds, bool recurse)
+{
+	List	   *wqueue = NIL;
+	List	   *lcmd;
 
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
-						RelationGetRelationName(rel))));
+	/* 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);
 
 	/*
-	 * 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.
+	 * 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).
 	 */
-	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))));
+	cmd = copyObject(cmd);
 
 	/*
-	 * 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.
+	 * Do permissions checking, recursion to child tables if needed,
+	 * and any additional phase-1 processing needed.
 	 */
-	if (recurse)
+	switch (cmd->subtype)
 	{
-		List	   *child,
-				   *children;
-		ColumnDef  *colDefChild = copyObject(colDef);
+		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);
 
-		/* Child should see column as singly inherited */
-		colDefChild->inhcount = 1;
-		colDefChild->is_local = false;
+				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;
+	}
 
-		/* We only want direct inheritors */
-		children = find_inheritance_children(myrelid);
+	/* Add the subcommand to the appropriate list for phase 2 */
+	tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd);
+}
 
-		foreach(child, children)
+/*
+ * 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)
 		{
-			Oid			childrelid = lfirsto(child);
-			HeapTuple	tuple;
-			Form_pg_attribute childatt;
-			Relation	childrel;
+			AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+			List	   *subcmds = tab->subcmds[pass];
+			Relation	rel;
+			List	   *lcmd;
 
-			if (childrelid == myrelid)
+			if (subcmds == NIL)
 				continue;
 
-			childrel = heap_open(childrelid, AccessExclusiveLock);
+			/* Exclusive lock was obtained by phase 1, needn't get it again */
+			rel = relation_open(tab->relid, NoLock);
 
-			/* Does child already have a column by this name? */
-			attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
-			tuple = SearchSysCacheCopyAttName(childrelid, colDef->colname);
-			if (!HeapTupleIsValid(tuple))
+			foreach(lcmd, subcmds)
 			{
-				/* No, recurse to add it normally */
-				heap_close(attrdesc, RowExclusiveLock);
-				heap_close(childrel, NoLock);
-				AlterTableAddColumn(childrelid, true, colDefChild);
-				continue;
+				ATExecCmd(tab, rel, (AlterTableCmd *) lfirst(lcmd));
 			}
-			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\"",
-							get_rel_name(childrelid), colDef->colname)));
 
 			/*
-			 * XXX if we supported NOT NULL or defaults, would need to do
-			 * more work here to verify child matches
+			 * 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).
 			 */
-			ereport(NOTICE,
-					(errmsg("merging definition of column \"%s\" for child \"%s\"",
-							colDef->colname, get_rel_name(childrelid))));
+			if (pass == AT_PASS_ALTER_TYPE)
+				ATPostAlterTypeCleanup(wqueue, tab);
 
-			/* Bump the existing child att's inhcount */
-			childatt->attinhcount++;
-			simple_heap_update(attrdesc, &tuple->t_self, tuple);
-			CatalogUpdateIndexes(attrdesc, tuple);
+			relation_close(rel, NoLock);
+		}
+	}
 
-			/*
-			 * Propagate any new CHECK constraints into the child table
-			 * and its descendants
-			 */
-			if (colDef->constraints != NIL)
-			{
-				CommandCounterIncrement();
-				AlterTableAddConstraint(childrelid, true, colDef->constraints);
-			}
+	/*
+	 * 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);
 
-			heap_freetuple(tuple);
-			heap_close(attrdesc, RowExclusiveLock);
-			heap_close(childrel, NoLock);
+		if (tab->subcmds[AT_PASS_ADD_COL] ||
+			tab->subcmds[AT_PASS_ALTER_TYPE] ||
+			tab->subcmds[AT_PASS_COL_ATTRS])
+		{
+			AlterTableCreateToastTable(tab->relid, true);
 		}
 	}
-	else
+}
+
+/*
+ * ATExecCmd: dispatch a subcommand to appropriate execution routine
+ */
+static void
+ATExecCmd(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd)
+{
+	switch (cmd->subtype)
 	{
-		/*
-		 * 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)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				 errmsg("column must be added to child tables too")));
+		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;
 	}
 
 	/*
-	 * 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)).
+	 * Bump the command counter to ensure the next subcommand in the sequence
+	 * can see the changes so far
 	 */
-	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.")));
+	CommandCounterIncrement();
+}
 
-	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.")));
+/*
+ * ATRewriteTables: ALTER TABLE phase 3
+ */
+static void
+ATRewriteTables(List **wqueue)
+{
+	List	   *ltab;
 
-	pgclass = heap_openr(RelationRelationName, RowExclusiveLock);
+	/* Go through each table that needs to be checked or rewritten */
+	foreach(ltab, *wqueue)
+	{
+		AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-	reltup = SearchSysCache(RELOID,
-							ObjectIdGetDatum(myrelid),
-							0, 0, 0);
-	if (!HeapTupleIsValid(reltup))
-		elog(ERROR, "cache lookup failed for relation %u", myrelid);
+		/*
+		 * 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);
 
-	/*
-	 * this test is deliberately not attisdropped-aware, since if one
-	 * tries to add a column matching a dropped column name, it's gonna
-	 * fail anyway.
-	 */
-	if (SearchSysCacheExists(ATTNAME,
-							 ObjectIdGetDatum(myrelid),
-							 PointerGetDatum(colDef->colname),
-							 0, 0))
-		ereport(ERROR,
-				(errcode(ERRCODE_DUPLICATE_COLUMN),
-			 errmsg("column \"%s\" of relation \"%s\" already exists",
-					colDef->colname, RelationGetRelationName(rel))));
+			/*
+			 * 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);
 
-	minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts;
-	maxatts = minattnum + 1;
-	if (maxatts > MaxHeapAttributeNumber)
-		ereport(ERROR,
-				(errcode(ERRCODE_TOO_MANY_COLUMNS),
-				 errmsg("tables can have at most %d columns",
-						MaxHeapAttributeNumber)));
-	i = minattnum + 1;
+			OIDNewHeap = make_new_heap(tab->relid, NewHeapName);
 
-	attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
+			/*
+			 * 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);
 
-	if (colDef->typename->arrayBounds)
-		attndims = length(colDef->typename->arrayBounds);
-	else
-		attndims = 0;
+			/* Swap the relfilenodes of the old and new heaps. */
+			swap_relfilenodes(tab->relid, OIDNewHeap);
 
-	typeTuple = typenameType(colDef->typename);
-	tform = (Form_pg_type) GETSTRUCT(typeTuple);
+			CommandCounterIncrement();
 
-	/* make sure datatype is legal for a column */
-	CheckAttributeType(colDef->colname, HeapTupleGetOid(typeTuple));
+			/* Destroy new heap with old filenode */
+			object.classId = RelOid_pg_class;
+			object.objectId = OIDNewHeap;
+			object.objectSubId = 0;
 
-	attributeTuple = heap_addheader(Natts_pg_attribute,
-									false,
-									ATTRIBUTE_TUPLE_SIZE,
-									(void *) &attributeD);
+			/*
+			 * 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 */
 
-	attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple);
+			/*
+			 * 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);
+		}
+	}
 
-	attribute->attrelid = myrelid;
-	namestrcpy(&(attribute->attname), colDef->colname);
-	attribute->atttypid = HeapTupleGetOid(typeTuple);
-	attribute->attstattarget = -1;
-	attribute->attlen = tform->typlen;
-	attribute->attcacheoff = -1;
-	attribute->atttypmod = colDef->typename->typmod;
-	attribute->attnum = i;
-	attribute->attbyval = tform->typbyval;
-	attribute->attndims = attndims;
-	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->attisdropped = false;
-	attribute->attislocal = colDef->is_local;
-	attribute->attinhcount = colDef->inhcount;
+	/*
+	 * 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;
 
-	ReleaseSysCache(typeTuple);
+		foreach(lcon, tab->constraints)
+		{
+			NewConstraint *con = lfirst(lcon);
 
-	simple_heap_insert(attrdesc, attributeTuple);
+			if (con->contype == CONSTR_FOREIGN)
+			{
+				FkConstraint *fkconstraint = (FkConstraint *) con->qual;
+				Relation	refrel;
 
-	/* Update indexes on pg_attribute */
-	CatalogUpdateIndexes(attrdesc, attributeTuple);
+				if (rel == NULL)
+				{
+					/* Long since locked, no need for another */
+					rel = heap_open(tab->relid, NoLock);
+				}
 
-	heap_close(attrdesc, RowExclusiveLock);
+				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;
+	List	   *l;
+	EState	   *estate;
 
 	/*
-	 * Update number of attributes in pg_class tuple
+	 * Open the relation(s).  We have surely already locked the existing
+	 * table.
+	 */
+	oldrel = heap_open(tab->relid, NoLock);
+	oldTupDesc = tab->oldDesc;
+	newTupDesc = RelationGetDescr(oldrel); /* includes all mods */
+
+	if (OidIsValid(OIDNewHeap))
+		newrel = heap_open(OIDNewHeap, AccessExclusiveLock);
+	else
+		newrel = NULL;
+
+	/*
+	 * Generate the constraint and default execution states
 	 */
-	newreltup = heap_copytuple(reltup);
 
-	((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts;
+	estate = CreateExecutorState();
 
-	simple_heap_update(pgclass, &newreltup->t_self, newreltup);
+	/* Build the needed expression execution states */
+	foreach(l, tab->constraints)
+	{
+		NewConstraint *con = lfirst(l);
 
-	/* keep catalog indexes current */
-	CatalogUpdateIndexes(pgclass, newreltup);
+		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);
+		}
+	}
 
-	heap_freetuple(newreltup);
-	ReleaseSysCache(reltup);
+	foreach(l, tab->newvals)
+	{
+		NewColumnValue   *ex = lfirst(l);
 
-	heap_close(pgclass, RowExclusiveLock);
+		needscan = true;
 
-	heap_close(rel, NoLock);	/* close rel but keep lock! */
+		ex->exprstate = ExecPrepareExpr((Expr *) ex->expr, estate);
+	}
 
-	/*
-	 * Add datatype dependency for the new column.
-	 */
-	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 (needscan)
+	{
+		ExprContext	   *econtext;
+		Datum	   *values;
+		char	   *nulls;
+		TupleTableSlot *oldslot;
+		TupleTableSlot *newslot;
+		HeapScanDesc	scan;
+		HeapTuple	tuple;
 
-	/*
-	 * Make our catalog updates visible for subsequent steps.
-	 */
-	CommandCounterIncrement();
+		econtext = GetPerTupleExprContext(estate);
 
-	/*
-	 * Add any CHECK constraints attached to the new column.
-	 *
-	 * To do this we must re-open the rel so that its new attr list gets
-	 * loaded into the relcache.
-	 */
-	if (colDef->constraints != NIL)
+		/*
+		 * 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)
 	{
-		rel = heap_open(myrelid, AccessExclusiveLock);
-		AddRelationRawConstraints(rel, NIL, colDef->constraints);
-		heap_close(rel, NoLock);
+		tab = (AlteredTableInfo *) lfirst(ltab);
+		if (tab->relid == relid)
+			return tab;
 	}
 
 	/*
-	 * Automatically create the secondary relation for TOAST if it
-	 * formerly had no such but now has toastable attributes.
+	 * Not there, so add it.  Note that we make a copy of the relation's
+	 * existing descriptor before anything interesting can happen to it.
 	 */
-	AlterTableCreateToastTable(myrelid, true);
+	tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
+	tab->relid = relid;
+	tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel));
+
+	*wqueue = lappend(*wqueue, tab);
+
+	return tab;
 }
 
 /*
- * ALTER TABLE ALTER COLUMN DROP NOT NULL
+ * 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
  */
-void
-AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse,
-								 const char *colName)
+static void
+ATSimplePermissions(Relation rel, bool allowView)
 {
-	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))));
+	{
+		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(myrelid, GetUserId()))
+	if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
 		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
 					   RelationGetRelationName(rel));
 
@@ -1901,17 +2488,32 @@ AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse,
 				(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)
+{
 	/*
-	 * Propagate to children if desired
+	 * 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;
 
 		/* this routine is actually in the planner */
-		children = find_all_inheritors(myrelid);
+		children = find_all_inheritors(relid);
 
 		/*
 		 * find_all_inheritors does the recursive search of the
@@ -1921,292 +2523,472 @@ AlterTableAlterColumnDropNotNull(Oid myrelid, bool recurse,
 		foreach(child, children)
 		{
 			Oid			childrelid = lfirsto(child);
+			Relation	childrel;
 
-			if (childrelid == myrelid)
+			if (childrelid == relid)
 				continue;
-			AlterTableAlterColumnDropNotNull(childrelid,
-											 false, colName);
+			childrel = relation_open(childrelid, AccessExclusiveLock);
+			ATPrepCmd(wqueue, childrel, cmd, false, true);
+			relation_close(childrel, NoLock);
 		}
 	}
+}
 
-	/* now do the thing on this relation */
+/*
+ * 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;
 
-	/*
-	 * get the number of the attribute
-	 */
-	attnum = get_attnum(myrelid, colName);
-	if (attnum == InvalidAttrNumber)
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_COLUMN),
-			 errmsg("column \"%s\" of relation \"%s\" does not exist",
-					colName, RelationGetRelationName(rel))));
+	/* this routine is actually in the planner */
+	children = find_inheritance_children(relid);
 
-	/* Prevent them from altering a system attribute */
-	if (attnum < 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("cannot alter system column \"%s\"",
-						colName)));
+	foreach(child, children)
+	{
+		Oid			childrelid = lfirsto(child);
+		Relation	childrel;
+
+		childrel = relation_open(childrelid, AccessExclusiveLock);
+		ATPrepCmd(wqueue, childrel, cmd, true, true);
+		relation_close(childrel, NoLock);
+	}
+}
 
+/* 
+ * 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)
+{
 	/*
-	 * Check that the attribute is not in a primary key
+	 * 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;
 
-	/* Loop over all indexes on the relation */
-	indexoidlist = RelationGetIndexList(rel);
+		/* Child should see column as singly inherited */
+		colDefChild->inhcount = 1;
+		colDefChild->is_local = false;
 
-	foreach(indexoidscan, indexoidlist)
+		ATOneLevelRecursion(wqueue, rel, childCmd);
+	}
+	else
 	{
-		Oid			indexoid = lfirsto(indexoidscan);
-		HeapTuple	indexTuple;
-		Form_pg_index indexStruct;
-		int			i;
-
-		indexTuple = SearchSysCache(INDEXRELID,
-									ObjectIdGetDatum(indexoid),
-									0, 0, 0);
-		if (!HeapTupleIsValid(indexTuple))
-			elog(ERROR, "cache lookup failed for index %u", indexoid);
-		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
+		/*
+		 * 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(RelationGetRelid(rel)) != NIL)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+				 errmsg("column must be added to child tables too")));
+	}
+}
 
-		/* If the index is not a primary key, skip the check */
-		if (indexStruct->indisprimary)
-		{
-			/*
-			 * Loop over each attribute in the primary key and see if it
-			 * matches the to-be-altered attribute
-			 */
-			for (i = 0; i < indexStruct->indnatts; i++)
-			{
-				if (indexStruct->indkey[i] == attnum)
-					ereport(ERROR,
-							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-						   errmsg("column \"%s\" is in a primary key",
-								  colName)));
-			}
-		}
-
-		ReleaseSysCache(indexTuple);
-	}
+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;
 
-	freeList(indexoidlist);
+	attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
 
 	/*
-	 * Okay, actually perform the catalog change
+	 * Are we adding the column to a recursion child?  If so, check whether
+	 * to merge with an existing definition for the column.
 	 */
-	attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
+	if (colDef->inhcount > 0)
+	{
+		HeapTuple	tuple;
 
-	tuple = SearchSysCacheCopyAttName(myrelid, colName);
-	if (!HeapTupleIsValid(tuple))		/* shouldn't happen */
-		elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
-			 colName, myrelid);
+		/* 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);
 
-	((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
+			/* 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)));
 
-	simple_heap_update(attr_rel, &tuple->t_self, tuple);
+			/* Bump the existing child att's inhcount */
+			childatt->attinhcount++;
+			simple_heap_update(attrdesc, &tuple->t_self, tuple);
+			CatalogUpdateIndexes(attrdesc, tuple);
 
-	/* keep the system catalog indexes current */
-	CatalogUpdateIndexes(attr_rel, tuple);
+			heap_freetuple(tuple);
 
-	heap_close(attr_rel, RowExclusiveLock);
+			/* Inform the user about the merge */
+			ereport(NOTICE,
+					(errmsg("merging definition of column \"%s\" for child \"%s\"",
+							colDef->colname, RelationGetRelationName(rel))));
 
-	heap_close(rel, NoLock);
-}
+			heap_close(attrdesc, RowExclusiveLock);
+			return;
+		}
+	}
 
-/*
- * ALTER TABLE ALTER COLUMN SET NOT NULL
- */
-void
-AlterTableAlterColumnSetNotNull(Oid myrelid, bool recurse,
-								const char *colName)
-{
-	Relation	rel;
-	HeapTuple	tuple;
-	AttrNumber	attnum;
-	Relation	attr_rel;
-	HeapScanDesc scan;
-	TupleDesc	tupdesc;
+	pgclass = heap_openr(RelationRelationName, RowExclusiveLock);
 
-	rel = heap_open(myrelid, AccessExclusiveLock);
+	reltup = SearchSysCacheCopy(RELOID,
+								ObjectIdGetDatum(myrelid),
+								0, 0, 0);
+	if (!HeapTupleIsValid(reltup))
+		elog(ERROR, "cache lookup failed for relation %u", myrelid);
 
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	/*
+	 * this test is deliberately not attisdropped-aware, since if one
+	 * tries to add a column matching a dropped column name, it's gonna
+	 * fail anyway.
+	 */
+	if (SearchSysCacheExists(ATTNAME,
+							 ObjectIdGetDatum(myrelid),
+							 PointerGetDatum(colDef->colname),
+							 0, 0))
 		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));
+				(errcode(ERRCODE_DUPLICATE_COLUMN),
+			 errmsg("column \"%s\" of relation \"%s\" already exists",
+					colDef->colname, RelationGetRelationName(rel))));
 
-	if (!allowSystemTableMods && IsSystemRelation(rel))
+	minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts;
+	maxatts = minattnum + 1;
+	if (maxatts > MaxHeapAttributeNumber)
 		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("permission denied: \"%s\" is a system catalog",
-						RelationGetRelationName(rel))));
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("tables can have at most %d columns",
+						MaxHeapAttributeNumber)));
+	i = minattnum + 1;
 
-	/*
-	 * Propagate to children if desired
-	 */
-	if (recurse)
-	{
-		List	   *child,
-				   *children;
+	typeTuple = typenameType(colDef->typename);
+	tform = (Form_pg_type) GETSTRUCT(typeTuple);
 
-		/* this routine is actually in the planner */
-		children = find_all_inheritors(myrelid);
+	/* make sure datatype is legal for a column */
+	CheckAttributeType(colDef->colname, HeapTupleGetOid(typeTuple));
 
-		/*
-		 * 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);
+	attributeTuple = heap_addheader(Natts_pg_attribute,
+									false,
+									ATTRIBUTE_TUPLE_SIZE,
+									(void *) &attributeD);
 
-			if (childrelid == myrelid)
-				continue;
-			AlterTableAlterColumnSetNotNull(childrelid,
-											false, colName);
-		}
-	}
+	attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple);
 
-	/* now do the thing on this relation */
+	attribute->attrelid = myrelid;
+	namestrcpy(&(attribute->attname), colDef->colname);
+	attribute->atttypid = HeapTupleGetOid(typeTuple);
+	attribute->attstattarget = -1;
+	attribute->attlen = tform->typlen;
+	attribute->attcacheoff = -1;
+	attribute->atttypmod = colDef->typename->typmod;
+	attribute->attnum = i;
+	attribute->attbyval = tform->typbyval;
+	attribute->attndims = length(colDef->typename->arrayBounds);
+	attribute->attstorage = tform->typstorage;
+	attribute->attalign = tform->typalign;
+	attribute->attnotnull = colDef->is_not_null;
+	attribute->atthasdef = false;
+	attribute->attisdropped = false;
+	attribute->attislocal = colDef->is_local;
+	attribute->attinhcount = colDef->inhcount;
 
-	/*
-	 * get the number of the attribute
-	 */
-	attnum = get_attnum(myrelid, colName);
-	if (attnum == InvalidAttrNumber)
-		ereport(ERROR,
-				(errcode(ERRCODE_UNDEFINED_COLUMN),
-			 errmsg("column \"%s\" of relation \"%s\" does not exist",
-					colName, RelationGetRelationName(rel))));
+	ReleaseSysCache(typeTuple);
 
-	/* Prevent them from altering a system attribute */
-	if (attnum < 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("cannot alter system column \"%s\"",
-						colName)));
+	simple_heap_insert(attrdesc, attributeTuple);
+
+	/* Update indexes on pg_attribute */
+	CatalogUpdateIndexes(attrdesc, attributeTuple);
+
+	heap_close(attrdesc, RowExclusiveLock);
 
 	/*
-	 * Perform a scan to ensure that there are no NULL values already in
-	 * the relation
+	 * Update number of attributes in pg_class tuple
 	 */
-	tupdesc = RelationGetDescr(rel);
+	((Form_pg_class) GETSTRUCT(reltup))->relnatts = maxatts;
 
-	scan = heap_beginscan(rel, SnapshotNow, 0, NULL);
+	simple_heap_update(pgclass, &reltup->t_self, reltup);
 
-	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
-	{
-		Datum		d;
-		bool		isnull;
+	/* keep catalog indexes current */
+	CatalogUpdateIndexes(pgclass, reltup);
 
-		d = heap_getattr(tuple, attnum, tupdesc, &isnull);
+	heap_freetuple(reltup);
 
-		if (isnull)
-			ereport(ERROR,
-					(errcode(ERRCODE_NOT_NULL_VIOLATION),
-					 errmsg("column \"%s\" contains null values",
-							colName)));
-	}
+	heap_close(pgclass, RowExclusiveLock);
 
-	heap_endscan(scan);
+	/* Make the attribute's catalog entry visible */
+	CommandCounterIncrement();
 
 	/*
-	 * Okay, actually perform the catalog change
+	 * Store the DEFAULT, if any, in the catalogs
 	 */
-	attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
+	if (colDef->raw_default)
+	{
+		RawColumnDefault *rawEnt;
 
-	tuple = SearchSysCacheCopyAttName(myrelid, colName);
-	if (!HeapTupleIsValid(tuple))		/* shouldn't happen */
-		elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
-			 colName, myrelid);
+		rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
+		rawEnt->attnum = attribute->attnum;
+		rawEnt->raw_default = copyObject(colDef->raw_default);
 
-	((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
+		/*
+		 * This function is intended for CREATE TABLE, so it processes a
+		 * _list_ of defaults, but we just do one.
+		 */
+		AddRelationRawConstraints(rel, makeList1(rawEnt), NIL);
 
-	simple_heap_update(attr_rel, &tuple->t_self, tuple);
+		/* Make the additional catalog changes visible */
+		CommandCounterIncrement();
+	}
 
-	/* keep the system catalog indexes current */
-	CatalogUpdateIndexes(attr_rel, tuple);
+	/*
+	 * 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.
+	 *
+	 * 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.
+	 */
+	defval = (Expr *) build_column_default(rel, attribute->attnum);
+	if (defval)
+	{
+		NewColumnValue   *newval;
 
-	heap_close(attr_rel, RowExclusiveLock);
+		newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
+		newval->attnum = attribute->attnum;
+		newval->expr = defval;
 
-	heap_close(rel, NoLock);
+		tab->newvals = lappend(tab->newvals, newval);
+	}
+
+	/*
+	 * Add datatype dependency for the new column.
+	 */
+	add_column_datatype_dependency(myrelid, i, attribute->atttypid);
 }
 
 /*
- * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
+ * Install a column's dependency on its datatype.
  */
-void
-AlterTableAlterColumnDefault(Oid myrelid, bool recurse,
-							 const char *colName,
-							 Node *newDefault)
+static void
+add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid)
 {
-	Relation	rel;
-	AttrNumber	attnum;
+	ObjectAddress myself,
+				referenced;
 
-	rel = heap_open(myrelid, AccessExclusiveLock);
+	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
+ */
+static void
+ATExecDropNotNull(Relation rel, const char *colName)
+{
+	HeapTuple	tuple;
+	AttrNumber	attnum;
+	Relation	attr_rel;
+	List	   *indexoidlist;
+	List	   *indexoidscan;
 
 	/*
-	 * 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.
+	 * lookup the attribute
 	 */
-	if (rel->rd_rel->relkind != RELKIND_RELATION &&
-		rel->rd_rel->relkind != RELKIND_VIEW)
+	attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);
+
+	if (!HeapTupleIsValid(tuple))
 		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table or view",
-						RelationGetRelationName(rel))));
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("column \"%s\" of relation \"%s\" does not exist",
+						colName, RelationGetRelationName(rel))));
 
-	/* Permissions checks */
-	if (!pg_class_ownercheck(myrelid, GetUserId()))
-		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
-					   RelationGetRelationName(rel));
+	attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
 
-	if (!allowSystemTableMods && IsSystemRelation(rel))
+	/* Prevent them from altering a system attribute */
+	if (attnum <= 0)
 		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("permission denied: \"%s\" is a system catalog",
-						RelationGetRelationName(rel))));
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot alter system column \"%s\"",
+						colName)));
 
 	/*
-	 * Propagate to children if desired
+	 * Check that the attribute is not in a primary key
 	 */
-	if (recurse)
-	{
-		List	   *child,
-				   *children;
 
-		/* this routine is actually in the planner */
-		children = find_all_inheritors(myrelid);
+	/* Loop over all indexes on the relation */
+	indexoidlist = RelationGetIndexList(rel);
 
-		/*
-		 * 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);
+	foreach(indexoidscan, indexoidlist)
+	{
+		Oid			indexoid = lfirsto(indexoidscan);
+		HeapTuple	indexTuple;
+		Form_pg_index indexStruct;
+		int			i;
 
-			if (childrelid == myrelid)
-				continue;
-			AlterTableAlterColumnDefault(childrelid,
-										 false, colName, newDefault);
+		indexTuple = SearchSysCache(INDEXRELID,
+									ObjectIdGetDatum(indexoid),
+									0, 0, 0);
+		if (!HeapTupleIsValid(indexTuple))
+			elog(ERROR, "cache lookup failed for index %u", indexoid);
+		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
+
+		/* If the index is not a primary key, skip the check */
+		if (indexStruct->indisprimary)
+		{
+			/*
+			 * Loop over each attribute in the primary key and see if it
+			 * matches the to-be-altered attribute
+			 */
+			for (i = 0; i < indexStruct->indnatts; i++)
+			{
+				if (indexStruct->indkey[i] == attnum)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						   errmsg("column \"%s\" is in a primary key",
+								  colName)));
+			}
 		}
+
+		ReleaseSysCache(indexTuple);
+	}
+
+	freeList(indexoidlist);
+
+	/*
+	 * Okay, actually perform the catalog change ... if needed
+	 */
+	if (((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
+	{
+		((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
+
+		simple_heap_update(attr_rel, &tuple->t_self, tuple);
+
+		/* keep the system catalog indexes current */
+		CatalogUpdateIndexes(attr_rel, tuple);
+	}
+
+	heap_close(attr_rel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE ALTER COLUMN SET NOT NULL
+ */
+static void
+ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
+				 const char *colName)
+{
+	HeapTuple	tuple;
+	AttrNumber	attnum;
+	Relation	attr_rel;
+	NewConstraint *newcon;
+
+	/*
+	 * lookup the attribute
+	 */
+	attr_rel = 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))));
+
+	attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
+
+	/* Prevent them from altering a system attribute */
+	if (attnum <= 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot alter system column \"%s\"",
+						colName)));
+
+	/*
+	 * Okay, actually perform the catalog change ... if needed
+	 */
+	if (! ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
+	{
+		((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);
+
+		/* Tell Phase 3 to test the constraint */
+		newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
+		newcon->contype = CONSTR_NOTNULL;
+		newcon->attnum = attnum;
+		newcon->name = "NOT NULL";
+
+		tab->constraints = lappend(tab->constraints, newcon);
 	}
 
-	/* now do the thing on this relation */
+	heap_close(attr_rel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
+ */
+static void
+ATExecColumnDefault(Relation rel, const char *colName,
+					Node *newDefault)
+{
+	AttrNumber	attnum;
 
 	/*
 	 * 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,18 +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.
+	 * 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 && !recursing)
+	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)
@@ -2554,71 +3267,50 @@ AlterTableDropColumn(Oid myrelid, bool recurse, bool recursing,
 			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
-	 * 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)
-	{
-		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;
-
-			if (childrelid == myrelid)
-				continue;
-
-			childrel = heap_open(childrelid, AccessExclusiveLock);
+			if (recurse)
+			{
+				/*
+				 * 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--;
 
-			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);
+					simple_heap_update(attr_rel, &tuple->t_self, tuple);
 
-			if (childatt->attinhcount <= 0)		/* shouldn't happen */
-				elog(ERROR, "relation %u has non-inherited attribute \"%s\"",
-					 childrelid, colName);
+					/* keep the system catalog indexes current */
+					CatalogUpdateIndexes(attr_rel, tuple);
 
-			if (childatt->attinhcount == 1 && !childatt->attislocal)
-			{
-				/* Time to delete this child column, too */
-				AlterTableDropColumn(childrelid, true, true, colName, behavior);
+					/* 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)
-	{
-		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;
-			AlterTableAddConstraint(childrelid, false, newConstraints);
-		}
-	}
-
-	foreach(listptr, newConstraints)
+	switch (nodeTag(newConstraint))
 	{
-		/*
-		 * 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:
 		{
-			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);
+			Constraint *constr = (Constraint *) newConstraint;
 
-					/*
-					 * 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.
@@ -3554,268 +4108,800 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
 				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 				 errmsg("number of referencing and referenced columns for foreign key disagree")));
 
-	while (fk_attr != NIL)
+	while (fk_attr != NIL)
+	{
+		fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
+		fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
+		fk_attr = lnext(fk_attr);
+		pk_attr = lnext(pk_attr);
+	}
+
+	trigobj.objectId = CreateTrigger(fk_trigger, true);
+
+	/* Register dependency from trigger to constraint */
+	recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+
+	/*
+	 * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
+	 * DELETE action on the referenced table.
+	 */
+	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->trigname = fkconstraint->constr_name;
+	fk_trigger->relation = fkconstraint->pktable;
+	fk_trigger->before = false;
+	fk_trigger->row = true;
+	fk_trigger->actions[0] = 'd';
+	fk_trigger->actions[1] = '\0';
+
+	fk_trigger->isconstraint = true;
+	fk_trigger->deferrable = fkconstraint->deferrable;
+	fk_trigger->initdeferred = fkconstraint->initdeferred;
+	fk_trigger->constrrel = myRel;
+	switch (fkconstraint->fk_del_action)
+	{
+		case FKCONSTR_ACTION_NOACTION:
+			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
+			break;
+		case FKCONSTR_ACTION_RESTRICT:
+			fk_trigger->deferrable = false;
+			fk_trigger->initdeferred = false;
+			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
+			break;
+		case FKCONSTR_ACTION_CASCADE:
+			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
+			break;
+		case FKCONSTR_ACTION_SETNULL:
+			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
+			break;
+		case FKCONSTR_ACTION_SETDEFAULT:
+			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
+			break;
+		default:
+			elog(ERROR, "unrecognized FK action type: %d",
+				 (int) fkconstraint->fk_del_action);
+			break;
+	}
+
+	fk_trigger->args = NIL;
+	fk_trigger->args = lappend(fk_trigger->args,
+							   makeString(fkconstraint->constr_name));
+	fk_trigger->args = lappend(fk_trigger->args,
+							   makeString(myRel->relname));
+	fk_trigger->args = lappend(fk_trigger->args,
+							 makeString(fkconstraint->pktable->relname));
+	fk_trigger->args = lappend(fk_trigger->args,
+			makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
+	fk_attr = fkconstraint->fk_attrs;
+	pk_attr = fkconstraint->pk_attrs;
+	while (fk_attr != NIL)
+	{
+		fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
+		fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
+		fk_attr = lnext(fk_attr);
+		pk_attr = lnext(pk_attr);
+	}
+
+	trigobj.objectId = CreateTrigger(fk_trigger, true);
+
+	/* Register dependency from trigger to constraint */
+	recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
+
+	/* Make changes-so-far visible */
+	CommandCounterIncrement();
+
+	/*
+	 * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
+	 * UPDATE action on the referenced table.
+	 */
+	fk_trigger = makeNode(CreateTrigStmt);
+	fk_trigger->trigname = fkconstraint->constr_name;
+	fk_trigger->relation = fkconstraint->pktable;
+	fk_trigger->before = false;
+	fk_trigger->row = true;
+	fk_trigger->actions[0] = 'u';
+	fk_trigger->actions[1] = '\0';
+	fk_trigger->isconstraint = true;
+	fk_trigger->deferrable = fkconstraint->deferrable;
+	fk_trigger->initdeferred = fkconstraint->initdeferred;
+	fk_trigger->constrrel = myRel;
+	switch (fkconstraint->fk_upd_action)
+	{
+		case FKCONSTR_ACTION_NOACTION:
+			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
+			break;
+		case FKCONSTR_ACTION_RESTRICT:
+			fk_trigger->deferrable = false;
+			fk_trigger->initdeferred = false;
+			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
+			break;
+		case FKCONSTR_ACTION_CASCADE:
+			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
+			break;
+		case FKCONSTR_ACTION_SETNULL:
+			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
+			break;
+		case FKCONSTR_ACTION_SETDEFAULT:
+			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
+			break;
+		default:
+			elog(ERROR, "unrecognized FK action type: %d",
+				 (int) fkconstraint->fk_upd_action);
+			break;
+	}
+
+	fk_trigger->args = NIL;
+	fk_trigger->args = lappend(fk_trigger->args,
+							   makeString(fkconstraint->constr_name));
+	fk_trigger->args = lappend(fk_trigger->args,
+							   makeString(myRel->relname));
+	fk_trigger->args = lappend(fk_trigger->args,
+							 makeString(fkconstraint->pktable->relname));
+	fk_trigger->args = lappend(fk_trigger->args,
+			makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
+	fk_attr = fkconstraint->fk_attrs;
+	pk_attr = fkconstraint->pk_attrs;
+	while (fk_attr != NIL)
+	{
+		fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
+		fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
+		fk_attr = lnext(fk_attr);
+		pk_attr = lnext(pk_attr);
+	}
+
+	trigobj.objectId = CreateTrigger(fk_trigger, true);
+
+	/* Register dependency from trigger to constraint */
+	recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
+}
+
+/*
+ * fkMatchTypeToString -
+ *	  convert FKCONSTR_MATCH_xxx code to string to use in trigger args
+ */
+static char *
+fkMatchTypeToString(char match_type)
+{
+	switch (match_type)
+	{
+		case FKCONSTR_MATCH_FULL:
+			return pstrdup("FULL");
+		case FKCONSTR_MATCH_PARTIAL:
+			return pstrdup("PARTIAL");
+		case FKCONSTR_MATCH_UNSPECIFIED:
+			return pstrdup("UNSPECIFIED");
+		default:
+			elog(ERROR, "unrecognized match type: %d",
+				 (int) match_type);
+	}
+	return NULL;				/* can't get here */
+}
+
+/*
+ * ALTER TABLE DROP CONSTRAINT
+ */
+static void
+ATPrepDropConstraint(List **wqueue, Relation rel,
+					 bool recurse, AlterTableCmd *cmd)
+{
+	/*
+	 * We don't want errors or noise from child tables, so we have to pass
+	 * down a modified command.
+	 */
+	if (recurse)
+	{
+		AlterTableCmd *childCmd = copyObject(cmd);
+
+		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_UNDEFINED_COLUMN),
+				 errmsg("column \"%s\" of relation \"%s\" does not exist",
+						colName, RelationGetRelationName(rel))));
+	attTup = (Form_pg_attribute) GETSTRUCT(tuple);
+	attnum = attTup->attnum;
+
+	/* Can't alter a system attribute */
+	if (attnum <= 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot alter system column \"%s\"",
+						colName)));
+
+	/* Don't alter inherited columns */
+	if (attTup->attinhcount > 0 && !recursing)
+		ereport(ERROR,
+				(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);
+
+	/*
+	 * 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)
+	{
+		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;
+
+	/*
+	 * 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;
+
+		/* 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))
+		{
+			case OCLASS_CLASS:
+			{
+				char		relKind = get_rel_relkind(foundObject.objectId);
+
+				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 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.
+	 */
+	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)))
 	{
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-		fk_attr = lnext(fk_attr);
-		pk_attr = lnext(pk_attr);
-	}
+		Form_pg_depend	foundDep = (Form_pg_depend) GETSTRUCT(depTup);
 
-	trigobj.objectId = CreateTrigger(fk_trigger, true);
+		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");
 
-	/* Register dependency from trigger to constraint */
-	recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
+		simple_heap_delete(depRel, &depTup->t_self);
+	}
 
-	/* Make changes-so-far visible */
-	CommandCounterIncrement();
+	systable_endscan(scan);
+
+	heap_close(depRel, RowExclusiveLock);
 
 	/*
-	 * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
-	 * DELETE action on the referenced table.
+	 * Here we go --- change the recorded column type.  (Note heapTup is
+	 * a copy of the syscache entry, so okay to scribble on.)
 	 */
-	fk_trigger = makeNode(CreateTrigStmt);
-	fk_trigger->trigname = fkconstraint->constr_name;
-	fk_trigger->relation = fkconstraint->pktable;
-	fk_trigger->before = false;
-	fk_trigger->row = true;
-	fk_trigger->actions[0] = 'd';
-	fk_trigger->actions[1] = '\0';
+	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;
 
-	fk_trigger->isconstraint = true;
-	fk_trigger->deferrable = fkconstraint->deferrable;
-	fk_trigger->initdeferred = fkconstraint->initdeferred;
-	fk_trigger->constrrel = myRel;
-	switch (fkconstraint->fk_del_action)
-	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_del_action);
-			break;
-	}
+	ReleaseSysCache(typeTuple);
 
-	fk_trigger->args = NIL;
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(fkconstraint->constr_name));
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(myRel->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-							 makeString(fkconstraint->pktable->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-			makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-	fk_attr = fkconstraint->fk_attrs;
-	pk_attr = fkconstraint->pk_attrs;
-	while (fk_attr != NIL)
-	{
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-		fk_attr = lnext(fk_attr);
-		pk_attr = lnext(pk_attr);
-	}
+	simple_heap_update(attrelation, &heapTup->t_self, heapTup);
 
-	trigobj.objectId = CreateTrigger(fk_trigger, true);
+	/* keep system catalog indexes current */
+	CatalogUpdateIndexes(attrelation, heapTup);
 
-	/* Register dependency from trigger to constraint */
-	recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
+	heap_close(attrelation, RowExclusiveLock);
 
-	/* Make changes-so-far visible */
-	CommandCounterIncrement();
+	/* 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);
 
 	/*
-	 * Build and execute a CREATE CONSTRAINT TRIGGER statement for the ON
-	 * UPDATE action on the referenced table.
+	 * 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.)
 	 */
-	fk_trigger = makeNode(CreateTrigStmt);
-	fk_trigger->trigname = fkconstraint->constr_name;
-	fk_trigger->relation = fkconstraint->pktable;
-	fk_trigger->before = false;
-	fk_trigger->row = true;
-	fk_trigger->actions[0] = 'u';
-	fk_trigger->actions[1] = '\0';
-	fk_trigger->isconstraint = true;
-	fk_trigger->deferrable = fkconstraint->deferrable;
-	fk_trigger->initdeferred = fkconstraint->initdeferred;
-	fk_trigger->constrrel = myRel;
-	switch (fkconstraint->fk_upd_action)
+	if (defaultexpr)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_upd_action);
-			break;
-	}
+		/* Must make new row visible since it will be updated again */
+		CommandCounterIncrement();
 
-	fk_trigger->args = NIL;
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(fkconstraint->constr_name));
-	fk_trigger->args = lappend(fk_trigger->args,
-							   makeString(myRel->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-							 makeString(fkconstraint->pktable->relname));
-	fk_trigger->args = lappend(fk_trigger->args,
-			makeString(fkMatchTypeToString(fkconstraint->fk_matchtype)));
-	fk_attr = fkconstraint->fk_attrs;
-	pk_attr = fkconstraint->pk_attrs;
-	while (fk_attr != NIL)
-	{
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(fk_attr));
-		fk_trigger->args = lappend(fk_trigger->args, lfirst(pk_attr));
-		fk_attr = lnext(fk_attr);
-		pk_attr = lnext(pk_attr);
-	}
+		/*
+		 * 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);
 
-	trigobj.objectId = CreateTrigger(fk_trigger, true);
+		StoreAttrDefault(rel, attnum, nodeToString(defaultexpr));
+	}
 
-	/* Register dependency from trigger to constraint */
-	recordDependencyOn(&trigobj, &constrobj, DEPENDENCY_INTERNAL);
+	/* Cleanup */
+	heap_freetuple(heapTup);
 }
 
 /*
- * fkMatchTypeToString -
- *	  convert FKCONSTR_MATCH_xxx code to string to use in trigger args
+ * 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 char *
-fkMatchTypeToString(char match_type)
+static void
+ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab)
 {
-	switch (match_type)
+	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)
 	{
-		case FKCONSTR_MATCH_FULL:
-			return pstrdup("FULL");
-		case FKCONSTR_MATCH_PARTIAL:
-			return pstrdup("PARTIAL");
-		case FKCONSTR_MATCH_UNSPECIFIED:
-			return pstrdup("UNSPECIFIED");
-		default:
-			elog(ERROR, "unrecognized match type: %d",
-				 (int) match_type);
+		ATPostAlterTypeParse((char *) lfirst(l), wqueue);
+	}
+	foreach(l, tab->changedConstraintDefs)
+	{
+		ATPostAlterTypeParse((char *) lfirst(l), wqueue);
 	}
-	return NULL;				/* can't get here */
-}
-
-/*
- * ALTER TABLE DROP CONSTRAINT
- */
-void
-AlterTableDropConstraint(Oid myrelid, bool recurse,
-						 const char *constrName,
-						 DropBehavior behavior)
-{
-	Relation	rel;
-	int			deleted = 0;
 
 	/*
-	 * Acquire an exclusive lock on the target relation for the duration
-	 * of the operation.
+	 * 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.
 	 */
-	rel = heap_open(myrelid, AccessExclusiveLock);
+	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);
+	}
 
-	/* Disallow DROP CONSTRAINT on views, indexes, sequences, etc */
-	if (rel->rd_rel->relkind != RELKIND_RELATION)
-		ereport(ERROR,
-				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-				 errmsg("\"%s\" is not a table",
-						RelationGetRelationName(rel))));
+	obj.classId = RelOid_pg_class;
+	foreach(l, tab->changedIndexOids)
+	{
+		obj.objectId = lfirsto(l);
+		obj.objectSubId = 0;
+		performDeletion(&obj, DROP_RESTRICT);
+	}
 
-	/* Permissions checks */
-	if (!pg_class_ownercheck(myrelid, GetUserId()))
-		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
-					   RelationGetRelationName(rel));
+	/*
+	 * The objects will get recreated during subsequent passes over the
+	 * work queue.
+	 */
+}
 
-	if (!allowSystemTableMods && IsSystemRelation(rel))
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("permission denied: \"%s\" is a system catalog",
-						RelationGetRelationName(rel))));
+static void
+ATPostAlterTypeParse(char *cmd, List **wqueue)
+{
+	List	   *raw_parsetree_list;
+	List	   *querytree_list;
+	List	   *list_item;
 
 	/*
-	 * Process child tables if requested.
+	 * We expect that we only have to do raw parsing and parse analysis, not
+	 * any rule rewriting, since these will all be utility statements.
 	 */
-	if (recurse)
+	raw_parsetree_list = raw_parser(cmd);
+	querytree_list = NIL;
+	foreach(list_item, raw_parsetree_list)
 	{
-		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);
-			Relation	inhrel;
+		Node	   *parsetree = (Node *) lfirst(list_item);
 
-			if (childrelid == myrelid)
-				continue;
-			inhrel = heap_open(childrelid, AccessExclusiveLock);
-			/* do NOT count child constraints in deleted. */
-			RemoveRelConstraints(inhrel, constrName, behavior);
-			heap_close(inhrel, NoLock);
-		}
+		querytree_list = nconc(querytree_list,
+							   parse_analyze(parsetree, NULL, 0));
 	}
 
 	/*
-	 * Now do the thing on this relation.
+	 * Attach each generated command to the proper place in the work queue.
+	 * Note this could result in creation of entirely new work-queue entries.
 	 */
-	deleted += RemoveRelConstraints(rel, constrName, behavior);
+	foreach(list_item, querytree_list)
+	{
+		Query	   *query = (Query *) lfirst(list_item);
+		Relation	rel;
+		AlteredTableInfo *tab;
 
-	/* Close the target relation */
-	heap_close(rel, NoLock);
+		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;
 
-	/* 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)));
+				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 c7d6193280ed58ca17d02dcc853b5bbf0da0affb..1466be98cbb4d65efe71af0c1a4409a181bda26f 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 900d98dc8c0f8f88d4ff52b7e6029784a54a00d0..eab30d122c2b1cf7f2cac86a3ecd4d8fcbf94ff1 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 2330bf18d43d8d34e4867813f87f237c14a1ddd3..4a251b63de4666c1155ab6dcb81b85e273f233d2 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;
 }
 
@@ -2946,51 +2924,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
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index de8597ed9efaa9068359c0ca8d033bae0cf80e55..64825893bc10b5a40f20da93a1018251236ff461 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 98c63fd0e64a6583a20340507247229eee025d74..68e9f84672549231fd3f258cf39b142abd9ff3e3 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 3960152f3876cdd97ea31eb62132ea04f30a692b..412aa252d62496a818ea53bbe629e38c098e47d1 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 583851c7493a14c7241bee9e697ac5a8cb5f4e9a..0e230d7e8628f89a5fdb8aa370f255ee33cf3ed0 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 f635ef1bd09d345e6cd33bd69be5fa3c3188f6ba..2913d53e521af0c470705dc2cbc7f19c27ae24ff 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 da210c483aea9adb70dc6e8a5ddc687b66b54865..13c367c9022845c36ccbfc8d26e6ff32cfc5cb84 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 72295803abbb7bcf730e0a877b673983d3d12c64..5474fdd54908cd85446a14c69d802937d36c936e 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 00f5fa1a4801c23e8a72d0791da80f3fc2d5e236..78fe4ab9071a0da97a6ee919286b7d31c4389001 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 889b4dfa626bd5bf9ce709a83f750c2144bd0c85..f9f03c1bd0349204118803c0dffa88e8bcfc23f8 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 d5d4f0832a7b8e157fbe5ab2fb1924f513415ab2..a776607ec660ab56fe7025c6adda76ae3efcb556 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 ab505939bc3b3a1fc79b81668e65465c1424ab6f..8eceb6e59cba3e9db547d4e4ff6831fb11e1593c 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 e3cf88b4fd76cac1c528afa096b97e282b7b7f42..76c77025ece1a874a091891558b78ecb4d43b3d3 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 02e6cbca49d814072c598952548ef3f4fb1207f1..59fb0a9a8533bf8eed49d6b3435838737ed1f5b4 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 089651c7278b0c35f75884e617ef6e5aabe67551..feb08520326a210268a54271accfe0e57a0bfa78 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 bd4bccfc9d460c22d35fea5bfde828d1d75353fa..5aae3720d3064d20d2e917d65d70bbfbd4c67717 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 f1890595ea041cbd1e931c3859594ecef86f3be4..e1205a9b001eea69868b4fdacf47dac4df118f86 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 dcd968a0b6560eb719aa7d8f9e5c1b5501eeb536..6077788583cf5c61ae1ac98c33e33cb6016afe44 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 34fb787680d8ead35ffa30a1f3b056b7358dd560..ad1274c7f8464ae98131364bb9fe9fc602b2c50e 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 57f18673bfa92a53491ad56e17575a2874775698..7bfe6cb7f2e7a0846c2e7f71325f0bf09a0c15dc 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;