Move renametrig() from tablecmds.c to trigger.c --- if we're going to
authorTom Lane
Fri, 26 Apr 2002 19:29:47 +0000 (19:29 +0000)
committerTom Lane
Fri, 26 Apr 2002 19:29:47 +0000 (19:29 +0000)
divide backend/commands by object type, let's try to pay at least
minimal attention to respecting that structure, eh?  Also reorder the
contents of tablecmds.c; it seems odd to me to put ALTER commands before
creation/deletion commands.

src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/commands/user.c
src/include/commands/tablecmds.h
src/include/commands/trigger.h

index e8b0d8bc013b3b527394e080a70069e220064d3c..7bdc238e77c635eed006053165daf3e166c9191f 100644 (file)
@@ -1,14 +1,14 @@
 /*-------------------------------------------------------------------------
  *
  * tablecmds.c
- *   Commands for altering table structures and settings
+ *   Commands for creating and altering table structures and settings
  *
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.9 2002/04/24 02:48:54 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.10 2002/04/26 19:29:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "utils/relcache.h"
 
 
-static void drop_default(Oid relid, int16 attnum);
-static bool needs_toast_table(Relation rel);
-static void CheckTupleType(Form_pg_class tuple_class);
-
+static List *MergeDomainAttributes(List *schema);
 static List *MergeAttributes(List *schema, List *supers, bool istemp,
                List **supOids, List **supconstr, bool *supHasOids);
 static bool change_varattnos_of_a_node(Node *node, const AttrNumber *newattno);
 static void StoreCatalogInheritance(Oid relationId, List *supers);
 static int findAttrByName(const char *attributeName, List *schema);
 static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass);
-static List *MergeDomainAttributes(List *schema);
+static void drop_default(Oid relid, int16 attnum);
+static void CheckTupleType(Form_pg_class tuple_class);
+static bool needs_toast_table(Relation rel);
 
 /* Used by attribute and relation renaming routines: */
 
@@ -74,902 +73,955 @@ static void update_ri_trigger_args(Oid relid,
                       bool update_relname);
 
 
-/* ----------------
- *     AlterTableAddColumn
- *     (formerly known as PerformAddAttribute)
- *
- *     adds an additional attribute to a relation
- *
- *     Adds attribute field(s) to a relation.  Each new attribute
- *     is given attnums in sequential order and is added to the
- *     ATTRIBUTE relation.  If the AMI fails, defunct tuples will
- *     remain in the ATTRIBUTE relation for later vacuuming.
- *     Later, there may be some reserved attribute names???
- *
- *     (If needed, can instead use elog to handle exceptions.)
- *
- *     Note:
- *             Initial idea of ordering the tuple attributes so that all
- *     the variable length domains occured last was scratched.  Doing
- *     so would not speed access too much (in general) and would create
- *     many complications in formtuple, heap_getattr, and addattribute.
+/* ----------------------------------------------------------------
+ *     DefineRelation
+ *             Creates a new relation.
  *
- *     scan attribute catalog for name conflict (within rel)
- *     scan type catalog for absence of data type (if not arg)
- *     create attnum magically???
- *     create attribute tuple
- *     insert attribute in attribute catalog
- *     modify reldesc
- *     create new relation tuple
- *     insert new relation in relation catalog
- *     delete original relation from relation catalog
- * ----------------
+ * If successful, returns the OID of the new relation.
+ * ----------------------------------------------------------------
  */
-void
-AlterTableAddColumn(Oid myrelid,
-                   bool inherits,
-                   ColumnDef *colDef)
+Oid
+DefineRelation(CreateStmt *stmt, char relkind)
 {
-   Relation    rel,
-               pgclass,
-               attrdesc;
-   HeapTuple   reltup;
-   HeapTuple   newreltup;
-   HeapTuple   attributeTuple;
-   Form_pg_attribute attribute;
-   FormData_pg_attribute attributeD;
+   char        relname[NAMEDATALEN];
+   Oid         namespaceId;
+   List       *schema = stmt->tableElts;
+   int         numberOfAttributes;
+   Oid         relationId;
+   Relation    rel;
+   TupleDesc   descriptor;
+   List       *inheritOids;
+   List       *old_constraints;
+   bool        parentHasOids;
+   List       *rawDefaults;
+   List       *listptr;
    int         i;
-   int         minattnum,
-               maxatts;
-   HeapTuple   typeTuple;
-   Form_pg_type tform;
-   int         attndims;
+   AttrNumber  attnum;
 
    /*
-    * Grab an exclusive lock on the target table, which we will NOT
-    * release until end of transaction.
+    * Truncate relname to appropriate length (probably a waste of time,
+    * as parser should have done this already).
     */
-   rel = heap_open(myrelid, AccessExclusiveLock);
+   StrNCpy(relname, stmt->relation->relname, NAMEDATALEN);
 
-   if (rel->rd_rel->relkind != RELKIND_RELATION)
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
-            RelationGetRelationName(rel));
+   /*
+    * Look up the namespace in which we are supposed to create the
+    * relation.
+    */
+   namespaceId = RangeVarGetCreationNamespace(stmt->relation);
 
    /*
-    * 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.
+    * Merge domain attributes into the known columns before processing table
+    * inheritance.  Otherwise we risk adding double constraints to a
+    * domain-type column that's inherited.
     */
-   if (!allowSystemTableMods
-       && IsSystemRelation(rel))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
-            RelationGetRelationName(rel));
-   if (!pg_class_ownercheck(myrelid, GetUserId()))
-       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
-            RelationGetRelationName(rel));
+   schema = MergeDomainAttributes(schema);
 
    /*
-    * 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.
+    * Look up inheritance ancestors and generate relation schema,
+    * including inherited attributes.
     */
-   if (inherits)
-   {
-       List       *child,
-                  *children;
+   schema = MergeAttributes(schema, stmt->inhRelations,
+                            stmt->relation->istemp,
+                            &inheritOids, &old_constraints, &parentHasOids);
 
-       /* this routine is actually in the planner */
-       children = find_all_inheritors(myrelid);
+   numberOfAttributes = length(schema);
+   if (numberOfAttributes <= 0)
+       elog(ERROR, "DefineRelation: please inherit from a relation or define an attribute");
 
-       /*
-        * 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)
+   /*
+    * Create a relation descriptor from the relation schema and create
+    * the relation.  Note that in this stage only inherited (pre-cooked)
+    * defaults and constraints will be included into the new relation.
+    * (BuildDescForRelation takes care of the inherited defaults, but we
+    * have to copy inherited constraints here.)
+    */
+   descriptor = BuildDescForRelation(schema);
+
+   if (old_constraints != NIL)
+   {
+       ConstrCheck *check = (ConstrCheck *) palloc(length(old_constraints) *
+                                                   sizeof(ConstrCheck));
+       int         ncheck = 0;
+
+       foreach(listptr, old_constraints)
        {
-           Oid         childrelid = lfirsti(child);
+           Constraint *cdef = (Constraint *) lfirst(listptr);
 
-           if (childrelid == myrelid)
+           if (cdef->contype != CONSTR_CHECK)
                continue;
 
-           AlterTableAddColumn(childrelid, false, colDef);
+           if (cdef->name != NULL)
+           {
+               for (i = 0; i < ncheck; i++)
+               {
+                   if (strcmp(check[i].ccname, cdef->name) == 0)
+                       elog(ERROR, "Duplicate CHECK constraint name: '%s'",
+                            cdef->name);
+               }
+               check[ncheck].ccname = cdef->name;
+           }
+           else
+           {
+               check[ncheck].ccname = (char *) palloc(NAMEDATALEN);
+               snprintf(check[ncheck].ccname, NAMEDATALEN, "$%d", ncheck + 1);
+           }
+           Assert(cdef->raw_expr == NULL && cdef->cooked_expr != NULL);
+           check[ncheck].ccbin = pstrdup(cdef->cooked_expr);
+           ncheck++;
+       }
+       if (ncheck > 0)
+       {
+           if (descriptor->constr == NULL)
+           {
+               descriptor->constr = (TupleConstr *) palloc(sizeof(TupleConstr));
+               descriptor->constr->defval = NULL;
+               descriptor->constr->num_defval = 0;
+               descriptor->constr->has_not_null = false;
+           }
+           descriptor->constr->num_check = ncheck;
+           descriptor->constr->check = check;
        }
    }
 
+   relationId = heap_create_with_catalog(relname,
+                                         namespaceId,
+                                         descriptor,
+                                         relkind,
+                                         stmt->hasoids || parentHasOids,
+                                         allowSystemTableMods);
+
+   StoreCatalogInheritance(relationId, inheritOids);
+
    /*
-    * 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 must bump the command counter to make the newly-created relation
+    * tuple visible for opening.
+    */
+   CommandCounterIncrement();
+
+   /*
+    * Open the new relation and acquire exclusive lock on it.  This isn't
+    * really necessary for locking out other backends (since they can't
+    * see the new rel anyway until we commit), but it keeps the lock
+    * manager from complaining about deadlock risks.
+    */
+   rel = heap_open(relationId, AccessExclusiveLock);
+
+   /*
+    * Now add any newly specified column default values and CHECK
+    * constraints to the new relation.  These are passed to us in the
+    * form of raw parsetrees; we need to transform them to executable
+    * expression trees before they can be added. The most convenient way
+    * to do that is to apply the parser's transformExpr routine, but
+    * transformExpr doesn't work unless we have a pre-existing relation.
+    * So, the transformation has to be postponed to this final step of
+    * CREATE TABLE.
     *
-    * We do allow CHECK constraints, even though these theoretically could
-    * fail for NULL rows (eg, CHECK (newcol IS NOT NULL)).
+    * First, scan schema to find new column defaults.
     */
-   if (colDef->raw_default || colDef->cooked_default)
-       elog(ERROR, "Adding columns with defaults is not implemented."
-            "\n\tAdd the column, then use ALTER TABLE SET DEFAULT.");
+   rawDefaults = NIL;
+   attnum = 0;
 
-   if (colDef->is_not_null)
-       elog(ERROR, "Adding NOT NULL columns is not implemented."
-            "\n\tAdd the column, then use ALTER TABLE ... SET NOT NULL.");
+   foreach(listptr, schema)
+   {
+       ColumnDef  *colDef = lfirst(listptr);
+       RawColumnDefault *rawEnt;
 
-   pgclass = heap_openr(RelationRelationName, RowExclusiveLock);
+       attnum++;
 
-   reltup = SearchSysCache(RELOID,
-                           ObjectIdGetDatum(myrelid),
-                           0, 0, 0);
-   if (!HeapTupleIsValid(reltup))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" not found",
-            RelationGetRelationName(rel));
+       if (colDef->raw_default == NULL)
+           continue;
+       Assert(colDef->cooked_default == NULL);
 
-   if (SearchSysCacheExists(ATTNAME,
-                            ObjectIdGetDatum(myrelid),
-                            PointerGetDatum(colDef->colname),
-                            0, 0))
-       elog(ERROR, "ALTER TABLE: column name \"%s\" already exists in table \"%s\"",
-            colDef->colname, RelationGetRelationName(rel));
-
-   minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts;
-   maxatts = minattnum + 1;
-   if (maxatts > MaxHeapAttributeNumber)
-       elog(ERROR, "ALTER TABLE: relations limited to %d columns",
-            MaxHeapAttributeNumber);
-   i = minattnum + 1;
-
-   attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
-
-   if (colDef->typename->arrayBounds)
-       attndims = length(colDef->typename->arrayBounds);
-   else
-       attndims = 0;
-
-   typeTuple = typenameType(colDef->typename);
-   tform = (Form_pg_type) GETSTRUCT(typeTuple);
-
-   attributeTuple = heap_addheader(Natts_pg_attribute,
-                                   ATTRIBUTE_TUPLE_SIZE,
-                                   (void *) &attributeD);
-
-   attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple);
-
-   attribute->attrelid = myrelid;
-   namestrcpy(&(attribute->attname), colDef->colname);
-   attribute->atttypid = typeTuple->t_data->t_oid;
-   attribute->attstattarget = DEFAULT_ATTSTATTARGET;
-   attribute->attlen = tform->typlen;
-   attribute->attcacheoff = -1;
-   attribute->atttypmod = colDef->typename->typmod;
-   attribute->attnum = i;
-   attribute->attbyval = tform->typbyval;
-   attribute->attndims = attndims;
-   attribute->attisset = (bool) (tform->typtype == 'c');
-   attribute->attstorage = tform->typstorage;
-   attribute->attalign = tform->typalign;
-   attribute->attnotnull = colDef->is_not_null;
-   attribute->atthasdef = (colDef->raw_default != NULL ||
-                           colDef->cooked_default != NULL);
-
-   ReleaseSysCache(typeTuple);
-
-   heap_insert(attrdesc, attributeTuple);
-
-   /* Update indexes on pg_attribute */
-   if (RelationGetForm(attrdesc)->relhasindex)
-   {
-       Relation    idescs[Num_pg_attr_indices];
-
-       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
-       CatalogIndexInsert(idescs, Num_pg_attr_indices, attrdesc, attributeTuple);
-       CatalogCloseIndices(Num_pg_attr_indices, idescs);
-   }
-
-   heap_close(attrdesc, RowExclusiveLock);
+       rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
+       rawEnt->attnum = attnum;
+       rawEnt->raw_default = colDef->raw_default;
+       rawDefaults = lappend(rawDefaults, rawEnt);
+   }
 
    /*
-    * Update number of attributes in pg_class tuple
+    * Parse and add the defaults/constraints, if any.
     */
-   newreltup = heap_copytuple(reltup);
-
-   ((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts;
-   simple_heap_update(pgclass, &newreltup->t_self, newreltup);
-
-   /* keep catalog indices current */
-   if (RelationGetForm(pgclass)->relhasindex)
-   {
-       Relation    ridescs[Num_pg_class_indices];
-
-       CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
-       CatalogIndexInsert(ridescs, Num_pg_class_indices, pgclass, newreltup);
-       CatalogCloseIndices(Num_pg_class_indices, ridescs);
-   }
-
-   heap_freetuple(newreltup);
-   ReleaseSysCache(reltup);
-
-   heap_close(pgclass, NoLock);
-
-   heap_close(rel, NoLock);    /* close rel but keep lock! */
+   if (rawDefaults || stmt->constraints)
+       AddRelationRawConstraints(rel, rawDefaults, stmt->constraints);
 
    /*
-    * Make our catalog updates visible for subsequent steps.
+    * Clean up.  We keep lock on new relation (although it shouldn't be
+    * visible to anyone else anyway, until commit).
     */
-   CommandCounterIncrement();
+   heap_close(rel, NoLock);
 
-   /*
-    * 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)
-   {
-       rel = heap_open(myrelid, AccessExclusiveLock);
-       AddRelationRawConstraints(rel, NIL, colDef->constraints);
-       heap_close(rel, NoLock);
-   }
+   return relationId;
+}
 
-   /*
-    * Automatically create the secondary relation for TOAST if it
-    * formerly had no such but now has toastable attributes.
-    */
-   AlterTableCreateToastTable(myrelid, true);
+/*
+ * RemoveRelation
+ *     Deletes a relation.
+ *
+ * Exceptions:
+ *     BadArg if name is invalid.
+ *
+ * Note:
+ *     If the relation has indices defined on it, then the index relations
+ * themselves will be destroyed, too.
+ */
+void
+RemoveRelation(const RangeVar *relation)
+{
+   Oid         relOid;
+
+   relOid = RangeVarGetRelid(relation, false);
+   heap_drop_with_catalog(relOid, allowSystemTableMods);
 }
 
 /*
- * ALTER TABLE ALTER COLUMN DROP NOT NULL
+ * TruncateRelation
+ *               Removes all the rows from a relation
+ *
+ * Exceptions:
+ *               BadArg if name is invalid
+ *
+ * Note:
+ *               Rows are removed, indices are truncated and reconstructed.
  */
 void
-AlterTableAlterColumnDropNotNull(Oid myrelid,
-                                bool inh, const char *colName)
+TruncateRelation(const RangeVar *relation)
 {
    Relation    rel;
-   HeapTuple   tuple;
-   AttrNumber  attnum;
-   Relation    attr_rel;
-   List        *indexoidlist;
-   List        *indexoidscan;
+   Oid         relid;
 
-   rel = heap_open(myrelid, AccessExclusiveLock);
+   /* Grab exclusive lock in preparation for truncate */
+   rel = heap_openrv(relation, AccessExclusiveLock);
+   relid = RelationGetRelid(rel);
 
-   if (rel->rd_rel->relkind != RELKIND_RELATION)
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
+   if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
+       elog(ERROR, "TRUNCATE cannot be used on sequences. '%s' is a sequence",
             RelationGetRelationName(rel));
 
-   if (!allowSystemTableMods
-       && IsSystemRelation(rel))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
+   if (rel->rd_rel->relkind == RELKIND_VIEW)
+       elog(ERROR, "TRUNCATE cannot be used on views. '%s' is a view",
             RelationGetRelationName(rel));
 
-   if (!pg_class_ownercheck(myrelid, GetUserId()))
-       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
+   if (!allowSystemTableMods && IsSystemRelation(rel))
+       elog(ERROR, "TRUNCATE cannot be used on system tables. '%s' is a system table",
             RelationGetRelationName(rel));
 
-   /*
-    * Propagate to children if desired
-    */
-   if (inh)
-   {
-       List       *child,
-                  *children;
+   if (!pg_class_ownercheck(relid, GetUserId()))
+       elog(ERROR, "you do not own relation \"%s\"",
+            RelationGetRelationName(rel));
 
-       /* this routine is actually in the planner */
-       children = find_all_inheritors(myrelid);
+   /* Keep the lock until transaction commit */
+   heap_close(rel, NoLock);
 
-       /*
-        * 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 = lfirsti(child);
+   heap_truncate(relid);
+}
 
-           if (childrelid == myrelid)
-               continue;
-           AlterTableAlterColumnDropNotNull(childrelid,
-                                            false, colName);
-       }
-   }
 
-   /* -= now do the thing on this relation =- */
+/*
+ * MergeDomainAttributes
+ *      Returns a new table schema with the constraints, types, and other
+ *      attributes of domains resolved for fields using a domain as
+ *      their type.
+ */
+static List *
+MergeDomainAttributes(List *schema)
+{
+   List       *entry;
 
    /*
-    * get the number of the attribute
+    * Loop through the table elements supplied. These should
+    * never include inherited domains else they'll be
+    * double (or more) processed.
     */
-   tuple = SearchSysCache(ATTNAME,
-                          ObjectIdGetDatum(myrelid),
-                          PointerGetDatum(colName),
-                          0, 0);
-   if (!HeapTupleIsValid(tuple))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
-            RelationGetRelationName(rel), colName);
-
-   attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
-   ReleaseSysCache(tuple);
+   foreach(entry, schema)
+   {
+       ColumnDef  *coldef = lfirst(entry);
+       HeapTuple  tuple;
+       Form_pg_type typeTup;
 
-   /* Prevent them from altering a system attribute */
-   if (attnum < 0)
-       elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"",
-            colName);
+       tuple = typenameType(coldef->typename);
+       typeTup = (Form_pg_type) GETSTRUCT(tuple);
 
-   /*
-    * Check that the attribute is not in a primary key
-    */
+       if (typeTup->typtype == 'd')
+       {
+           /* Force the column to have the correct typmod. */
+           coldef->typename->typmod = typeTup->typtypmod;
+           /* XXX more to do here? */
+       }
 
-   /* Loop over all indices on the relation */
-   indexoidlist = RelationGetIndexList(rel);
+       /* Enforce type NOT NULL || column definition NOT NULL -> NOT NULL */
+       /* Currently only used for domains, but could be valid for all */
+       coldef->is_not_null |= typeTup->typnotnull;
 
-   foreach(indexoidscan, indexoidlist)
-   {
-       Oid     indexoid = lfirsti(indexoidscan);
-       HeapTuple   indexTuple;
-       Form_pg_index   indexStruct;
-       int     i;
+       ReleaseSysCache(tuple);
+   }
 
-       indexTuple = SearchSysCache(INDEXRELID,
-                                   ObjectIdGetDatum(indexoid),
-                                   0, 0, 0);
-       if (!HeapTupleIsValid(indexTuple))
-           elog(ERROR, "ALTER TABLE: Index %u not found",
-                indexoid);
-       indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
+   return schema;
+}
 
-       /* 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 < INDEX_MAX_KEYS &&
-                    indexStruct->indkey[i] != InvalidAttrNumber; i++)
-           {
-               if (indexStruct->indkey[i] == attnum)
-                   elog(ERROR, "ALTER TABLE: Attribute \"%s\" is in a primary key", colName);
-           }
-       }
-
-       ReleaseSysCache(indexTuple);
-   }
-
-   freeList(indexoidlist);
-
-   /*
-    * Okay, actually perform the catalog change
-    */
-   attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
-
-   tuple = SearchSysCacheCopy(ATTNAME,
-                              ObjectIdGetDatum(myrelid),
-                              PointerGetDatum(colName),
-                              0, 0);
-   if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
-       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
-            RelationGetRelationName(rel), colName);
-
-   ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
-
-   simple_heap_update(attr_rel, &tuple->t_self, tuple);
-
-   /* keep the system catalog indices current */
-   if (RelationGetForm(attr_rel)->relhasindex)
-   {
-       Relation    idescs[Num_pg_attr_indices];
-
-       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
-       CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple);
-       CatalogCloseIndices(Num_pg_attr_indices, idescs);
-   }
-
-   heap_close(attr_rel, RowExclusiveLock);
-
-   heap_close(rel, NoLock);
-}
-
-/*
- * ALTER TABLE ALTER COLUMN SET NOT NULL
+/*----------
+ * MergeAttributes
+ *     Returns new schema given initial schema and superclasses.
+ *
+ * Input arguments:
+ * 'schema' is the column/attribute definition for the table. (It's a list
+ *     of ColumnDef's.) It is destructively changed.
+ * 'supers' is a list of names (as RangeVar nodes) of parent relations.
+ * 'istemp' is TRUE if we are creating a temp relation.
+ *
+ * Output arguments:
+ * 'supOids' receives an integer list of the OIDs of the parent relations.
+ * 'supconstr' receives a list of constraints belonging to the parents,
+ *     updated as necessary to be valid for the child.
+ * 'supHasOids' is set TRUE if any parent has OIDs, else it is set FALSE.
+ *
+ * Return value:
+ * Completed schema list.
+ *
+ * Notes:
+ *   The order in which the attributes are inherited is very important.
+ *   Intuitively, the inherited attributes should come first. If a table
+ *   inherits from multiple parents, the order of those attributes are
+ *   according to the order of the parents specified in CREATE TABLE.
+ *
+ *   Here's an example:
+ *
+ *     create table person (name text, age int4, location point);
+ *     create table emp (salary int4, manager text) inherits(person);
+ *     create table student (gpa float8) inherits (person);
+ *     create table stud_emp (percent int4) inherits (emp, student);
+ *
+ *   The order of the attributes of stud_emp is:
+ *
+ *                         person {1:name, 2:age, 3:location}
+ *                         /    \
+ *            {6:gpa}  student   emp {4:salary, 5:manager}
+ *                         \    /
+ *                        stud_emp {7:percent}
+ *
+ *    If the same attribute name appears multiple times, then it appears
+ *    in the result table in the proper location for its first appearance.
+ *
+ *    Constraints (including NOT NULL constraints) for the child table
+ *    are the union of all relevant constraints, from both the child schema
+ *    and parent tables.
+ *
+ *    The default value for a child column is defined as:
+ *     (1) If the child schema specifies a default, that value is used.
+ *     (2) If neither the child nor any parent specifies a default, then
+ *         the column will not have a default.
+ *     (3) If conflicting defaults are inherited from different parents
+ *         (and not overridden by the child), an error is raised.
+ *     (4) Otherwise the inherited default is used.
+ *     Rule (3) is new in Postgres 7.1; in earlier releases you got a
+ *     rather arbitrary choice of which parent default to use.
+ *----------
  */
-void
-AlterTableAlterColumnSetNotNull(Oid myrelid,
-                               bool inh, const char *colName)
+static List *
+MergeAttributes(List *schema, List *supers, bool istemp,
+               List **supOids, List **supconstr, bool *supHasOids)
 {
-   Relation    rel;
-   HeapTuple   tuple;
-   AttrNumber  attnum;
-   Relation    attr_rel;
-   HeapScanDesc    scan;
-   TupleDesc   tupdesc;
-
-   rel = heap_open(myrelid, AccessExclusiveLock);
-
-   if (rel->rd_rel->relkind != RELKIND_RELATION)
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
-            RelationGetRelationName(rel));
-
-   if (!allowSystemTableMods
-       && IsSystemRelation(rel))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
-            RelationGetRelationName(rel));
-
-   if (!pg_class_ownercheck(myrelid, GetUserId()))
-       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
-            RelationGetRelationName(rel));
+   List       *entry;
+   List       *inhSchema = NIL;
+   List       *parentOids = NIL;
+   List       *constraints = NIL;
+   bool        parentHasOids = false;
+   bool        have_bogus_defaults = false;
+   char       *bogus_marker = "Bogus!";        /* marks conflicting
+                                                * defaults */
+   int         child_attno;
 
    /*
-    * Propagate to children if desired
+    * Check for duplicate names in the explicit list of attributes.
+    *
+    * Although we might consider merging such entries in the same way that
+    * we handle name conflicts for inherited attributes, it seems to make
+    * more sense to assume such conflicts are errors.
     */
-   if (inh)
+   foreach(entry, schema)
    {
-       List       *child,
-                  *children;
-
-       /* this routine is actually in the planner */
-       children = find_all_inheritors(myrelid);
+       ColumnDef  *coldef = lfirst(entry);
+       List       *rest;
 
-       /*
-        * 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)
+       foreach(rest, lnext(entry))
        {
-           Oid         childrelid = lfirsti(child);
+           ColumnDef  *restdef = lfirst(rest);
 
-           if (childrelid == myrelid)
-               continue;
-           AlterTableAlterColumnSetNotNull(childrelid,
-                                           false, colName);
+           if (strcmp(coldef->colname, restdef->colname) == 0)
+               elog(ERROR, "CREATE TABLE: attribute \"%s\" duplicated",
+                    coldef->colname);
        }
    }
 
-   /* -= now do the thing on this relation =- */
-
    /*
-    * get the number of the attribute
+    * Scan the parents left-to-right, and merge their attributes to form
+    * a list of inherited attributes (inhSchema).  Also check to see if
+    * we need to inherit an OID column.
     */
-   tuple = SearchSysCache(ATTNAME,
-                          ObjectIdGetDatum(myrelid),
-                          PointerGetDatum(colName),
-                          0, 0);
-   if (!HeapTupleIsValid(tuple))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
-            RelationGetRelationName(rel), colName);
+   child_attno = 0;
+   foreach(entry, supers)
+   {
+       RangeVar   *parent = (RangeVar *) lfirst(entry);
+       Relation    relation;
+       TupleDesc   tupleDesc;
+       TupleConstr *constr;
+       AttrNumber *newattno;
+       AttrNumber  parent_attno;
 
-   attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
-   ReleaseSysCache(tuple);
+       relation = heap_openrv(parent, AccessShareLock);
 
-   /* Prevent them from altering a system attribute */
-   if (attnum < 0)
-       elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"",
-            colName);
+       if (relation->rd_rel->relkind != RELKIND_RELATION)
+           elog(ERROR, "CREATE TABLE: inherited relation \"%s\" is not a table",
+                parent->relname);
+       /* Permanent rels cannot inherit from temporary ones */
+       if (!istemp && isTempNamespace(RelationGetNamespace(relation)))
+           elog(ERROR, "CREATE TABLE: cannot inherit from temp relation \"%s\"",
+                parent->relname);
 
-   /*
-    * Perform a scan to ensure that there are no NULL
-    * values already in the relation
-    */
-   tupdesc = RelationGetDescr(rel);
+       /*
+        * We should have an UNDER permission flag for this, but for now,
+        * demand that creator of a child table own the parent.
+        */
+       if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
+           elog(ERROR, "you do not own table \"%s\"",
+                parent->relname);
 
-   scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+       /*
+        * Reject duplications in the list of parents.
+        */
+       if (intMember(RelationGetRelid(relation), parentOids))
+           elog(ERROR, "CREATE TABLE: inherited relation \"%s\" duplicated",
+                parent->relname);
 
-   while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
-   {
-       Datum       d;
-       bool        isnull;
+       parentOids = lappendi(parentOids, RelationGetRelid(relation));
+       setRelhassubclassInRelation(RelationGetRelid(relation), true);
 
-       d = heap_getattr(tuple, attnum, tupdesc, &isnull);
+       parentHasOids |= relation->rd_rel->relhasoids;
 
-       if (isnull)
-           elog(ERROR, "ALTER TABLE: Attribute \"%s\" contains NULL values",
-                colName);
-   }
+       tupleDesc = RelationGetDescr(relation);
+       constr = tupleDesc->constr;
 
-   heap_endscan(scan);
+       /*
+        * newattno[] will contain the child-table attribute numbers for
+        * the attributes of this parent table.  (They are not the same
+        * for parents after the first one.)
+        */
+       newattno = (AttrNumber *) palloc(tupleDesc->natts * sizeof(AttrNumber));
 
-   /*
-    * Okay, actually perform the catalog change
-    */
-   attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
-
-   tuple = SearchSysCacheCopy(ATTNAME,
-                              ObjectIdGetDatum(myrelid),
-                              PointerGetDatum(colName),
-                              0, 0);
-   if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
-       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
-            RelationGetRelationName(rel), colName);
+       for (parent_attno = 1; parent_attno <= tupleDesc->natts;
+            parent_attno++)
+       {
+           Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
+           char       *attributeName = NameStr(attribute->attname);
+           int         exist_attno;
+           ColumnDef  *def;
+           TypeName   *typename;
 
-   ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
+           /*
+            * Does it conflict with some previously inherited column?
+            */
+           exist_attno = findAttrByName(attributeName, inhSchema);
+           if (exist_attno > 0)
+           {
+               /*
+                * Yes, try to merge the two column definitions. They must
+                * have the same type and typmod.
+                */
+               elog(NOTICE, "CREATE TABLE: merging multiple inherited definitions of attribute \"%s\"",
+                    attributeName);
+               def = (ColumnDef *) nth(exist_attno - 1, inhSchema);
+               if (typenameTypeId(def->typename) != attribute->atttypid ||
+                   def->typename->typmod != attribute->atttypmod)
+                   elog(ERROR, "CREATE TABLE: inherited attribute \"%s\" type conflict (%s and %s)",
+                        attributeName,
+                        TypeNameToString(def->typename),
+                        typeidTypeName(attribute->atttypid));
+               /* Merge of NOT NULL constraints = OR 'em together */
+               def->is_not_null |= attribute->attnotnull;
+               /* Default and other constraints are handled below */
+               newattno[parent_attno - 1] = exist_attno;
+           }
+           else
+           {
+               /*
+                * No, create a new inherited column
+                */
+               def = makeNode(ColumnDef);
+               def->colname = pstrdup(attributeName);
+               typename = makeNode(TypeName);
+               typename->typeid = attribute->atttypid;
+               typename->typmod = attribute->atttypmod;
+               def->typename = typename;
+               def->is_not_null = attribute->attnotnull;
+               def->raw_default = NULL;
+               def->cooked_default = NULL;
+               def->constraints = NIL;
+               inhSchema = lappend(inhSchema, def);
+               newattno[parent_attno - 1] = ++child_attno;
+           }
 
-   simple_heap_update(attr_rel, &tuple->t_self, tuple);
+           /*
+            * Copy default if any
+            */
+           if (attribute->atthasdef)
+           {
+               char       *this_default = NULL;
+               AttrDefault *attrdef;
+               int         i;
 
-   /* keep the system catalog indices current */
-   if (RelationGetForm(attr_rel)->relhasindex)
-   {
-       Relation    idescs[Num_pg_attr_indices];
+               /* Find default in constraint structure */
+               Assert(constr != NULL);
+               attrdef = constr->defval;
+               for (i = 0; i < constr->num_defval; i++)
+               {
+                   if (attrdef[i].adnum == parent_attno)
+                   {
+                       this_default = attrdef[i].adbin;
+                       break;
+                   }
+               }
+               Assert(this_default != NULL);
 
-       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
-       CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple);
-       CatalogCloseIndices(Num_pg_attr_indices, idescs);
-   }
+               /*
+                * If default expr could contain any vars, we'd need to
+                * fix 'em, but it can't; so default is ready to apply to
+                * child.
+                *
+                * If we already had a default from some prior parent, check
+                * to see if they are the same.  If so, no problem; if
+                * not, mark the column as having a bogus default. Below,
+                * we will complain if the bogus default isn't overridden
+                * by the child schema.
+                */
+               Assert(def->raw_default == NULL);
+               if (def->cooked_default == NULL)
+                   def->cooked_default = pstrdup(this_default);
+               else if (strcmp(def->cooked_default, this_default) != 0)
+               {
+                   def->cooked_default = bogus_marker;
+                   have_bogus_defaults = true;
+               }
+           }
+       }
 
-   heap_close(attr_rel, RowExclusiveLock);
+       /*
+        * Now copy the constraints of this parent, adjusting attnos using
+        * the completed newattno[] map
+        */
+       if (constr && constr->num_check > 0)
+       {
+           ConstrCheck *check = constr->check;
+           int         i;
 
-   heap_close(rel, NoLock);
-}
+           for (i = 0; i < constr->num_check; i++)
+           {
+               Constraint *cdef = makeNode(Constraint);
+               Node       *expr;
 
+               cdef->contype = CONSTR_CHECK;
+               if (check[i].ccname[0] == '$')
+                   cdef->name = NULL;
+               else
+                   cdef->name = pstrdup(check[i].ccname);
+               cdef->raw_expr = NULL;
+               /* adjust varattnos of ccbin here */
+               expr = stringToNode(check[i].ccbin);
+               change_varattnos_of_a_node(expr, newattno);
+               cdef->cooked_expr = nodeToString(expr);
+               constraints = lappend(constraints, cdef);
+           }
+       }
 
-/*
- * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
- */
-void
-AlterTableAlterColumnDefault(Oid myrelid,
-                            bool inh, const char *colName,
-                            Node *newDefault)
-{
-   Relation    rel;
-   HeapTuple   tuple;
-   AttrNumber  attnum;
+       pfree(newattno);
 
-   rel = heap_open(myrelid, AccessExclusiveLock);
+       /*
+        * Close the parent rel, but keep our AccessShareLock on it until
+        * xact commit.  That will prevent someone else from deleting or
+        * ALTERing the parent before the child is committed.
+        */
+       heap_close(relation, NoLock);
+   }
 
    /*
-    * We allow defaults on views so that INSERT into a view can have
-    * default-ish behavior.  This works because the rewriter substitutes
-    * default values into INSERTs before it expands rules.
+    * If we had no inherited attributes, the result schema is just the
+    * explicitly declared columns.  Otherwise, we need to merge the
+    * declared columns into the inherited schema list.
     */
-   if (rel->rd_rel->relkind != RELKIND_RELATION &&
-       rel->rd_rel->relkind != RELKIND_VIEW)
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table or view",
-            RelationGetRelationName(rel));
+   if (inhSchema != NIL)
+   {
+       foreach(entry, schema)
+       {
+           ColumnDef  *newdef = lfirst(entry);
+           char       *attributeName = newdef->colname;
+           int         exist_attno;
 
-   if (!allowSystemTableMods
-       && IsSystemRelation(rel))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
-            RelationGetRelationName(rel));
+           /*
+            * Does it conflict with some previously inherited column?
+            */
+           exist_attno = findAttrByName(attributeName, inhSchema);
+           if (exist_attno > 0)
+           {
+               ColumnDef  *def;
 
-   if (!pg_class_ownercheck(myrelid, GetUserId()))
-       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
-            RelationGetRelationName(rel));
+               /*
+                * Yes, try to merge the two column definitions. They must
+                * have the same type and typmod.
+                */
+               elog(NOTICE, "CREATE TABLE: merging attribute \"%s\" with inherited definition",
+                    attributeName);
+               def = (ColumnDef *) nth(exist_attno - 1, inhSchema);
+               if (typenameTypeId(def->typename) != typenameTypeId(newdef->typename) ||
+                   def->typename->typmod != newdef->typename->typmod)
+                   elog(ERROR, "CREATE TABLE: attribute \"%s\" type conflict (%s and %s)",
+                        attributeName,
+                        TypeNameToString(def->typename),
+                        TypeNameToString(newdef->typename));
+               /* Merge of NOT NULL constraints = OR 'em together */
+               def->is_not_null |= newdef->is_not_null;
+               /* If new def has a default, override previous default */
+               if (newdef->raw_default != NULL)
+               {
+                   def->raw_default = newdef->raw_default;
+                   def->cooked_default = newdef->cooked_default;
+               }
+           }
+           else
+           {
+               /*
+                * No, attach new column to result schema
+                */
+               inhSchema = lappend(inhSchema, newdef);
+           }
+       }
+
+       schema = inhSchema;
+   }
 
    /*
-    * Propagate to children if desired
+    * If we found any conflicting parent default values, check to make
+    * sure they were overridden by the child.
     */
-   if (inh)
+   if (have_bogus_defaults)
    {
-       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)
+       foreach(entry, schema)
        {
-           Oid         childrelid = lfirsti(child);
+           ColumnDef  *def = lfirst(entry);
 
-           if (childrelid == myrelid)
-               continue;
-           AlterTableAlterColumnDefault(childrelid,
-                                        false, colName, newDefault);
+           if (def->cooked_default == bogus_marker)
+               elog(ERROR, "CREATE TABLE: attribute \"%s\" inherits conflicting default values"
+                    "\n\tTo resolve the conflict, specify a default explicitly",
+                    def->colname);
        }
    }
 
-   /* -= now do the thing on this relation =- */
+   *supOids = parentOids;
+   *supconstr = constraints;
+   *supHasOids = parentHasOids;
+   return schema;
+}
 
-   /*
-    * get the number of the attribute
-    */
-   tuple = SearchSysCache(ATTNAME,
-                          ObjectIdGetDatum(myrelid),
-                          PointerGetDatum(colName),
-                          0, 0);
-   if (!HeapTupleIsValid(tuple))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
-            RelationGetRelationName(rel), colName);
-
-   attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
-   ReleaseSysCache(tuple);
-
-   if (newDefault)
-   {
-       /* SET DEFAULT */
-       RawColumnDefault *rawEnt;
-
-       /* Get rid of the old one first */
-       drop_default(myrelid, attnum);
-
-       rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
-       rawEnt->attnum = attnum;
-       rawEnt->raw_default = newDefault;
-
-       /*
-        * This function is intended for CREATE TABLE, so it processes a
-        * _list_ of defaults, but we just do one.
-        */
-       AddRelationRawConstraints(rel, makeList1(rawEnt), NIL);
-   }
-   else
+/*
+ * complementary static functions for MergeAttributes().
+ *
+ * Varattnos of pg_relcheck.rcbin must be rewritten when subclasses inherit
+ * constraints from parent classes, since the inherited attributes could
+ * be given different column numbers in multiple-inheritance cases.
+ *
+ * Note that the passed node tree is modified in place!
+ */
+static bool
+change_varattnos_walker(Node *node, const AttrNumber *newattno)
+{
+   if (node == NULL)
+       return false;
+   if (IsA(node, Var))
    {
-       /* DROP DEFAULT */
-       Relation    attr_rel;
-
-       /* Fix the pg_attribute row */
-       attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
-
-       tuple = SearchSysCacheCopy(ATTNAME,
-                                  ObjectIdGetDatum(myrelid),
-                                  PointerGetDatum(colName),
-                                  0, 0);
-       if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
-           elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
-                RelationGetRelationName(rel), colName);
-
-       ((Form_pg_attribute) GETSTRUCT(tuple))->atthasdef = FALSE;
-
-       simple_heap_update(attr_rel, &tuple->t_self, tuple);
+       Var        *var = (Var *) node;
 
-       /* keep the system catalog indices current */
-       if (RelationGetForm(attr_rel)->relhasindex)
+       if (var->varlevelsup == 0 && var->varno == 1 &&
+           var->varattno > 0)
        {
-           Relation    idescs[Num_pg_attr_indices];
-
-           CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
-           CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple);
-           CatalogCloseIndices(Num_pg_attr_indices, idescs);
+           /*
+            * ??? the following may be a problem when the node is
+            * multiply referenced though stringToNode() doesn't create
+            * such a node currently.
+            */
+           Assert(newattno[var->varattno - 1] > 0);
+           var->varattno = newattno[var->varattno - 1];
        }
-
-       heap_close(attr_rel, RowExclusiveLock);
-
-       /* get rid of actual default definition in pg_attrdef */
-       drop_default(myrelid, attnum);
+       return false;
    }
-
-   heap_close(rel, NoLock);
+   return expression_tree_walker(node, change_varattnos_walker,
+                                 (void *) newattno);
 }
 
-
-static void
-drop_default(Oid relid, int16 attnum)
+static bool
+change_varattnos_of_a_node(Node *node, const AttrNumber *newattno)
 {
-   ScanKeyData scankeys[2];
-   HeapScanDesc scan;
-   Relation    attrdef_rel;
-   HeapTuple   tuple;
-
-   attrdef_rel = heap_openr(AttrDefaultRelationName, RowExclusiveLock);
-   ScanKeyEntryInitialize(&scankeys[0], 0x0,
-                          Anum_pg_attrdef_adrelid, F_OIDEQ,
-                          ObjectIdGetDatum(relid));
-   ScanKeyEntryInitialize(&scankeys[1], 0x0,
-                          Anum_pg_attrdef_adnum, F_INT2EQ,
-                          Int16GetDatum(attnum));
-
-   scan = heap_beginscan(attrdef_rel, false, SnapshotNow, 2, scankeys);
-
-   if (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
-       simple_heap_delete(attrdef_rel, &tuple->t_self);
-
-   heap_endscan(scan);
-
-   heap_close(attrdef_rel, NoLock);
+   return change_varattnos_walker(node, newattno);
 }
 
-
 /*
- * ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE
+ * StoreCatalogInheritance
+ *     Updates the system catalogs with proper inheritance information.
+ *
+ * supers is an integer list of the OIDs of the new relation's direct
+ * ancestors.  NB: it is destructively changed to include indirect ancestors.
  */
-void
-AlterTableAlterColumnFlags(Oid myrelid,
-                          bool inh, const char *colName,
-                          Node *flagValue, const char *flagType)
+static void
+StoreCatalogInheritance(Oid relationId, List *supers)
 {
-   Relation    rel;
-   int         newtarget = 1;
-   char        newstorage = 'p';
-   Relation    attrelation;
+   Relation    relation;
+   TupleDesc   desc;
+   int16       seqNumber;
+   List       *entry;
    HeapTuple   tuple;
-   Form_pg_attribute attrtuple;
-
-   rel = heap_open(myrelid, AccessExclusiveLock);
-
-   if (rel->rd_rel->relkind != RELKIND_RELATION)
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
-            RelationGetRelationName(rel));
 
    /*
-    * we allow statistics case for system tables
+    * sanity checks
     */
-   if (*flagType != 'S' && !allowSystemTableMods && IsSystemRelation(rel))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
-            RelationGetRelationName(rel));
+   AssertArg(OidIsValid(relationId));
 
-   if (!pg_class_ownercheck(myrelid, GetUserId()))
-       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
-            RelationGetRelationName(rel));
+   if (supers == NIL)
+       return;
 
    /*
-    * Check the supplied parameters before anything else
+    * Catalog INHERITS information using direct ancestors only.
     */
-   if (*flagType == 'S')
-   {
-       /* STATISTICS */
-       Assert(IsA(flagValue, Integer));
-       newtarget = intVal(flagValue);
+   relation = heap_openr(InheritsRelationName, RowExclusiveLock);
+   desc = RelationGetDescr(relation);
 
-       /*
-        * Limit target to sane range (should we raise an error instead?)
-        */
-       if (newtarget < 0)
-           newtarget = 0;
-       else if (newtarget > 1000)
-           newtarget = 1000;
-   }
-   else if (*flagType == 'M')
+   seqNumber = 1;
+   foreach(entry, supers)
    {
-       /* STORAGE */
-       char        *storagemode;
+       Oid         entryOid = lfirsti(entry);
+       Datum       datum[Natts_pg_inherits];
+       char        nullarr[Natts_pg_inherits];
 
-       Assert(IsA(flagValue, String));
-       storagemode = strVal(flagValue);
+       datum[0] = ObjectIdGetDatum(relationId);        /* inhrel */
+       datum[1] = ObjectIdGetDatum(entryOid);  /* inhparent */
+       datum[2] = Int16GetDatum(seqNumber);    /* inhseqno */
 
-       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
-           elog(ERROR, "ALTER TABLE: \"%s\" storage not recognized",
-                storagemode);
-   }
-   else
-   {
-       elog(ERROR, "ALTER TABLE: Invalid column flag: %c",
-            (int) *flagType);
-   }
+       nullarr[0] = ' ';
+       nullarr[1] = ' ';
+       nullarr[2] = ' ';
 
-   /*
-    * Propagate to children if desired
-    */
-   if (inh)
-   {
-       List       *child,
-                  *children;
+       tuple = heap_formtuple(desc, datum, nullarr);
 
-       /* this routine is actually in the planner */
-       children = find_all_inheritors(myrelid);
+       heap_insert(relation, tuple);
 
-       /*
-        * 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)
+       if (RelationGetForm(relation)->relhasindex)
        {
-           Oid         childrelid = lfirsti(child);
+           Relation    idescs[Num_pg_inherits_indices];
 
-           if (childrelid == myrelid)
-               continue;
-           AlterTableAlterColumnFlags(childrelid,
-                                      false, colName, flagValue, flagType);
+           CatalogOpenIndices(Num_pg_inherits_indices, Name_pg_inherits_indices, idescs);
+           CatalogIndexInsert(idescs, Num_pg_inherits_indices, relation, tuple);
+           CatalogCloseIndices(Num_pg_inherits_indices, idescs);
        }
-   }
 
-   /* -= now do the thing on this relation =- */
+       heap_freetuple(tuple);
 
-   attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
+       seqNumber += 1;
+   }
 
-   tuple = SearchSysCacheCopy(ATTNAME,
-                              ObjectIdGetDatum(myrelid),
-                              PointerGetDatum(colName),
-                              0, 0);
-   if (!HeapTupleIsValid(tuple))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
-            RelationGetRelationName(rel), colName);
-   attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
+   heap_close(relation, RowExclusiveLock);
 
-   if (attrtuple->attnum < 0)
-       elog(ERROR, "ALTER TABLE: cannot change system attribute \"%s\"",
-            colName);
-   /*
-    * Now change the appropriate field
-    */
-   if (*flagType == 'S')
-       attrtuple->attstattarget = newtarget;
-   else if (*flagType == 'M')
+   /* ----------------
+    * Expand supers list to include indirect ancestors as well.
+    *
+    * Algorithm:
+    *  0. begin with list of direct superclasses.
+    *  1. append after each relationId, its superclasses, recursively.
+    *  2. remove all but last of duplicates.
+    * ----------------
+    */
+
+   /*
+    * 1. append after each relationId, its superclasses, recursively.
+    */
+   foreach(entry, supers)
    {
-       /*
-        * safety check: do not allow toasted storage modes unless column
-        * datatype is TOAST-aware.
-        */
-       if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid))
-           attrtuple->attstorage = newstorage;
-       else
-           elog(ERROR, "ALTER TABLE: Column datatype %s can only have storage \"plain\"",
-                format_type_be(attrtuple->atttypid));
-   }
+       HeapTuple   tuple;
+       Oid         id;
+       int16       number;
+       List       *next;
+       List       *current;
 
-   simple_heap_update(attrelation, &tuple->t_self, tuple);
+       id = (Oid) lfirsti(entry);
+       current = entry;
+       next = lnext(entry);
 
-   /* keep system catalog indices current */
-   {
-       Relation    irelations[Num_pg_attr_indices];
+       for (number = 1;; number += 1)
+       {
+           tuple = SearchSysCache(INHRELID,
+                                  ObjectIdGetDatum(id),
+                                  Int16GetDatum(number),
+                                  0, 0);
+           if (!HeapTupleIsValid(tuple))
+               break;
 
-       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations);
-       CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, tuple);
-       CatalogCloseIndices(Num_pg_attr_indices, irelations);
+           lnext(current) = lconsi(((Form_pg_inherits)
+                                    GETSTRUCT(tuple))->inhparent,
+                                   NIL);
+
+           ReleaseSysCache(tuple);
+
+           current = lnext(current);
+       }
+       lnext(current) = next;
    }
 
-   heap_freetuple(tuple);
-   heap_close(attrelation, NoLock);
-   heap_close(rel, NoLock);    /* close rel, but keep lock! */
-}
+   /*
+    * 2. remove all but last of duplicates.
+    */
+   foreach(entry, supers)
+   {
+       Oid         thisone;
+       bool        found;
+       List       *rest;
 
+again:
+       thisone = lfirsti(entry);
+       found = false;
+       foreach(rest, lnext(entry))
+       {
+           if (thisone == lfirsti(rest))
+           {
+               found = true;
+               break;
+           }
+       }
+       if (found)
+       {
+           /*
+            * found a later duplicate, so remove this entry.
+            */
+           lfirsti(entry) = lfirsti(lnext(entry));
+           lnext(entry) = lnext(lnext(entry));
 
+           goto again;
+       }
+   }
+}
 
 /*
- * ALTER TABLE DROP COLUMN
+ * Look for an existing schema entry with the given name.
+ *
+ * Returns the index (starting with 1) if attribute already exists in schema,
+ * 0 if it doesn't.
  */
-void
-AlterTableDropColumn(Oid myrelid,
-                    bool inh, const char *colName,
-                    int behavior)
+static int
+findAttrByName(const char *attributeName, List *schema)
 {
-   elog(ERROR, "ALTER TABLE / DROP COLUMN is not implemented");
+   List       *s;
+   int         i = 0;
+
+   foreach(s, schema)
+   {
+       ColumnDef  *def = lfirst(s);
+
+       ++i;
+       if (strcmp(attributeName, def->colname) == 0)
+           return i;
+   }
+   return 0;
 }
 
+/*
+ * Update a relation's pg_class.relhassubclass entry to the given value
+ */
+static void
+setRelhassubclassInRelation(Oid relationId, bool relhassubclass)
+{
+   Relation    relationRelation;
+   HeapTuple   tuple;
+   Relation    idescs[Num_pg_class_indices];
+
+   /*
+    * Fetch a modifiable copy of the tuple, modify it, update pg_class.
+    */
+   relationRelation = heap_openr(RelationRelationName, RowExclusiveLock);
+   tuple = SearchSysCacheCopy(RELOID,
+                              ObjectIdGetDatum(relationId),
+                              0, 0, 0);
+   if (!HeapTupleIsValid(tuple))
+       elog(ERROR, "setRelhassubclassInRelation: cache lookup failed for relation %u", relationId);
+
+   ((Form_pg_class) GETSTRUCT(tuple))->relhassubclass = relhassubclass;
+   simple_heap_update(relationRelation, &tuple->t_self, tuple);
+
+   /* keep the catalog indices up to date */
+   CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs);
+   CatalogIndexInsert(idescs, Num_pg_class_indices, relationRelation, tuple);
+   CatalogCloseIndices(Num_pg_class_indices, idescs);
+
+   heap_freetuple(tuple);
+   heap_close(relationRelation, RowExclusiveLock);
+}
 
 
 /*
- * ALTER TABLE ADD CONSTRAINT
+ *     renameatt       - changes the name of a attribute in a relation
+ *
+ *     Attname attribute is changed in attribute catalog.
+ *     No record of the previous attname is kept (correct?).
+ *
+ *     get proper relrelation from relation catalog (if not arg)
+ *     scan attribute catalog
+ *             for name conflict (within rel)
+ *             for original attribute (if not arg)
+ *     modify attname in attribute tuple
+ *     insert modified attribute in attribute catalog
+ *     delete original attribute from attribute catalog
  */
 void
-AlterTableAddConstraint(Oid myrelid,
-                       bool inh, List *newConstraints)
+renameatt(Oid relid,
+         const char *oldattname,
+         const char *newattname,
+         bool recurse)
 {
-   Relation    rel;
-   List       *listptr;
+   Relation    targetrelation;
+   Relation    attrelation;
+   HeapTuple   atttup;
+   List       *indexoidlist;
+   List       *indexoidscan;
 
    /*
     * 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)
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
-            RelationGetRelationName(rel));
-
-   if (!allowSystemTableMods
-       && IsSystemRelation(rel))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
-            RelationGetRelationName(rel));
+   targetrelation = heap_open(relid, AccessExclusiveLock);
 
-   if (!pg_class_ownercheck(myrelid, GetUserId()))
-       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
-            RelationGetRelationName(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.
+    */
+   if (!allowSystemTableMods 
+       && IsSystemRelation(targetrelation))
+       elog(ERROR, "renameatt: class \"%s\" is a system catalog",
+            RelationGetRelationName(targetrelation));
+   if (!pg_class_ownercheck(relid, GetUserId()))
+       elog(ERROR, "renameatt: you do not own class \"%s\"",
+            RelationGetRelationName(targetrelation));
 
-   if (inh)
+   /*
+    * if the 'recurse' flag is set then we are supposed to rename this
+    * attribute in all classes that inherit from 'relname' (as well as in
+    * 'relname').
+    *
+    * any permissions or problems with duplicate attributes will cause the
+    * whole transaction to abort, which is what we want -- all or
+    * nothing.
+    */
+   if (recurse)
    {
        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
@@ -980,1641 +1032,1354 @@ AlterTableAddConstraint(Oid myrelid,
        {
            Oid         childrelid = lfirsti(child);
 
-           if (childrelid == myrelid)
+           if (childrelid == relid)
                continue;
-           AlterTableAddConstraint(childrelid, false, newConstraints);
+           /* note we need not recurse again! */
+           renameatt(childrelid, oldattname, newattname, false);
        }
    }
 
-   foreach(listptr, newConstraints)
-   {
-       Node       *newConstraint = lfirst(listptr);
+   attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
 
-       switch (nodeTag(newConstraint))
-       {
-           case T_Constraint:
-               {
-                   Constraint *constr = (Constraint *) newConstraint;
+   atttup = SearchSysCacheCopy(ATTNAME,
+                               ObjectIdGetDatum(relid),
+                               PointerGetDatum(oldattname),
+                               0, 0);
+   if (!HeapTupleIsValid(atttup))
+       elog(ERROR, "renameatt: attribute \"%s\" does not exist", oldattname);
 
-                   /*
-                    * 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:
-                           {
-                               ParseState *pstate;
-                               bool        successful = true;
-                               HeapScanDesc scan;
-                               ExprContext *econtext;
-                               TupleTableSlot *slot;
-                               HeapTuple   tuple;
-                               RangeTblEntry *rte;
-                               List       *qual;
-                               Node       *expr;
-                               char       *name;
+   if (((Form_pg_attribute) GETSTRUCT(atttup))->attnum < 0)
+       elog(ERROR, "renameatt: system attribute \"%s\" not renamed", oldattname);
 
-                               if (constr->name)
-                                   name = constr->name;
-                               else
-                                   name = "";
-
-                               /*
-                                * We need to make a parse state and range
-                                * table to allow us to transformExpr and
-                                * fix_opids to get a version of the
-                                * expression we can pass to ExecQual
-                                */
-                               pstate = make_parsestate(NULL);
-                               rte = addRangeTableEntryForRelation(pstate,
-                                                                   myrelid,
-                                           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.
-                                */
-                               if (exprType(expr) != BOOLOID)
-                                   elog(ERROR, "CHECK '%s' does not yield boolean result",
-                                        name);
-
-                               /*
-                                * Make sure no outside relations are
-                                * referred to.
-                                */
-                               if (length(pstate->p_rtable) != 1)
-                                   elog(ERROR, "Only relation '%s' can be referenced in CHECK",
-                                        RelationGetRelationName(rel));
-
-                               /*
-                                * Might as well try to reduce any
-                                * constant expressions.
-                                */
-                               expr = eval_const_expressions(expr);
-
-                               /* And fix the opids */
-                               fix_opids(expr);
-
-                               qual = makeList1(expr);
-
-                               /* Make tuple slot to hold tuples */
-                               slot = MakeTupleTableSlot();
-                               ExecSetSlotDescriptor(slot, RelationGetDescr(rel), false);
-                               /* Make an expression context for ExecQual */
-                               econtext = MakeExprContext(slot, CurrentMemoryContext);
-
-                               /*
-                                * Scan through the rows now, checking the
-                                * expression at each row.
-                                */
-                               scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
-
-                               while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
-                               {
-                                   ExecStoreTuple(tuple, slot, InvalidBuffer, false);
-                                   if (!ExecQual(qual, econtext, true))
-                                   {
-                                       successful = false;
-                                       break;
-                                   }
-                                   ResetExprContext(econtext);
-                               }
-
-                               heap_endscan(scan);
-
-                               FreeExprContext(econtext);
-                               pfree(slot);
-
-                               if (!successful)
-                                   elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", 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));
-
-                               break;
-                           }
-                       default:
-                           elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
-                   }
-                   break;
-               }
-           case T_FkConstraint:
-               {
-                   FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
-                   Relation    pkrel;
-                   HeapScanDesc scan;
-                   HeapTuple   tuple;
-                   Trigger     trig;
-                   List       *list;
-                   int         count;
-
-                   /*
-                    * Grab an exclusive lock on the pk table, so that
-                    * someone doesn't delete rows out from under us.
-                    *
-                    * XXX wouldn't a lesser lock be sufficient?
-                    */
-                   pkrel = heap_openrv(fkconstraint->pktable,
-                                       AccessExclusiveLock);
-
-                   /*
-                    * Validity checks
-                    */
-                   if (pkrel->rd_rel->relkind != RELKIND_RELATION)
-                       elog(ERROR, "referenced table \"%s\" not a relation",
-                            fkconstraint->pktable->relname);
-
-                   if (isTempNamespace(RelationGetNamespace(pkrel)) &&
-                       !isTempNamespace(RelationGetNamespace(rel)))
-                       elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
+   /* should not already exist */
+   if (SearchSysCacheExists(ATTNAME,
+                            ObjectIdGetDatum(relid),
+                            PointerGetDatum(newattname),
+                            0, 0))
+       elog(ERROR, "renameatt: attribute \"%s\" exists", newattname);
 
-                   /*
-                    * First we check for limited correctness of the
-                    * constraint.
-                    *
-                    * NOTE: we assume parser has already checked for
-                    * existence of an appropriate unique index on the
-                    * referenced relation, and that the column datatypes
-                    * are comparable.
-                    *
-                    * Scan through each tuple, calling RI_FKey_check_ins
-                    * (insert trigger) as if that tuple had just been
-                    * inserted.  If any of those fail, it should
-                    * elog(ERROR) and that's that.
-                    */
-                   MemSet(&trig, 0, sizeof(trig));
-                   trig.tgoid = InvalidOid;
-                   if (fkconstraint->constr_name)
-                       trig.tgname = fkconstraint->constr_name;
-                   else
-                       trig.tgname = "";
-                   trig.tgenabled = TRUE;
-                   trig.tgisconstraint = TRUE;
-                   trig.tgconstrrelid = RelationGetRelid(pkrel);
-                   trig.tgdeferrable = FALSE;
-                   trig.tginitdeferred = FALSE;
+   namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname),
+              newattname);
 
-                   trig.tgargs = (char **) palloc(
-                    sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
-                                      + length(fkconstraint->pk_attrs)));
+   simple_heap_update(attrelation, &atttup->t_self, atttup);
 
-                   trig.tgargs[0] = trig.tgname;
-                   trig.tgargs[1] = RelationGetRelationName(rel);
-                   trig.tgargs[2] = RelationGetRelationName(pkrel);
-                   trig.tgargs[3] = fkconstraint->match_type;
-                   count = 4;
-                   foreach(list, fkconstraint->fk_attrs)
-                   {
-                       Ident      *fk_at = lfirst(list);
+   /* keep system catalog indices current */
+   {
+       Relation    irelations[Num_pg_attr_indices];
 
-                       trig.tgargs[count] = fk_at->name;
-                       count += 2;
-                   }
-                   count = 5;
-                   foreach(list, fkconstraint->pk_attrs)
-                   {
-                       Ident      *pk_at = lfirst(list);
+       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations);
+       CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup);
+       CatalogCloseIndices(Num_pg_attr_indices, irelations);
+   }
 
-                       trig.tgargs[count] = pk_at->name;
-                       count += 2;
-                   }
-                   trig.tgnargs = count - 1;
+   heap_freetuple(atttup);
 
-                   scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+   /*
+    * Update column names of indexes that refer to the column being
+    * renamed.
+    */
+   indexoidlist = RelationGetIndexList(targetrelation);
 
-                   while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
-                   {
-                       /* Make a call to the check function */
+   foreach(indexoidscan, indexoidlist)
+   {
+       Oid         indexoid = lfirsti(indexoidscan);
+       HeapTuple   indextup;
 
-                       /*
-                        * No parameters are passed, but we do set a
-                        * context
-                        */
-                       FunctionCallInfoData fcinfo;
-                       TriggerData trigdata;
+       /*
+        * First check to see if index is a functional index. If so, its
+        * column name is a function name and shouldn't be renamed here.
+        */
+       indextup = SearchSysCache(INDEXRELID,
+                                 ObjectIdGetDatum(indexoid),
+                                 0, 0, 0);
+       if (!HeapTupleIsValid(indextup))
+           elog(ERROR, "renameatt: can't find index id %u", indexoid);
+       if (OidIsValid(((Form_pg_index) GETSTRUCT(indextup))->indproc))
+       {
+           ReleaseSysCache(indextup);
+           continue;
+       }
+       ReleaseSysCache(indextup);
 
-                       MemSet(&fcinfo, 0, sizeof(fcinfo));
+       /*
+        * Okay, look to see if any column name of the index matches the
+        * old attribute name.
+        */
+       atttup = SearchSysCacheCopy(ATTNAME,
+                                   ObjectIdGetDatum(indexoid),
+                                   PointerGetDatum(oldattname),
+                                   0, 0);
+       if (!HeapTupleIsValid(atttup))
+           continue;           /* Nope, so ignore it */
 
-                       /*
-                        * We assume RI_FKey_check_ins won't look at
-                        * flinfo...
-                        */
+       /*
+        * Update the (copied) attribute tuple.
+        */
+       namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname),
+                  newattname);
 
-                       trigdata.type = T_TriggerData;
-                       trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
-                       trigdata.tg_relation = rel;
-                       trigdata.tg_trigtuple = tuple;
-                       trigdata.tg_newtuple = NULL;
-                       trigdata.tg_trigger = &trig;
+       simple_heap_update(attrelation, &atttup->t_self, atttup);
 
-                       fcinfo.context = (Node *) &trigdata;
+       /* keep system catalog indices current */
+       {
+           Relation    irelations[Num_pg_attr_indices];
 
-                       RI_FKey_check_ins(&fcinfo);
-                   }
-                   heap_endscan(scan);
+           CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations);
+           CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup);
+           CatalogCloseIndices(Num_pg_attr_indices, irelations);
+       }
+       heap_freetuple(atttup);
+   }
 
-                   pfree(trig.tgargs);
+   freeList(indexoidlist);
 
-                   heap_close(pkrel, NoLock);
+   heap_close(attrelation, RowExclusiveLock);
 
-                   break;
-               }
-           default:
-               elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed");
-       }
+   /*
+    * Update att name in any RI triggers associated with the relation.
+    */
+   if (targetrelation->rd_rel->reltriggers > 0)
+   {
+       /* update tgargs column reference where att is primary key */
+       update_ri_trigger_args(RelationGetRelid(targetrelation),
+                              oldattname, newattname,
+                              false, false);
+       /* update tgargs column reference where att is foreign key */
+       update_ri_trigger_args(RelationGetRelid(targetrelation),
+                              oldattname, newattname,
+                              true, false);
    }
 
-   /* Close rel, but keep lock till commit */
-   heap_close(rel, NoLock);
+   heap_close(targetrelation, NoLock); /* close rel but keep lock! */
 }
 
-
-
 /*
- * ALTER TABLE DROP CONSTRAINT
- * Note: It is legal to remove a constraint with name "" as it is possible
- * to add a constraint with name "".
- * Christopher Kings-Lynne
+ *     renamerel       - change the name of a relation
+ *
+ *     XXX - When renaming sequences, we don't bother to modify the
+ *           sequence name that is stored within the sequence itself
+ *           (this would cause problems with MVCC). In the future,
+ *           the sequence name should probably be removed from the
+ *           sequence, AFAIK there's no need for it to be there.
  */
 void
-AlterTableDropConstraint(Oid myrelid,
-                        bool inh, const char *constrName,
-                        int behavior)
+renamerel(Oid relid, const char *newrelname)
 {
-   Relation    rel;
-   int         deleted;
+   Relation    targetrelation;
+   Relation    relrelation;    /* for RELATION relation */
+   HeapTuple   reltup;
+   Oid         namespaceId;
+   char       *oldrelname;
+   char        relkind;
+   bool        relhastriggers;
+   Relation    irelations[Num_pg_class_indices];
 
    /*
-    * We don't support CASCADE yet  - in fact, RESTRICT doesn't work to
-    * the spec either!
+    * Grab an exclusive lock on the target table or index, which we will
+    * NOT release until end of transaction.
     */
-   if (behavior == CASCADE)
-       elog(ERROR, "ALTER TABLE / DROP CONSTRAINT does not support the CASCADE keyword");
+   targetrelation = relation_open(relid, AccessExclusiveLock);
+
+   oldrelname = pstrdup(RelationGetRelationName(targetrelation));
+   namespaceId = RelationGetNamespace(targetrelation);
+
+   /* Validity checks */
+   if (!allowSystemTableMods &&
+       IsSystemRelation(targetrelation))
+       elog(ERROR, "renamerel: system relation \"%s\" may not be renamed",
+            oldrelname);
+
+   relkind = targetrelation->rd_rel->relkind;
+   relhastriggers = (targetrelation->rd_rel->reltriggers > 0);
 
    /*
-    * Acquire an exclusive lock on the target relation for the duration
-    * of the operation.
+    * Find relation's pg_class tuple, and make sure newrelname isn't in
+    * use.
     */
-   rel = heap_open(myrelid, AccessExclusiveLock);
-
-   /* Disallow DROP CONSTRAINT on views, indexes, sequences, etc */
-   if (rel->rd_rel->relkind != RELKIND_RELATION)
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
-            RelationGetRelationName(rel));
+   relrelation = heap_openr(RelationRelationName, RowExclusiveLock);
 
-   if (!allowSystemTableMods
-       && IsSystemRelation(rel))
-       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
-            RelationGetRelationName(rel));
+   reltup = SearchSysCacheCopy(RELOID,
+                               PointerGetDatum(relid),
+                               0, 0, 0);
+   if (!HeapTupleIsValid(reltup))
+       elog(ERROR, "renamerel: relation \"%s\" does not exist",
+            oldrelname);
 
-   if (!pg_class_ownercheck(myrelid, GetUserId()))
-       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
-            RelationGetRelationName(rel));
+   if (get_relname_relid(newrelname, namespaceId) != InvalidOid)
+       elog(ERROR, "renamerel: relation \"%s\" exists", newrelname);
 
    /*
-    * Since all we have is the name of the constraint, we have to look
-    * through all catalogs that could possibly contain a constraint for
-    * this relation. We also keep a count of the number of constraints
-    * removed.
+    * Update pg_class tuple with new relname.  (Scribbling on reltup is
+    * OK because it's a copy...)
     */
+   namestrcpy(&(((Form_pg_class) GETSTRUCT(reltup))->relname), newrelname);
 
-   deleted = 0;
+   simple_heap_update(relrelation, &reltup->t_self, reltup);
+
+   /* keep the system catalog indices current */
+   CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, irelations);
+   CatalogIndexInsert(irelations, Num_pg_class_indices, relrelation, reltup);
+   CatalogCloseIndices(Num_pg_class_indices, irelations);
+
+   heap_close(relrelation, NoLock);
+   heap_freetuple(reltup);
 
    /*
-    * First, we remove all CHECK constraints with the given name
+    * Also rename the associated type, if any.
     */
+   if (relkind != RELKIND_INDEX)
+       TypeRename(oldrelname, namespaceId, newrelname);
 
-   deleted += RemoveCheckConstraint(rel, constrName, inh);
+   /*
+    * Update rel name in any RI triggers associated with the relation.
+    */
+   if (relhastriggers)
+   {
+       /* update tgargs where relname is primary key */
+       update_ri_trigger_args(relid,
+                              oldrelname,
+                              newrelname,
+                              false, true);
+       /* update tgargs where relname is foreign key */
+       update_ri_trigger_args(relid,
+                              oldrelname,
+                              newrelname,
+                              true, true);
+   }
 
    /*
-    * Now we remove NULL, UNIQUE, PRIMARY KEY and FOREIGN KEY
-    * constraints.
-    *
-    * Unimplemented.
+    * Close rel, but keep exclusive lock!
     */
+   relation_close(targetrelation, NoLock);
+}
 
-   /* Close the target relation */
-   heap_close(rel, NoLock);
 
-   /* If zero constraints deleted, complain */
-   if (deleted == 0)
-       elog(ERROR, "ALTER TABLE / DROP CONSTRAINT: %s does not exist",
-            constrName);
-   /* Otherwise if more than one constraint deleted, notify */
-   else if (deleted > 1)
-       elog(NOTICE, "Multiple constraints dropped");
+/*
+ * Given a trigger function OID, determine whether it is an RI trigger,
+ * and if so whether it is attached to PK or FK relation.
+ *
+ * XXX this probably doesn't belong here; should be exported by
+ * ri_triggers.c
+ */
+static int
+ri_trigger_type(Oid tgfoid)
+{
+   switch (tgfoid)
+   {
+       case F_RI_FKEY_CASCADE_DEL:
+       case F_RI_FKEY_CASCADE_UPD:
+       case F_RI_FKEY_RESTRICT_DEL:
+       case F_RI_FKEY_RESTRICT_UPD:
+       case F_RI_FKEY_SETNULL_DEL:
+       case F_RI_FKEY_SETNULL_UPD:
+       case F_RI_FKEY_SETDEFAULT_DEL:
+       case F_RI_FKEY_SETDEFAULT_UPD:
+       case F_RI_FKEY_NOACTION_DEL:
+       case F_RI_FKEY_NOACTION_UPD:
+           return RI_TRIGGER_PK;
+
+       case F_RI_FKEY_CHECK_INS:
+       case F_RI_FKEY_CHECK_UPD:
+           return RI_TRIGGER_FK;
+   }
+
+   return RI_TRIGGER_NONE;
 }
 
-/*
- * ALTER TABLE OWNER
- */
-void
-AlterTableOwner(Oid relationOid, int32 newOwnerSysId)
-{
-   Relation        target_rel;
-   Relation        class_rel;
-   HeapTuple       tuple;
-   Relation        idescs[Num_pg_class_indices];
-   Form_pg_class   tuple_class;
+/*
+ * Scan pg_trigger for RI triggers that are on the specified relation
+ * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan
+ * is true).  Update RI trigger args fields matching oldname to contain
+ * newname instead.  If update_relname is true, examine the relname
+ * fields; otherwise examine the attname fields.
+ */
+static void
+update_ri_trigger_args(Oid relid,
+                      const char *oldname,
+                      const char *newname,
+                      bool fk_scan,
+                      bool update_relname)
+{
+   Relation    tgrel;
+   Relation    irel;
+   ScanKeyData skey[1];
+   IndexScanDesc idxtgscan;
+   RetrieveIndexResult idxres;
+   Datum       values[Natts_pg_trigger];
+   char        nulls[Natts_pg_trigger];
+   char        replaces[Natts_pg_trigger];
+
+   tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
+   if (fk_scan)
+       irel = index_openr(TriggerConstrRelidIndex);
+   else
+       irel = index_openr(TriggerRelidNameIndex);
+
+   ScanKeyEntryInitialize(&skey[0], 0x0,
+                          1,   /* column 1 of index in either case */
+                          F_OIDEQ,
+                          ObjectIdGetDatum(relid));
+   idxtgscan = index_beginscan(irel, false, 1, skey);
+
+   while ((idxres = index_getnext(idxtgscan, ForwardScanDirection)) != NULL)
+   {
+       HeapTupleData tupledata;
+       Buffer      buffer;
+       HeapTuple   tuple;
+       Form_pg_trigger pg_trigger;
+       bytea      *val;
+       bytea      *newtgargs;
+       bool        isnull;
+       int         tg_type;
+       bool        examine_pk;
+       bool        changed;
+       int         tgnargs;
+       int         i;
+       int         newlen;
+       const char *arga[RI_MAX_ARGUMENTS];
+       const char *argp;
+
+       tupledata.t_self = idxres->heap_iptr;
+       heap_fetch(tgrel, SnapshotNow, &tupledata, &buffer, idxtgscan);
+       pfree(idxres);
+       if (!tupledata.t_data)
+           continue;
+       tuple = &tupledata;
+       pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+       tg_type = ri_trigger_type(pg_trigger->tgfoid);
+       if (tg_type == RI_TRIGGER_NONE)
+       {
+           /* Not an RI trigger, forget it */
+           ReleaseBuffer(buffer);
+           continue;
+       }
+
+       /*
+        * It is an RI trigger, so parse the tgargs bytea.
+        *
+        * NB: we assume the field will never be compressed or moved out of
+        * line; so does trigger.c ...
+        */
+       tgnargs = pg_trigger->tgnargs;
+       val = (bytea *) fastgetattr(tuple,
+                                   Anum_pg_trigger_tgargs,
+                                   tgrel->rd_att, &isnull);
+       if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO ||
+           tgnargs > RI_MAX_ARGUMENTS)
+       {
+           /* This probably shouldn't happen, but ignore busted triggers */
+           ReleaseBuffer(buffer);
+           continue;
+       }
+       argp = (const char *) VARDATA(val);
+       for (i = 0; i < tgnargs; i++)
+       {
+           arga[i] = argp;
+           argp += strlen(argp) + 1;
+       }
 
-   /* Get exclusive lock till end of transaction on the target table */
-   target_rel = heap_open(relationOid, AccessExclusiveLock);
+       /*
+        * Figure out which item(s) to look at.  If the trigger is
+        * primary-key type and attached to my rel, I should look at the
+        * PK fields; if it is foreign-key type and attached to my rel, I
+        * should look at the FK fields.  But the opposite rule holds when
+        * examining triggers found by tgconstrrel search.
+        */
+       examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan);
 
-   /* Get its pg_class tuple, too */
-   class_rel = heap_openr(RelationRelationName, RowExclusiveLock);
+       changed = false;
+       if (update_relname)
+       {
+           /* Change the relname if needed */
+           i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO;
+           if (strcmp(arga[i], oldname) == 0)
+           {
+               arga[i] = newname;
+               changed = true;
+           }
+       }
+       else
+       {
+           /* Change attname(s) if needed */
+           i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX :
+               RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX;
+           for (; i < tgnargs; i += 2)
+           {
+               if (strcmp(arga[i], oldname) == 0)
+               {
+                   arga[i] = newname;
+                   changed = true;
+               }
+           }
+       }
 
-   tuple = SearchSysCacheCopy(RELOID,
-                              ObjectIdGetDatum(relationOid),
-                              0, 0, 0);
-   if (!HeapTupleIsValid(tuple))
-       elog(ERROR, "ALTER TABLE: relation %u not found", relationOid);
-   tuple_class = (Form_pg_class) GETSTRUCT(tuple);
+       if (!changed)
+       {
+           /* Don't need to update this tuple */
+           ReleaseBuffer(buffer);
+           continue;
+       }
 
-   /* Can we change the ownership of this tuple? */
-   CheckTupleType(tuple_class);
+       /*
+        * Construct modified tgargs bytea.
+        */
+       newlen = VARHDRSZ;
+       for (i = 0; i < tgnargs; i++)
+           newlen += strlen(arga[i]) + 1;
+       newtgargs = (bytea *) palloc(newlen);
+       VARATT_SIZEP(newtgargs) = newlen;
+       newlen = VARHDRSZ;
+       for (i = 0; i < tgnargs; i++)
+       {
+           strcpy(((char *) newtgargs) + newlen, arga[i]);
+           newlen += strlen(arga[i]) + 1;
+       }
 
-   /*
-    * Okay, this is a valid tuple: change its ownership and
-    * write to the heap.
-    */
-   tuple_class->relowner = newOwnerSysId;
-   simple_heap_update(class_rel, &tuple->t_self, tuple);
+       /*
+        * Build modified tuple.
+        */
+       for (i = 0; i < Natts_pg_trigger; i++)
+       {
+           values[i] = (Datum) 0;
+           replaces[i] = ' ';
+           nulls[i] = ' ';
+       }
+       values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs);
+       replaces[Anum_pg_trigger_tgargs - 1] = 'r';
 
-   /* Keep the catalog indices up to date */
-   CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs);
-   CatalogIndexInsert(idescs, Num_pg_class_indices, class_rel, tuple);
-   CatalogCloseIndices(Num_pg_class_indices, idescs);
+       tuple = heap_modifytuple(tuple, tgrel, values, nulls, replaces);
 
-   /*
-    * If we are operating on a table, also change the ownership of any
-    * indexes that belong to the table, as well as the table's toast
-    * table (if it has one)
-    */
-   if (tuple_class->relkind == RELKIND_RELATION ||
-       tuple_class->relkind == RELKIND_TOASTVALUE)
-   {
-       List *index_oid_list, *i;
+       /*
+        * Now we can release hold on original tuple.
+        */
+       ReleaseBuffer(buffer);
 
-       /* Find all the indexes belonging to this relation */
-       index_oid_list = RelationGetIndexList(target_rel);
+       /*
+        * Update pg_trigger and its indexes
+        */
+       simple_heap_update(tgrel, &tuple->t_self, tuple);
 
-       /* For each index, recursively change its ownership */
-       foreach(i, index_oid_list)
        {
-           AlterTableOwner(lfirsti(i), newOwnerSysId);
+           Relation    irelations[Num_pg_attr_indices];
+
+           CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, irelations);
+           CatalogIndexInsert(irelations, Num_pg_trigger_indices, tgrel, tuple);
+           CatalogCloseIndices(Num_pg_trigger_indices, irelations);
        }
 
-       freeList(index_oid_list);
+       /* free up our scratch memory */
+       pfree(newtgargs);
+       heap_freetuple(tuple);
    }
 
-   if (tuple_class->relkind == RELKIND_RELATION)
-   {
-       /* If it has a toast table, recurse to change its ownership */
-       if (tuple_class->reltoastrelid != InvalidOid)
-       {
-           AlterTableOwner(tuple_class->reltoastrelid, newOwnerSysId);
-       }
-   }
+   index_endscan(idxtgscan);
+   index_close(irel);
 
-   heap_freetuple(tuple);
-   heap_close(class_rel, RowExclusiveLock);
-   heap_close(target_rel, NoLock);
-}
+   heap_close(tgrel, RowExclusiveLock);
 
-static void
-CheckTupleType(Form_pg_class tuple_class)
-{
-   switch (tuple_class->relkind)
-   {
-       case RELKIND_RELATION:
-       case RELKIND_INDEX:
-       case RELKIND_VIEW:
-       case RELKIND_SEQUENCE:
-       case RELKIND_TOASTVALUE:
-           /* ok to change owner */
-           break;
-       default:
-           elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table, TOAST table, index, view, or sequence",
-                NameStr(tuple_class->relname));
-   }
+   /*
+    * Increment cmd counter to make updates visible; this is needed in
+    * case the same tuple has to be updated again by next pass (can
+    * happen in case of a self-referential FK relationship).
+    */
+   CommandCounterIncrement();
 }
 
-/*
- * ALTER TABLE CREATE TOAST TABLE
+
+/* ----------------
+ *     AlterTableAddColumn
+ *     (formerly known as PerformAddAttribute)
+ *
+ *     adds an additional attribute to a relation
+ *
+ *     Adds attribute field(s) to a relation.  Each new attribute
+ *     is given attnums in sequential order and is added to the
+ *     ATTRIBUTE relation.  If the AMI fails, defunct tuples will
+ *     remain in the ATTRIBUTE relation for later vacuuming.
+ *     Later, there may be some reserved attribute names???
+ *
+ *     (If needed, can instead use elog to handle exceptions.)
+ *
+ *     Note:
+ *             Initial idea of ordering the tuple attributes so that all
+ *     the variable length domains occured last was scratched.  Doing
+ *     so would not speed access too much (in general) and would create
+ *     many complications in formtuple, heap_getattr, and addattribute.
+ *
+ *     scan attribute catalog for name conflict (within rel)
+ *     scan type catalog for absence of data type (if not arg)
+ *     create attnum magically???
+ *     create attribute tuple
+ *     insert attribute in attribute catalog
+ *     modify reldesc
+ *     create new relation tuple
+ *     insert new relation in relation catalog
+ *     delete original relation from relation catalog
+ * ----------------
  */
 void
-AlterTableCreateToastTable(Oid relOid, bool silent)
+AlterTableAddColumn(Oid myrelid,
+                   bool inherits,
+                   ColumnDef *colDef)
 {
-   Relation    rel;
+   Relation    rel,
+               pgclass,
+               attrdesc;
    HeapTuple   reltup;
-   HeapTupleData classtuple;
-   TupleDesc   tupdesc;
-   Relation    class_rel;
-   Buffer      buffer;
-   Relation    ridescs[Num_pg_class_indices];
-   Oid         toast_relid;
-   Oid         toast_idxid;
-   char        toast_relname[NAMEDATALEN];
-   char        toast_idxname[NAMEDATALEN];
-   IndexInfo  *indexInfo;
-   Oid         classObjectId[2];
+   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;
 
    /*
     * Grab an exclusive lock on the target table, which we will NOT
     * release until end of transaction.
     */
-   rel = heap_open(relOid, AccessExclusiveLock);
+   rel = heap_open(myrelid, AccessExclusiveLock);
 
    if (rel->rd_rel->relkind != RELKIND_RELATION)
        elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
             RelationGetRelationName(rel));
 
-   if (!pg_class_ownercheck(relOid, GetUserId()))
+   /*
+    * 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.
+    */
+   if (!allowSystemTableMods
+       && IsSystemRelation(rel))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
+            RelationGetRelationName(rel));
+   if (!pg_class_ownercheck(myrelid, GetUserId()))
        elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
             RelationGetRelationName(rel));
 
    /*
-    * lock the pg_class tuple for update (is that really needed?)
+    * 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.
     */
-   class_rel = heap_openr(RelationRelationName, RowExclusiveLock);
+   if (inherits)
+   {
+       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 = lfirsti(child);
+
+           if (childrelid == myrelid)
+               continue;
+
+           AlterTableAddColumn(childrelid, false, colDef);
+       }
+   }
+
+   /*
+    * 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)).
+    */
+   if (colDef->raw_default || colDef->cooked_default)
+       elog(ERROR, "Adding columns with defaults is not implemented."
+            "\n\tAdd the column, then use ALTER TABLE SET DEFAULT.");
+
+   if (colDef->is_not_null)
+       elog(ERROR, "Adding NOT NULL columns is not implemented."
+            "\n\tAdd the column, then use ALTER TABLE ... SET NOT NULL.");
+
+   pgclass = heap_openr(RelationRelationName, RowExclusiveLock);
 
    reltup = SearchSysCache(RELOID,
-                           ObjectIdGetDatum(relOid),
+                           ObjectIdGetDatum(myrelid),
                            0, 0, 0);
    if (!HeapTupleIsValid(reltup))
        elog(ERROR, "ALTER TABLE: relation \"%s\" not found",
             RelationGetRelationName(rel));
-   classtuple.t_self = reltup->t_self;
-   ReleaseSysCache(reltup);
 
-   switch (heap_mark4update(class_rel, &classtuple, &buffer))
-   {
-       case HeapTupleSelfUpdated:
-       case HeapTupleMayBeUpdated:
-           break;
-       default:
-           elog(ERROR, "couldn't lock pg_class tuple");
-   }
-   reltup = heap_copytuple(&classtuple);
-   ReleaseBuffer(buffer);
+   if (SearchSysCacheExists(ATTNAME,
+                            ObjectIdGetDatum(myrelid),
+                            PointerGetDatum(colDef->colname),
+                            0, 0))
+       elog(ERROR, "ALTER TABLE: column name \"%s\" already exists in table \"%s\"",
+            colDef->colname, RelationGetRelationName(rel));
 
-   /*
-    * Is it already toasted?
-    */
-   if (((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid != InvalidOid)
-   {
-       if (silent)
-       {
-           heap_close(rel, NoLock);
-           heap_close(class_rel, NoLock);
-           heap_freetuple(reltup);
-           return;
-       }
+   minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts;
+   maxatts = minattnum + 1;
+   if (maxatts > MaxHeapAttributeNumber)
+       elog(ERROR, "ALTER TABLE: relations limited to %d columns",
+            MaxHeapAttributeNumber);
+   i = minattnum + 1;
 
-       elog(ERROR, "ALTER TABLE: relation \"%s\" already has a toast table",
-            RelationGetRelationName(rel));
-   }
+   attrdesc = heap_openr(AttributeRelationName, RowExclusiveLock);
 
-   /*
-    * Check to see whether the table actually needs a TOAST table.
-    */
-   if (!needs_toast_table(rel))
-   {
-       if (silent)
-       {
-           heap_close(rel, NoLock);
-           heap_close(class_rel, NoLock);
-           heap_freetuple(reltup);
-           return;
-       }
+   if (colDef->typename->arrayBounds)
+       attndims = length(colDef->typename->arrayBounds);
+   else
+       attndims = 0;
 
-       elog(ERROR, "ALTER TABLE: relation \"%s\" does not need a toast table",
-            RelationGetRelationName(rel));
-   }
+   typeTuple = typenameType(colDef->typename);
+   tform = (Form_pg_type) GETSTRUCT(typeTuple);
 
-   /*
-    * Create the toast table and its index
-    */
-   sprintf(toast_relname, "pg_toast_%u", relOid);
-   sprintf(toast_idxname, "pg_toast_%u_index", relOid);
+   attributeTuple = heap_addheader(Natts_pg_attribute,
+                                   ATTRIBUTE_TUPLE_SIZE,
+                                   (void *) &attributeD);
 
-   /* this is pretty painful...  need a tuple descriptor */
-   tupdesc = CreateTemplateTupleDesc(3);
-   TupleDescInitEntry(tupdesc, (AttrNumber) 1,
-                      "chunk_id",
-                      OIDOID,
-                      -1, 0, false);
-   TupleDescInitEntry(tupdesc, (AttrNumber) 2,
-                      "chunk_seq",
-                      INT4OID,
-                      -1, 0, false);
-   TupleDescInitEntry(tupdesc, (AttrNumber) 3,
-                      "chunk_data",
-                      BYTEAOID,
-                      -1, 0, false);
+   attribute = (Form_pg_attribute) GETSTRUCT(attributeTuple);
 
-   /*
-    * Ensure that the toast table doesn't itself get toasted, or we'll be
-    * toast :-(.  This is essential for chunk_data because type bytea is
-    * toastable; hit the other two just to be sure.
-    */
-   tupdesc->attrs[0]->attstorage = 'p';
-   tupdesc->attrs[1]->attstorage = 'p';
-   tupdesc->attrs[2]->attstorage = 'p';
+   attribute->attrelid = myrelid;
+   namestrcpy(&(attribute->attname), colDef->colname);
+   attribute->atttypid = typeTuple->t_data->t_oid;
+   attribute->attstattarget = DEFAULT_ATTSTATTARGET;
+   attribute->attlen = tform->typlen;
+   attribute->attcacheoff = -1;
+   attribute->atttypmod = colDef->typename->typmod;
+   attribute->attnum = i;
+   attribute->attbyval = tform->typbyval;
+   attribute->attndims = attndims;
+   attribute->attisset = (bool) (tform->typtype == 'c');
+   attribute->attstorage = tform->typstorage;
+   attribute->attalign = tform->typalign;
+   attribute->attnotnull = colDef->is_not_null;
+   attribute->atthasdef = (colDef->raw_default != NULL ||
+                           colDef->cooked_default != NULL);
 
-   /*
-    * Note: the toast relation is placed in the regular pg_toast namespace
-    * even if its master relation is a temp table.  There cannot be any
-    * naming collision, and the toast rel will be destroyed when its master
-    * is, so there's no need to handle the toast rel as temp.
-    */
-   toast_relid = heap_create_with_catalog(toast_relname,
-                                          PG_TOAST_NAMESPACE,
-                                          tupdesc,
-                                          RELKIND_TOASTVALUE,
-                                          false,
-                                          true);
+   ReleaseSysCache(typeTuple);
 
-   /* make the toast relation visible, else index creation will fail */
-   CommandCounterIncrement();
+   heap_insert(attrdesc, attributeTuple);
+
+   /* Update indexes on pg_attribute */
+   if (RelationGetForm(attrdesc)->relhasindex)
+   {
+       Relation    idescs[Num_pg_attr_indices];
+
+       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
+       CatalogIndexInsert(idescs, Num_pg_attr_indices, attrdesc, attributeTuple);
+       CatalogCloseIndices(Num_pg_attr_indices, idescs);
+   }
+
+   heap_close(attrdesc, RowExclusiveLock);
 
    /*
-    * Create unique index on chunk_id, chunk_seq.
-    *
-    * NOTE: the tuple toaster could actually function with a single-column
-    * index on chunk_id only.  However, it couldn't be unique then.  We
-    * want it to be unique as a check against the possibility of
-    * duplicate TOAST chunk OIDs.  Too, the index might be a little more
-    * efficient this way, since btree isn't all that happy with large
-    * numbers of equal keys.
+    * Update number of attributes in pg_class tuple
     */
+   newreltup = heap_copytuple(reltup);
 
-   indexInfo = makeNode(IndexInfo);
-   indexInfo->ii_NumIndexAttrs = 2;
-   indexInfo->ii_NumKeyAttrs = 2;
-   indexInfo->ii_KeyAttrNumbers[0] = 1;
-   indexInfo->ii_KeyAttrNumbers[1] = 2;
-   indexInfo->ii_Predicate = NIL;
-   indexInfo->ii_FuncOid = InvalidOid;
-   indexInfo->ii_Unique = true;
+   ((Form_pg_class) GETSTRUCT(newreltup))->relnatts = maxatts;
+   simple_heap_update(pgclass, &newreltup->t_self, newreltup);
 
-   classObjectId[0] = OID_BTREE_OPS_OID;
-   classObjectId[1] = INT4_BTREE_OPS_OID;
+   /* keep catalog indices current */
+   if (RelationGetForm(pgclass)->relhasindex)
+   {
+       Relation    ridescs[Num_pg_class_indices];
 
-   toast_idxid = index_create(toast_relid, toast_idxname, indexInfo,
-                              BTREE_AM_OID, classObjectId,
-                              true, true);
+       CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
+       CatalogIndexInsert(ridescs, Num_pg_class_indices, pgclass, newreltup);
+       CatalogCloseIndices(Num_pg_class_indices, ridescs);
+   }
 
-   /*
-    * Update toast rel's pg_class entry to show that it has an index. The
-    * index OID is stored into the reltoastidxid field for easy access by
-    * the tuple toaster.
-    */
-   setRelhasindex(toast_relid, true, true, toast_idxid);
+   heap_freetuple(newreltup);
+   ReleaseSysCache(reltup);
 
-   /*
-    * Store the toast table's OID in the parent relation's tuple
-    */
-   ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
-   simple_heap_update(class_rel, &reltup->t_self, reltup);
+   heap_close(pgclass, NoLock);
+
+   heap_close(rel, NoLock);    /* close rel but keep lock! */
 
    /*
-    * Keep catalog indices current
+    * Make our catalog updates visible for subsequent steps.
     */
-   CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
-   CatalogIndexInsert(ridescs, Num_pg_class_indices, class_rel, reltup);
-   CatalogCloseIndices(Num_pg_class_indices, ridescs);
-
-   heap_freetuple(reltup);
+   CommandCounterIncrement();
 
    /*
-    * Close relations and make changes visible
+    * 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.
     */
-   heap_close(class_rel, NoLock);
-   heap_close(rel, NoLock);
+   if (colDef->constraints != NIL)
+   {
+       rel = heap_open(myrelid, AccessExclusiveLock);
+       AddRelationRawConstraints(rel, NIL, colDef->constraints);
+       heap_close(rel, NoLock);
+   }
 
-   CommandCounterIncrement();
+   /*
+    * Automatically create the secondary relation for TOAST if it
+    * formerly had no such but now has toastable attributes.
+    */
+   AlterTableCreateToastTable(myrelid, true);
 }
 
 /*
- * Check to see whether the table needs a TOAST table. It does only if
- * (1) there are any toastable attributes, and (2) the maximum length
- * of a tuple could exceed TOAST_TUPLE_THRESHOLD.  (We don't want to
- * create a toast table for something like "f1 varchar(20)".)
+ * ALTER TABLE ALTER COLUMN DROP NOT NULL
  */
-static bool
-needs_toast_table(Relation rel)
+void
+AlterTableAlterColumnDropNotNull(Oid myrelid,
+                                bool inh, const char *colName)
 {
-   int32       data_length = 0;
-   bool        maxlength_unknown = false;
-   bool        has_toastable_attrs = false;
-   TupleDesc   tupdesc;
-   Form_pg_attribute *att;
-   int32       tuple_length;
-   int         i;
+   Relation    rel;
+   HeapTuple   tuple;
+   AttrNumber  attnum;
+   Relation    attr_rel;
+   List        *indexoidlist;
+   List        *indexoidscan;
 
-   tupdesc = rel->rd_att;
-   att = tupdesc->attrs;
+   rel = heap_open(myrelid, AccessExclusiveLock);
 
-   for (i = 0; i < tupdesc->natts; i++)
+   if (rel->rd_rel->relkind != RELKIND_RELATION)
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
+            RelationGetRelationName(rel));
+
+   if (!allowSystemTableMods
+       && IsSystemRelation(rel))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
+            RelationGetRelationName(rel));
+
+   if (!pg_class_ownercheck(myrelid, GetUserId()))
+       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
+            RelationGetRelationName(rel));
+
+   /*
+    * Propagate to children if desired
+    */
+   if (inh)
    {
-       data_length = att_align(data_length, att[i]->attlen, att[i]->attalign);
-       if (att[i]->attlen >= 0)
-       {
-           /* Fixed-length types are never toastable */
-           data_length += att[i]->attlen;
-       }
-       else
+       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)
        {
-           int32       maxlen = type_maximum_size(att[i]->atttypid,
-                                                  att[i]->atttypmod);
+           Oid         childrelid = lfirsti(child);
 
-           if (maxlen < 0)
-               maxlength_unknown = true;
-           else
-               data_length += maxlen;
-           if (att[i]->attstorage != 'p')
-               has_toastable_attrs = true;
+           if (childrelid == myrelid)
+               continue;
+           AlterTableAlterColumnDropNotNull(childrelid,
+                                            false, colName);
        }
    }
-   if (!has_toastable_attrs)
-       return false;           /* nothing to toast? */
-   if (maxlength_unknown)
-       return true;            /* any unlimited-length attrs? */
-   tuple_length = MAXALIGN(offsetof(HeapTupleHeaderData, t_bits) +
-                           BITMAPLEN(tupdesc->natts)) +
-       MAXALIGN(data_length);
-   return (tuple_length > TOAST_TUPLE_THRESHOLD);
-}
 
-
-/* ----------------------------------------------------------------
- *     DefineRelation
- *             Creates a new relation.
- *
- * If successful, returns the OID of the new relation.
- * ----------------------------------------------------------------
- */
-Oid
-DefineRelation(CreateStmt *stmt, char relkind)
-{
-   char        relname[NAMEDATALEN];
-   Oid         namespaceId;
-   List       *schema = stmt->tableElts;
-   int         numberOfAttributes;
-   Oid         relationId;
-   Relation    rel;
-   TupleDesc   descriptor;
-   List       *inheritOids;
-   List       *old_constraints;
-   bool        parentHasOids;
-   List       *rawDefaults;
-   List       *listptr;
-   int         i;
-   AttrNumber  attnum;
+   /* -= now do the thing on this relation =- */
 
    /*
-    * Truncate relname to appropriate length (probably a waste of time,
-    * as parser should have done this already).
+    * get the number of the attribute
     */
-   StrNCpy(relname, stmt->relation->relname, NAMEDATALEN);
+   tuple = SearchSysCache(ATTNAME,
+                          ObjectIdGetDatum(myrelid),
+                          PointerGetDatum(colName),
+                          0, 0);
+   if (!HeapTupleIsValid(tuple))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
+            RelationGetRelationName(rel), colName);
 
-   /*
-    * Look up the namespace in which we are supposed to create the
-    * relation.
-    */
-   namespaceId = RangeVarGetCreationNamespace(stmt->relation);
+   attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
+   ReleaseSysCache(tuple);
 
-   /*
-    * Merge domain attributes into the known columns before processing table
-    * inheritance.  Otherwise we risk adding double constraints to a
-    * domain-type column that's inherited.
-    */
-   schema = MergeDomainAttributes(schema);
+   /* Prevent them from altering a system attribute */
+   if (attnum < 0)
+       elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"",
+            colName);
 
    /*
-    * Look up inheritance ancestors and generate relation schema,
-    * including inherited attributes.
+    * Check that the attribute is not in a primary key
     */
-   schema = MergeAttributes(schema, stmt->inhRelations,
-                            stmt->relation->istemp,
-                            &inheritOids, &old_constraints, &parentHasOids);
-
-   numberOfAttributes = length(schema);
-   if (numberOfAttributes <= 0)
-       elog(ERROR, "DefineRelation: please inherit from a relation or define an attribute");
 
-   /*
-    * Create a relation descriptor from the relation schema and create
-    * the relation.  Note that in this stage only inherited (pre-cooked)
-    * defaults and constraints will be included into the new relation.
-    * (BuildDescForRelation takes care of the inherited defaults, but we
-    * have to copy inherited constraints here.)
-    */
-   descriptor = BuildDescForRelation(schema);
+   /* Loop over all indices on the relation */
+   indexoidlist = RelationGetIndexList(rel);
 
-   if (old_constraints != NIL)
+   foreach(indexoidscan, indexoidlist)
    {
-       ConstrCheck *check = (ConstrCheck *) palloc(length(old_constraints) *
-                                                   sizeof(ConstrCheck));
-       int         ncheck = 0;
-
-       foreach(listptr, old_constraints)
-       {
-           Constraint *cdef = (Constraint *) lfirst(listptr);
+       Oid     indexoid = lfirsti(indexoidscan);
+       HeapTuple   indexTuple;
+       Form_pg_index   indexStruct;
+       int     i;
 
-           if (cdef->contype != CONSTR_CHECK)
-               continue;
+       indexTuple = SearchSysCache(INDEXRELID,
+                                   ObjectIdGetDatum(indexoid),
+                                   0, 0, 0);
+       if (!HeapTupleIsValid(indexTuple))
+           elog(ERROR, "ALTER TABLE: Index %u not found",
+                indexoid);
+       indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
 
-           if (cdef->name != NULL)
-           {
-               for (i = 0; i < ncheck; i++)
-               {
-                   if (strcmp(check[i].ccname, cdef->name) == 0)
-                       elog(ERROR, "Duplicate CHECK constraint name: '%s'",
-                            cdef->name);
-               }
-               check[ncheck].ccname = cdef->name;
-           }
-           else
-           {
-               check[ncheck].ccname = (char *) palloc(NAMEDATALEN);
-               snprintf(check[ncheck].ccname, NAMEDATALEN, "$%d", ncheck + 1);
-           }
-           Assert(cdef->raw_expr == NULL && cdef->cooked_expr != NULL);
-           check[ncheck].ccbin = pstrdup(cdef->cooked_expr);
-           ncheck++;
-       }
-       if (ncheck > 0)
+       /* If the index is not a primary key, skip the check */
+       if (indexStruct->indisprimary)
        {
-           if (descriptor->constr == NULL)
+           /*
+            * Loop over each attribute in the primary key and
+            * see if it matches the to-be-altered attribute
+            */
+           for (i = 0; i < INDEX_MAX_KEYS &&
+                    indexStruct->indkey[i] != InvalidAttrNumber; i++)
            {
-               descriptor->constr = (TupleConstr *) palloc(sizeof(TupleConstr));
-               descriptor->constr->defval = NULL;
-               descriptor->constr->num_defval = 0;
-               descriptor->constr->has_not_null = false;
+               if (indexStruct->indkey[i] == attnum)
+                   elog(ERROR, "ALTER TABLE: Attribute \"%s\" is in a primary key", colName);
            }
-           descriptor->constr->num_check = ncheck;
-           descriptor->constr->check = check;
        }
-   }
-
-   relationId = heap_create_with_catalog(relname,
-                                         namespaceId,
-                                         descriptor,
-                                         relkind,
-                                         stmt->hasoids || parentHasOids,
-                                         allowSystemTableMods);
 
-   StoreCatalogInheritance(relationId, inheritOids);
+       ReleaseSysCache(indexTuple);
+   }
 
-   /*
-    * We must bump the command counter to make the newly-created relation
-    * tuple visible for opening.
-    */
-   CommandCounterIncrement();
+   freeList(indexoidlist);
 
    /*
-    * Open the new relation and acquire exclusive lock on it.  This isn't
-    * really necessary for locking out other backends (since they can't
-    * see the new rel anyway until we commit), but it keeps the lock
-    * manager from complaining about deadlock risks.
+    * Okay, actually perform the catalog change
     */
-   rel = heap_open(relationId, AccessExclusiveLock);
+   attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
 
-   /*
-    * Now add any newly specified column default values and CHECK
-    * constraints to the new relation.  These are passed to us in the
-    * form of raw parsetrees; we need to transform them to executable
-    * expression trees before they can be added. The most convenient way
-    * to do that is to apply the parser's transformExpr routine, but
-    * transformExpr doesn't work unless we have a pre-existing relation.
-    * So, the transformation has to be postponed to this final step of
-    * CREATE TABLE.
-    *
-    * First, scan schema to find new column defaults.
-    */
-   rawDefaults = NIL;
-   attnum = 0;
+   tuple = SearchSysCacheCopy(ATTNAME,
+                              ObjectIdGetDatum(myrelid),
+                              PointerGetDatum(colName),
+                              0, 0);
+   if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
+       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
+            RelationGetRelationName(rel), colName);
 
-   foreach(listptr, schema)
-   {
-       ColumnDef  *colDef = lfirst(listptr);
-       RawColumnDefault *rawEnt;
+   ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = FALSE;
 
-       attnum++;
+   simple_heap_update(attr_rel, &tuple->t_self, tuple);
 
-       if (colDef->raw_default == NULL)
-           continue;
-       Assert(colDef->cooked_default == NULL);
+   /* keep the system catalog indices current */
+   if (RelationGetForm(attr_rel)->relhasindex)
+   {
+       Relation    idescs[Num_pg_attr_indices];
 
-       rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
-       rawEnt->attnum = attnum;
-       rawEnt->raw_default = colDef->raw_default;
-       rawDefaults = lappend(rawDefaults, rawEnt);
+       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
+       CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple);
+       CatalogCloseIndices(Num_pg_attr_indices, idescs);
    }
 
-   /*
-    * Parse and add the defaults/constraints, if any.
-    */
-   if (rawDefaults || stmt->constraints)
-       AddRelationRawConstraints(rel, rawDefaults, stmt->constraints);
+   heap_close(attr_rel, RowExclusiveLock);
 
-   /*
-    * Clean up.  We keep lock on new relation (although it shouldn't be
-    * visible to anyone else anyway, until commit).
-    */
    heap_close(rel, NoLock);
-
-   return relationId;
-}
-
-/*
- * RemoveRelation
- *     Deletes a relation.
- *
- * Exceptions:
- *     BadArg if name is invalid.
- *
- * Note:
- *     If the relation has indices defined on it, then the index relations
- * themselves will be destroyed, too.
- */
-void
-RemoveRelation(const RangeVar *relation)
-{
-   Oid         relOid;
-
-   relOid = RangeVarGetRelid(relation, false);
-   heap_drop_with_catalog(relOid, allowSystemTableMods);
 }
 
 /*
- * TruncateRelation
- *               Removes all the rows from a relation
- *
- * Exceptions:
- *               BadArg if name is invalid
- *
- * Note:
- *               Rows are removed, indices are truncated and reconstructed.
+ * ALTER TABLE ALTER COLUMN SET NOT NULL
  */
 void
-TruncateRelation(const RangeVar *relation)
+AlterTableAlterColumnSetNotNull(Oid myrelid,
+                               bool inh, const char *colName)
 {
    Relation    rel;
-   Oid         relid;
-
-   /* Grab exclusive lock in preparation for truncate */
-   rel = heap_openrv(relation, AccessExclusiveLock);
-   relid = RelationGetRelid(rel);
+   HeapTuple   tuple;
+   AttrNumber  attnum;
+   Relation    attr_rel;
+   HeapScanDesc    scan;
+   TupleDesc   tupdesc;
 
-   if (rel->rd_rel->relkind == RELKIND_SEQUENCE)
-       elog(ERROR, "TRUNCATE cannot be used on sequences. '%s' is a sequence",
-            RelationGetRelationName(rel));
+   rel = heap_open(myrelid, AccessExclusiveLock);
 
-   if (rel->rd_rel->relkind == RELKIND_VIEW)
-       elog(ERROR, "TRUNCATE cannot be used on views. '%s' is a view",
+   if (rel->rd_rel->relkind != RELKIND_RELATION)
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
             RelationGetRelationName(rel));
 
-   if (!allowSystemTableMods && IsSystemRelation(rel))
-       elog(ERROR, "TRUNCATE cannot be used on system tables. '%s' is a system table",
+   if (!allowSystemTableMods
+       && IsSystemRelation(rel))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
             RelationGetRelationName(rel));
 
-   if (!pg_class_ownercheck(relid, GetUserId()))
-       elog(ERROR, "you do not own relation \"%s\"",
+   if (!pg_class_ownercheck(myrelid, GetUserId()))
+       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
             RelationGetRelationName(rel));
 
-   /* Keep the lock until transaction commit */
-   heap_close(rel, NoLock);
-
-   heap_truncate(relid);
-}
-
-
-/*
- * MergeDomainAttributes
- *      Returns a new table schema with the constraints, types, and other
- *      attributes of domains resolved for fields using a domain as
- *      their type.
- */
-static List *
-MergeDomainAttributes(List *schema)
-{
-   List       *entry;
-
    /*
-    * Loop through the table elements supplied. These should
-    * never include inherited domains else they'll be
-    * double (or more) processed.
+    * Propagate to children if desired
     */
-   foreach(entry, schema)
+   if (inh)
    {
-       ColumnDef  *coldef = lfirst(entry);
-       HeapTuple  tuple;
-       Form_pg_type typeTup;
+       List       *child,
+                  *children;
 
-       tuple = typenameType(coldef->typename);
-       typeTup = (Form_pg_type) GETSTRUCT(tuple);
+       /* this routine is actually in the planner */
+       children = find_all_inheritors(myrelid);
 
-       if (typeTup->typtype == 'd')
+       /*
+        * 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)
        {
-           /* Force the column to have the correct typmod. */
-           coldef->typename->typmod = typeTup->typtypmod;
-           /* XXX more to do here? */
-       }
-
-       /* Enforce type NOT NULL || column definition NOT NULL -> NOT NULL */
-       /* Currently only used for domains, but could be valid for all */
-       coldef->is_not_null |= typeTup->typnotnull;
+           Oid         childrelid = lfirsti(child);
 
-       ReleaseSysCache(tuple);
+           if (childrelid == myrelid)
+               continue;
+           AlterTableAlterColumnSetNotNull(childrelid,
+                                           false, colName);
+       }
    }
 
-   return schema;
-}
-
-/*----------
- * MergeAttributes
- *     Returns new schema given initial schema and superclasses.
- *
- * Input arguments:
- * 'schema' is the column/attribute definition for the table. (It's a list
- *     of ColumnDef's.) It is destructively changed.
- * 'supers' is a list of names (as RangeVar nodes) of parent relations.
- * 'istemp' is TRUE if we are creating a temp relation.
- *
- * Output arguments:
- * 'supOids' receives an integer list of the OIDs of the parent relations.
- * 'supconstr' receives a list of constraints belonging to the parents,
- *     updated as necessary to be valid for the child.
- * 'supHasOids' is set TRUE if any parent has OIDs, else it is set FALSE.
- *
- * Return value:
- * Completed schema list.
- *
- * Notes:
- *   The order in which the attributes are inherited is very important.
- *   Intuitively, the inherited attributes should come first. If a table
- *   inherits from multiple parents, the order of those attributes are
- *   according to the order of the parents specified in CREATE TABLE.
- *
- *   Here's an example:
- *
- *     create table person (name text, age int4, location point);
- *     create table emp (salary int4, manager text) inherits(person);
- *     create table student (gpa float8) inherits (person);
- *     create table stud_emp (percent int4) inherits (emp, student);
- *
- *   The order of the attributes of stud_emp is:
- *
- *                         person {1:name, 2:age, 3:location}
- *                         /    \
- *            {6:gpa}  student   emp {4:salary, 5:manager}
- *                         \    /
- *                        stud_emp {7:percent}
- *
- *    If the same attribute name appears multiple times, then it appears
- *    in the result table in the proper location for its first appearance.
- *
- *    Constraints (including NOT NULL constraints) for the child table
- *    are the union of all relevant constraints, from both the child schema
- *    and parent tables.
- *
- *    The default value for a child column is defined as:
- *     (1) If the child schema specifies a default, that value is used.
- *     (2) If neither the child nor any parent specifies a default, then
- *         the column will not have a default.
- *     (3) If conflicting defaults are inherited from different parents
- *         (and not overridden by the child), an error is raised.
- *     (4) Otherwise the inherited default is used.
- *     Rule (3) is new in Postgres 7.1; in earlier releases you got a
- *     rather arbitrary choice of which parent default to use.
- *----------
- */
-static List *
-MergeAttributes(List *schema, List *supers, bool istemp,
-               List **supOids, List **supconstr, bool *supHasOids)
-{
-   List       *entry;
-   List       *inhSchema = NIL;
-   List       *parentOids = NIL;
-   List       *constraints = NIL;
-   bool        parentHasOids = false;
-   bool        have_bogus_defaults = false;
-   char       *bogus_marker = "Bogus!";        /* marks conflicting
-                                                * defaults */
-   int         child_attno;
-
+   /* -= now do the thing on this relation =- */
+
    /*
-    * Check for duplicate names in the explicit list of attributes.
-    *
-    * Although we might consider merging such entries in the same way that
-    * we handle name conflicts for inherited attributes, it seems to make
-    * more sense to assume such conflicts are errors.
+    * get the number of the attribute
     */
-   foreach(entry, schema)
-   {
-       ColumnDef  *coldef = lfirst(entry);
-       List       *rest;
+   tuple = SearchSysCache(ATTNAME,
+                          ObjectIdGetDatum(myrelid),
+                          PointerGetDatum(colName),
+                          0, 0);
+   if (!HeapTupleIsValid(tuple))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
+            RelationGetRelationName(rel), colName);
 
-       foreach(rest, lnext(entry))
-       {
-           ColumnDef  *restdef = lfirst(rest);
+   attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
+   ReleaseSysCache(tuple);
 
-           if (strcmp(coldef->colname, restdef->colname) == 0)
-               elog(ERROR, "CREATE TABLE: attribute \"%s\" duplicated",
-                    coldef->colname);
-       }
-   }
+   /* Prevent them from altering a system attribute */
+   if (attnum < 0)
+       elog(ERROR, "ALTER TABLE: Cannot alter system attribute \"%s\"",
+            colName);
 
    /*
-    * Scan the parents left-to-right, and merge their attributes to form
-    * a list of inherited attributes (inhSchema).  Also check to see if
-    * we need to inherit an OID column.
+    * Perform a scan to ensure that there are no NULL
+    * values already in the relation
     */
-   child_attno = 0;
-   foreach(entry, supers)
-   {
-       RangeVar   *parent = (RangeVar *) lfirst(entry);
-       Relation    relation;
-       TupleDesc   tupleDesc;
-       TupleConstr *constr;
-       AttrNumber *newattno;
-       AttrNumber  parent_attno;
+   tupdesc = RelationGetDescr(rel);
 
-       relation = heap_openrv(parent, AccessShareLock);
+   scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
 
-       if (relation->rd_rel->relkind != RELKIND_RELATION)
-           elog(ERROR, "CREATE TABLE: inherited relation \"%s\" is not a table",
-                parent->relname);
-       /* Permanent rels cannot inherit from temporary ones */
-       if (!istemp && isTempNamespace(RelationGetNamespace(relation)))
-           elog(ERROR, "CREATE TABLE: cannot inherit from temp relation \"%s\"",
-                parent->relname);
+   while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+   {
+       Datum       d;
+       bool        isnull;
 
-       /*
-        * We should have an UNDER permission flag for this, but for now,
-        * demand that creator of a child table own the parent.
-        */
-       if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId()))
-           elog(ERROR, "you do not own table \"%s\"",
-                parent->relname);
+       d = heap_getattr(tuple, attnum, tupdesc, &isnull);
 
-       /*
-        * Reject duplications in the list of parents.
-        */
-       if (intMember(RelationGetRelid(relation), parentOids))
-           elog(ERROR, "CREATE TABLE: inherited relation \"%s\" duplicated",
-                parent->relname);
+       if (isnull)
+           elog(ERROR, "ALTER TABLE: Attribute \"%s\" contains NULL values",
+                colName);
+   }
 
-       parentOids = lappendi(parentOids, RelationGetRelid(relation));
-       setRelhassubclassInRelation(RelationGetRelid(relation), true);
+   heap_endscan(scan);
 
-       parentHasOids |= relation->rd_rel->relhasoids;
+   /*
+    * Okay, actually perform the catalog change
+    */
+   attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
 
-       tupleDesc = RelationGetDescr(relation);
-       constr = tupleDesc->constr;
+   tuple = SearchSysCacheCopy(ATTNAME,
+                              ObjectIdGetDatum(myrelid),
+                              PointerGetDatum(colName),
+                              0, 0);
+   if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
+       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
+            RelationGetRelationName(rel), colName);
 
-       /*
-        * newattno[] will contain the child-table attribute numbers for
-        * the attributes of this parent table.  (They are not the same
-        * for parents after the first one.)
-        */
-       newattno = (AttrNumber *) palloc(tupleDesc->natts * sizeof(AttrNumber));
+   ((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull = TRUE;
 
-       for (parent_attno = 1; parent_attno <= tupleDesc->natts;
-            parent_attno++)
-       {
-           Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
-           char       *attributeName = NameStr(attribute->attname);
-           int         exist_attno;
-           ColumnDef  *def;
-           TypeName   *typename;
+   simple_heap_update(attr_rel, &tuple->t_self, tuple);
 
-           /*
-            * Does it conflict with some previously inherited column?
-            */
-           exist_attno = findAttrByName(attributeName, inhSchema);
-           if (exist_attno > 0)
-           {
-               /*
-                * Yes, try to merge the two column definitions. They must
-                * have the same type and typmod.
-                */
-               elog(NOTICE, "CREATE TABLE: merging multiple inherited definitions of attribute \"%s\"",
-                    attributeName);
-               def = (ColumnDef *) nth(exist_attno - 1, inhSchema);
-               if (typenameTypeId(def->typename) != attribute->atttypid ||
-                   def->typename->typmod != attribute->atttypmod)
-                   elog(ERROR, "CREATE TABLE: inherited attribute \"%s\" type conflict (%s and %s)",
-                        attributeName,
-                        TypeNameToString(def->typename),
-                        typeidTypeName(attribute->atttypid));
-               /* Merge of NOT NULL constraints = OR 'em together */
-               def->is_not_null |= attribute->attnotnull;
-               /* Default and other constraints are handled below */
-               newattno[parent_attno - 1] = exist_attno;
-           }
-           else
-           {
-               /*
-                * No, create a new inherited column
-                */
-               def = makeNode(ColumnDef);
-               def->colname = pstrdup(attributeName);
-               typename = makeNode(TypeName);
-               typename->typeid = attribute->atttypid;
-               typename->typmod = attribute->atttypmod;
-               def->typename = typename;
-               def->is_not_null = attribute->attnotnull;
-               def->raw_default = NULL;
-               def->cooked_default = NULL;
-               def->constraints = NIL;
-               inhSchema = lappend(inhSchema, def);
-               newattno[parent_attno - 1] = ++child_attno;
-           }
+   /* keep the system catalog indices current */
+   if (RelationGetForm(attr_rel)->relhasindex)
+   {
+       Relation    idescs[Num_pg_attr_indices];
 
-           /*
-            * Copy default if any
-            */
-           if (attribute->atthasdef)
-           {
-               char       *this_default = NULL;
-               AttrDefault *attrdef;
-               int         i;
+       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
+       CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple);
+       CatalogCloseIndices(Num_pg_attr_indices, idescs);
+   }
 
-               /* Find default in constraint structure */
-               Assert(constr != NULL);
-               attrdef = constr->defval;
-               for (i = 0; i < constr->num_defval; i++)
-               {
-                   if (attrdef[i].adnum == parent_attno)
-                   {
-                       this_default = attrdef[i].adbin;
-                       break;
-                   }
-               }
-               Assert(this_default != NULL);
+   heap_close(attr_rel, RowExclusiveLock);
 
-               /*
-                * If default expr could contain any vars, we'd need to
-                * fix 'em, but it can't; so default is ready to apply to
-                * child.
-                *
-                * If we already had a default from some prior parent, check
-                * to see if they are the same.  If so, no problem; if
-                * not, mark the column as having a bogus default. Below,
-                * we will complain if the bogus default isn't overridden
-                * by the child schema.
-                */
-               Assert(def->raw_default == NULL);
-               if (def->cooked_default == NULL)
-                   def->cooked_default = pstrdup(this_default);
-               else if (strcmp(def->cooked_default, this_default) != 0)
-               {
-                   def->cooked_default = bogus_marker;
-                   have_bogus_defaults = true;
-               }
-           }
-       }
+   heap_close(rel, NoLock);
+}
 
-       /*
-        * Now copy the constraints of this parent, adjusting attnos using
-        * the completed newattno[] map
-        */
-       if (constr && constr->num_check > 0)
-       {
-           ConstrCheck *check = constr->check;
-           int         i;
 
-           for (i = 0; i < constr->num_check; i++)
-           {
-               Constraint *cdef = makeNode(Constraint);
-               Node       *expr;
+/*
+ * ALTER TABLE ALTER COLUMN SET/DROP DEFAULT
+ */
+void
+AlterTableAlterColumnDefault(Oid myrelid,
+                            bool inh, const char *colName,
+                            Node *newDefault)
+{
+   Relation    rel;
+   HeapTuple   tuple;
+   AttrNumber  attnum;
 
-               cdef->contype = CONSTR_CHECK;
-               if (check[i].ccname[0] == '$')
-                   cdef->name = NULL;
-               else
-                   cdef->name = pstrdup(check[i].ccname);
-               cdef->raw_expr = NULL;
-               /* adjust varattnos of ccbin here */
-               expr = stringToNode(check[i].ccbin);
-               change_varattnos_of_a_node(expr, newattno);
-               cdef->cooked_expr = nodeToString(expr);
-               constraints = lappend(constraints, cdef);
-           }
-       }
+   rel = heap_open(myrelid, AccessExclusiveLock);
 
-       pfree(newattno);
+   /*
+    * We allow defaults on views so that INSERT into a view can have
+    * default-ish behavior.  This works because the rewriter substitutes
+    * default values into INSERTs before it expands rules.
+    */
+   if (rel->rd_rel->relkind != RELKIND_RELATION &&
+       rel->rd_rel->relkind != RELKIND_VIEW)
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table or view",
+            RelationGetRelationName(rel));
+
+   if (!allowSystemTableMods
+       && IsSystemRelation(rel))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
+            RelationGetRelationName(rel));
 
-       /*
-        * Close the parent rel, but keep our AccessShareLock on it until
-        * xact commit.  That will prevent someone else from deleting or
-        * ALTERing the parent before the child is committed.
-        */
-       heap_close(relation, NoLock);
-   }
+   if (!pg_class_ownercheck(myrelid, GetUserId()))
+       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
+            RelationGetRelationName(rel));
 
    /*
-    * If we had no inherited attributes, the result schema is just the
-    * explicitly declared columns.  Otherwise, we need to merge the
-    * declared columns into the inherited schema list.
+    * Propagate to children if desired
     */
-   if (inhSchema != NIL)
+   if (inh)
    {
-       foreach(entry, schema)
-       {
-           ColumnDef  *newdef = lfirst(entry);
-           char       *attributeName = newdef->colname;
-           int         exist_attno;
+       List       *child,
+                  *children;
 
-           /*
-            * Does it conflict with some previously inherited column?
-            */
-           exist_attno = findAttrByName(attributeName, inhSchema);
-           if (exist_attno > 0)
-           {
-               ColumnDef  *def;
+       /* this routine is actually in the planner */
+       children = find_all_inheritors(myrelid);
 
-               /*
-                * Yes, try to merge the two column definitions. They must
-                * have the same type and typmod.
-                */
-               elog(NOTICE, "CREATE TABLE: merging attribute \"%s\" with inherited definition",
-                    attributeName);
-               def = (ColumnDef *) nth(exist_attno - 1, inhSchema);
-               if (typenameTypeId(def->typename) != typenameTypeId(newdef->typename) ||
-                   def->typename->typmod != newdef->typename->typmod)
-                   elog(ERROR, "CREATE TABLE: attribute \"%s\" type conflict (%s and %s)",
-                        attributeName,
-                        TypeNameToString(def->typename),
-                        TypeNameToString(newdef->typename));
-               /* Merge of NOT NULL constraints = OR 'em together */
-               def->is_not_null |= newdef->is_not_null;
-               /* If new def has a default, override previous default */
-               if (newdef->raw_default != NULL)
-               {
-                   def->raw_default = newdef->raw_default;
-                   def->cooked_default = newdef->cooked_default;
-               }
-           }
-           else
-           {
-               /*
-                * No, attach new column to result schema
-                */
-               inhSchema = lappend(inhSchema, newdef);
-           }
-       }
+       /*
+        * 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 = lfirsti(child);
 
-       schema = inhSchema;
+           if (childrelid == myrelid)
+               continue;
+           AlterTableAlterColumnDefault(childrelid,
+                                        false, colName, newDefault);
+       }
    }
 
+   /* -= now do the thing on this relation =- */
+
    /*
-    * If we found any conflicting parent default values, check to make
-    * sure they were overridden by the child.
+    * get the number of the attribute
     */
-   if (have_bogus_defaults)
+   tuple = SearchSysCache(ATTNAME,
+                          ObjectIdGetDatum(myrelid),
+                          PointerGetDatum(colName),
+                          0, 0);
+   if (!HeapTupleIsValid(tuple))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
+            RelationGetRelationName(rel), colName);
+
+   attnum = ((Form_pg_attribute) GETSTRUCT(tuple))->attnum;
+   ReleaseSysCache(tuple);
+
+   if (newDefault)
    {
-       foreach(entry, schema)
-       {
-           ColumnDef  *def = lfirst(entry);
+       /* SET DEFAULT */
+       RawColumnDefault *rawEnt;
 
-           if (def->cooked_default == bogus_marker)
-               elog(ERROR, "CREATE TABLE: attribute \"%s\" inherits conflicting default values"
-                    "\n\tTo resolve the conflict, specify a default explicitly",
-                    def->colname);
-       }
-   }
+       /* Get rid of the old one first */
+       drop_default(myrelid, attnum);
 
-   *supOids = parentOids;
-   *supconstr = constraints;
-   *supHasOids = parentHasOids;
-   return schema;
-}
+       rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
+       rawEnt->attnum = attnum;
+       rawEnt->raw_default = newDefault;
 
-/*
- * complementary static functions for MergeAttributes().
- *
- * Varattnos of pg_relcheck.rcbin must be rewritten when subclasses inherit
- * constraints from parent classes, since the inherited attributes could
- * be given different column numbers in multiple-inheritance cases.
- *
- * Note that the passed node tree is modified in place!
- */
-static bool
-change_varattnos_walker(Node *node, const AttrNumber *newattno)
-{
-   if (node == NULL)
-       return false;
-   if (IsA(node, Var))
+       /*
+        * This function is intended for CREATE TABLE, so it processes a
+        * _list_ of defaults, but we just do one.
+        */
+       AddRelationRawConstraints(rel, makeList1(rawEnt), NIL);
+   }
+   else
    {
-       Var        *var = (Var *) node;
+       /* DROP DEFAULT */
+       Relation    attr_rel;
 
-       if (var->varlevelsup == 0 && var->varno == 1 &&
-           var->varattno > 0)
+       /* Fix the pg_attribute row */
+       attr_rel = heap_openr(AttributeRelationName, RowExclusiveLock);
+
+       tuple = SearchSysCacheCopy(ATTNAME,
+                                  ObjectIdGetDatum(myrelid),
+                                  PointerGetDatum(colName),
+                                  0, 0);
+       if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
+           elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
+                RelationGetRelationName(rel), colName);
+
+       ((Form_pg_attribute) GETSTRUCT(tuple))->atthasdef = FALSE;
+
+       simple_heap_update(attr_rel, &tuple->t_self, tuple);
+
+       /* keep the system catalog indices current */
+       if (RelationGetForm(attr_rel)->relhasindex)
        {
-           /*
-            * ??? the following may be a problem when the node is
-            * multiply referenced though stringToNode() doesn't create
-            * such a node currently.
-            */
-           Assert(newattno[var->varattno - 1] > 0);
-           var->varattno = newattno[var->varattno - 1];
+           Relation    idescs[Num_pg_attr_indices];
+
+           CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, idescs);
+           CatalogIndexInsert(idescs, Num_pg_attr_indices, attr_rel, tuple);
+           CatalogCloseIndices(Num_pg_attr_indices, idescs);
        }
-       return false;
+
+       heap_close(attr_rel, RowExclusiveLock);
+
+       /* get rid of actual default definition in pg_attrdef */
+       drop_default(myrelid, attnum);
    }
-   return expression_tree_walker(node, change_varattnos_walker,
-                                 (void *) newattno);
-}
 
-static bool
-change_varattnos_of_a_node(Node *node, const AttrNumber *newattno)
-{
-   return change_varattnos_walker(node, newattno);
+   heap_close(rel, NoLock);
 }
 
-/*
- * StoreCatalogInheritance
- *     Updates the system catalogs with proper inheritance information.
- *
- * supers is an integer list of the OIDs of the new relation's direct
- * ancestors.  NB: it is destructively changed to include indirect ancestors.
- */
+
 static void
-StoreCatalogInheritance(Oid relationId, List *supers)
+drop_default(Oid relid, int16 attnum)
 {
-   Relation    relation;
-   TupleDesc   desc;
-   int16       seqNumber;
-   List       *entry;
+   ScanKeyData scankeys[2];
+   HeapScanDesc scan;
+   Relation    attrdef_rel;
    HeapTuple   tuple;
 
-   /*
-    * sanity checks
-    */
-   AssertArg(OidIsValid(relationId));
+   attrdef_rel = heap_openr(AttrDefaultRelationName, RowExclusiveLock);
+   ScanKeyEntryInitialize(&scankeys[0], 0x0,
+                          Anum_pg_attrdef_adrelid, F_OIDEQ,
+                          ObjectIdGetDatum(relid));
+   ScanKeyEntryInitialize(&scankeys[1], 0x0,
+                          Anum_pg_attrdef_adnum, F_INT2EQ,
+                          Int16GetDatum(attnum));
 
-   if (supers == NIL)
-       return;
+   scan = heap_beginscan(attrdef_rel, false, SnapshotNow, 2, scankeys);
 
-   /*
-    * Catalog INHERITS information using direct ancestors only.
-    */
-   relation = heap_openr(InheritsRelationName, RowExclusiveLock);
-   desc = RelationGetDescr(relation);
+   if (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+       simple_heap_delete(attrdef_rel, &tuple->t_self);
 
-   seqNumber = 1;
-   foreach(entry, supers)
-   {
-       Oid         entryOid = lfirsti(entry);
-       Datum       datum[Natts_pg_inherits];
-       char        nullarr[Natts_pg_inherits];
+   heap_endscan(scan);
 
-       datum[0] = ObjectIdGetDatum(relationId);        /* inhrel */
-       datum[1] = ObjectIdGetDatum(entryOid);  /* inhparent */
-       datum[2] = Int16GetDatum(seqNumber);    /* inhseqno */
+   heap_close(attrdef_rel, NoLock);
+}
 
-       nullarr[0] = ' ';
-       nullarr[1] = ' ';
-       nullarr[2] = ' ';
 
-       tuple = heap_formtuple(desc, datum, nullarr);
+/*
+ * ALTER TABLE ALTER COLUMN SET STATISTICS / STORAGE
+ */
+void
+AlterTableAlterColumnFlags(Oid myrelid,
+                          bool inh, const char *colName,
+                          Node *flagValue, const char *flagType)
+{
+   Relation    rel;
+   int         newtarget = 1;
+   char        newstorage = 'p';
+   Relation    attrelation;
+   HeapTuple   tuple;
+   Form_pg_attribute attrtuple;
+
+   rel = heap_open(myrelid, AccessExclusiveLock);
 
-       heap_insert(relation, tuple);
+   if (rel->rd_rel->relkind != RELKIND_RELATION)
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
+            RelationGetRelationName(rel));
 
-       if (RelationGetForm(relation)->relhasindex)
-       {
-           Relation    idescs[Num_pg_inherits_indices];
+   /*
+    * we allow statistics case for system tables
+    */
+   if (*flagType != 'S' && !allowSystemTableMods && IsSystemRelation(rel))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
+            RelationGetRelationName(rel));
 
-           CatalogOpenIndices(Num_pg_inherits_indices, Name_pg_inherits_indices, idescs);
-           CatalogIndexInsert(idescs, Num_pg_inherits_indices, relation, tuple);
-           CatalogCloseIndices(Num_pg_inherits_indices, idescs);
-       }
+   if (!pg_class_ownercheck(myrelid, GetUserId()))
+       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
+            RelationGetRelationName(rel));
 
-       heap_freetuple(tuple);
+   /*
+    * Check the supplied parameters before anything else
+    */
+   if (*flagType == 'S')
+   {
+       /* STATISTICS */
+       Assert(IsA(flagValue, Integer));
+       newtarget = intVal(flagValue);
 
-       seqNumber += 1;
+       /*
+        * Limit target to sane range (should we raise an error instead?)
+        */
+       if (newtarget < 0)
+           newtarget = 0;
+       else if (newtarget > 1000)
+           newtarget = 1000;
    }
+   else if (*flagType == 'M')
+   {
+       /* STORAGE */
+       char        *storagemode;
 
-   heap_close(relation, RowExclusiveLock);
+       Assert(IsA(flagValue, String));
+       storagemode = strVal(flagValue);
 
-   /* ----------------
-    * Expand supers list to include indirect ancestors as well.
-    *
-    * Algorithm:
-    *  0. begin with list of direct superclasses.
-    *  1. append after each relationId, its superclasses, recursively.
-    *  2. remove all but last of duplicates.
-    * ----------------
-    */
+       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
+           elog(ERROR, "ALTER TABLE: \"%s\" storage not recognized",
+                storagemode);
+   }
+   else
+   {
+       elog(ERROR, "ALTER TABLE: Invalid column flag: %c",
+            (int) *flagType);
+   }
 
    /*
-    * 1. append after each relationId, its superclasses, recursively.
+    * Propagate to children if desired
     */
-   foreach(entry, supers)
+   if (inh)
    {
-       HeapTuple   tuple;
-       Oid         id;
-       int16       number;
-       List       *next;
-       List       *current;
+       List       *child,
+                  *children;
 
-       id = (Oid) lfirsti(entry);
-       current = entry;
-       next = lnext(entry);
+       /* this routine is actually in the planner */
+       children = find_all_inheritors(myrelid);
 
-       for (number = 1;; number += 1)
+       /*
+        * 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)
        {
-           tuple = SearchSysCache(INHRELID,
-                                  ObjectIdGetDatum(id),
-                                  Int16GetDatum(number),
-                                  0, 0);
-           if (!HeapTupleIsValid(tuple))
-               break;
-
-           lnext(current) = lconsi(((Form_pg_inherits)
-                                    GETSTRUCT(tuple))->inhparent,
-                                   NIL);
-
-           ReleaseSysCache(tuple);
+           Oid         childrelid = lfirsti(child);
 
-           current = lnext(current);
+           if (childrelid == myrelid)
+               continue;
+           AlterTableAlterColumnFlags(childrelid,
+                                      false, colName, flagValue, flagType);
        }
-       lnext(current) = next;
    }
 
+   /* -= now do the thing on this relation =- */
+
+   attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
+
+   tuple = SearchSysCacheCopy(ATTNAME,
+                              ObjectIdGetDatum(myrelid),
+                              PointerGetDatum(colName),
+                              0, 0);
+   if (!HeapTupleIsValid(tuple))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" has no column \"%s\"",
+            RelationGetRelationName(rel), colName);
+   attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
+
+   if (attrtuple->attnum < 0)
+       elog(ERROR, "ALTER TABLE: cannot change system attribute \"%s\"",
+            colName);
    /*
-    * 2. remove all but last of duplicates.
+    * Now change the appropriate field
     */
-   foreach(entry, supers)
+   if (*flagType == 'S')
+       attrtuple->attstattarget = newtarget;
+   else if (*flagType == 'M')
    {
-       Oid         thisone;
-       bool        found;
-       List       *rest;
-
-again:
-       thisone = lfirsti(entry);
-       found = false;
-       foreach(rest, lnext(entry))
-       {
-           if (thisone == lfirsti(rest))
-           {
-               found = true;
-               break;
-           }
-       }
-       if (found)
-       {
-           /*
-            * found a later duplicate, so remove this entry.
-            */
-           lfirsti(entry) = lfirsti(lnext(entry));
-           lnext(entry) = lnext(lnext(entry));
-
-           goto again;
-       }
+       /*
+        * safety check: do not allow toasted storage modes unless column
+        * datatype is TOAST-aware.
+        */
+       if (newstorage == 'p' || TypeIsToastable(attrtuple->atttypid))
+           attrtuple->attstorage = newstorage;
+       else
+           elog(ERROR, "ALTER TABLE: Column datatype %s can only have storage \"plain\"",
+                format_type_be(attrtuple->atttypid));
    }
-}
 
-/*
- * Look for an existing schema entry with the given name.
- *
- * Returns the index (starting with 1) if attribute already exists in schema,
- * 0 if it doesn't.
- */
-static int
-findAttrByName(const char *attributeName, List *schema)
-{
-   List       *s;
-   int         i = 0;
+   simple_heap_update(attrelation, &tuple->t_self, tuple);
 
-   foreach(s, schema)
+   /* keep system catalog indices current */
    {
-       ColumnDef  *def = lfirst(s);
+       Relation    irelations[Num_pg_attr_indices];
 
-       ++i;
-       if (strcmp(attributeName, def->colname) == 0)
-           return i;
+       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations);
+       CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, tuple);
+       CatalogCloseIndices(Num_pg_attr_indices, irelations);
    }
-   return 0;
+
+   heap_freetuple(tuple);
+   heap_close(attrelation, NoLock);
+   heap_close(rel, NoLock);    /* close rel, but keep lock! */
 }
 
+
 /*
- * Update a relation's pg_class.relhassubclass entry to the given value
+ * ALTER TABLE DROP COLUMN
  */
-static void
-setRelhassubclassInRelation(Oid relationId, bool relhassubclass)
+void
+AlterTableDropColumn(Oid myrelid,
+                    bool inh, const char *colName,
+                    int behavior)
 {
-   Relation    relationRelation;
-   HeapTuple   tuple;
-   Relation    idescs[Num_pg_class_indices];
-
-   /*
-    * Fetch a modifiable copy of the tuple, modify it, update pg_class.
-    */
-   relationRelation = heap_openr(RelationRelationName, RowExclusiveLock);
-   tuple = SearchSysCacheCopy(RELOID,
-                              ObjectIdGetDatum(relationId),
-                              0, 0, 0);
-   if (!HeapTupleIsValid(tuple))
-       elog(ERROR, "setRelhassubclassInRelation: cache lookup failed for relation %u", relationId);
-
-   ((Form_pg_class) GETSTRUCT(tuple))->relhassubclass = relhassubclass;
-   simple_heap_update(relationRelation, &tuple->t_self, tuple);
-
-   /* keep the catalog indices up to date */
-   CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs);
-   CatalogIndexInsert(idescs, Num_pg_class_indices, relationRelation, tuple);
-   CatalogCloseIndices(Num_pg_class_indices, idescs);
-
-   heap_freetuple(tuple);
-   heap_close(relationRelation, RowExclusiveLock);
+   elog(ERROR, "ALTER TABLE / DROP COLUMN is not implemented");
 }
 
 
 /*
- *     renameatt       - changes the name of a attribute in a relation
- *
- *     Attname attribute is changed in attribute catalog.
- *     No record of the previous attname is kept (correct?).
- *
- *     get proper relrelation from relation catalog (if not arg)
- *     scan attribute catalog
- *             for name conflict (within rel)
- *             for original attribute (if not arg)
- *     modify attname in attribute tuple
- *     insert modified attribute in attribute catalog
- *     delete original attribute from attribute catalog
+ * ALTER TABLE ADD CONSTRAINT
  */
 void
-renameatt(Oid relid,
-         const char *oldattname,
-         const char *newattname,
-         bool recurse)
-{
-   Relation    targetrelation;
-   Relation    attrelation;
-   HeapTuple   atttup;
-   List       *indexoidlist;
-   List       *indexoidscan;
+AlterTableAddConstraint(Oid myrelid,
+                       bool inh, List *newConstraints)
+{
+   Relation    rel;
+   List       *listptr;
 
    /*
     * Grab an exclusive lock on the target table, which we will NOT
     * release until end of transaction.
     */
-   targetrelation = heap_open(relid, AccessExclusiveLock);
+   rel = heap_open(myrelid, AccessExclusiveLock);
 
-   /*
-    * 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.
-    */
-   if (!allowSystemTableMods 
-       && IsSystemRelation(targetrelation))
-       elog(ERROR, "renameatt: class \"%s\" is a system catalog",
-            RelationGetRelationName(targetrelation));
-   if (!pg_class_ownercheck(relid, GetUserId()))
-       elog(ERROR, "renameatt: you do not own class \"%s\"",
-            RelationGetRelationName(targetrelation));
+   if (rel->rd_rel->relkind != RELKIND_RELATION)
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
+            RelationGetRelationName(rel));
 
-   /*
-    * if the 'recurse' flag is set then we are supposed to rename this
-    * attribute in all classes that inherit from 'relname' (as well as in
-    * 'relname').
-    *
-    * any permissions or problems with duplicate attributes will cause the
-    * whole transaction to abort, which is what we want -- all or
-    * nothing.
-    */
-   if (recurse)
+   if (!allowSystemTableMods
+       && IsSystemRelation(rel))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
+            RelationGetRelationName(rel));
+
+   if (!pg_class_ownercheck(myrelid, GetUserId()))
+       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
+            RelationGetRelationName(rel));
+
+   if (inh)
    {
        List       *child,
                   *children;
 
        /* this routine is actually in the planner */
-       children = find_all_inheritors(relid);
+       children = find_all_inheritors(myrelid);
 
        /*
         * find_all_inheritors does the recursive search of the
@@ -2625,571 +2390,686 @@ renameatt(Oid relid,
        {
            Oid         childrelid = lfirsti(child);
 
-           if (childrelid == relid)
+           if (childrelid == myrelid)
                continue;
-           /* note we need not recurse again! */
-           renameatt(childrelid, oldattname, newattname, false);
+           AlterTableAddConstraint(childrelid, false, newConstraints);
        }
    }
 
-   attrelation = heap_openr(AttributeRelationName, RowExclusiveLock);
+   foreach(listptr, newConstraints)
+   {
+       Node       *newConstraint = lfirst(listptr);
 
-   atttup = SearchSysCacheCopy(ATTNAME,
-                               ObjectIdGetDatum(relid),
-                               PointerGetDatum(oldattname),
-                               0, 0);
-   if (!HeapTupleIsValid(atttup))
-       elog(ERROR, "renameatt: attribute \"%s\" does not exist", oldattname);
+       switch (nodeTag(newConstraint))
+       {
+           case T_Constraint:
+               {
+                   Constraint *constr = (Constraint *) newConstraint;
 
-   if (((Form_pg_attribute) GETSTRUCT(atttup))->attnum < 0)
-       elog(ERROR, "renameatt: system attribute \"%s\" not renamed", oldattname);
+                   /*
+                    * 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:
+                           {
+                               ParseState *pstate;
+                               bool        successful = true;
+                               HeapScanDesc scan;
+                               ExprContext *econtext;
+                               TupleTableSlot *slot;
+                               HeapTuple   tuple;
+                               RangeTblEntry *rte;
+                               List       *qual;
+                               Node       *expr;
+                               char       *name;
 
-   /* should not already exist */
-   if (SearchSysCacheExists(ATTNAME,
-                            ObjectIdGetDatum(relid),
-                            PointerGetDatum(newattname),
-                            0, 0))
-       elog(ERROR, "renameatt: attribute \"%s\" exists", newattname);
+                               if (constr->name)
+                                   name = constr->name;
+                               else
+                                   name = "";
+
+                               /*
+                                * We need to make a parse state and range
+                                * table to allow us to transformExpr and
+                                * fix_opids to get a version of the
+                                * expression we can pass to ExecQual
+                                */
+                               pstate = make_parsestate(NULL);
+                               rte = addRangeTableEntryForRelation(pstate,
+                                                                   myrelid,
+                                           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.
+                                */
+                               if (exprType(expr) != BOOLOID)
+                                   elog(ERROR, "CHECK '%s' does not yield boolean result",
+                                        name);
+
+                               /*
+                                * Make sure no outside relations are
+                                * referred to.
+                                */
+                               if (length(pstate->p_rtable) != 1)
+                                   elog(ERROR, "Only relation '%s' can be referenced in CHECK",
+                                        RelationGetRelationName(rel));
+
+                               /*
+                                * Might as well try to reduce any
+                                * constant expressions.
+                                */
+                               expr = eval_const_expressions(expr);
+
+                               /* And fix the opids */
+                               fix_opids(expr);
+
+                               qual = makeList1(expr);
+
+                               /* Make tuple slot to hold tuples */
+                               slot = MakeTupleTableSlot();
+                               ExecSetSlotDescriptor(slot, RelationGetDescr(rel), false);
+                               /* Make an expression context for ExecQual */
+                               econtext = MakeExprContext(slot, CurrentMemoryContext);
+
+                               /*
+                                * Scan through the rows now, checking the
+                                * expression at each row.
+                                */
+                               scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
+
+                               while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+                               {
+                                   ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+                                   if (!ExecQual(qual, econtext, true))
+                                   {
+                                       successful = false;
+                                       break;
+                                   }
+                                   ResetExprContext(econtext);
+                               }
+
+                               heap_endscan(scan);
+
+                               FreeExprContext(econtext);
+                               pfree(slot);
+
+                               if (!successful)
+                                   elog(ERROR, "AlterTableAddConstraint: rejected due to CHECK constraint %s", 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));
+
+                               break;
+                           }
+                       default:
+                           elog(ERROR, "ALTER TABLE / ADD CONSTRAINT is not implemented for that constraint type.");
+                   }
+                   break;
+               }
+           case T_FkConstraint:
+               {
+                   FkConstraint *fkconstraint = (FkConstraint *) newConstraint;
+                   Relation    pkrel;
+                   HeapScanDesc scan;
+                   HeapTuple   tuple;
+                   Trigger     trig;
+                   List       *list;
+                   int         count;
+
+                   /*
+                    * Grab an exclusive lock on the pk table, so that
+                    * someone doesn't delete rows out from under us.
+                    *
+                    * XXX wouldn't a lesser lock be sufficient?
+                    */
+                   pkrel = heap_openrv(fkconstraint->pktable,
+                                       AccessExclusiveLock);
+
+                   /*
+                    * Validity checks
+                    */
+                   if (pkrel->rd_rel->relkind != RELKIND_RELATION)
+                       elog(ERROR, "referenced table \"%s\" not a relation",
+                            fkconstraint->pktable->relname);
+
+                   if (isTempNamespace(RelationGetNamespace(pkrel)) &&
+                       !isTempNamespace(RelationGetNamespace(rel)))
+                       elog(ERROR, "ALTER TABLE / ADD CONSTRAINT: Unable to reference temporary table from permanent table constraint.");
+
+                   /*
+                    * First we check for limited correctness of the
+                    * constraint.
+                    *
+                    * NOTE: we assume parser has already checked for
+                    * existence of an appropriate unique index on the
+                    * referenced relation, and that the column datatypes
+                    * are comparable.
+                    *
+                    * Scan through each tuple, calling RI_FKey_check_ins
+                    * (insert trigger) as if that tuple had just been
+                    * inserted.  If any of those fail, it should
+                    * elog(ERROR) and that's that.
+                    */
+                   MemSet(&trig, 0, sizeof(trig));
+                   trig.tgoid = InvalidOid;
+                   if (fkconstraint->constr_name)
+                       trig.tgname = fkconstraint->constr_name;
+                   else
+                       trig.tgname = "";
+                   trig.tgenabled = TRUE;
+                   trig.tgisconstraint = TRUE;
+                   trig.tgconstrrelid = RelationGetRelid(pkrel);
+                   trig.tgdeferrable = FALSE;
+                   trig.tginitdeferred = FALSE;
+
+                   trig.tgargs = (char **) palloc(
+                    sizeof(char *) * (4 + length(fkconstraint->fk_attrs)
+                                      + length(fkconstraint->pk_attrs)));
+
+                   trig.tgargs[0] = trig.tgname;
+                   trig.tgargs[1] = RelationGetRelationName(rel);
+                   trig.tgargs[2] = RelationGetRelationName(pkrel);
+                   trig.tgargs[3] = fkconstraint->match_type;
+                   count = 4;
+                   foreach(list, fkconstraint->fk_attrs)
+                   {
+                       Ident      *fk_at = lfirst(list);
 
-   namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname),
-              newattname);
+                       trig.tgargs[count] = fk_at->name;
+                       count += 2;
+                   }
+                   count = 5;
+                   foreach(list, fkconstraint->pk_attrs)
+                   {
+                       Ident      *pk_at = lfirst(list);
 
-   simple_heap_update(attrelation, &atttup->t_self, atttup);
+                       trig.tgargs[count] = pk_at->name;
+                       count += 2;
+                   }
+                   trig.tgnargs = count - 1;
 
-   /* keep system catalog indices current */
-   {
-       Relation    irelations[Num_pg_attr_indices];
+                   scan = heap_beginscan(rel, false, SnapshotNow, 0, NULL);
 
-       CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations);
-       CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup);
-       CatalogCloseIndices(Num_pg_attr_indices, irelations);
-   }
+                   while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
+                   {
+                       /* Make a call to the check function */
 
-   heap_freetuple(atttup);
+                       /*
+                        * No parameters are passed, but we do set a
+                        * context
+                        */
+                       FunctionCallInfoData fcinfo;
+                       TriggerData trigdata;
 
-   /*
-    * Update column names of indexes that refer to the column being
-    * renamed.
-    */
-   indexoidlist = RelationGetIndexList(targetrelation);
+                       MemSet(&fcinfo, 0, sizeof(fcinfo));
 
-   foreach(indexoidscan, indexoidlist)
-   {
-       Oid         indexoid = lfirsti(indexoidscan);
-       HeapTuple   indextup;
+                       /*
+                        * We assume RI_FKey_check_ins won't look at
+                        * flinfo...
+                        */
 
-       /*
-        * First check to see if index is a functional index. If so, its
-        * column name is a function name and shouldn't be renamed here.
-        */
-       indextup = SearchSysCache(INDEXRELID,
-                                 ObjectIdGetDatum(indexoid),
-                                 0, 0, 0);
-       if (!HeapTupleIsValid(indextup))
-           elog(ERROR, "renameatt: can't find index id %u", indexoid);
-       if (OidIsValid(((Form_pg_index) GETSTRUCT(indextup))->indproc))
-       {
-           ReleaseSysCache(indextup);
-           continue;
-       }
-       ReleaseSysCache(indextup);
+                       trigdata.type = T_TriggerData;
+                       trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
+                       trigdata.tg_relation = rel;
+                       trigdata.tg_trigtuple = tuple;
+                       trigdata.tg_newtuple = NULL;
+                       trigdata.tg_trigger = &trig;
 
-       /*
-        * Okay, look to see if any column name of the index matches the
-        * old attribute name.
-        */
-       atttup = SearchSysCacheCopy(ATTNAME,
-                                   ObjectIdGetDatum(indexoid),
-                                   PointerGetDatum(oldattname),
-                                   0, 0);
-       if (!HeapTupleIsValid(atttup))
-           continue;           /* Nope, so ignore it */
+                       fcinfo.context = (Node *) &trigdata;
 
-       /*
-        * Update the (copied) attribute tuple.
-        */
-       namestrcpy(&(((Form_pg_attribute) GETSTRUCT(atttup))->attname),
-                  newattname);
+                       RI_FKey_check_ins(&fcinfo);
+                   }
+                   heap_endscan(scan);
 
-       simple_heap_update(attrelation, &atttup->t_self, atttup);
+                   pfree(trig.tgargs);
 
-       /* keep system catalog indices current */
-       {
-           Relation    irelations[Num_pg_attr_indices];
+                   heap_close(pkrel, NoLock);
 
-           CatalogOpenIndices(Num_pg_attr_indices, Name_pg_attr_indices, irelations);
-           CatalogIndexInsert(irelations, Num_pg_attr_indices, attrelation, atttup);
-           CatalogCloseIndices(Num_pg_attr_indices, irelations);
+                   break;
+               }
+           default:
+               elog(ERROR, "ALTER TABLE / ADD CONSTRAINT unable to determine type of constraint passed");
        }
-       heap_freetuple(atttup);
-   }
-
-   freeList(indexoidlist);
-
-   heap_close(attrelation, RowExclusiveLock);
-
-   /*
-    * Update att name in any RI triggers associated with the relation.
-    */
-   if (targetrelation->rd_rel->reltriggers > 0)
-   {
-       /* update tgargs column reference where att is primary key */
-       update_ri_trigger_args(RelationGetRelid(targetrelation),
-                              oldattname, newattname,
-                              false, false);
-       /* update tgargs column reference where att is foreign key */
-       update_ri_trigger_args(RelationGetRelid(targetrelation),
-                              oldattname, newattname,
-                              true, false);
    }
 
-   heap_close(targetrelation, NoLock); /* close rel but keep lock! */
+   /* Close rel, but keep lock till commit */
+   heap_close(rel, NoLock);
 }
 
+
 /*
- *     renamerel       - change the name of a relation
- *
- *     XXX - When renaming sequences, we don't bother to modify the
- *           sequence name that is stored within the sequence itself
- *           (this would cause problems with MVCC). In the future,
- *           the sequence name should probably be removed from the
- *           sequence, AFAIK there's no need for it to be there.
+ * ALTER TABLE DROP CONSTRAINT
+ * Note: It is legal to remove a constraint with name "" as it is possible
+ * to add a constraint with name "".
+ * Christopher Kings-Lynne
  */
 void
-renamerel(Oid relid, const char *newrelname)
+AlterTableDropConstraint(Oid myrelid,
+                        bool inh, const char *constrName,
+                        int behavior)
 {
-   Relation    targetrelation;
-   Relation    relrelation;    /* for RELATION relation */
-   HeapTuple   reltup;
-   Oid         namespaceId;
-   char       *oldrelname;
-   char        relkind;
-   bool        relhastriggers;
-   Relation    irelations[Num_pg_class_indices];
+   Relation    rel;
+   int         deleted;
 
    /*
-    * Grab an exclusive lock on the target table or index, which we will
-    * NOT release until end of transaction.
+    * We don't support CASCADE yet  - in fact, RESTRICT doesn't work to
+    * the spec either!
     */
-   targetrelation = relation_open(relid, AccessExclusiveLock);
-
-   oldrelname = pstrdup(RelationGetRelationName(targetrelation));
-   namespaceId = RelationGetNamespace(targetrelation);
-
-   /* Validity checks */
-   if (!allowSystemTableMods &&
-       IsSystemRelation(targetrelation))
-       elog(ERROR, "renamerel: system relation \"%s\" may not be renamed",
-            oldrelname);
-
-   relkind = targetrelation->rd_rel->relkind;
-   relhastriggers = (targetrelation->rd_rel->reltriggers > 0);
+   if (behavior == CASCADE)
+       elog(ERROR, "ALTER TABLE / DROP CONSTRAINT does not support the CASCADE keyword");
 
    /*
-    * Find relation's pg_class tuple, and make sure newrelname isn't in
-    * use.
+    * Acquire an exclusive lock on the target relation for the duration
+    * of the operation.
     */
-   relrelation = heap_openr(RelationRelationName, RowExclusiveLock);
+   rel = heap_open(myrelid, AccessExclusiveLock);
 
-   reltup = SearchSysCacheCopy(RELOID,
-                               PointerGetDatum(relid),
-                               0, 0, 0);
-   if (!HeapTupleIsValid(reltup))
-       elog(ERROR, "renamerel: relation \"%s\" does not exist",
-            oldrelname);
+   /* Disallow DROP CONSTRAINT on views, indexes, sequences, etc */
+   if (rel->rd_rel->relkind != RELKIND_RELATION)
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
+            RelationGetRelationName(rel));
 
-   if (get_relname_relid(newrelname, namespaceId) != InvalidOid)
-       elog(ERROR, "renamerel: relation \"%s\" exists", newrelname);
+   if (!allowSystemTableMods
+       && IsSystemRelation(rel))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is a system catalog",
+            RelationGetRelationName(rel));
+
+   if (!pg_class_ownercheck(myrelid, GetUserId()))
+       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
+            RelationGetRelationName(rel));
 
    /*
-    * Update pg_class tuple with new relname.  (Scribbling on reltup is
-    * OK because it's a copy...)
+    * Since all we have is the name of the constraint, we have to look
+    * through all catalogs that could possibly contain a constraint for
+    * this relation. We also keep a count of the number of constraints
+    * removed.
     */
-   namestrcpy(&(((Form_pg_class) GETSTRUCT(reltup))->relname), newrelname);
-
-   simple_heap_update(relrelation, &reltup->t_self, reltup);
-
-   /* keep the system catalog indices current */
-   CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, irelations);
-   CatalogIndexInsert(irelations, Num_pg_class_indices, relrelation, reltup);
-   CatalogCloseIndices(Num_pg_class_indices, irelations);
 
-   heap_close(relrelation, NoLock);
-   heap_freetuple(reltup);
+   deleted = 0;
 
    /*
-    * Also rename the associated type, if any.
+    * First, we remove all CHECK constraints with the given name
     */
-   if (relkind != RELKIND_INDEX)
-       TypeRename(oldrelname, namespaceId, newrelname);
 
-   /*
-    * Update rel name in any RI triggers associated with the relation.
-    */
-   if (relhastriggers)
-   {
-       /* update tgargs where relname is primary key */
-       update_ri_trigger_args(relid,
-                              oldrelname,
-                              newrelname,
-                              false, true);
-       /* update tgargs where relname is foreign key */
-       update_ri_trigger_args(relid,
-                              oldrelname,
-                              newrelname,
-                              true, true);
-   }
+   deleted += RemoveCheckConstraint(rel, constrName, inh);
 
    /*
-    * Close rel, but keep exclusive lock!
+    * Now we remove NULL, UNIQUE, PRIMARY KEY and FOREIGN KEY
+    * constraints.
+    *
+    * Unimplemented.
     */
-   relation_close(targetrelation, NoLock);
-}
 
-/*
- *     renametrig      - changes the name of a trigger on a relation
- *
- *     trigger name is changed in trigger catalog.
- *     No record of the previous name is kept.
- *
- *     get proper relrelation from relation catalog (if not arg)
- *     scan trigger catalog
- *             for name conflict (within rel)
- *             for original trigger (if not arg)
- *     modify tgname in trigger tuple
- *     insert modified trigger in trigger catalog
- *     delete original trigger from trigger catalog
+   /* Close the target relation */
+   heap_close(rel, NoLock);
+
+   /* If zero constraints deleted, complain */
+   if (deleted == 0)
+       elog(ERROR, "ALTER TABLE / DROP CONSTRAINT: %s does not exist",
+            constrName);
+   /* Otherwise if more than one constraint deleted, notify */
+   else if (deleted > 1)
+       elog(NOTICE, "Multiple constraints dropped");
+}
+
+/*
+ * ALTER TABLE OWNER
  */
-extern void renametrig(Oid relid,
-         const char *oldname,
-         const char *newname)
+void
+AlterTableOwner(Oid relationOid, int32 newOwnerSysId)
 {
-   Relation    targetrel;
-   Relation    tgrel;
-   HeapTuple   tuple;
-   SysScanDesc tgscan;
-   ScanKeyData key;
-   bool        found = FALSE;
-   Relation    idescs[Num_pg_trigger_indices];
+   Relation        target_rel;
+   Relation        class_rel;
+   HeapTuple       tuple;
+   Relation        idescs[Num_pg_class_indices];
+   Form_pg_class   tuple_class;
 
-   /*
-    * Grab an exclusive lock on the target table, which we will NOT
-    * release until end of transaction.
-    */
-   targetrel = heap_open(relid, AccessExclusiveLock);
+   /* Get exclusive lock till end of transaction on the target table */
+   target_rel = heap_open(relationOid, AccessExclusiveLock);
 
-   /*
-    * Scan pg_trigger twice for existing triggers on relation.  We do this in
-    * order to ensure a trigger does not exist with newname (The unique index
-    * on tgrelid/tgname would complain anyway) and to ensure a trigger does
-    * exist with oldname.
-    *
-    * NOTE that this is cool only because we have AccessExclusiveLock on the
-    * relation, so the trigger set won't be changing underneath us.
-    */
-   tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
+   /* Get its pg_class tuple, too */
+   class_rel = heap_openr(RelationRelationName, RowExclusiveLock);
+
+   tuple = SearchSysCacheCopy(RELOID,
+                              ObjectIdGetDatum(relationOid),
+                              0, 0, 0);
+   if (!HeapTupleIsValid(tuple))
+       elog(ERROR, "ALTER TABLE: relation %u not found", relationOid);
+   tuple_class = (Form_pg_class) GETSTRUCT(tuple);
+
+   /* Can we change the ownership of this tuple? */
+   CheckTupleType(tuple_class);
 
    /*
-    * First pass -- look for name conflict
+    * Okay, this is a valid tuple: change its ownership and
+    * write to the heap.
     */
-   ScanKeyEntryInitialize(&key, 0,
-                          Anum_pg_trigger_tgrelid,
-                          F_OIDEQ,
-                          ObjectIdGetDatum(relid));
-   tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
-                               SnapshotNow, 1, &key);
-   while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
-   {
-       Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+   tuple_class->relowner = newOwnerSysId;
+   simple_heap_update(class_rel, &tuple->t_self, tuple);
 
-       if (namestrcmp(&(pg_trigger->tgname), newname) == 0)
-           elog(ERROR, "renametrig: trigger %s already defined on relation %s",
-                newname, RelationGetRelationName(targetrel));
-   }
-   systable_endscan(tgscan);
+   /* Keep the catalog indices up to date */
+   CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, idescs);
+   CatalogIndexInsert(idescs, Num_pg_class_indices, class_rel, tuple);
+   CatalogCloseIndices(Num_pg_class_indices, idescs);
 
    /*
-    * Second pass -- look for trigger existing with oldname and update
+    * If we are operating on a table, also change the ownership of any
+    * indexes that belong to the table, as well as the table's toast
+    * table (if it has one)
     */
-   ScanKeyEntryInitialize(&key, 0,
-                          Anum_pg_trigger_tgrelid,
-                          F_OIDEQ,
-                          ObjectIdGetDatum(relid));
-   tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
-                               SnapshotNow, 1, &key);
-   while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+   if (tuple_class->relkind == RELKIND_RELATION ||
+       tuple_class->relkind == RELKIND_TOASTVALUE)
    {
-       Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+       List *index_oid_list, *i;
 
-       if (namestrcmp(&(pg_trigger->tgname), oldname) == 0)
-       {
-           /*
-            * Update pg_trigger tuple with new tgname.
-            * (Scribbling on tuple is OK because it's a copy...)
-            */
-           namestrcpy(&(pg_trigger->tgname), newname);
-           simple_heap_update(tgrel, &tuple->t_self, tuple);
+       /* Find all the indexes belonging to this relation */
+       index_oid_list = RelationGetIndexList(target_rel);
 
-           /*
-            * keep system catalog indices current
-            */
-           CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, idescs);
-           CatalogIndexInsert(idescs, Num_pg_trigger_indices, tgrel, tuple);
-           CatalogCloseIndices(Num_pg_trigger_indices, idescs);
+       /* For each index, recursively change its ownership */
+       foreach(i, index_oid_list)
+       {
+           AlterTableOwner(lfirsti(i), newOwnerSysId);
+       }
 
-           /*
-            * Invalidate relation's relcache entry so that other
-            * backends (and this one too!) are sent SI message to make them
-            * rebuild relcache entries.
-            */
-           CacheInvalidateRelcache(relid);
+       freeList(index_oid_list);
+   }
 
-           found = TRUE;
-           break;
+   if (tuple_class->relkind == RELKIND_RELATION)
+   {
+       /* If it has a toast table, recurse to change its ownership */
+       if (tuple_class->reltoastrelid != InvalidOid)
+       {
+           AlterTableOwner(tuple_class->reltoastrelid, newOwnerSysId);
        }
    }
-   systable_endscan(tgscan);
-
-   heap_close(tgrel, RowExclusiveLock);
-
-   if (!found)
-       elog(ERROR, "renametrig: trigger %s not defined on relation %s",
-            oldname, RelationGetRelationName(targetrel));
 
-   /*
-    * Close rel, but keep exclusive lock!
-    */
-   heap_close(targetrel, NoLock);
+   heap_freetuple(tuple);
+   heap_close(class_rel, RowExclusiveLock);
+   heap_close(target_rel, NoLock);
 }
 
-
-/*
- * Given a trigger function OID, determine whether it is an RI trigger,
- * and if so whether it is attached to PK or FK relation.
- *
- * XXX this probably doesn't belong here; should be exported by
- * ri_triggers.c
- */
-static int
-ri_trigger_type(Oid tgfoid)
+static void
+CheckTupleType(Form_pg_class tuple_class)
 {
-   switch (tgfoid)
+   switch (tuple_class->relkind)
    {
-       case F_RI_FKEY_CASCADE_DEL:
-       case F_RI_FKEY_CASCADE_UPD:
-       case F_RI_FKEY_RESTRICT_DEL:
-       case F_RI_FKEY_RESTRICT_UPD:
-       case F_RI_FKEY_SETNULL_DEL:
-       case F_RI_FKEY_SETNULL_UPD:
-       case F_RI_FKEY_SETDEFAULT_DEL:
-       case F_RI_FKEY_SETDEFAULT_UPD:
-       case F_RI_FKEY_NOACTION_DEL:
-       case F_RI_FKEY_NOACTION_UPD:
-           return RI_TRIGGER_PK;
-
-       case F_RI_FKEY_CHECK_INS:
-       case F_RI_FKEY_CHECK_UPD:
-           return RI_TRIGGER_FK;
+       case RELKIND_RELATION:
+       case RELKIND_INDEX:
+       case RELKIND_VIEW:
+       case RELKIND_SEQUENCE:
+       case RELKIND_TOASTVALUE:
+           /* ok to change owner */
+           break;
+       default:
+           elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table, TOAST table, index, view, or sequence",
+                NameStr(tuple_class->relname));
    }
-
-   return RI_TRIGGER_NONE;
 }
 
 /*
- * Scan pg_trigger for RI triggers that are on the specified relation
- * (if fk_scan is false) or have it as the tgconstrrel (if fk_scan
- * is true).  Update RI trigger args fields matching oldname to contain
- * newname instead.  If update_relname is true, examine the relname
- * fields; otherwise examine the attname fields.
+ * ALTER TABLE CREATE TOAST TABLE
  */
-static void
-update_ri_trigger_args(Oid relid,
-                      const char *oldname,
-                      const char *newname,
-                      bool fk_scan,
-                      bool update_relname)
+void
+AlterTableCreateToastTable(Oid relOid, bool silent)
 {
-   Relation    tgrel;
-   Relation    irel;
-   ScanKeyData skey[1];
-   IndexScanDesc idxtgscan;
-   RetrieveIndexResult idxres;
-   Datum       values[Natts_pg_trigger];
-   char        nulls[Natts_pg_trigger];
-   char        replaces[Natts_pg_trigger];
+   Relation    rel;
+   HeapTuple   reltup;
+   HeapTupleData classtuple;
+   TupleDesc   tupdesc;
+   Relation    class_rel;
+   Buffer      buffer;
+   Relation    ridescs[Num_pg_class_indices];
+   Oid         toast_relid;
+   Oid         toast_idxid;
+   char        toast_relname[NAMEDATALEN];
+   char        toast_idxname[NAMEDATALEN];
+   IndexInfo  *indexInfo;
+   Oid         classObjectId[2];
 
-   tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
-   if (fk_scan)
-       irel = index_openr(TriggerConstrRelidIndex);
-   else
-       irel = index_openr(TriggerRelidNameIndex);
+   /*
+    * Grab an exclusive lock on the target table, which we will NOT
+    * release until end of transaction.
+    */
+   rel = heap_open(relOid, AccessExclusiveLock);
 
-   ScanKeyEntryInitialize(&skey[0], 0x0,
-                          1,   /* column 1 of index in either case */
-                          F_OIDEQ,
-                          ObjectIdGetDatum(relid));
-   idxtgscan = index_beginscan(irel, false, 1, skey);
+   if (rel->rd_rel->relkind != RELKIND_RELATION)
+       elog(ERROR, "ALTER TABLE: relation \"%s\" is not a table",
+            RelationGetRelationName(rel));
 
-   while ((idxres = index_getnext(idxtgscan, ForwardScanDirection)) != NULL)
-   {
-       HeapTupleData tupledata;
-       Buffer      buffer;
-       HeapTuple   tuple;
-       Form_pg_trigger pg_trigger;
-       bytea      *val;
-       bytea      *newtgargs;
-       bool        isnull;
-       int         tg_type;
-       bool        examine_pk;
-       bool        changed;
-       int         tgnargs;
-       int         i;
-       int         newlen;
-       const char *arga[RI_MAX_ARGUMENTS];
-       const char *argp;
+   if (!pg_class_ownercheck(relOid, GetUserId()))
+       elog(ERROR, "ALTER TABLE: \"%s\": permission denied",
+            RelationGetRelationName(rel));
 
-       tupledata.t_self = idxres->heap_iptr;
-       heap_fetch(tgrel, SnapshotNow, &tupledata, &buffer, idxtgscan);
-       pfree(idxres);
-       if (!tupledata.t_data)
-           continue;
-       tuple = &tupledata;
-       pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
-       tg_type = ri_trigger_type(pg_trigger->tgfoid);
-       if (tg_type == RI_TRIGGER_NONE)
-       {
-           /* Not an RI trigger, forget it */
-           ReleaseBuffer(buffer);
-           continue;
-       }
+   /*
+    * lock the pg_class tuple for update (is that really needed?)
+    */
+   class_rel = heap_openr(RelationRelationName, RowExclusiveLock);
 
-       /*
-        * It is an RI trigger, so parse the tgargs bytea.
-        *
-        * NB: we assume the field will never be compressed or moved out of
-        * line; so does trigger.c ...
-        */
-       tgnargs = pg_trigger->tgnargs;
-       val = (bytea *) fastgetattr(tuple,
-                                   Anum_pg_trigger_tgargs,
-                                   tgrel->rd_att, &isnull);
-       if (isnull || tgnargs < RI_FIRST_ATTNAME_ARGNO ||
-           tgnargs > RI_MAX_ARGUMENTS)
-       {
-           /* This probably shouldn't happen, but ignore busted triggers */
-           ReleaseBuffer(buffer);
-           continue;
-       }
-       argp = (const char *) VARDATA(val);
-       for (i = 0; i < tgnargs; i++)
+   reltup = SearchSysCache(RELOID,
+                           ObjectIdGetDatum(relOid),
+                           0, 0, 0);
+   if (!HeapTupleIsValid(reltup))
+       elog(ERROR, "ALTER TABLE: relation \"%s\" not found",
+            RelationGetRelationName(rel));
+   classtuple.t_self = reltup->t_self;
+   ReleaseSysCache(reltup);
+
+   switch (heap_mark4update(class_rel, &classtuple, &buffer))
+   {
+       case HeapTupleSelfUpdated:
+       case HeapTupleMayBeUpdated:
+           break;
+       default:
+           elog(ERROR, "couldn't lock pg_class tuple");
+   }
+   reltup = heap_copytuple(&classtuple);
+   ReleaseBuffer(buffer);
+
+   /*
+    * Is it already toasted?
+    */
+   if (((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid != InvalidOid)
+   {
+       if (silent)
        {
-           arga[i] = argp;
-           argp += strlen(argp) + 1;
+           heap_close(rel, NoLock);
+           heap_close(class_rel, NoLock);
+           heap_freetuple(reltup);
+           return;
        }
 
-       /*
-        * Figure out which item(s) to look at.  If the trigger is
-        * primary-key type and attached to my rel, I should look at the
-        * PK fields; if it is foreign-key type and attached to my rel, I
-        * should look at the FK fields.  But the opposite rule holds when
-        * examining triggers found by tgconstrrel search.
-        */
-       examine_pk = (tg_type == RI_TRIGGER_PK) == (!fk_scan);
+       elog(ERROR, "ALTER TABLE: relation \"%s\" already has a toast table",
+            RelationGetRelationName(rel));
+   }
 
-       changed = false;
-       if (update_relname)
-       {
-           /* Change the relname if needed */
-           i = examine_pk ? RI_PK_RELNAME_ARGNO : RI_FK_RELNAME_ARGNO;
-           if (strcmp(arga[i], oldname) == 0)
-           {
-               arga[i] = newname;
-               changed = true;
-           }
-       }
-       else
+   /*
+    * Check to see whether the table actually needs a TOAST table.
+    */
+   if (!needs_toast_table(rel))
+   {
+       if (silent)
        {
-           /* Change attname(s) if needed */
-           i = examine_pk ? RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_PK_IDX :
-               RI_FIRST_ATTNAME_ARGNO + RI_KEYPAIR_FK_IDX;
-           for (; i < tgnargs; i += 2)
-           {
-               if (strcmp(arga[i], oldname) == 0)
-               {
-                   arga[i] = newname;
-                   changed = true;
-               }
-           }
+           heap_close(rel, NoLock);
+           heap_close(class_rel, NoLock);
+           heap_freetuple(reltup);
+           return;
        }
 
-       if (!changed)
-       {
-           /* Don't need to update this tuple */
-           ReleaseBuffer(buffer);
-           continue;
-       }
+       elog(ERROR, "ALTER TABLE: relation \"%s\" does not need a toast table",
+            RelationGetRelationName(rel));
+   }
 
-       /*
-        * Construct modified tgargs bytea.
-        */
-       newlen = VARHDRSZ;
-       for (i = 0; i < tgnargs; i++)
-           newlen += strlen(arga[i]) + 1;
-       newtgargs = (bytea *) palloc(newlen);
-       VARATT_SIZEP(newtgargs) = newlen;
-       newlen = VARHDRSZ;
-       for (i = 0; i < tgnargs; i++)
-       {
-           strcpy(((char *) newtgargs) + newlen, arga[i]);
-           newlen += strlen(arga[i]) + 1;
-       }
+   /*
+    * Create the toast table and its index
+    */
+   sprintf(toast_relname, "pg_toast_%u", relOid);
+   sprintf(toast_idxname, "pg_toast_%u_index", relOid);
 
-       /*
-        * Build modified tuple.
-        */
-       for (i = 0; i < Natts_pg_trigger; i++)
-       {
-           values[i] = (Datum) 0;
-           replaces[i] = ' ';
-           nulls[i] = ' ';
-       }
-       values[Anum_pg_trigger_tgargs - 1] = PointerGetDatum(newtgargs);
-       replaces[Anum_pg_trigger_tgargs - 1] = 'r';
+   /* this is pretty painful...  need a tuple descriptor */
+   tupdesc = CreateTemplateTupleDesc(3);
+   TupleDescInitEntry(tupdesc, (AttrNumber) 1,
+                      "chunk_id",
+                      OIDOID,
+                      -1, 0, false);
+   TupleDescInitEntry(tupdesc, (AttrNumber) 2,
+                      "chunk_seq",
+                      INT4OID,
+                      -1, 0, false);
+   TupleDescInitEntry(tupdesc, (AttrNumber) 3,
+                      "chunk_data",
+                      BYTEAOID,
+                      -1, 0, false);
 
-       tuple = heap_modifytuple(tuple, tgrel, values, nulls, replaces);
+   /*
+    * Ensure that the toast table doesn't itself get toasted, or we'll be
+    * toast :-(.  This is essential for chunk_data because type bytea is
+    * toastable; hit the other two just to be sure.
+    */
+   tupdesc->attrs[0]->attstorage = 'p';
+   tupdesc->attrs[1]->attstorage = 'p';
+   tupdesc->attrs[2]->attstorage = 'p';
 
-       /*
-        * Now we can release hold on original tuple.
-        */
-       ReleaseBuffer(buffer);
+   /*
+    * Note: the toast relation is placed in the regular pg_toast namespace
+    * even if its master relation is a temp table.  There cannot be any
+    * naming collision, and the toast rel will be destroyed when its master
+    * is, so there's no need to handle the toast rel as temp.
+    */
+   toast_relid = heap_create_with_catalog(toast_relname,
+                                          PG_TOAST_NAMESPACE,
+                                          tupdesc,
+                                          RELKIND_TOASTVALUE,
+                                          false,
+                                          true);
 
-       /*
-        * Update pg_trigger and its indexes
-        */
-       simple_heap_update(tgrel, &tuple->t_self, tuple);
+   /* make the toast relation visible, else index creation will fail */
+   CommandCounterIncrement();
 
-       {
-           Relation    irelations[Num_pg_attr_indices];
+   /*
+    * Create unique index on chunk_id, chunk_seq.
+    *
+    * NOTE: the tuple toaster could actually function with a single-column
+    * index on chunk_id only.  However, it couldn't be unique then.  We
+    * want it to be unique as a check against the possibility of
+    * duplicate TOAST chunk OIDs.  Too, the index might be a little more
+    * efficient this way, since btree isn't all that happy with large
+    * numbers of equal keys.
+    */
 
-           CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, irelations);
-           CatalogIndexInsert(irelations, Num_pg_trigger_indices, tgrel, tuple);
-           CatalogCloseIndices(Num_pg_trigger_indices, irelations);
-       }
+   indexInfo = makeNode(IndexInfo);
+   indexInfo->ii_NumIndexAttrs = 2;
+   indexInfo->ii_NumKeyAttrs = 2;
+   indexInfo->ii_KeyAttrNumbers[0] = 1;
+   indexInfo->ii_KeyAttrNumbers[1] = 2;
+   indexInfo->ii_Predicate = NIL;
+   indexInfo->ii_FuncOid = InvalidOid;
+   indexInfo->ii_Unique = true;
 
-       /* free up our scratch memory */
-       pfree(newtgargs);
-       heap_freetuple(tuple);
-   }
+   classObjectId[0] = OID_BTREE_OPS_OID;
+   classObjectId[1] = INT4_BTREE_OPS_OID;
 
-   index_endscan(idxtgscan);
-   index_close(irel);
+   toast_idxid = index_create(toast_relid, toast_idxname, indexInfo,
+                              BTREE_AM_OID, classObjectId,
+                              true, true);
 
-   heap_close(tgrel, RowExclusiveLock);
+   /*
+    * Update toast rel's pg_class entry to show that it has an index. The
+    * index OID is stored into the reltoastidxid field for easy access by
+    * the tuple toaster.
+    */
+   setRelhasindex(toast_relid, true, true, toast_idxid);
 
    /*
-    * Increment cmd counter to make updates visible; this is needed in
-    * case the same tuple has to be updated again by next pass (can
-    * happen in case of a self-referential FK relationship).
+    * Store the toast table's OID in the parent relation's tuple
+    */
+   ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
+   simple_heap_update(class_rel, &reltup->t_self, reltup);
+
+   /*
+    * Keep catalog indices current
+    */
+   CatalogOpenIndices(Num_pg_class_indices, Name_pg_class_indices, ridescs);
+   CatalogIndexInsert(ridescs, Num_pg_class_indices, class_rel, reltup);
+   CatalogCloseIndices(Num_pg_class_indices, ridescs);
+
+   heap_freetuple(reltup);
+
+   /*
+    * Close relations and make changes visible
     */
+   heap_close(class_rel, NoLock);
+   heap_close(rel, NoLock);
+
    CommandCounterIncrement();
 }
+
+/*
+ * Check to see whether the table needs a TOAST table. It does only if
+ * (1) there are any toastable attributes, and (2) the maximum length
+ * of a tuple could exceed TOAST_TUPLE_THRESHOLD.  (We don't want to
+ * create a toast table for something like "f1 varchar(20)".)
+ */
+static bool
+needs_toast_table(Relation rel)
+{
+   int32       data_length = 0;
+   bool        maxlength_unknown = false;
+   bool        has_toastable_attrs = false;
+   TupleDesc   tupdesc;
+   Form_pg_attribute *att;
+   int32       tuple_length;
+   int         i;
+
+   tupdesc = rel->rd_att;
+   att = tupdesc->attrs;
+
+   for (i = 0; i < tupdesc->natts; i++)
+   {
+       data_length = att_align(data_length, att[i]->attlen, att[i]->attalign);
+       if (att[i]->attlen >= 0)
+       {
+           /* Fixed-length types are never toastable */
+           data_length += att[i]->attlen;
+       }
+       else
+       {
+           int32       maxlen = type_maximum_size(att[i]->atttypid,
+                                                  att[i]->atttypmod);
+
+           if (maxlen < 0)
+               maxlength_unknown = true;
+           else
+               data_length += maxlen;
+           if (att[i]->attstorage != 'p')
+               has_toastable_attrs = true;
+       }
+   }
+   if (!has_toastable_attrs)
+       return false;           /* nothing to toast? */
+   if (maxlength_unknown)
+       return true;            /* any unlimited-length attrs? */
+   tuple_length = MAXALIGN(offsetof(HeapTupleHeaderData, t_bits) +
+                           BITMAPLEN(tupdesc->natts)) +
+       MAXALIGN(data_length);
+   return (tuple_length > TOAST_TUPLE_THRESHOLD);
+}
index c00b4bebac8f9bdc5f1d6dbc5ead48107df4c307..b826297a7189aff893d02c2b15569e2c8ac3d9b1 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.114 2002/04/19 16:36:08 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.115 2002/04/26 19:29:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -503,6 +503,123 @@ RelationRemoveTriggers(Relation rel)
    heap_close(tgrel, RowExclusiveLock);
 }
 
+/*
+ *     renametrig      - changes the name of a trigger on a relation
+ *
+ *     trigger name is changed in trigger catalog.
+ *     No record of the previous name is kept.
+ *
+ *     get proper relrelation from relation catalog (if not arg)
+ *     scan trigger catalog
+ *             for name conflict (within rel)
+ *             for original trigger (if not arg)
+ *     modify tgname in trigger tuple
+ *     insert modified trigger in trigger catalog
+ *     delete original trigger from trigger catalog
+ */
+void
+renametrig(Oid relid,
+          const char *oldname,
+          const char *newname)
+{
+   Relation    targetrel;
+   Relation    tgrel;
+   HeapTuple   tuple;
+   SysScanDesc tgscan;
+   ScanKeyData key;
+   bool        found = FALSE;
+   Relation    idescs[Num_pg_trigger_indices];
+
+   /*
+    * Grab an exclusive lock on the target table, which we will NOT
+    * release until end of transaction.
+    */
+   targetrel = heap_open(relid, AccessExclusiveLock);
+
+   /*
+    * Scan pg_trigger twice for existing triggers on relation.  We do this in
+    * order to ensure a trigger does not exist with newname (The unique index
+    * on tgrelid/tgname would complain anyway) and to ensure a trigger does
+    * exist with oldname.
+    *
+    * NOTE that this is cool only because we have AccessExclusiveLock on the
+    * relation, so the trigger set won't be changing underneath us.
+    */
+   tgrel = heap_openr(TriggerRelationName, RowExclusiveLock);
+
+   /*
+    * First pass -- look for name conflict
+    */
+   ScanKeyEntryInitialize(&key, 0,
+                          Anum_pg_trigger_tgrelid,
+                          F_OIDEQ,
+                          ObjectIdGetDatum(relid));
+   tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
+                               SnapshotNow, 1, &key);
+   while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+   {
+       Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+       if (namestrcmp(&(pg_trigger->tgname), newname) == 0)
+           elog(ERROR, "renametrig: trigger %s already defined on relation %s",
+                newname, RelationGetRelationName(targetrel));
+   }
+   systable_endscan(tgscan);
+
+   /*
+    * Second pass -- look for trigger existing with oldname and update
+    */
+   ScanKeyEntryInitialize(&key, 0,
+                          Anum_pg_trigger_tgrelid,
+                          F_OIDEQ,
+                          ObjectIdGetDatum(relid));
+   tgscan = systable_beginscan(tgrel, TriggerRelidNameIndex, true,
+                               SnapshotNow, 1, &key);
+   while (HeapTupleIsValid(tuple = systable_getnext(tgscan)))
+   {
+       Form_pg_trigger pg_trigger = (Form_pg_trigger) GETSTRUCT(tuple);
+
+       if (namestrcmp(&(pg_trigger->tgname), oldname) == 0)
+       {
+           /*
+            * Update pg_trigger tuple with new tgname.
+            * (Scribbling on tuple is OK because it's a copy...)
+            */
+           namestrcpy(&(pg_trigger->tgname), newname);
+           simple_heap_update(tgrel, &tuple->t_self, tuple);
+
+           /*
+            * keep system catalog indices current
+            */
+           CatalogOpenIndices(Num_pg_trigger_indices, Name_pg_trigger_indices, idescs);
+           CatalogIndexInsert(idescs, Num_pg_trigger_indices, tgrel, tuple);
+           CatalogCloseIndices(Num_pg_trigger_indices, idescs);
+
+           /*
+            * Invalidate relation's relcache entry so that other
+            * backends (and this one too!) are sent SI message to make them
+            * rebuild relcache entries.
+            */
+           CacheInvalidateRelcache(relid);
+
+           found = TRUE;
+           break;
+       }
+   }
+   systable_endscan(tgscan);
+
+   heap_close(tgrel, RowExclusiveLock);
+
+   if (!found)
+       elog(ERROR, "renametrig: trigger %s not defined on relation %s",
+            oldname, RelationGetRelationName(targetrel));
+
+   /*
+    * Close rel, but keep exclusive lock!
+    */
+   heap_close(targetrel, NoLock);
+}
+
 /*
  * Build trigger data to attach to the given relcache entry.
  *
index eb80acbd008396e13b71ef3d97d2112a04505e47..d9d219edd0087f612f170d0cbef25f0af27a5d18 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.96 2002/04/18 21:16:16 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/user.c,v 1.97 2002/04/26 19:29:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -646,7 +646,7 @@ CreateUser(CreateUserStmt *stmt)
 /*
  * ALTER USER
  */
-extern void
+void
 AlterUser(AlterUserStmt *stmt)
 {
    Datum       new_record[Natts_pg_shadow];
index f3dfcd6b9b1af0c6c5afbb14725e36b6ceb618e1..1ce35eb164806fb9f17481129440510c97a68bbd 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: tablecmds.h,v 1.2 2002/04/24 02:48:55 momjian Exp $
+ * $Id: tablecmds.h,v 1.3 2002/04/26 19:29:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,8 +61,4 @@ extern void renameatt(Oid relid,
 extern void renamerel(Oid relid,
          const char *newrelname);
 
-extern void renametrig(Oid relid,
-         const char *oldname,
-         const char *newname);
-
 #endif   /* TABLECMDS_H */
index 4166b47bb74dfd649fc3f760d1d94bbf71e8ec4d..4719bf0c914c5006c34aaad1fb167913e8e9e09a 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: trigger.h,v 1.34 2002/04/01 22:36:13 tgl Exp $
+ * $Id: trigger.h,v 1.35 2002/04/26 19:29:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -105,6 +105,8 @@ extern void CreateTrigger(CreateTrigStmt *stmt);
 extern void DropTrigger(Oid relid, const char *trigname);
 extern void RelationRemoveTriggers(Relation rel);
 
+extern void renametrig(Oid relid, const char *oldname, const char *newname);
+
 extern void RelationBuildTriggers(Relation relation);
 
 extern void FreeTriggerDesc(TriggerDesc *trigdesc);