Обсуждение: Rearranging ALTER TABLE to avoid multi-operations bugs

Поиск
Список
Период
Сортировка

Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
We've had numerous bug reports complaining about the fact that ALTER TABLE
generates subsidiary commands that get executed unconditionally, even
if they should be discarded due to an IF NOT EXISTS or other condition;
see e.g. #14827, #15180, #15670, #15710.  In [1] I speculated about
fixing this by having ALTER TABLE maintain an array of flags that record
the results of initial tests for column existence, and then letting it
conditionalize execution of subcommands on those flags.  I started to
fool around with that concept today, and quickly realized that my
original thought of just adding execute-if-this-flag-is-true markers to
AlterTableCmd subcommands was insufficient.  Most of the problems are with
independent commands that execute before or after the main AlterTable,
and would not have any easy connection to state maintained by AlterTable.

The way to fix this, I think, is to provide an AlterTableCmd subcommand
type that just wraps an arbitrary utility statement, and then we can
conditionalize execution of such subcommands using the flag mechanism.
So instead of generating independent "before" and "after" statements,
transformAlterTableStmt would just produce a single AlterTable with
everything in its list of subcommands --- but we'd still use the generic
ProcessUtility infrastructure to execute subcommands that correspond
to existing standalone statements.

Looking into parse_utilcmd.c with an eye to making it do that, I almost
immediately ran across bugs we hadn't even known were there in ALTER TABLE
ADD/DROP GENERATED.  These have got a different but arguably-related
flavor of bug: they are making decisions inside transformAlterTableStmt
that might be wrong by the time we get to execution.  Thus for example

regression=# create table t1 (f1 int);
CREATE TABLE
regression=# alter table t1 add column f2 int not null,
alter column f2 add generated always as identity;
ALTER TABLE
regression=# insert into t1 values(0);
ERROR:  no owned sequence found

This happens because transformAlterTableStmt thinks it can generate
the sequence creation commands for the AT_AddIdentity subcommand,
and also figures it's okay to just ignore the case where the column
doesn't exist.  So we create the column but then we don't make the
sequence.  There are similar bugs in AT_SetIdentity processing, and
I rather suspect that it's also unsafe for AT_AlterColumnType to be
looking at the column's attidentity state --- though I couldn't
demonstrate a bug in that path, because of the fact that 
AT_AlterColumnType executes in a pass earlier than anything that
could change attidentity.

This can't be fixed just by conditionalizing execution of subcommands,
because we need to know the target column's type in order to set up the
sequence correctly.  So what has to happen to fix these things is to
move the decisions, and the creation of the subcommand parsetrees,
into ALTER TABLE execution.

That requires pretty much the same support for recursively calling
ProcessUtility() from AlterTable() that we'd need for the subcommand
wrapper idea.  So I went ahead and tackled it as a separate project,
and attached is the result.

I'm not quite sure if I'm satisfied with the approach shown here.
I made a struct containing the ProcessUtility parameters that need
to be passed down through the recursion, originally with the idea
that this struct might be completely opaque outside utility.c.
However, there's a good deal of redundancy in that approach ---
the relid and stmt parameters of AlterTable() are really redundant
with stuff in the struct.  So now I'm wondering if it would be better
to merge all that stuff and just have the struct as AlterTable's sole
argument.  I'm also not very sure whether AlterTableInternal() ought
to be modified so that it uses or at least creates a valid struct;
it doesn't *need* to do so today, but maybe someday it will.

And the whole thing has a faint air of grottiness about it too.
This makes the minimum changes to what we've got now, but I can't
help thinking it'd look different if we'd designed from scratch.
The interactions with event triggers seem particularly ad-hoc.
It's also ugly that CreateTable's recursion is handled differently
from AlterTable's.

Anybody have thoughts about a different way to approach it?

            regards, tom lane

[1] https://postgr.es/m/7824.1525200461@sss.pgh.pa.us

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e34d4cc..73c56ed 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -86,6 +86,7 @@
 #include "storage/lock.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
+#include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -340,12 +341,15 @@ static void validateForeignKeyConstraint(char *conname,
                                          Relation rel, Relation pkrel,
                                          Oid pkindOid, Oid constraintOid);
 static void ATController(AlterTableStmt *parsetree,
-                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
+                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+                         ProcessUtilityForAlterTableContext *pcontext);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
                       bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode);
+static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                              ProcessUtilityForAlterTableContext *pcontext);
 static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                      AlterTableCmd *cmd, LOCKMODE lockmode);
+                      AlterTableCmd *cmd, LOCKMODE lockmode,
+                      ProcessUtilityForAlterTableContext *pcontext);
 static void ATRewriteTables(AlterTableStmt *parsetree,
                             List **wqueue, LOCKMODE lockmode);
 static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
@@ -383,9 +387,11 @@ static bool ConstraintImpliedByRelConstraint(Relation scanrel,
 static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
                                          Node *newDefault, LOCKMODE lockmode);
 static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
-                                       Node *def, LOCKMODE lockmode);
+                                       Node *def, LOCKMODE lockmode,
+                                       ProcessUtilityForAlterTableContext *pcontext);
 static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
-                                       Node *def, LOCKMODE lockmode);
+                                       Node *def, LOCKMODE lockmode,
+                                       ProcessUtilityForAlterTableContext *pcontext);
 static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
 static void ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum,
                                 Node *newValue, LOCKMODE lockmode);
@@ -3467,7 +3473,8 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * rather than reassess it at lower levels.
  */
 void
-AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
+AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt,
+           ProcessUtilityForAlterTableContext *pcontext)
 {
     Relation    rel;

@@ -3476,7 +3483,8 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)

     CheckTableNotInUse(rel, "ALTER TABLE");

-    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
+    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode,
+                 pcontext);
 }

 /*
@@ -3489,6 +3497,9 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
  * is unsafe to use this entry point for alterations that could break
  * existing query plans.  On the assumption it's not used for such, we
  * don't have to reject pending AFTER triggers, either.
+ *
+ * This also doesn't support subcommands that need to recursively call
+ * ProcessUtility, so no pcontext argument is needed.
  */
 void
 AlterTableInternal(Oid relid, List *cmds, bool recurse)
@@ -3500,7 +3511,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)

     EventTriggerAlterTableRelid(relid);

-    ATController(NULL, rel, cmds, recurse, lockmode);
+    ATController(NULL, rel, cmds, recurse, lockmode, NULL);
 }

 /*
@@ -3798,7 +3809,8 @@ AlterTableGetLockLevel(List *cmds)
  */
 static void
 ATController(AlterTableStmt *parsetree,
-             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
+             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+             ProcessUtilityForAlterTableContext *pcontext)
 {
     List       *wqueue = NIL;
     ListCell   *lcmd;
@@ -3815,7 +3827,7 @@ ATController(AlterTableStmt *parsetree,
     relation_close(rel, NoLock);

     /* Phase 2: update system catalogs */
-    ATRewriteCatalogs(&wqueue, lockmode);
+    ATRewriteCatalogs(&wqueue, lockmode, pcontext);

     /* Phase 3: scan/rewrite tables as needed */
     ATRewriteTables(parsetree, &wqueue, lockmode);
@@ -3884,7 +3896,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_AddIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
-            pass = AT_PASS_ADD_CONSTR;
+            pass = AT_PASS_COL_ATTRS;
             break;
         case AT_DropIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
@@ -4122,7 +4134,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  * conflicts).
  */
 static void
-ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
+ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                  ProcessUtilityForAlterTableContext *pcontext)
 {
     int            pass;
     ListCell   *ltab;
@@ -4153,9 +4166,12 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
             rel = relation_open(tab->relid, NoLock);

             foreach(lcmd, subcmds)
+            {
                 ATExecCmd(wqueue, tab, rel,
                           castNode(AlterTableCmd, lfirst(lcmd)),
-                          lockmode);
+                          lockmode,
+                          pcontext);
+            }

             /*
              * After the ALTER TYPE pass, do cleanup work (this is not done in
@@ -4192,7 +4208,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
  */
 static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-          AlterTableCmd *cmd, LOCKMODE lockmode)
+          AlterTableCmd *cmd, LOCKMODE lockmode,
+          ProcessUtilityForAlterTableContext *pcontext)
 {
     ObjectAddress address = InvalidObjectAddress;

@@ -4213,10 +4230,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
             address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_AddIdentity:
-            address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
+            address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode,
+                                        pcontext);
             break;
         case AT_SetIdentity:
-            address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode);
+            address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode,
+                                        pcontext);
             break;
         case AT_DropIdentity:
             address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode);
@@ -6398,14 +6417,17 @@ ATExecColumnDefault(Relation rel, const char *colName,
  */
 static ObjectAddress
 ATExecAddIdentity(Relation rel, const char *colName,
-                  Node *def, LOCKMODE lockmode)
+                  Node *def, LOCKMODE lockmode,
+                  ProcessUtilityForAlterTableContext *pcontext)
 {
+    Constraint *condef = castNode(Constraint, def);
     Relation    attrelation;
     HeapTuple    tuple;
     Form_pg_attribute attTup;
     AttrNumber    attnum;
+    List       *seqcmds;
+    ListCell   *lc;
     ObjectAddress address;
-    ColumnDef  *cdef = castNode(ColumnDef, def);

     attrelation = table_open(AttributeRelationId, RowExclusiveLock);

@@ -6448,9 +6470,22 @@ ATExecAddIdentity(Relation rel, const char *colName,
                  errmsg("column \"%s\" of relation \"%s\" already has a default value",
                         colName, RelationGetRelationName(rel))));

-    attTup->attidentity = cdef->identity;
+    /* Update column's attidentity state */
+    attTup->attidentity = condef->generated_when;
     CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);

+    /* Create the required supporting sequence */
+    seqcmds = transformIdentityColumnSerialOptions(rel,
+                                                   NameStr(attTup->attname),
+                                                   attTup->atttypid,
+                                                   condef->options);
+    foreach(lc, seqcmds)
+    {
+        Node       *stmt = lfirst(lc);
+
+        ProcessUtilityForAlterTable(stmt, pcontext);
+    }
+
     InvokeObjectPostAlterHook(RelationRelationId,
                               RelationGetRelid(rel),
                               attTup->attnum);
@@ -6469,17 +6504,21 @@ ATExecAddIdentity(Relation rel, const char *colName,
  * Return the address of the affected column.
  */
 static ObjectAddress
-ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode)
+ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode,
+                  ProcessUtilityForAlterTableContext *pcontext)
 {
+    List       *options = castNode(List, def);
     ListCell   *option;
     DefElem    *generatedEl = NULL;
+    List       *seqoptions = NIL;
     HeapTuple    tuple;
     Form_pg_attribute attTup;
     AttrNumber    attnum;
     Relation    attrelation;
     ObjectAddress address;

-    foreach(option, castNode(List, def))
+    /* Examine options */
+    foreach(option, options)
     {
         DefElem    *defel = lfirst_node(DefElem, option);

@@ -6492,14 +6531,15 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
             generatedEl = defel;
         }
         else
-            elog(ERROR, "option \"%s\" not recognized",
-                 defel->defname);
+        {
+            /* Assume it is an option for ALTER SEQUENCE */
+            seqoptions = lappend(seqoptions, defel);
+        }
     }

     /*
-     * Even if there is nothing to change here, we run all the checks.  There
-     * will be a subsequent ALTER SEQUENCE that relies on everything being
-     * there.
+     * Even if there is nothing to change, verify that target column is valid
+     * for the command.
      */

     attrelation = table_open(AttributeRelationId, RowExclusiveLock);
@@ -6525,6 +6565,7 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
                  errmsg("column \"%s\" of relation \"%s\" is not an identity column",
                         colName, RelationGetRelationName(rel))));

+    /* Apply attidentity change if given */
     if (generatedEl)
     {
         attTup->attidentity = defGetInt32(generatedEl);
@@ -6532,13 +6573,35 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod

         InvokeObjectPostAlterHook(RelationRelationId,
                                   RelationGetRelid(rel),
-                                  attTup->attnum);
+                                  attnum);
         ObjectAddressSubSet(address, RelationRelationId,
                             RelationGetRelid(rel), attnum);
     }
     else
         address = InvalidObjectAddress;

+    /* Apply sequence options if given */
+    if (seqoptions)
+    {
+        List       *seqlist = getOwnedSequences(RelationGetRelid(rel), attnum);
+        ListCell   *seqcell;
+
+        foreach(seqcell, seqlist)
+        {
+            Oid            seq_relid = lfirst_oid(seqcell);
+            AlterSeqStmt *seqstmt;
+
+            seqstmt = makeNode(AlterSeqStmt);
+            seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
+                                             get_rel_name(seq_relid), -1);
+            seqstmt->options = seqoptions;
+            seqstmt->for_identity = true;
+            seqstmt->missing_ok = false;
+
+            ProcessUtilityForAlterTable((Node *) seqstmt, pcontext);
+        }
+    }
+
     heap_freetuple(tuple);
     table_close(attrelation, RowExclusiveLock);

diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 7450d74..a0526d4 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -113,6 +113,10 @@ typedef struct
 } CreateSchemaStmtContext;


+static void generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
+                                     Oid seqtypid, List *seqoptions,
+                                     bool for_identity,
+                                     char **snamespace_p, char **sname_p);
 static void transformColumnDefinition(CreateStmtContext *cxt,
                                       ColumnDef *column);
 static void transformTableConstraint(CreateStmtContext *cxt,
@@ -337,6 +341,38 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 }

 /*
+ * transformIdentityColumnSerialOptions
+ *        Generate CREATE SEQUENCE and ALTER SEQUENCE ... OWNED BY statements
+ *        to create the sequence for an identity column.
+ *
+ * This is used during ALTER TABLE ADD IDENTITY.  We don't need to separate
+ * the execution of the two commands, because the column already exists and
+ * doesn't need its default expression set.  So just pass them back as a
+ * single List.
+ */
+List *
+transformIdentityColumnSerialOptions(Relation rel,
+                                     char *colName, Oid colTypeOid,
+                                     List *seqoptions)
+{
+    CreateStmtContext cxt;
+    ColumnDef  *column = makeNode(ColumnDef);
+
+    /* Set up just enough of cxt for generateSerialExtraStmts() */
+    memset(&cxt, 0, sizeof(cxt));
+    cxt.stmtType = "ALTER TABLE";
+    cxt.rel = rel;
+
+    /* Need a mostly-dummy ColumnDef, too */
+    column->colname = colName;
+
+    generateSerialExtraStmts(&cxt, column, colTypeOid, seqoptions, true,
+                             NULL, NULL);
+
+    return list_concat(cxt.blist, cxt.alist);
+}
+
+/*
  * generateSerialExtraStmts
  *        Generate CREATE SEQUENCE and ALTER SEQUENCE ... OWNED BY statements
  *        to create the sequence for a serial or identity column.
@@ -350,7 +386,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
                          Oid seqtypid, List *seqoptions, bool for_identity,
                          char **snamespace_p, char **sname_p)
 {
-    ListCell   *option;
+    char       *relname;
     DefElem    *nameEl = NULL;
     Oid            snamespaceid;
     char       *snamespace;
@@ -358,6 +394,14 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
     CreateSeqStmt *seqstmt;
     AlterSeqStmt *altseqstmt;
     List       *attnamelist;
+    ListCell   *option;
+
+    /*
+     * Get name of relation.  Note: this function mustn't access cxt->relation
+     * when cxt->rel is set, because transformIdentityColumnSerialOptions()
+     * only provides the latter.
+     */
+    relname = cxt->rel ? RelationGetRelationName(cxt->rel) : cxt->relation->relname;

     /*
      * Determine namespace and name to use for the sequence.
@@ -415,7 +459,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
             RangeVarAdjustRelationPersistence(cxt->relation, snamespaceid);
         }
         snamespace = get_namespace_name(snamespaceid);
-        sname = ChooseRelationName(cxt->relation->relname,
+        sname = ChooseRelationName(relname,
                                    column->colname,
                                    "seq",
                                    snamespaceid,
@@ -425,7 +469,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
     ereport(DEBUG1,
             (errmsg("%s will create implicit sequence \"%s\" for serial column \"%s.%s\"",
                     cxt->stmtType, sname,
-                    cxt->relation->relname, column->colname)));
+                    relname, column->colname)));

     /*
      * Build a CREATE SEQUENCE command to create the sequence object, and add
@@ -478,7 +522,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
     altseqstmt = makeNode(AlterSeqStmt);
     altseqstmt->sequence = makeRangeVar(snamespace, sname, -1);
     attnamelist = list_make3(makeString(snamespace),
-                             makeString(cxt->relation->relname),
+                             makeString(relname),
                              makeString(column->colname));
     altseqstmt->options = list_make1(makeDefElem("owned_by",
                                                  (Node *) attnamelist, -1));
@@ -3077,8 +3121,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,

     /*
      * The only subtypes that currently require parse transformation handling
-     * are ADD COLUMN, ADD CONSTRAINT and SET DATA TYPE.  These largely re-use
-     * code from CREATE TABLE.
+     * are ADD COLUMN, ADD CONSTRAINT, SET DATA TYPE, and ATTACH/DETACH
+     * PARTITION.  These largely re-use code from CREATE TABLE.
+     *
+     * NOW HEAR THIS: you can NOT put code here that examines the current
+     * properties of the target table or anything associated with it.  Such
+     * code will do the wrong thing if any preceding ALTER TABLE subcommand
+     * changes the property in question.  Wait till runtime to look at the
+     * table.
      */
     foreach(lcmd, stmt->cmds)
     {
@@ -3155,6 +3205,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     /*
                      * For identity column, create ALTER SEQUENCE command to
                      * change the data type of the sequence.
+                     *
+                     * XXX This is a direct violation of the advice given
+                     * above to not look at the table's properties yet.  It
+                     * accidentally works (at least for most cases) because of
+                     * the ordering of operations in ALTER TABLE --- note in
+                     * particular that we must add the new command to blist
+                     * not alist.  But we ought to move this to be done at
+                     * execution.
                      */
                     attnum = get_attnum(relid, cmd->name);

@@ -3181,90 +3239,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     break;
                 }

-            case AT_AddIdentity:
-                {
-                    Constraint *def = castNode(Constraint, cmd->def);
-                    ColumnDef  *newdef = makeNode(ColumnDef);
-                    AttrNumber    attnum;
-
-                    newdef->colname = cmd->name;
-                    newdef->identity = def->generated_when;
-                    cmd->def = (Node *) newdef;
-
-                    attnum = get_attnum(relid, cmd->name);
-
-                    /*
-                     * if attribute not found, something will error about it
-                     * later
-                     */
-                    if (attnum != InvalidAttrNumber)
-                        generateSerialExtraStmts(&cxt, newdef,
-                                                 get_atttype(relid, attnum),
-                                                 def->options, true,
-                                                 NULL, NULL);
-
-                    newcmds = lappend(newcmds, cmd);
-                    break;
-                }
-
-            case AT_SetIdentity:
-                {
-                    /*
-                     * Create an ALTER SEQUENCE statement for the internal
-                     * sequence of the identity column.
-                     */
-                    ListCell   *lc;
-                    List       *newseqopts = NIL;
-                    List       *newdef = NIL;
-                    List       *seqlist;
-                    AttrNumber    attnum;
-
-                    /*
-                     * Split options into those handled by ALTER SEQUENCE and
-                     * those for ALTER TABLE proper.
-                     */
-                    foreach(lc, castNode(List, cmd->def))
-                    {
-                        DefElem    *def = lfirst_node(DefElem, lc);
-
-                        if (strcmp(def->defname, "generated") == 0)
-                            newdef = lappend(newdef, def);
-                        else
-                            newseqopts = lappend(newseqopts, def);
-                    }
-
-                    attnum = get_attnum(relid, cmd->name);
-
-                    if (attnum)
-                    {
-                        seqlist = getOwnedSequences(relid, attnum);
-                        if (seqlist)
-                        {
-                            AlterSeqStmt *seqstmt;
-                            Oid            seq_relid;
-
-                            seqstmt = makeNode(AlterSeqStmt);
-                            seq_relid = linitial_oid(seqlist);
-                            seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
-                                                             get_rel_name(seq_relid), -1);
-                            seqstmt->options = newseqopts;
-                            seqstmt->for_identity = true;
-                            seqstmt->missing_ok = false;
-
-                            cxt.alist = lappend(cxt.alist, seqstmt);
-                        }
-                    }
-
-                    /*
-                     * If column was not found or was not an identity column,
-                     * we just let the ALTER TABLE command error out later.
-                     */
-
-                    cmd->def = (Node *) newdef;
-                    newcmds = lappend(newcmds, cmd);
-                    break;
-                }
-
             case AT_AttachPartition:
             case AT_DetachPartition:
                 {
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9578b5c..937a9b7 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1075,7 +1075,7 @@ ProcessUtilitySlow(ParseState *pstate,
                                            queryString,
                                            PROCESS_UTILITY_SUBCOMMAND,
                                            params,
-                                           NULL,
+                                           queryEnv,
                                            None_Receiver,
                                            NULL);
                         }
@@ -1097,8 +1097,6 @@ ProcessUtilitySlow(ParseState *pstate,
                 {
                     AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
                     Oid            relid;
-                    List       *stmts;
-                    ListCell   *l;
                     LOCKMODE    lockmode;

                     /*
@@ -1112,10 +1110,21 @@ ProcessUtilitySlow(ParseState *pstate,

                     if (OidIsValid(relid))
                     {
+                        List       *stmts;
+                        ProcessUtilityForAlterTableContext pcontext;
+                        ListCell   *l;
+
                         /* Run parse analysis ... */
                         stmts = transformAlterTableStmt(relid, atstmt,
                                                         queryString);

+                        /* ... set up info for possible recursion ... */
+                        pcontext.pstmt = pstmt;
+                        pcontext.queryString = queryString;
+                        pcontext.params = params;
+                        pcontext.queryEnv = queryEnv;
+                        pcontext.relid = relid;
+
                         /* ... ensure we have an event trigger context ... */
                         EventTriggerAlterTableStart(parsetree);
                         EventTriggerAlterTableRelid(relid);
@@ -1129,36 +1138,21 @@ ProcessUtilitySlow(ParseState *pstate,
                             {
                                 /* Do the table alteration proper */
                                 AlterTable(relid, lockmode,
-                                           (AlterTableStmt *) stmt);
+                                           (AlterTableStmt *) stmt,
+                                           &pcontext);
                             }
                             else
                             {
                                 /*
-                                 * Recurse for anything else.  If we need to
-                                 * do so, "close" the current complex-command
-                                 * set, and start a new one at the bottom;
-                                 * this is needed to ensure the ordering of
-                                 * queued commands is consistent with the way
-                                 * they are executed here.
+                                 * Recurse for anything else.  We get here if
+                                 * transformAlterTableStmt() tacked extra
+                                 * commands onto its output, but it's also
+                                 * possible for AlterTable() to generate extra
+                                 * commands on-the-fly, in which case it will
+                                 * call ProcessUtilityForAlterTable directly.
                                  */
-                                PlannedStmt *wrapper;
-
-                                EventTriggerAlterTableEnd();
-                                wrapper = makeNode(PlannedStmt);
-                                wrapper->commandType = CMD_UTILITY;
-                                wrapper->canSetTag = false;
-                                wrapper->utilityStmt = stmt;
-                                wrapper->stmt_location = pstmt->stmt_location;
-                                wrapper->stmt_len = pstmt->stmt_len;
-                                ProcessUtility(wrapper,
-                                               queryString,
-                                               PROCESS_UTILITY_SUBCOMMAND,
-                                               params,
-                                               NULL,
-                                               None_Receiver,
-                                               NULL);
-                                EventTriggerAlterTableStart(parsetree);
-                                EventTriggerAlterTableRelid(relid);
+                                ProcessUtilityForAlterTable(stmt,
+                                                            &pcontext);
                             }

                             /* Need CCI between commands */
@@ -1719,6 +1713,42 @@ ProcessUtilitySlow(ParseState *pstate,
 }

 /*
+ * ProcessUtilityForAlterTable
+ *        Recursively process an arbitrary utility command as a subcommand
+ *        of ALTER TABLE.
+ */
+void
+ProcessUtilityForAlterTable(Node *stmt,
+                            ProcessUtilityForAlterTableContext *pcontext)
+{
+    PlannedStmt *wrapper = makeNode(PlannedStmt);
+
+    /*
+     * When recursing, "close" the current complex-command set, and start a
+     * new one afterwards; this is needed to ensure the ordering of queued
+     * commands is consistent with the way they are executed here.
+     */
+    EventTriggerAlterTableEnd();
+
+    wrapper->commandType = CMD_UTILITY;
+    wrapper->canSetTag = false;
+    wrapper->utilityStmt = stmt;
+    wrapper->stmt_location = pcontext->pstmt->stmt_location;
+    wrapper->stmt_len = pcontext->pstmt->stmt_len;
+
+    ProcessUtility(wrapper,
+                   pcontext->queryString,
+                   PROCESS_UTILITY_SUBCOMMAND,
+                   pcontext->params,
+                   pcontext->queryEnv,
+                   None_Receiver,
+                   NULL);
+
+    EventTriggerAlterTableStart(pcontext->pstmt->utilityStmt);
+    EventTriggerAlterTableRelid(pcontext->relid);
+}
+
+/*
  * Dispatch function for DropStmt
  */
 static void
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index b09afa2..25282c0 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -21,6 +21,8 @@
 #include "storage/lock.h"
 #include "utils/relcache.h"

+struct ProcessUtilityForAlterTableContext;    /* avoid including utility.h */
+

 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
                                     ObjectAddress *typaddress, const char *queryString);
@@ -29,7 +31,8 @@ extern void RemoveRelations(DropStmt *drop);

 extern Oid    AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);

-extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt);
+extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt,
+                       struct ProcessUtilityForAlterTableContext *pcontext);

 extern LOCKMODE AlterTableGetLockLevel(List *cmds);

diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 1348064..3f30fc6 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -27,6 +27,9 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
                                                    PartitionBoundSpec *spec);
+extern List *transformIdentityColumnSerialOptions(Relation rel,
+                                                  char *colName, Oid colTypeOid,
+                                                  List *seqoptions);
 extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel,
                                           Relation source_idx,
                                           const AttrNumber *attmap, int attmap_length,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 5abcacf..34390e6 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -25,6 +25,16 @@ typedef enum
     PROCESS_UTILITY_SUBCOMMAND    /* a portion of a query */
 } ProcessUtilityContext;

+typedef struct ProcessUtilityForAlterTableContext
+{
+    /* Info that has to be passed through an ALTER TABLE */
+    PlannedStmt *pstmt;
+    const char *queryString;
+    ParamListInfo params;
+    QueryEnvironment *queryEnv;
+    Oid            relid;
+} ProcessUtilityForAlterTableContext;
+
 /* Hook for plugins to get control in ProcessUtility() */
 typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
                                           const char *queryString, ProcessUtilityContext context,
@@ -42,6 +52,9 @@ extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
                                     QueryEnvironment *queryEnv,
                                     DestReceiver *dest, char *completionTag);

+extern void ProcessUtilityForAlterTable(Node *stmt,
+                                        ProcessUtilityForAlterTableContext *pcontext);
+
 extern bool UtilityReturnsTuples(Node *parsetree);

 extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 2286519..b72c9d0 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -39,7 +39,7 @@ SELECT pg_get_serial_sequence('itest1', 'a');
  integer |     1 |       1 | 2147483647 |         1 | no      |     1
 Sequence for identity column: public.itest1.a

-CREATE TABLE itest4 (a int, b text);
+CREATE TABLE itest4 (a int, b text not null);
 ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error, requires NOT NULL
 ERROR:  column "a" of relation "itest4" must be declared NOT NULL before identity can be added
 ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL;
@@ -387,6 +387,68 @@ SELECT * FROM itest8;
 RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;
+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+INSERT INTO itest8 VALUES(0), (1);
+TABLE itest8;
+ f1 | f2 | f3 | f4 | f5
+----+----+----+----+----
+  0 |  1 |  1 |  1 |
+  1 |  2 | 11 |  2 |
+(2 rows)
+
+\d+ itest8
+                                               Table "public.itest8"
+ Column |  Type   | Collation | Nullable |             Default              | Storage | Stats target | Description
+--------+---------+-----------+----------+----------------------------------+---------+--------------+-------------
+ f1     | integer |           |          |                                  | plain   |              |
+ f2     | integer |           | not null | generated always as identity     | plain   |              |
+ f3     | integer |           | not null | generated by default as identity | plain   |              |
+ f4     | bigint  |           | not null | generated always as identity     | plain   |              |
+ f5     | bigint  |           |          |                                  | plain   |              |
+
+\d itest8_f2_seq
+                   Sequence "public.itest8_f2_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |         1 | no      |     1
+Sequence for identity column: public.itest8.f2
+
+\d itest8_f3_seq
+                   Sequence "public.itest8_f3_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |        10 | no      |     1
+Sequence for identity column: public.itest8.f3
+
+\d itest8_f4_seq
+                       Sequence "public.itest8_f4_seq"
+  Type  | Start | Minimum |       Maximum       | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint |     1 |       1 | 9223372036854775807 |         1 | no      |     1
+Sequence for identity column: public.itest8.f4
+
+\d itest8_f5_seq
+DROP TABLE itest8;
 -- typed tables (currently not supported)
 CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint);
 CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index 8dcfdf3..5126a66 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -16,7 +16,7 @@ SELECT pg_get_serial_sequence('itest1', 'a');

 \d itest1_a_seq

-CREATE TABLE itest4 (a int, b text);
+CREATE TABLE itest4 (a int, b text not null);
 ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- error, requires NOT NULL
 ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL;
 ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY;  -- ok
@@ -239,6 +239,44 @@ RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;

+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+
+INSERT INTO itest8 VALUES(0), (1);
+
+TABLE itest8;
+\d+ itest8
+\d itest8_f2_seq
+\d itest8_f3_seq
+\d itest8_f4_seq
+\d itest8_f5_seq
+DROP TABLE itest8;
+

 -- typed tables (currently not supported)


Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Robert Haas
Дата:
On Sun, May 26, 2019 at 6:24 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Anybody have thoughts about a different way to approach it?

I mean, in an ideal world, I think we'd never call back out to
ProcessUtility() from within AlterTable().  That seems like a pretty
clear layering violation.  I assume the reason we've never tried to do
better is a lack of round tuits and/or sufficient motivation.

In terms of what we'd do instead, I suppose we'd try to move as much
as possible inside the ALTER TABLE framework proper and have
everything call into that.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> On Sun, May 26, 2019 at 6:24 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Anybody have thoughts about a different way to approach it?

> I mean, in an ideal world, I think we'd never call back out to
> ProcessUtility() from within AlterTable().  That seems like a pretty
> clear layering violation.  I assume the reason we've never tried to do
> better is a lack of round tuits and/or sufficient motivation.

> In terms of what we'd do instead, I suppose we'd try to move as much
> as possible inside the ALTER TABLE framework proper and have
> everything call into that.

Hm ... I'm not exactly clear on why that would be a superior solution.
It would imply that standalone CREATE INDEX etc would call into the
ALTER TABLE framework --- how is that not equally a layering violation?

Also, recursive ProcessUtility cases exist independently of this issue,
in particular in CreateSchemaCommand.  My worry about my patch upthread
is not really that it introduces another one, but that it doesn't do
anything towards providing a uniform framework/notation for all these
cases.

            regards, tom lane



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Robert Haas
Дата:
On Wed, May 29, 2019 at 5:52 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Hm ... I'm not exactly clear on why that would be a superior solution.
> It would imply that standalone CREATE INDEX etc would call into the
> ALTER TABLE framework --- how is that not equally a layering violation?

Well, the framework could be renamed to something more general, I
suppose, but I don't see a *layering* concern.

From my point of view, the DDL code doesn't do a great job separating
parsing/parse analysis from optimization/execution.  The ALTER TABLE
stuff is actually pretty good in this regard.  But when you build
something that is basically a parse tree and pass it to some other
function that thinks that parse tree may well be coming straight from
the user, you are not doing a good job distinguishing between a
statement and an action which that statement may caused to be
performed.

> Also, recursive ProcessUtility cases exist independently of this issue,
> in particular in CreateSchemaCommand.  My worry about my patch upthread
> is not really that it introduces another one, but that it doesn't do
> anything towards providing a uniform framework/notation for all these
> cases.

I'm not really sure I understand this.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> From my point of view, the DDL code doesn't do a great job separating
> parsing/parse analysis from optimization/execution.  The ALTER TABLE
> stuff is actually pretty good in this regard.

Meh.  I think a pretty fair characterization of the bug(s) I'm trying to
fix is "we separated parse analysis from execution when we should not
have, because it leads to parse analysis being done against the wrong
database state".  So I'm *very* suspicious of any argument that we should
try to separate them more, let alone that doing so will somehow fix this
set of bugs.

>> Also, recursive ProcessUtility cases exist independently of this issue,
>> in particular in CreateSchemaCommand.  My worry about my patch upthread
>> is not really that it introduces another one, but that it doesn't do
>> anything towards providing a uniform framework/notation for all these
>> cases.

> I'm not really sure I understand this.

Well, I tried to wrap what are currently a random set of ProcessUtility
arguments into one struct to reduce the notational burden.  But as things
are set up, that's specific to the ALTER TABLE case.  I'm feeling like it
should not be, but I'm not very sure where to draw the line between
arguments that should be folded into the struct and ones that shouldn't.

Note that I think there are live bugs in here that are directly traceable
to not having tried to fold those arguments before.  Of the four existing
recursive ProcessUtility calls with context = PROCESS_UTILITY_SUBCOMMAND,
two pass down the outer call's "ParamListInfo params", and two don't ---
how is it not a bug that they don't all behave alike?  And none of the
four pass down the outer call's QueryEnvironment, which seems like even
more of a bug.  So it feels like we ought to have a uniform approach
to what gets passed down during recursion, and enforce it by passing
all such values in a struct rather than as independent arguments.

            regards, tom lane



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
movead li
Дата:
I applied the 'alter-table-with-recursive-process-utility-calls-wip.patch'
on the master(e788e849addd56007a0e75f3b5514f294a0f3bca). And 
when I test the cases, I find it works well on 'alter table t1 add column
f2 int not null, alter column f2 add generated always as identity' case, 
but it doesn't work on #14827, #15180, #15670, #15710.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is the test result with #14827 failed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
postgres=# create table t10 (f1 int);
CREATE TABLE
postgres=# alter table t10 add column f2 int not null,
postgres-# alter column f2 add generated always as identity;
ALTER TABLE
postgres=# 
postgres=# insert into t10 values(0);
INSERT 0 1
postgres=# create table test_serial ( teststring varchar(5));
CREATE TABLE
postgres=# alter table test_serial add column if not exists uid BIGSERIAL;
ALTER TABLE
postgres=# alter table test_serial add column if not exists uid BIGSERIAL;
psql: NOTICE:  column "uid" of relation "test_serial" already exists, skipping
ALTER TABLE
postgres=# 
postgres=# \d
                    List of relations
 Schema |         Name         |   Type   |    Owner     
--------+----------------------+----------+--------------
 public | t10                  | table    | lichuancheng
 public | t10_f2_seq           | sequence | lichuancheng
 public | test_serial          | table    | lichuancheng
 public | test_serial_uid_seq  | sequence | lichuancheng
 public | test_serial_uid_seq1 | sequence | lichuancheng
(5 rows)

postgres=#
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
So it's wrong with a 'test_serial_uid_seq1' sequence to appear.

The new status of this patch is: Waiting on Author

Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
movead li <movead.li@highgo.ca> writes:
> I applied the 'alter-table-with-recursive-process-utility-calls-wip.patch'
> on the master(e788e849addd56007a0e75f3b5514f294a0f3bca). And 
> when I test the cases, I find it works well on 'alter table t1 add column
> f2 int not null, alter column f2 add generated always as identity' case, 
> but it doesn't work on #14827, #15180, #15670, #15710.

This review seems not very on-point, because I made no claim to have fixed
any of those bugs.  The issue at the moment is how to structure the code
to allow ALTER TABLE to call other utility statements --- or, if we aren't
going to do that as Robert seems not to want to, what exactly we're going
to do instead.

The patch at hand does fix some ALTER TABLE ... IDENTITY bugs, because
fixing those doesn't require any conditional execution of utility
statements.  But we'll need infrastructure for such conditional execution
to fix the original bugs.  I don't see much point in working on that part
until we have some agreement about how to handle what this patch is
already doing.

            regards, tom lane



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Thomas Munro
Дата:
On Tue, Jul 2, 2019 at 2:00 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> movead li <movead.li@highgo.ca> writes:
> > I applied the 'alter-table-with-recursive-process-utility-calls-wip.patch'
> > on the master(e788e849addd56007a0e75f3b5514f294a0f3bca). And
> > when I test the cases, I find it works well on 'alter table t1 add column
> > f2 int not null, alter column f2 add generated always as identity' case,
> > but it doesn't work on #14827, #15180, #15670, #15710.
>
> This review seems not very on-point, because I made no claim to have fixed
> any of those bugs.  The issue at the moment is how to structure the code
> to allow ALTER TABLE to call other utility statements --- or, if we aren't
> going to do that as Robert seems not to want to, what exactly we're going
> to do instead.
>
> The patch at hand does fix some ALTER TABLE ... IDENTITY bugs, because
> fixing those doesn't require any conditional execution of utility
> statements.  But we'll need infrastructure for such conditional execution
> to fix the original bugs.  I don't see much point in working on that part
> until we have some agreement about how to handle what this patch is
> already doing.

With my CF manager hat:  I've moved this to the next CF so we can
close this one soon, but since it's really a bug report it might be
good to get more eyeballs on the problem sooner than September.

With my hacker hat:  Hmm.  I haven't looked at the patch, but not
passing down the QueryEnvironment when recursing is probably my fault,
and folding all such things into a new mechanism that would avoid such
bugs in the future sounds like a reasonable approach, if potentially
complicated to back-patch.  I'm hoping to come back and look at this
properly in a while.

-- 
Thomas Munro
https://enterprisedb.com



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
movead li
Дата:
> This review seems not very on-point, because I made no claim to have fixed
> any of those bugs.  The issue at the moment is how to structure the code

I am sorry for that and I have another question now. I researched the related 
code and find something as below:
Code:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
case AT_AddIdentity:
{
...
attnum = get_attnum(relid, cmd->name);
/*
 * if attribute not found, something will error about it
 * later
 */
if (attnum != InvalidAttrNumber)
    generateSerialExtraStmts(&cxt, newdef,
     get_atttype(relid, attnum),def->options, true,
     NULL, NULL);
​...
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Test case1:
################################################
create table t10 (f1 int);
alter table t10 add column f2 int not null,
alter column f2 add generated always as identity;
################################################
I find that the value of 'attnum' is 0 because now we do not have the 'f2'
column when I run the Test case1, so it can not generate a sequence
(because it can not run the generateSerialExtraStmts function).
You can see the code annotation that 'something will error about it later',
so I thank it may be an error report instead of executing successfully.

Test case2:
################################################
create table t11 (f1 int);
alter table t11 add column f2 int,
alter column f2 type int8;
################################################ 
Code about 'alter column type' have the same code annotation, and
if you run the Test case2, then you can get an error report. I use Test case2
to prove that it may be an error report instead of executing successfully. 

--
Movead.Li

The new status of this patch is: Waiting on Author

Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Alvaro Herrera
Дата:
On 2019-Aug-01, Thomas Munro wrote:

> With my hacker hat:  Hmm.  I haven't looked at the patch, but not
> passing down the QueryEnvironment when recursing is probably my fault,
> and folding all such things into a new mechanism that would avoid such
> bugs in the future sounds like a reasonable approach, if potentially
> complicated to back-patch.  I'm hoping to come back and look at this
> properly in a while.

Thomas: Any further input on this?  If I understand you correctly,
you're not saying that there's anything wrong with Tom's patch, just
that you would like to do some further hacking afterwards.

Tom: CFbot says this patch doesn't apply anymore.  Could you please
rebase?  Also: There's further input from Movead; his proposed test
cases might be useful to add.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
> Tom: CFbot says this patch doesn't apply anymore.  Could you please
> rebase?

Robert doesn't like the whole approach [1], so I'm not seeing much
point in rebasing the current patch.  The idea I'd been thinking
about instead was to invent a new AlterTableType enum value for
each type of utility command that we can currently generate as a
result of parse analysis of ALTER TABLE, then emit those currently
separate commands as AlterTableCmds with "def" pointing to the
relevant utility-command parsetree, and then add code to ALTER
TABLE to call the appropriate execution functions directly rather
than via ProcessUtility.  (This will add significantly more code
than what I had, and I'm not convinced it's better, just different.)

I haven't gotten to that yet, and now that the CF has started I'm
not sure if I'll have time for it this month.  Maybe we should just
mark the CF entry as RWF for now, or push it out to the next fest.

            regards, tom lane

[1] https://www.postgresql.org/message-id/CA%2BTgmoa3FzZvWriJmqquvAbf8GxrC9YM9umBb18j5M69iuq9bg%40mail.gmail.com



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
[ starting to think about this issue again ]

I wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> I mean, in an ideal world, I think we'd never call back out to
>> ProcessUtility() from within AlterTable().  That seems like a pretty
>> clear layering violation.  I assume the reason we've never tried to do
>> better is a lack of round tuits and/or sufficient motivation.

> ...
> Also, recursive ProcessUtility cases exist independently of this issue,
> in particular in CreateSchemaCommand.  My worry about my patch upthread
> is not really that it introduces another one, but that it doesn't do
> anything towards providing a uniform framework/notation for all these
> cases.

Actually ... looking closer at this, the cases I'm concerned about
*already* do recursive ProcessUtility calls.  Look at utility.c around
line 1137.  The case of interest here is when transformAlterTableStmt
returns any subcommands that are not AlterTableStmts.  As the code
stands, ProcessUtility merrily recurses to itself to handle them.
What I was proposing to do was have the recursion happen from inside
AlterTable(); maybe that's less clean, but surely not by much.

The thing I think you are actually worried about is the interaction
with event triggers, which is already a pretty horrid mess in this
code today.  I don't really follow the comment here about
"ordering of queued commands".  It looks like that comment dates to
Alvaro's commit b488c580a ... can either of you elucidate that?

Anyway, with the benefit of more time to let this thing percolate
in my hindbrain, I am thinking that the fundamental error we've made
is to do transformAlterTableStmt in advance of execution *at all*.
The idea I now have is to scrap that, and instead apply the
parse_utilcmd.c transformations individually to each AlterTable
subcommand when it reaches execution in "phase 2" of AlterTable().
In that way, the bugs associated with interference between different
AlterTable subcommands touching the same column are removed because
the column's catalog state is up-to-date when we do the parse
transformations.  We can probably also get rid of the problems with
IF NOT EXISTS, because that check would be made in advance of applying
parse transformations for a particular subcommand, and thus its
side-effects would not happen when IF NOT EXISTS fires.  I've not
worked this out in any detail, and there might still be a few ALTER
bugs this framework doesn't fix --- but I think my original idea
of "flags" controlling AlterTable execution probably isn't needed
if we go this way.

Now, if we move things around like that, it will have some effects
on what event triggers see --- certainly the order of operations
at least.  But do we feel a need to retain the same sort of
"encapsulation" that is currently happening due to the aforesaid
logic in utility.c?  I don't fully understand what that's for.

            regards, tom lane



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Alvaro Herrera
Дата:
On 2019-Oct-29, Tom Lane wrote:

> The thing I think you are actually worried about is the interaction
> with event triggers, which is already a pretty horrid mess in this
> code today.  I don't really follow the comment here about
> "ordering of queued commands".  It looks like that comment dates to
> Alvaro's commit b488c580a ... can either of you elucidate that?

The point of that comment is that if you enqueue the commands as they
are returned by pg_event_trigger_ddl_commands() (say by writing them to
a table) they must be emitted in an order that allows them to be
re-executed in a remote server that duplicates this one, and the final
state should be "the same".

> Now, if we move things around like that, it will have some effects
> on what event triggers see --- certainly the order of operations
> at least.  But do we feel a need to retain the same sort of
> "encapsulation" that is currently happening due to the aforesaid
> logic in utility.c?  I don't fully understand what that's for.

Sadly, the DDL replay logic is not being used for anything at present,
so I don't have a good test case to ensure that a proposed change is
good in this regard.  I've been approached by a couple people interested
in finishing the DDL conversion thing, but no takers so far.  I know
there's people using code based on the src/test/modules/test_ddl_deparse
module, but not for replicating a server's state to a different server, as
far as I know.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
> On 2019-Oct-29, Tom Lane wrote:
>> The thing I think you are actually worried about is the interaction
>> with event triggers, which is already a pretty horrid mess in this
>> code today.  I don't really follow the comment here about
>> "ordering of queued commands".  It looks like that comment dates to
>> Alvaro's commit b488c580a ... can either of you elucidate that?

> The point of that comment is that if you enqueue the commands as they
> are returned by pg_event_trigger_ddl_commands() (say by writing them to
> a table) they must be emitted in an order that allows them to be
> re-executed in a remote server that duplicates this one, and the final
> state should be "the same".

Hm.  I don't think I understand what is the use-case behind all this.
If "ALTER TABLE tab DO SOMETHING" generates some subcommands to do what
it's supposed to do, and then an event trigger is interested in replaying
that ALTER, how is it supposed to avoid having the subcommands happen
twice?  That is, it seems like we'd be better off to suppress the
generated subcommands from the event stream, because they'd just get
generated again anyway from execution of the primary command.  Or, if
there's something that is interested in knowing that those subcommands
happened, that's fine, but they'd better be marked somehow as informative
rather than something you want to explicitly replay.  (And if they are
just informative, why is the ordering so critical?)

            regards, tom lane



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
I wrote:
> Anyway, with the benefit of more time to let this thing percolate
> in my hindbrain, I am thinking that the fundamental error we've made
> is to do transformAlterTableStmt in advance of execution *at all*.
> The idea I now have is to scrap that, and instead apply the
> parse_utilcmd.c transformations individually to each AlterTable
> subcommand when it reaches execution in "phase 2" of AlterTable().

Attached is a patch that does things that way.  This appears to fix
all of the previously reported order-of-operations bugs in ALTER
TABLE, although there's still some squirrely-ness around identity
columns.

My original thought of postponing all parse analysis into the
execution phase turned out to be not quite right.  We still want
to analyze ALTER COLUMN TYPE subcommands before we start doing
anything.  The reason why is that any USING expressions in those
subcommands should all be parsed against the table's starting
rowtype, since those expressions will all be evaluated against
that state during a single rewrite pass in phase 3.  Fortunately
(but not coincidentally, I think) the execution-passes design is
"DROP, then ALTER COLUMN TYPE, then everything else", so that this
is okay.

I had to do some other finagling to get it to work, notably breaking
down some of the passes a bit more.  This allows us to have a rule
that any new subcommands deduced during mid-execution parse analysis
steps will be executed in a strictly later pass.  It might've been
possible to allow it to be "same pass", but I thought that would
be putting an undesirable amount of reliance on the semantics of
appending to a list that some other function is busy scanning.

What I did about the API issues we were arguing about before was
just to move the logic ProcessUtilitySlow had for handling
non-AlterTableStmts generated by ALTER TABLE parse analysis into
a new function that tablecmds.c calls.  This doesn't really resolve
any of the questions I had about event trigger processing, but
I think it at least doesn't make anything worse.  (The event
trigger, logical decoding, and sepgsql tests all pass without
any changes.)  It's tempting to consider providing a similar
API for CREATE SCHEMA to use, but I didn't do so here.

The squirrely-ness around identity is that while this now works:

regression=# CREATE TABLE itest8 (f1 int);
CREATE TABLE
regression=# ALTER TABLE itest8
regression-#   ADD COLUMN f2 int NOT NULL,
regression-#   ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
ALTER TABLE

it doesn't work if there's rows in the table:

regression=# CREATE TABLE itest8 (f1 int);
CREATE TABLE
regression=# insert into itest8 default values;
INSERT 0 1
regression=# ALTER TABLE itest8
  ADD COLUMN f2 int NOT NULL,
  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
ERROR:  column "f2" contains null values

The same would be true if you tried to do the ALTER as two separate
operations (because the ADD ... NOT NULL, without a default, will
naturally fail on a nonempty table).  So I don't feel *too* awful
about that.  But it'd be better if this worked.  It'll require
some refactoring of where the dependency link from an identity
column to its sequence gets set up.  This patch seems large enough
as-is, and it covers all the cases we've gotten field complaints
about, so I'm content to leave the residual identity issues for later.

            regards, tom lane

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8d25d14..7dc5d9a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -86,6 +86,7 @@
 #include "storage/lock.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
+#include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -144,11 +145,13 @@ static List *on_commits = NIL;
 #define AT_PASS_OLD_CONSTR        3    /* re-add existing constraints */
 /* We could support a RENAME COLUMN pass here, but not currently used */
 #define AT_PASS_ADD_COL            4    /* ADD COLUMN */
-#define AT_PASS_COL_ATTRS        5    /* set other column attributes */
-#define AT_PASS_ADD_INDEX        6    /* ADD indexes */
-#define AT_PASS_ADD_CONSTR        7    /* ADD constraints, defaults */
-#define AT_PASS_MISC            8    /* other stuff */
-#define AT_NUM_PASSES            9
+#define AT_PASS_ADD_CONSTR        5    /* ADD constraints (initial examination) */
+#define AT_PASS_COL_ATTRS        6    /* set column attributes, eg NOT NULL */
+#define AT_PASS_ADD_INDEXCONSTR    7    /* ADD index-based constraints */
+#define AT_PASS_ADD_INDEX        8    /* ADD indexes */
+#define AT_PASS_ADD_OTHERCONSTR    9    /* ADD other constraints, defaults */
+#define AT_PASS_MISC            10    /* other stuff */
+#define AT_NUM_PASSES            11

 typedef struct AlteredTableInfo
 {
@@ -161,6 +164,7 @@ typedef struct AlteredTableInfo
     /* Information saved by Phases 1/2 for Phase 3: */
     List       *constraints;    /* List of NewConstraint */
     List       *newvals;        /* List of NewColumnValue */
+    List       *afterStmts;        /* List of utility command parsetrees */
     bool        verify_new_notnull; /* T if we should recheck NOT NULL */
     int            rewrite;        /* Reason for forced rewrite, if any */
     Oid            newTableSpace;    /* new tablespace; 0 means no change */
@@ -340,31 +344,45 @@ static void validateForeignKeyConstraint(char *conname,
                                          Relation rel, Relation pkrel,
                                          Oid pkindOid, Oid constraintOid);
 static void ATController(AlterTableStmt *parsetree,
-                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
+                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+                         AlterTableUtilityContext *context);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                      bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode);
+                      bool recurse, bool recursing, LOCKMODE lockmode,
+                      AlterTableUtilityContext *context);
+static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                              AlterTableUtilityContext *context);
 static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                      AlterTableCmd *cmd, LOCKMODE lockmode);
+                      AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass,
+                      AlterTableUtilityContext *context);
+static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
+                                          Relation rel, AlterTableCmd *cmd,
+                                          bool recurse, LOCKMODE lockmode,
+                                          int cur_pass,
+                                          AlterTableUtilityContext *context);
 static void ATRewriteTables(AlterTableStmt *parsetree,
-                            List **wqueue, LOCKMODE lockmode);
+                            List **wqueue, LOCKMODE lockmode,
+                            AlterTableUtilityContext *context);
 static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
 static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
 static void ATSimplePermissions(Relation rel, int allowed_targets);
 static void ATWrongRelkindError(Relation rel, int allowed_targets);
 static void ATSimpleRecursion(List **wqueue, Relation rel,
-                              AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
+                              AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                              AlterTableUtilityContext *context);
 static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
 static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                                  LOCKMODE lockmode);
+                                  LOCKMODE lockmode,
+                                  AlterTableUtilityContext *context);
 static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
                                            DropBehavior behavior);
 static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                            bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode);
+                            bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
+                            AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
-                                     Relation rel, ColumnDef *colDef,
+                                     Relation rel, AlterTableCmd **cmd,
                                      bool recurse, bool recursing,
-                                     bool if_not_exists, LOCKMODE lockmode);
+                                     LOCKMODE lockmode, int cur_pass,
+                                     AlterTableUtilityContext *context);
 static bool check_for_column_name_collision(Relation rel, const char *colname,
                                             bool if_not_exists);
 static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
@@ -373,7 +391,8 @@ static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
 static void ATPrepSetNotNull(List **wqueue, Relation rel,
                              AlterTableCmd *cmd, bool recurse, bool recursing,
-                             LOCKMODE lockmode);
+                             LOCKMODE lockmode,
+                             AlterTableUtilityContext *context);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
                                       const char *colName, LOCKMODE lockmode);
 static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel,
@@ -397,7 +416,8 @@ static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
 static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
                                       Node *newValue, LOCKMODE lockmode);
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                             AlterTableCmd *cmd, LOCKMODE lockmode);
+                             AlterTableCmd *cmd, LOCKMODE lockmode,
+                             AlterTableUtilityContext *context);
 static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
                                       DropBehavior behavior,
                                       bool recurse, bool recursing,
@@ -454,7 +474,8 @@ static void ATExecDropConstraint(Relation rel, const char *constrName,
 static void ATPrepAlterColumnType(List **wqueue,
                                   AlteredTableInfo *tab, Relation rel,
                                   bool recurse, bool recursing,
-                                  AlterTableCmd *cmd, LOCKMODE lockmode);
+                                  AlterTableCmd *cmd, LOCKMODE lockmode,
+                                  AlterTableUtilityContext *context);
 static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
 static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                            AlterTableCmd *cmd, LOCKMODE lockmode);
@@ -3455,7 +3476,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  *
  * ALTER TABLE is performed in three phases:
  *        1. Examine subcommands and perform pre-transformation checking.
- *        2. Update system catalogs.
+ *        2. Validate and transform subcommands, and update system catalogs.
  *        3. Scan table(s) to check new constraints, and optionally recopy
  *           the data into new table(s).
  * Phase 3 is not performed unless one or more of the subcommands requires
@@ -3466,9 +3487,10 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * ATPrepCmd performs phase 1.  A "work queue" entry is created for
  * each table to be affected (there may be multiple affected tables if the
  * commands traverse a table inheritance hierarchy).  Also we do preliminary
- * validation of the subcommands, including parse transformation of those
- * expressions that need to be evaluated with respect to the old table
- * schema.
+ * validation of the subcommands.  Because earlier subcommands may change
+ * the catalog state seen by later commands, there are limits to what can
+ * be done in this phase.  Generally, this phase acquires table locks,
+ * checks permissions and relkind, and recurses to find child tables.
  *
  * ATRewriteCatalogs performs phase 2 for each affected table.  (Note that
  * phases 2 and 3 normally do no explicit recursion, since phase 1 already
@@ -3490,18 +3512,23 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
  * rather than reassess it at lower levels.
+ *
+ * The caller also provides a "context" which is to be passed back to
+ * utility.c when we need to execute a subcommand such as CREATE INDEX.
+ * Some of the fields therein, such as the relid, are used here as well.
  */
 void
-AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
+AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+           AlterTableUtilityContext *context)
 {
     Relation    rel;

     /* Caller is required to provide an adequate lock. */
-    rel = relation_open(relid, NoLock);
+    rel = relation_open(context->relid, NoLock);

     CheckTableNotInUse(rel, "ALTER TABLE");

-    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
+    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
 }

 /*
@@ -3514,6 +3541,10 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
  * is unsafe to use this entry point for alterations that could break
  * existing query plans.  On the assumption it's not used for such, we
  * don't have to reject pending AFTER triggers, either.
+ *
+ * Also, since we don't have an AlterTableUtilityContext, this cannot be
+ * used for any subcommand types that require parse transformation or
+ * could generate subcommands that have to be passed to ProcessUtility.
  */
 void
 AlterTableInternal(Oid relid, List *cmds, bool recurse)
@@ -3525,7 +3556,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)

     EventTriggerAlterTableRelid(relid);

-    ATController(NULL, rel, cmds, recurse, lockmode);
+    ATController(NULL, rel, cmds, recurse, lockmode, NULL);
 }

 /*
@@ -3670,7 +3701,6 @@ AlterTableGetLockLevel(List *cmds)
                 break;

             case AT_AddConstraint:
-            case AT_ProcessedConstraint:    /* becomes AT_AddConstraint */
             case AT_AddConstraintRecurse:    /* becomes AT_AddConstraint */
             case AT_ReAddConstraint:    /* becomes AT_AddConstraint */
             case AT_ReAddDomainConstraint:    /* becomes AT_AddConstraint */
@@ -3823,7 +3853,8 @@ AlterTableGetLockLevel(List *cmds)
  */
 static void
 ATController(AlterTableStmt *parsetree,
-             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
+             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+             AlterTableUtilityContext *context)
 {
     List       *wqueue = NIL;
     ListCell   *lcmd;
@@ -3833,17 +3864,17 @@ ATController(AlterTableStmt *parsetree,
     {
         AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);

-        ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode);
+        ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context);
     }

     /* Close the relation, but keep lock until commit */
     relation_close(rel, NoLock);

     /* Phase 2: update system catalogs */
-    ATRewriteCatalogs(&wqueue, lockmode);
+    ATRewriteCatalogs(&wqueue, lockmode, context);

-    /* Phase 3: scan/rewrite tables as needed */
-    ATRewriteTables(parsetree, &wqueue, lockmode);
+    /* Phase 3: scan/rewrite tables as needed, and run afterStmts */
+    ATRewriteTables(parsetree, &wqueue, lockmode, context);
 }

 /*
@@ -3857,7 +3888,8 @@ ATController(AlterTableStmt *parsetree,
  */
 static void
 ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
-          bool recurse, bool recursing, LOCKMODE lockmode)
+          bool recurse, bool recursing, LOCKMODE lockmode,
+          AlterTableUtilityContext *context)
 {
     AlteredTableInfo *tab;
     int            pass = AT_PASS_UNSET;
@@ -3869,13 +3901,17 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
      * Copy the original subcommand for each table.  This avoids conflicts
      * when different child tables need to make different parse
      * transformations (for example, the same column may have different column
-     * numbers in different children).
+     * numbers in different children).  It also ensures that we don't corrupt
+     * the original parse tree, in case it is saved in plancache.
      */
     cmd = copyObject(cmd);

     /*
-     * Do permissions checking, recursion to child tables if needed, and any
-     * additional phase-1 processing needed.
+     * Do permissions and relkind checking, recursion to child tables if
+     * needed, and any additional phase-1 processing needed.  (But beware of
+     * adding any processing that looks at table details that another
+     * subcommand could change.  In some cases we reject multiple subcommands
+     * that could try to change the same state in contrary ways.)
      */
     switch (cmd->subtype)
     {
@@ -3883,14 +3919,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
             ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
-                            lockmode);
+                            lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_ADD_COL;
             break;
         case AT_AddColumnToView:    /* add column via CREATE OR REPLACE VIEW */
             ATSimplePermissions(rel, ATT_VIEW);
             ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
-                            lockmode);
+                            lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_ADD_COL;
             break;
@@ -3903,19 +3939,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
              * rules.
              */
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
-            pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
+            pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
             break;
         case AT_AddIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
             /* This command never recurses */
-            pass = AT_PASS_ADD_CONSTR;
+            pass = AT_PASS_ADD_OTHERCONSTR;
             break;
         case AT_SetIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
             /* This command never recurses */
-            pass = AT_PASS_COL_ATTRS;
+            /* This should run after AddIdentity, so do it in MISC pass */
+            pass = AT_PASS_MISC;
             break;
         case AT_DropIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
@@ -3925,23 +3962,24 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
         case AT_DropNotNull:    /* ALTER COLUMN DROP NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
             ATPrepDropNotNull(rel, recurse, recursing);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             pass = AT_PASS_DROP;
             break;
         case AT_SetNotNull:        /* ALTER COLUMN SET NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
             /* Need command-specific recursion decision */
-            ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing, lockmode);
+            ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing,
+                             lockmode, context);
             pass = AT_PASS_COL_ATTRS;
             break;
         case AT_CheckNotNull:    /* check column is already marked NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_COL_ATTRS;
             break;
         case AT_SetStatistics:    /* ALTER COLUMN SET STATISTICS */
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* Performs own permission checks */
             ATPrepSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
             pass = AT_PASS_MISC;
@@ -3954,14 +3992,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetStorage:        /* ALTER COLUMN SET STORAGE */
             ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_MISC;
             break;
         case AT_DropColumn:        /* DROP COLUMN */
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
-            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
+            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
+                             lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_DROP;
             break;
@@ -3983,7 +4022,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             ATSimplePermissions(rel, ATT_TABLE);
             /* This command never recurses */
             /* No command-specific prep needed */
-            pass = AT_PASS_ADD_CONSTR;
+            pass = AT_PASS_ADD_INDEXCONSTR;
             break;
         case AT_DropConstraint: /* DROP CONSTRAINT */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
@@ -3997,8 +4036,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
         case AT_AlterColumnType:    /* ALTER COLUMN TYPE */
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
+            /* See comments for ATPrepAlterColumnType */
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
+                                      AT_PASS_UNSET, context);
+            Assert(cmd != NULL);
             /* Performs own recursion */
-            ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
+            ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
+                                  lockmode, context);
             pass = AT_PASS_ALTER_TYPE;
             break;
         case AT_AlterColumnGenericOptions:
@@ -4021,6 +4065,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetLogged:        /* SET LOGGED */
             ATSimplePermissions(rel, ATT_TABLE);
+            if (tab->chgPersistence)
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("cannot change persistence setting twice")));
             tab->chgPersistence = ATPrepChangePersistence(rel, true);
             /* force rewrite if necessary; see comment in ATRewriteTables */
             if (tab->chgPersistence)
@@ -4032,6 +4080,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetUnLogged:    /* SET UNLOGGED */
             ATSimplePermissions(rel, ATT_TABLE);
+            if (tab->chgPersistence)
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("cannot change persistence setting twice")));
             tab->chgPersistence = ATPrepChangePersistence(rel, false);
             /* force rewrite if necessary; see comment in ATRewriteTables */
             if (tab->chgPersistence)
@@ -4151,7 +4203,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  * conflicts).
  */
 static void
-ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
+ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                  AlterTableUtilityContext *context)
 {
     int            pass;
     ListCell   *ltab;
@@ -4184,7 +4237,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
             foreach(lcmd, subcmds)
                 ATExecCmd(wqueue, tab, rel,
                           castNode(AlterTableCmd, lfirst(lcmd)),
-                          lockmode);
+                          lockmode, pass, context);

             /*
              * After the ALTER TYPE pass, do cleanup work (this is not done in
@@ -4221,7 +4274,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
  */
 static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-          AlterTableCmd *cmd, LOCKMODE lockmode)
+          AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass,
+          AlterTableUtilityContext *context)
 {
     ObjectAddress address = InvalidObjectAddress;

@@ -4229,22 +4283,28 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
     {
         case AT_AddColumn:        /* ADD COLUMN */
         case AT_AddColumnToView:    /* add column via CREATE OR REPLACE VIEW */
-            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+            address = ATExecAddColumn(wqueue, tab, rel, &cmd,
                                       false, false,
-                                      cmd->missing_ok, lockmode);
+                                      lockmode, cur_pass, context);
             break;
         case AT_AddColumnRecurse:
-            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+            address = ATExecAddColumn(wqueue, tab, rel, &cmd,
                                       true, false,
-                                      cmd->missing_ok, lockmode);
+                                      lockmode, cur_pass, context);
             break;
         case AT_ColumnDefault:    /* ALTER COLUMN DEFAULT */
             address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_AddIdentity:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_SetIdentity:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_DropIdentity:
@@ -4292,14 +4352,24 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                      lockmode);
             break;
         case AT_AddConstraint:    /* ADD CONSTRAINT */
-            address =
-                ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                                    false, false, lockmode);
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            /* Might not have gotten AddConstraint back from parse transform */
+            if (cmd != NULL)
+                address =
+                    ATExecAddConstraint(wqueue, tab, rel,
+                                        (Constraint *) cmd->def,
+                                        false, false, lockmode);
             break;
         case AT_AddConstraintRecurse:    /* ADD CONSTRAINT with recursion */
-            address =
-                ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                                    true, false, lockmode);
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, true, lockmode,
+                                      cur_pass, context);
+            /* Might not have gotten AddConstraint back from parse transform */
+            if (cmd != NULL)
+                address =
+                    ATExecAddConstraint(wqueue, tab, rel,
+                                        (Constraint *) cmd->def,
+                                        true, false, lockmode);
             break;
         case AT_ReAddConstraint:    /* Re-add pre-existing check constraint */
             address =
@@ -4343,6 +4413,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                  cmd->missing_ok, lockmode);
             break;
         case AT_AlterColumnType:    /* ALTER COLUMN TYPE */
+            /* parse transformation was done earlier */
             address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
             break;
         case AT_AlterColumnGenericOptions:    /* ALTER COLUMN OPTIONS */
@@ -4465,6 +4536,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
             ATExecGenericOptions(rel, (List *) cmd->def);
             break;
         case AT_AttachPartition:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
                 ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
             else
@@ -4472,6 +4546,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                          ((PartitionCmd *) cmd->def)->name);
             break;
         case AT_DetachPartition:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             /* ATPrepCmd ensures it must be a table */
             Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
             ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
@@ -4485,7 +4562,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
     /*
      * Report the subcommand to interested event triggers.
      */
-    EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
+    if (cmd)
+        EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);

     /*
      * Bump the command counter to ensure the next subcommand in the sequence
@@ -4495,10 +4573,143 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 }

 /*
+ * ATParseTransformCmd: perform parse transformation for one subcommand
+ *
+ * Returns the transformed subcommand tree, if there is one, else NULL.
+ *
+ * The parser may hand back additional AlterTableCmd(s) and/or other
+ * utility statements, either before or after the original subcommand.
+ * Other AlterTableCmds are scheduled into the appropriate slot of the
+ * AlteredTableInfo (they had better be for later passes than the current one).
+ * Utility statements that are supposed to happen before the AlterTableCmd
+ * are executed immediately.  Those that are supposed to happen afterwards
+ * are added to the tab->afterStmts list to be done at the very end.
+ */
+static AlterTableCmd *
+ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
+                    AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                    int cur_pass, AlterTableUtilityContext *context)
+{
+    AlterTableCmd *newcmd = NULL;
+    AlterTableStmt *atstmt = makeNode(AlterTableStmt);
+    List       *beforeStmts;
+    List       *afterStmts;
+    ListCell   *lc;
+
+    /* Gin up an AlterTableStmt with just this subcommand and this table */
+    atstmt->relation =
+        makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+                     pstrdup(RelationGetRelationName(rel)),
+                     -1);
+    atstmt->relation->inh = recurse;
+    atstmt->cmds = list_make1(cmd);
+    atstmt->relkind = OBJECT_TABLE; /* needn't be picky here */
+    atstmt->missing_ok = false;
+
+    /* Transform the AlterTableStmt */
+    atstmt = transformAlterTableStmt(RelationGetRelid(rel),
+                                     atstmt,
+                                     context->queryString,
+                                     &beforeStmts,
+                                     &afterStmts);
+
+    /* Execute any statements that should happen before these subcommand(s) */
+    foreach(lc, beforeStmts)
+    {
+        Node       *stmt = (Node *) lfirst(lc);
+
+        ProcessUtilityForAlterTable(stmt, context);
+        CommandCounterIncrement();
+    }
+
+    /* Examine the transformed subcommands and schedule them appropriately */
+    foreach(lc, atstmt->cmds)
+    {
+        AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc);
+
+        if (newcmd == NULL &&
+            (cmd->subtype == cmd2->subtype ||
+             (cmd->subtype == AT_AddConstraintRecurse &&
+              cmd2->subtype == AT_AddConstraint)))
+        {
+            /* Found the transformed version of our subcommand */
+            cmd2->subtype = cmd->subtype;    /* copy recursion flag */
+            newcmd = cmd2;
+        }
+        else
+        {
+            int            pass;
+
+            /*
+             * Schedule added subcommand appropriately.  We assume we needn't
+             * do any phase-1 checks for it.  This switch only has to cover
+             * the subcommand types that can be added by parse_utilcmd.c.
+             */
+            switch (cmd2->subtype)
+            {
+                case AT_SetNotNull:
+                    /* Need command-specific recursion decision */
+                    ATPrepSetNotNull(wqueue, rel, cmd2,
+                                     recurse, false,
+                                     lockmode, context);
+                    pass = AT_PASS_COL_ATTRS;
+                    break;
+                case AT_AddIndex:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_ADD_INDEX;
+                    break;
+                case AT_AddIndexConstraint:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_ADD_INDEXCONSTR;
+                    break;
+                case AT_AddConstraint:
+                    /* Recursion occurs during execution phase */
+                    if (recurse)
+                        cmd2->subtype = AT_AddConstraintRecurse;
+                    switch (castNode(Constraint, cmd2->def)->contype)
+                    {
+                        case CONSTR_PRIMARY:
+                        case CONSTR_UNIQUE:
+                        case CONSTR_EXCLUSION:
+                            pass = AT_PASS_ADD_INDEXCONSTR;
+                            break;
+                        default:
+                            pass = AT_PASS_ADD_OTHERCONSTR;
+                            break;
+                    }
+                    break;
+                case AT_AlterColumnGenericOptions:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_MISC;
+                    break;
+                default:
+                    elog(ERROR, "unexpected AlterTableType: %d",
+                         (int) cmd2->subtype);
+                    pass = AT_PASS_UNSET;
+                    break;
+            }
+            /* Must be for a later pass than we're currently doing */
+            if (pass <= cur_pass)
+                elog(ERROR, "ALTER TABLE scheduling failure");
+            tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2);
+        }
+    }
+
+    /* Queue up any after-statements to happen at the end */
+    tab->afterStmts = list_concat(tab->afterStmts, afterStmts);
+
+    return newcmd;
+}
+
+/*
  * ATRewriteTables: ALTER TABLE phase 3
  */
 static void
-ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
+ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
+                AlterTableUtilityContext *context)
 {
     ListCell   *ltab;

@@ -4725,6 +4936,21 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
         if (rel)
             table_close(rel, NoLock);
     }
+
+    /* Finally, run any afterStmts that were queued up */
+    foreach(ltab, *wqueue)
+    {
+        AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+        ListCell   *lc;
+
+        foreach(lc, tab->afterStmts)
+        {
+            Node       *stmt = (Node *) lfirst(lc);
+
+            ProcessUtilityForAlterTable(stmt, context);
+            CommandCounterIncrement();
+        }
+    }
 }

 /*
@@ -4896,8 +5122,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)

             /*
              * Set all columns in the new slot to NULL initially, to ensure
-             * columns added as part of the rewrite are initialized to
-             * NULL. That is necessary as tab->newvals will not contain an
+             * columns added as part of the rewrite are initialized to NULL.
+             * That is necessary as tab->newvals will not contain an
              * expression for columns with a NULL default, e.g. when adding a
              * column without a default together with a column with a default
              * requiring an actual rewrite.
@@ -5236,7 +5462,8 @@ ATWrongRelkindError(Relation rel, int allowed_targets)
  */
 static void
 ATSimpleRecursion(List **wqueue, Relation rel,
-                  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode)
+                  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                  AlterTableUtilityContext *context)
 {
     /*
      * Propagate to children if desired.  Only plain tables, foreign tables
@@ -5269,7 +5496,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
             /* find_all_inheritors already got lock */
             childrel = relation_open(childrelid, NoLock);
             CheckTableNotInUse(childrel, "ALTER TABLE");
-            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
+            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
             relation_close(childrel, NoLock);
         }
     }
@@ -5314,7 +5541,7 @@ ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
  */
 static void
 ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                      LOCKMODE lockmode)
+                      LOCKMODE lockmode, AlterTableUtilityContext *context)
 {
     ListCell   *child;
     List       *children;
@@ -5332,7 +5559,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,

         childrel = relation_open(childrelid, lockmode);
         CheckTableNotInUse(childrel, "ALTER TABLE");
-        ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode);
+        ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
         relation_close(childrel, NoLock);
     }
 }
@@ -5569,7 +5796,8 @@ check_of_type(HeapTuple typetuple)
  */
 static void
 ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode)
+                bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
+                AlterTableUtilityContext *context)
 {
     if (rel->rd_rel->reloftype && !recursing)
         ereport(ERROR,
@@ -5577,7 +5805,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
                  errmsg("cannot add column to typed table")));

     if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

     if (recurse && !is_view)
         cmd->subtype = AT_AddColumnRecurse;
@@ -5586,14 +5814,20 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 /*
  * Add a column to a table.  The return value is the address of the
  * new column in the parent relation.
+ *
+ * cmd is pass-by-ref so that we can replace it with the parse-transformed
+ * copy (but that happens only after we check for IF NOT EXISTS).
  */
 static ObjectAddress
 ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                ColumnDef *colDef,
+                AlterTableCmd **cmd,
                 bool recurse, bool recursing,
-                bool if_not_exists, LOCKMODE lockmode)
+                LOCKMODE lockmode, int cur_pass,
+                AlterTableUtilityContext *context)
 {
     Oid            myrelid = RelationGetRelid(rel);
+    ColumnDef  *colDef = castNode(ColumnDef, (*cmd)->def);
+    bool        if_not_exists = (*cmd)->missing_ok;
     Relation    pgclass,
                 attrdesc;
     HeapTuple    reltup;
@@ -5608,6 +5842,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
     Expr       *defval;
     List       *children;
     ListCell   *child;
+    AlterTableCmd *childcmd;
     AclResult    aclresult;
     ObjectAddress address;

@@ -5675,12 +5910,31 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
         }
     }

-    pgclass = table_open(RelationRelationId, RowExclusiveLock);
+    /* skip if the name already exists and if_not_exists is true */
+    if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
+    {
+        table_close(attrdesc, RowExclusiveLock);
+        return InvalidObjectAddress;
+    }

-    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
-    if (!HeapTupleIsValid(reltup))
-        elog(ERROR, "cache lookup failed for relation %u", myrelid);
-    relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
+    /*
+     * Okay, we need to add the column, so go ahead and do parse
+     * transformation.  This can result in queueing up, or even immediately
+     * executing, subsidiary operations (such as creation of unique indexes);
+     * so we mustn't do it until we have made the if_not_exists check.
+     *
+     * When recursing, the command was already transformed and we needn't do
+     * so again.  Also, if context isn't given we can't transform.  (That
+     * currently happens only for AT_AddColumnToView; we expect that view.c
+     * passed us a ColumnDef that doesn't need work.)
+     */
+    if (context != NULL && !recursing)
+    {
+        *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode,
+                                   cur_pass, context);
+        Assert(*cmd != NULL);
+        colDef = castNode(ColumnDef, (*cmd)->def);
+    }

     /*
      * Cannot add identity column if table has children, because identity does
@@ -5693,14 +5947,12 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
                 (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                  errmsg("cannot recursively add identity column to table that has child tables")));

-    /* skip if the name already exists and if_not_exists is true */
-    if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
-    {
-        table_close(attrdesc, RowExclusiveLock);
-        heap_freetuple(reltup);
-        table_close(pgclass, RowExclusiveLock);
-        return InvalidObjectAddress;
-    }
+    pgclass = table_open(RelationRelationId, RowExclusiveLock);
+
+    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
+    if (!HeapTupleIsValid(reltup))
+        elog(ERROR, "cache lookup failed for relation %u", myrelid);
+    relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;

     /* Determine the new attribute's number */
     newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1;
@@ -5930,10 +6182,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
     /* Children should see column as singly inherited */
     if (!recursing)
     {
-        colDef = copyObject(colDef);
+        childcmd = copyObject(*cmd);
+        colDef = castNode(ColumnDef, childcmd->def);
         colDef->inhcount = 1;
         colDef->is_local = false;
     }
+    else
+        childcmd = *cmd;        /* no need to copy again */

     foreach(child, children)
     {
@@ -5950,8 +6205,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,

         /* Recurse to child; return value is ignored */
         ATExecAddColumn(wqueue, childtab, childrel,
-                        colDef, recurse, true,
-                        if_not_exists, lockmode);
+                        &childcmd, recurse, true,
+                        lockmode, cur_pass, context);

         table_close(childrel, NoLock);
     }
@@ -6210,7 +6465,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 static void
 ATPrepSetNotNull(List **wqueue, Relation rel,
                  AlterTableCmd *cmd, bool recurse, bool recursing,
-                 LOCKMODE lockmode)
+                 LOCKMODE lockmode, AlterTableUtilityContext *context)
 {
     /*
      * If we're already recursing, there's nothing to do; the topmost
@@ -6231,10 +6486,10 @@ ATPrepSetNotNull(List **wqueue, Relation rel,

         newcmd->subtype = AT_CheckNotNull;
         newcmd->name = pstrdup(cmd->name);
-        ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode);
+        ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode, context);
     }
     else
-        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
 }

 /*
@@ -7001,7 +7256,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
  */
 static void
 ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                 AlterTableCmd *cmd, LOCKMODE lockmode)
+                 AlterTableCmd *cmd, LOCKMODE lockmode,
+                 AlterTableUtilityContext *context)
 {
     if (rel->rd_rel->reloftype && !recursing)
         ereport(ERROR,
@@ -7009,7 +7265,7 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
                  errmsg("cannot drop column from typed table")));

     if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

     if (recurse)
         cmd->subtype = AT_DropColumnRecurse;
@@ -10261,12 +10517,27 @@ ATExecDropConstraint(Relation rel, const char *constrName,

 /*
  * ALTER COLUMN TYPE
+ *
+ * Unlike other subcommand types, we do parse transformation for ALTER COLUMN
+ * TYPE during phase 1 --- the AlterTableCmd passed in here is already
+ * transformed (and must be, because we rely on some transformed fields).
+ *
+ * The point of this is that the execution of all ALTER COLUMN TYPEs for a
+ * table will be done "in parallel" during phase 3, so all the USING
+ * expressions should be parsed assuming the original column types.  Also,
+ * this allows a USING expression to refer to a field that will be dropped.
+ *
+ * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be
+ * the first two execution steps in phase 2; they must not see the effects
+ * of any other subcommand types, since the USING expressions are parsed
+ * against the unmodified table's state.
  */
 static void
 ATPrepAlterColumnType(List **wqueue,
                       AlteredTableInfo *tab, Relation rel,
                       bool recurse, bool recursing,
-                      AlterTableCmd *cmd, LOCKMODE lockmode)
+                      AlterTableCmd *cmd, LOCKMODE lockmode,
+                      AlterTableUtilityContext *context)
 {
     char       *colName = cmd->name;
     ColumnDef  *def = (ColumnDef *) cmd->def;
@@ -10512,7 +10783,7 @@ ATPrepAlterColumnType(List **wqueue,
                              errdetail("USING expression contains a whole-row table reference.")));
                 pfree(attmap);
             }
-            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
+            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
             relation_close(childrel, NoLock);
         }
     }
@@ -10524,7 +10795,7 @@ ATPrepAlterColumnType(List **wqueue,
                         colName)));

     if (tab->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
 }

 /*
@@ -11303,10 +11574,19 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
                                                         (IndexStmt *) stmt,
                                                         cmd));
         else if (IsA(stmt, AlterTableStmt))
-            querytree_list = list_concat(querytree_list,
-                                         transformAlterTableStmt(oldRelId,
-                                                                 (AlterTableStmt *) stmt,
-                                                                 cmd));
+        {
+            List       *beforeStmts;
+            List       *afterStmts;
+
+            stmt = (Node *) transformAlterTableStmt(oldRelId,
+                                                    (AlterTableStmt *) stmt,
+                                                    cmd,
+                                                    &beforeStmts,
+                                                    &afterStmts);
+            querytree_list = list_concat(querytree_list, beforeStmts);
+            querytree_list = lappend(querytree_list, stmt);
+            querytree_list = list_concat(querytree_list, afterStmts);
+        }
         else
             querytree_list = lappend(querytree_list, stmt);
     }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index bea890f..20e0a88 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -146,6 +146,10 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
          * Note that we must do this before updating the query for the view,
          * since the rules system requires that the correct view columns be in
          * place when defining the new rules.
+         *
+         * Also note that ALTER TABLE doesn't run parse transformation on
+         * AT_AddColumnToView commands.  The ColumnDef we supply must be ready
+         * to execute as-is.
          */
         if (list_length(attrList) > rel->rd_att->natts)
         {
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee47547..29717db 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -347,7 +347,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
  */
 static void
 generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
-                         Oid seqtypid, List *seqoptions, bool for_identity,
+                         Oid seqtypid, List *seqoptions,
+                         bool for_identity, bool col_exists,
                          char **snamespace_p, char **sname_p)
 {
     ListCell   *option;
@@ -472,8 +473,12 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,

     /*
      * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as
-     * owned by this column, and add it to the list of things to be done after
-     * this CREATE/ALTER TABLE.
+     * owned by this column, and add it to the appropriate list of things to
+     * be done along with this CREATE/ALTER TABLE.  In a CREATE or ALTER ADD
+     * COLUMN, it must be done after the statement because we don't know the
+     * column's attnum yet.  But if we do have the attnum (in AT_AddIdentity),
+     * we can do the marking immediately, which improves some ALTER TABLE
+     * behaviors.
      */
     altseqstmt = makeNode(AlterSeqStmt);
     altseqstmt->sequence = makeRangeVar(snamespace, sname, -1);
@@ -484,7 +489,10 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
                                                  (Node *) attnamelist, -1));
     altseqstmt->for_identity = for_identity;

-    cxt->alist = lappend(cxt->alist, altseqstmt);
+    if (col_exists)
+        cxt->blist = lappend(cxt->blist, altseqstmt);
+    else
+        cxt->alist = lappend(cxt->alist, altseqstmt);

     if (snamespace_p)
         *snamespace_p = snamespace;
@@ -568,7 +576,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
         Constraint *constraint;

         generateSerialExtraStmts(cxt, column,
-                                 column->typeName->typeOid, NIL, false,
+                                 column->typeName->typeOid, NIL,
+                                 false, false,
                                  &snamespace, &sname);

         /*
@@ -684,7 +693,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
                                                     constraint->location)));

                     generateSerialExtraStmts(cxt, column,
-                                             typeOid, constraint->options, true,
+                                             typeOid, constraint->options,
+                                             true, false,
                                              NULL, NULL);

                     column->identity = constraint->generated_when;
@@ -1086,7 +1096,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
             seq_relid = getIdentitySequence(RelationGetRelid(relation), attribute->attnum, false);
             seq_options = sequence_options(seq_relid);
             generateSerialExtraStmts(cxt, def,
-                                     InvalidOid, seq_options, true,
+                                     InvalidOid, seq_options,
+                                     true, false,
                                      NULL, NULL);
             def->identity = attribute->attidentity;
         }
@@ -2570,7 +2581,7 @@ transformFKConstraints(CreateStmtContext *cxt,
             Constraint *constraint = (Constraint *) lfirst(fkclist);
             AlterTableCmd *altercmd = makeNode(AlterTableCmd);

-            altercmd->subtype = AT_ProcessedConstraint;
+            altercmd->subtype = AT_AddConstraint;
             altercmd->name = NULL;
             altercmd->def = (Node *) constraint;
             alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
@@ -3000,23 +3011,23 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  * transformAlterTableStmt -
  *        parse analysis for ALTER TABLE
  *
- * Returns a List of utility commands to be done in sequence.  One of these
- * will be the transformed AlterTableStmt, but there may be additional actions
- * to be done before and after the actual AlterTable() call.
+ * Returns the transformed AlterTableStmt.  There may be additional actions
+ * to be done before and after the transformed statement, which are returned
+ * in *beforeStmts and *afterStmts as lists of utility command parsetrees.
  *
  * To avoid race conditions, it's important that this function rely only on
  * the passed-in relid (and not on stmt->relation) to determine the target
  * relation.
  */
-List *
+AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-                        const char *queryString)
+                        const char *queryString,
+                        List **beforeStmts, List **afterStmts)
 {
     Relation    rel;
     TupleDesc    tupdesc;
     ParseState *pstate;
     CreateStmtContext cxt;
-    List       *result;
     List       *save_alist;
     ListCell   *lcmd,
                *l;
@@ -3048,7 +3059,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,

     /* Set up CreateStmtContext */
     cxt.pstate = pstate;
-    if (stmt->relkind == OBJECT_FOREIGN_TABLE)
+    if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
     {
         cxt.stmtType = "ALTER FOREIGN TABLE";
         cxt.isforeign = true;
@@ -3076,9 +3087,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
     cxt.ofType = false;

     /*
-     * The only subtypes that currently require parse transformation handling
-     * are ADD COLUMN, ADD CONSTRAINT and SET DATA TYPE.  These largely re-use
-     * code from CREATE TABLE.
+     * Transform ALTER subcommands that need it (most don't).  These largely
+     * re-use code from CREATE TABLE.
      */
     foreach(lcmd, stmt->cmds)
     {
@@ -3087,7 +3097,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
         switch (cmd->subtype)
         {
             case AT_AddColumn:
-            case AT_AddColumnToView:
+            case AT_AddColumnRecurse:
                 {
                     ColumnDef  *def = castNode(ColumnDef, cmd->def);

@@ -3111,6 +3121,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                 }

             case AT_AddConstraint:
+            case AT_AddConstraintRecurse:

                 /*
                  * The original AddConstraint cmd node doesn't go to newcmds
@@ -3126,19 +3137,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                          (int) nodeTag(cmd->def));
                 break;

-            case AT_ProcessedConstraint:
-
-                /*
-                 * Already-transformed ADD CONSTRAINT, so just make it look
-                 * like the standard case.
-                 */
-                cmd->subtype = AT_AddConstraint;
-                newcmds = lappend(newcmds, cmd);
-                break;
-
             case AT_AlterColumnType:
                 {
-                    ColumnDef  *def = (ColumnDef *) cmd->def;
+                    ColumnDef  *def = castNode(ColumnDef, cmd->def);
                     AttrNumber    attnum;

                     /*
@@ -3157,13 +3158,13 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                      * change the data type of the sequence.
                      */
                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    /*
-                     * if attribute not found, something will error about it
-                     * later
-                     */
-                    if (attnum != InvalidAttrNumber &&
-                        TupleDescAttr(tupdesc, attnum - 1)->attidentity)
+                    if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
                     {
                         Oid            seq_relid = getIdentitySequence(relid, attnum, false);
                         Oid            typeOid = typenameTypeId(pstate, def->typeName);
@@ -3192,16 +3193,16 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     cmd->def = (Node *) newdef;

                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    /*
-                     * if attribute not found, something will error about it
-                     * later
-                     */
-                    if (attnum != InvalidAttrNumber)
-                        generateSerialExtraStmts(&cxt, newdef,
-                                                 get_atttype(relid, attnum),
-                                                 def->options, true,
-                                                 NULL, NULL);
+                    generateSerialExtraStmts(&cxt, newdef,
+                                             get_atttype(relid, attnum),
+                                             def->options, true, true,
+                                             NULL, NULL);

                     newcmds = lappend(newcmds, cmd);
                     break;
@@ -3217,6 +3218,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     List       *newseqopts = NIL;
                     List       *newdef = NIL;
                     AttrNumber    attnum;
+                    Oid            seq_relid;

                     /*
                      * Split options into those handled by ALTER SEQUENCE and
@@ -3233,29 +3235,34 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     }

                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    if (attnum)
-                    {
-                        Oid            seq_relid = getIdentitySequence(relid, attnum, true);
+                    seq_relid = getIdentitySequence(relid, attnum, true);

-                        if (seq_relid)
-                        {
-                            AlterSeqStmt *seqstmt;
+                    if (seq_relid)
+                    {
+                        AlterSeqStmt *seqstmt;

-                            seqstmt = makeNode(AlterSeqStmt);
-                            seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
-                                                             get_rel_name(seq_relid), -1);
-                            seqstmt->options = newseqopts;
-                            seqstmt->for_identity = true;
-                            seqstmt->missing_ok = false;
+                        seqstmt = makeNode(AlterSeqStmt);
+                        seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
+                                                         get_rel_name(seq_relid), -1);
+                        seqstmt->options = newseqopts;
+                        seqstmt->for_identity = true;
+                        seqstmt->missing_ok = false;

-                            cxt.alist = lappend(cxt.alist, seqstmt);
-                        }
+                        cxt.blist = lappend(cxt.blist, seqstmt);
                     }

                     /*
-                     * If column was not found or was not an identity column,
-                     * we just let the ALTER TABLE command error out later.
+                     * If column was not an identity column, we just let the
+                     * ALTER TABLE command error out later.  (There are cases
+                     * this fails to cover, but we'll need to restructure
+                     * where creation of the sequence dependency linkage
+                     * happens before we can fix it.)
                      */

                     cmd->def = (Node *) newdef;
@@ -3277,6 +3284,12 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                 break;

             default:
+
+                /*
+                 * Currently, we shouldn't actually get here for subcommand
+                 * types that don't require transformation; but if we do, just
+                 * emit them unchanged.
+                 */
                 newcmds = lappend(newcmds, cmd);
                 break;
         }
@@ -3357,11 +3370,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
      */
     stmt->cmds = newcmds;

-    result = lappend(cxt.blist, stmt);
-    result = list_concat(result, cxt.alist);
-    result = list_concat(result, save_alist);
+    *beforeStmts = cxt.blist;
+    *afterStmts = list_concat(cxt.alist, save_alist);

-    return result;
+    return stmt;
 }


diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index c6faa66..69edbd7 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1097,8 +1097,6 @@ ProcessUtilitySlow(ParseState *pstate,
                 {
                     AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
                     Oid            relid;
-                    List       *stmts;
-                    ListCell   *l;
                     LOCKMODE    lockmode;

                     /*
@@ -1112,59 +1110,21 @@ ProcessUtilitySlow(ParseState *pstate,

                     if (OidIsValid(relid))
                     {
-                        /* Run parse analysis ... */
-                        stmts = transformAlterTableStmt(relid, atstmt,
-                                                        queryString);
+                        AlterTableUtilityContext atcontext;
+
+                        /* Set up info needed for recursive callbacks ... */
+                        atcontext.pstmt = pstmt;
+                        atcontext.queryString = queryString;
+                        atcontext.relid = relid;
+                        atcontext.params = params;
+                        atcontext.queryEnv = queryEnv;

                         /* ... ensure we have an event trigger context ... */
                         EventTriggerAlterTableStart(parsetree);
                         EventTriggerAlterTableRelid(relid);

                         /* ... and do it */
-                        foreach(l, stmts)
-                        {
-                            Node       *stmt = (Node *) lfirst(l);
-
-                            if (IsA(stmt, AlterTableStmt))
-                            {
-                                /* Do the table alteration proper */
-                                AlterTable(relid, lockmode,
-                                           (AlterTableStmt *) stmt);
-                            }
-                            else
-                            {
-                                /*
-                                 * Recurse for anything else.  If we need to
-                                 * do so, "close" the current complex-command
-                                 * set, and start a new one at the bottom;
-                                 * this is needed to ensure the ordering of
-                                 * queued commands is consistent with the way
-                                 * they are executed here.
-                                 */
-                                PlannedStmt *wrapper;
-
-                                EventTriggerAlterTableEnd();
-                                wrapper = makeNode(PlannedStmt);
-                                wrapper->commandType = CMD_UTILITY;
-                                wrapper->canSetTag = false;
-                                wrapper->utilityStmt = stmt;
-                                wrapper->stmt_location = pstmt->stmt_location;
-                                wrapper->stmt_len = pstmt->stmt_len;
-                                ProcessUtility(wrapper,
-                                               queryString,
-                                               PROCESS_UTILITY_SUBCOMMAND,
-                                               params,
-                                               NULL,
-                                               None_Receiver,
-                                               NULL);
-                                EventTriggerAlterTableStart(parsetree);
-                                EventTriggerAlterTableRelid(relid);
-                            }
-
-                            /* Need CCI between commands */
-                            if (lnext(stmts, l) != NULL)
-                                CommandCounterIncrement();
-                        }
+                        AlterTable(atstmt, lockmode, &atcontext);

                         /* done */
                         EventTriggerAlterTableEnd();
@@ -1729,6 +1689,52 @@ ProcessUtilitySlow(ParseState *pstate,
 }

 /*
+ * ProcessUtilityForAlterTable
+ *        Recursive entry from ALTER TABLE
+ *
+ * ALTER TABLE sometimes generates subcommands such as CREATE INDEX.
+ * It calls this, not the main entry point ProcessUtility, to execute
+ * such subcommands.
+ *
+ * stmt: the utility command to execute
+ * context: opaque passthrough struct with the info we need
+ *
+ * It's caller's responsibility to do CommandCounterIncrement after
+ * calling this, if needed.
+ */
+void
+ProcessUtilityForAlterTable(Node *stmt, AlterTableUtilityContext *context)
+{
+    PlannedStmt *wrapper;
+
+    /*
+     * For event triggers, we must "close" the current complex-command set,
+     * and start a new one afterwards; this is needed to ensure the ordering
+     * of command events is consistent with the way they were executed.
+     */
+    EventTriggerAlterTableEnd();
+
+    /* Create a suitable wrapper */
+    wrapper = makeNode(PlannedStmt);
+    wrapper->commandType = CMD_UTILITY;
+    wrapper->canSetTag = false;
+    wrapper->utilityStmt = stmt;
+    wrapper->stmt_location = context->pstmt->stmt_location;
+    wrapper->stmt_len = context->pstmt->stmt_len;
+
+    ProcessUtility(wrapper,
+                   context->queryString,
+                   PROCESS_UTILITY_SUBCOMMAND,
+                   context->params,
+                   context->queryEnv,
+                   None_Receiver,
+                   NULL);
+
+    EventTriggerAlterTableStart(context->pstmt->utilityStmt);
+    EventTriggerAlterTableRelid(context->relid);
+}
+
+/*
  * Dispatch function for DropStmt
  */
 static void
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 9c25a80..7245f83 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -21,6 +21,8 @@
 #include "storage/lock.h"
 #include "utils/relcache.h"

+struct AlterTableUtilityContext;    /* avoid including tcop/utility.h here */
+

 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
                                     ObjectAddress *typaddress, const char *queryString);
@@ -29,7 +31,8 @@ extern void RemoveRelations(DropStmt *drop);

 extern Oid    AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);

-extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt);
+extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+                       struct AlterTableUtilityContext *context);

 extern LOCKMODE AlterTableGetLockLevel(List *cmds);

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a..bc8418e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1780,8 +1780,6 @@ typedef enum AlterTableType
     AT_AlterConstraint,            /* alter constraint */
     AT_ValidateConstraint,        /* validate constraint */
     AT_ValidateConstraintRecurse,    /* internal to commands/tablecmds.c */
-    AT_ProcessedConstraint,        /* pre-processed add constraint (local in
-                                 * parser/parse_utilcmd.c) */
     AT_AddIndexConstraint,        /* add constraint using existing index */
     AT_DropConstraint,            /* drop constraint */
     AT_DropConstraintRecurse,    /* internal to commands/tablecmds.c */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 1348064..7d640cf 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -18,8 +18,10 @@


 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
-extern List *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-                                     const char *queryString);
+extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
+                                               const char *queryString,
+                                               List **beforeStmts,
+                                               List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
                                      const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 5abcacf..d8f5d4f 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -25,6 +25,16 @@ typedef enum
     PROCESS_UTILITY_SUBCOMMAND    /* a portion of a query */
 } ProcessUtilityContext;

+/* Info needed when recursing from ALTER TABLE */
+typedef struct AlterTableUtilityContext
+{
+    PlannedStmt *pstmt;            /* PlannedStmt for outer ALTER TABLE command */
+    const char *queryString;    /* its query string */
+    Oid            relid;            /* OID of ALTER's target table */
+    ParamListInfo params;        /* any parameters available to ALTER TABLE */
+    QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+} AlterTableUtilityContext;
+
 /* Hook for plugins to get control in ProcessUtility() */
 typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
                                           const char *queryString, ProcessUtilityContext context,
@@ -42,6 +52,9 @@ extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
                                     QueryEnvironment *queryEnv,
                                     DestReceiver *dest, char *completionTag);

+extern void ProcessUtilityForAlterTable(Node *stmt,
+                                        AlterTableUtilityContext *context);
+
 extern bool UtilityReturnsTuples(Node *parsetree);

 extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 7f77f19..04d963b 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -162,9 +162,6 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
             case AT_ValidateConstraintRecurse:
                 strtype = "VALIDATE CONSTRAINT (and recurse)";
                 break;
-            case AT_ProcessedConstraint:
-                strtype = "ADD (processed) CONSTRAINT";
-                break;
             case AT_AddIndexConstraint:
                 strtype = "ADD CONSTRAINT (using index)";
                 break;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 5189fd8..615b48e 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1958,27 +1958,29 @@ Indexes:
     "anothertab_f4_idx" UNIQUE, btree (f4)

 drop table anothertab;
-create table another (f1 int, f2 text);
-insert into another values(1, 'one');
-insert into another values(2, 'two');
-insert into another values(3, 'three');
+-- test that USING expressions are parsed before column alter type / drop steps
+create table another (f1 int, f2 text, f3 text);
+insert into another values(1, 'one', 'uno');
+insert into another values(2, 'two', 'due');
+insert into another values(3, 'three', 'tre');
 select * from another;
- f1 |  f2
-----+-------
-  1 | one
-  2 | two
-  3 | three
+ f1 |  f2   | f3
+----+-------+-----
+  1 | one   | uno
+  2 | two   | due
+  3 | three | tre
 (3 rows)

 alter table another
-  alter f1 type text using f2 || ' more',
-  alter f2 type bigint using f1 * 10;
+  alter f1 type text using f2 || ' and ' || f3 || ' more',
+  alter f2 type bigint using f1 * 10,
+  drop column f3;
 select * from another;
-     f1     | f2
-------------+----
- one more   | 10
- two more   | 20
- three more | 30
+         f1         | f2
+--------------------+----
+ one and uno more   | 10
+ two and due more   | 20
+ three and tre more | 30
 (3 rows)

 drop table another;
@@ -3473,7 +3475,7 @@ NOTICE:  column "c2" of relation "test_add_column" already exists, skipping

 ALTER TABLE test_add_column
     ADD COLUMN c2 integer, -- fail because c2 already exists
-    ADD COLUMN c3 integer;
+    ADD COLUMN c3 integer primary key;
 ERROR:  column "c2" of relation "test_add_column" already exists
 \d test_add_column
           Table "public.test_add_column"
@@ -3484,7 +3486,7 @@ ERROR:  column "c2" of relation "test_add_column" already exists

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN c3 integer; -- fail because c3 already exists
+    ADD COLUMN c3 integer primary key;
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 \d test_add_column
           Table "public.test_add_column"
@@ -3492,11 +3494,13 @@ NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
+    ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 \d test_add_column
@@ -3505,12 +3509,14 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
     ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
-    ADD COLUMN c4 integer;
+    ADD COLUMN c4 integer REFERENCES test_add_column;
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 \d test_add_column
@@ -3519,10 +3525,84 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
+NOTICE:  column "c4" of relation "test_add_column" already exists, skipping
+\d test_add_column
+          Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL;
+\d test_add_column
+                            Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable |                   Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
  c4     | integer |           |          |
+ c5     | integer |           | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL;
+NOTICE:  column "c5" of relation "test_add_column" already exists, skipping
+\d test_add_column*
+                            Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable |                   Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+ c5     | integer |           | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+               Sequence "public.test_add_column_c5_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |         1 | no      |     1
+Owned by: public.test_add_column.c5
+
+ Index "public.test_add_column_pkey"
+ Column |  Type   | Key? | Definition
+--------+---------+------+------------
+ c3     | integer | yes  | c3
+primary key, btree, for table "public.test_add_column"

 DROP TABLE test_add_column;
+\d test_add_column*
 -- unsupported constraint types for partitioned tables
 CREATE TABLE partitioned (
     a int,
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 36a2393..7cf4696 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -387,6 +387,68 @@ SELECT * FROM itest8;
 RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;
+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+INSERT INTO itest8 VALUES(0), (1);
+TABLE itest8;
+ f1 | f2 | f3 | f4 | f5
+----+----+----+----+----
+  0 |  1 |  1 |  1 |
+  1 |  2 | 11 |  2 |
+(2 rows)
+
+\d+ itest8
+                                               Table "public.itest8"
+ Column |  Type   | Collation | Nullable |             Default              | Storage | Stats target | Description
+--------+---------+-----------+----------+----------------------------------+---------+--------------+-------------
+ f1     | integer |           |          |                                  | plain   |              |
+ f2     | integer |           | not null | generated always as identity     | plain   |              |
+ f3     | integer |           | not null | generated by default as identity | plain   |              |
+ f4     | bigint  |           | not null | generated always as identity     | plain   |              |
+ f5     | bigint  |           |          |                                  | plain   |              |
+
+\d itest8_f2_seq
+                   Sequence "public.itest8_f2_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |         1 | no      |     1
+Sequence for identity column: public.itest8.f2
+
+\d itest8_f3_seq
+                   Sequence "public.itest8_f3_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |        10 | no      |     1
+Sequence for identity column: public.itest8.f3
+
+\d itest8_f4_seq
+                       Sequence "public.itest8_f4_seq"
+  Type  | Start | Minimum |       Maximum       | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint |     1 |       1 | 9223372036854775807 |         1 | no      |     1
+Sequence for identity column: public.itest8.f4
+
+\d itest8_f5_seq
+DROP TABLE itest8;
 -- typed tables (currently not supported)
 CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint);
 CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index 14d06c1..ea5afc9 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1342,17 +1342,19 @@ alter table anothertab alter column f5 type bigint;

 drop table anothertab;

-create table another (f1 int, f2 text);
+-- test that USING expressions are parsed before column alter type / drop steps
+create table another (f1 int, f2 text, f3 text);

-insert into another values(1, 'one');
-insert into another values(2, 'two');
-insert into another values(3, 'three');
+insert into another values(1, 'one', 'uno');
+insert into another values(2, 'two', 'due');
+insert into another values(3, 'three', 'tre');

 select * from another;

 alter table another
-  alter f1 type text using f2 || ' more',
-  alter f2 type bigint using f1 * 10;
+  alter f1 type text using f2 || ' and ' || f3 || ' more',
+  alter f2 type bigint using f1 * 10,
+  drop column f3;

 select * from another;

@@ -2174,22 +2176,32 @@ ALTER TABLE ONLY test_add_column
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN c2 integer, -- fail because c2 already exists
-    ADD COLUMN c3 integer;
+    ADD COLUMN c3 integer primary key;
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN c3 integer; -- fail because c3 already exists
+    ADD COLUMN c3 integer primary key;
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
+    ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
     ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
-    ADD COLUMN c4 integer;
+    ADD COLUMN c4 integer REFERENCES test_add_column;
 \d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
+\d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL;
+\d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL;
+\d test_add_column*
 DROP TABLE test_add_column;
+\d test_add_column*

 -- unsupported constraint types for partitioned tables
 CREATE TABLE partitioned (
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index 4b03d24..685607c 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -239,6 +239,44 @@ RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;

+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+
+INSERT INTO itest8 VALUES(0), (1);
+
+TABLE itest8;
+\d+ itest8
+\d itest8_f2_seq
+\d itest8_f3_seq
+\d itest8_f4_seq
+\d itest8_f5_seq
+DROP TABLE itest8;
+

 -- typed tables (currently not supported)


Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
I wrote:
> [ fix-alter-table-order-of-operations-1.patch ]

The cfbot noticed that this failed to apply over a recent commit,
so here's v2.  No substantive changes.

            regards, tom lane

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index daa80ec..b04ef36 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -85,6 +85,7 @@
 #include "storage/lock.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
+#include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -142,11 +143,13 @@ static List *on_commits = NIL;
 #define AT_PASS_OLD_CONSTR        3    /* re-add existing constraints */
 /* We could support a RENAME COLUMN pass here, but not currently used */
 #define AT_PASS_ADD_COL            4    /* ADD COLUMN */
-#define AT_PASS_COL_ATTRS        5    /* set other column attributes */
-#define AT_PASS_ADD_INDEX        6    /* ADD indexes */
-#define AT_PASS_ADD_CONSTR        7    /* ADD constraints, defaults */
-#define AT_PASS_MISC            8    /* other stuff */
-#define AT_NUM_PASSES            9
+#define AT_PASS_ADD_CONSTR        5    /* ADD constraints (initial examination) */
+#define AT_PASS_COL_ATTRS        6    /* set column attributes, eg NOT NULL */
+#define AT_PASS_ADD_INDEXCONSTR    7    /* ADD index-based constraints */
+#define AT_PASS_ADD_INDEX        8    /* ADD indexes */
+#define AT_PASS_ADD_OTHERCONSTR    9    /* ADD other constraints, defaults */
+#define AT_PASS_MISC            10    /* other stuff */
+#define AT_NUM_PASSES            11

 typedef struct AlteredTableInfo
 {
@@ -159,6 +162,7 @@ typedef struct AlteredTableInfo
     /* Information saved by Phases 1/2 for Phase 3: */
     List       *constraints;    /* List of NewConstraint */
     List       *newvals;        /* List of NewColumnValue */
+    List       *afterStmts;        /* List of utility command parsetrees */
     bool        verify_new_notnull; /* T if we should recheck NOT NULL */
     int            rewrite;        /* Reason for forced rewrite, if any */
     Oid            newTableSpace;    /* new tablespace; 0 means no change */
@@ -338,31 +342,45 @@ static void validateForeignKeyConstraint(char *conname,
                                          Relation rel, Relation pkrel,
                                          Oid pkindOid, Oid constraintOid);
 static void ATController(AlterTableStmt *parsetree,
-                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
+                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+                         AlterTableUtilityContext *context);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                      bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode);
+                      bool recurse, bool recursing, LOCKMODE lockmode,
+                      AlterTableUtilityContext *context);
+static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                              AlterTableUtilityContext *context);
 static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                      AlterTableCmd *cmd, LOCKMODE lockmode);
+                      AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass,
+                      AlterTableUtilityContext *context);
+static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
+                                          Relation rel, AlterTableCmd *cmd,
+                                          bool recurse, LOCKMODE lockmode,
+                                          int cur_pass,
+                                          AlterTableUtilityContext *context);
 static void ATRewriteTables(AlterTableStmt *parsetree,
-                            List **wqueue, LOCKMODE lockmode);
+                            List **wqueue, LOCKMODE lockmode,
+                            AlterTableUtilityContext *context);
 static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
 static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
 static void ATSimplePermissions(Relation rel, int allowed_targets);
 static void ATWrongRelkindError(Relation rel, int allowed_targets);
 static void ATSimpleRecursion(List **wqueue, Relation rel,
-                              AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
+                              AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                              AlterTableUtilityContext *context);
 static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
 static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                                  LOCKMODE lockmode);
+                                  LOCKMODE lockmode,
+                                  AlterTableUtilityContext *context);
 static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
                                            DropBehavior behavior);
 static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                            bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode);
+                            bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
+                            AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
-                                     Relation rel, ColumnDef *colDef,
+                                     Relation rel, AlterTableCmd **cmd,
                                      bool recurse, bool recursing,
-                                     bool if_not_exists, LOCKMODE lockmode);
+                                     LOCKMODE lockmode, int cur_pass,
+                                     AlterTableUtilityContext *context);
 static bool check_for_column_name_collision(Relation rel, const char *colname,
                                             bool if_not_exists);
 static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
@@ -371,7 +389,8 @@ static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
 static void ATPrepSetNotNull(List **wqueue, Relation rel,
                              AlterTableCmd *cmd, bool recurse, bool recursing,
-                             LOCKMODE lockmode);
+                             LOCKMODE lockmode,
+                             AlterTableUtilityContext *context);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
                                       const char *colName, LOCKMODE lockmode);
 static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel,
@@ -393,7 +412,8 @@ static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
 static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
                                       Node *newValue, LOCKMODE lockmode);
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                             AlterTableCmd *cmd, LOCKMODE lockmode);
+                             AlterTableCmd *cmd, LOCKMODE lockmode,
+                             AlterTableUtilityContext *context);
 static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
                                       DropBehavior behavior,
                                       bool recurse, bool recursing,
@@ -450,7 +470,8 @@ static void ATExecDropConstraint(Relation rel, const char *constrName,
 static void ATPrepAlterColumnType(List **wqueue,
                                   AlteredTableInfo *tab, Relation rel,
                                   bool recurse, bool recursing,
-                                  AlterTableCmd *cmd, LOCKMODE lockmode);
+                                  AlterTableCmd *cmd, LOCKMODE lockmode,
+                                  AlterTableUtilityContext *context);
 static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
 static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                            AlterTableCmd *cmd, LOCKMODE lockmode);
@@ -3460,7 +3481,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  *
  * ALTER TABLE is performed in three phases:
  *        1. Examine subcommands and perform pre-transformation checking.
- *        2. Update system catalogs.
+ *        2. Validate and transform subcommands, and update system catalogs.
  *        3. Scan table(s) to check new constraints, and optionally recopy
  *           the data into new table(s).
  * Phase 3 is not performed unless one or more of the subcommands requires
@@ -3471,9 +3492,10 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * ATPrepCmd performs phase 1.  A "work queue" entry is created for
  * each table to be affected (there may be multiple affected tables if the
  * commands traverse a table inheritance hierarchy).  Also we do preliminary
- * validation of the subcommands, including parse transformation of those
- * expressions that need to be evaluated with respect to the old table
- * schema.
+ * validation of the subcommands.  Because earlier subcommands may change
+ * the catalog state seen by later commands, there are limits to what can
+ * be done in this phase.  Generally, this phase acquires table locks,
+ * checks permissions and relkind, and recurses to find child tables.
  *
  * ATRewriteCatalogs performs phase 2 for each affected table.  (Note that
  * phases 2 and 3 normally do no explicit recursion, since phase 1 already
@@ -3495,18 +3517,23 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
  * rather than reassess it at lower levels.
+ *
+ * The caller also provides a "context" which is to be passed back to
+ * utility.c when we need to execute a subcommand such as CREATE INDEX.
+ * Some of the fields therein, such as the relid, are used here as well.
  */
 void
-AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
+AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+           AlterTableUtilityContext *context)
 {
     Relation    rel;

     /* Caller is required to provide an adequate lock. */
-    rel = relation_open(relid, NoLock);
+    rel = relation_open(context->relid, NoLock);

     CheckTableNotInUse(rel, "ALTER TABLE");

-    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
+    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
 }

 /*
@@ -3519,6 +3546,10 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
  * is unsafe to use this entry point for alterations that could break
  * existing query plans.  On the assumption it's not used for such, we
  * don't have to reject pending AFTER triggers, either.
+ *
+ * Also, since we don't have an AlterTableUtilityContext, this cannot be
+ * used for any subcommand types that require parse transformation or
+ * could generate subcommands that have to be passed to ProcessUtility.
  */
 void
 AlterTableInternal(Oid relid, List *cmds, bool recurse)
@@ -3530,7 +3561,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)

     EventTriggerAlterTableRelid(relid);

-    ATController(NULL, rel, cmds, recurse, lockmode);
+    ATController(NULL, rel, cmds, recurse, lockmode, NULL);
 }

 /*
@@ -3675,7 +3706,6 @@ AlterTableGetLockLevel(List *cmds)
                 break;

             case AT_AddConstraint:
-            case AT_ProcessedConstraint:    /* becomes AT_AddConstraint */
             case AT_AddConstraintRecurse:    /* becomes AT_AddConstraint */
             case AT_ReAddConstraint:    /* becomes AT_AddConstraint */
             case AT_ReAddDomainConstraint:    /* becomes AT_AddConstraint */
@@ -3828,7 +3858,8 @@ AlterTableGetLockLevel(List *cmds)
  */
 static void
 ATController(AlterTableStmt *parsetree,
-             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
+             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+             AlterTableUtilityContext *context)
 {
     List       *wqueue = NIL;
     ListCell   *lcmd;
@@ -3838,17 +3869,17 @@ ATController(AlterTableStmt *parsetree,
     {
         AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);

-        ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode);
+        ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context);
     }

     /* Close the relation, but keep lock until commit */
     relation_close(rel, NoLock);

     /* Phase 2: update system catalogs */
-    ATRewriteCatalogs(&wqueue, lockmode);
+    ATRewriteCatalogs(&wqueue, lockmode, context);

-    /* Phase 3: scan/rewrite tables as needed */
-    ATRewriteTables(parsetree, &wqueue, lockmode);
+    /* Phase 3: scan/rewrite tables as needed, and run afterStmts */
+    ATRewriteTables(parsetree, &wqueue, lockmode, context);
 }

 /*
@@ -3862,7 +3893,8 @@ ATController(AlterTableStmt *parsetree,
  */
 static void
 ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
-          bool recurse, bool recursing, LOCKMODE lockmode)
+          bool recurse, bool recursing, LOCKMODE lockmode,
+          AlterTableUtilityContext *context)
 {
     AlteredTableInfo *tab;
     int            pass = AT_PASS_UNSET;
@@ -3874,13 +3906,17 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
      * Copy the original subcommand for each table.  This avoids conflicts
      * when different child tables need to make different parse
      * transformations (for example, the same column may have different column
-     * numbers in different children).
+     * numbers in different children).  It also ensures that we don't corrupt
+     * the original parse tree, in case it is saved in plancache.
      */
     cmd = copyObject(cmd);

     /*
-     * Do permissions checking, recursion to child tables if needed, and any
-     * additional phase-1 processing needed.
+     * Do permissions and relkind checking, recursion to child tables if
+     * needed, and any additional phase-1 processing needed.  (But beware of
+     * adding any processing that looks at table details that another
+     * subcommand could change.  In some cases we reject multiple subcommands
+     * that could try to change the same state in contrary ways.)
      */
     switch (cmd->subtype)
     {
@@ -3888,14 +3924,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
             ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
-                            lockmode);
+                            lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_ADD_COL;
             break;
         case AT_AddColumnToView:    /* add column via CREATE OR REPLACE VIEW */
             ATSimplePermissions(rel, ATT_VIEW);
             ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
-                            lockmode);
+                            lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_ADD_COL;
             break;
@@ -3908,19 +3944,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
              * rules.
              */
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
-            pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
+            pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
             break;
         case AT_AddIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
             /* This command never recurses */
-            pass = AT_PASS_ADD_CONSTR;
+            pass = AT_PASS_ADD_OTHERCONSTR;
             break;
         case AT_SetIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
             /* This command never recurses */
-            pass = AT_PASS_COL_ATTRS;
+            /* This should run after AddIdentity, so do it in MISC pass */
+            pass = AT_PASS_MISC;
             break;
         case AT_DropIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
@@ -3930,24 +3967,25 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
         case AT_DropNotNull:    /* ALTER COLUMN DROP NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
             ATPrepDropNotNull(rel, recurse, recursing);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             pass = AT_PASS_DROP;
             break;
         case AT_SetNotNull:        /* ALTER COLUMN SET NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
             /* Need command-specific recursion decision */
-            ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing, lockmode);
+            ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing,
+                             lockmode, context);
             pass = AT_PASS_COL_ATTRS;
             break;
         case AT_CheckNotNull:    /* check column is already marked NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_COL_ATTRS;
             break;
         case AT_SetStatistics:    /* ALTER COLUMN SET STATISTICS */
             ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_MISC;
             break;
@@ -3959,14 +3997,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetStorage:        /* ALTER COLUMN SET STORAGE */
             ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_MISC;
             break;
         case AT_DropColumn:        /* DROP COLUMN */
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
-            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
+            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
+                             lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_DROP;
             break;
@@ -3988,7 +4027,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             ATSimplePermissions(rel, ATT_TABLE);
             /* This command never recurses */
             /* No command-specific prep needed */
-            pass = AT_PASS_ADD_CONSTR;
+            pass = AT_PASS_ADD_INDEXCONSTR;
             break;
         case AT_DropConstraint: /* DROP CONSTRAINT */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
@@ -4002,8 +4041,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
         case AT_AlterColumnType:    /* ALTER COLUMN TYPE */
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
+            /* See comments for ATPrepAlterColumnType */
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
+                                      AT_PASS_UNSET, context);
+            Assert(cmd != NULL);
             /* Performs own recursion */
-            ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
+            ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
+                                  lockmode, context);
             pass = AT_PASS_ALTER_TYPE;
             break;
         case AT_AlterColumnGenericOptions:
@@ -4026,6 +4070,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetLogged:        /* SET LOGGED */
             ATSimplePermissions(rel, ATT_TABLE);
+            if (tab->chgPersistence)
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("cannot change persistence setting twice")));
             tab->chgPersistence = ATPrepChangePersistence(rel, true);
             /* force rewrite if necessary; see comment in ATRewriteTables */
             if (tab->chgPersistence)
@@ -4037,6 +4085,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetUnLogged:    /* SET UNLOGGED */
             ATSimplePermissions(rel, ATT_TABLE);
+            if (tab->chgPersistence)
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("cannot change persistence setting twice")));
             tab->chgPersistence = ATPrepChangePersistence(rel, false);
             /* force rewrite if necessary; see comment in ATRewriteTables */
             if (tab->chgPersistence)
@@ -4156,7 +4208,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  * conflicts).
  */
 static void
-ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
+ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                  AlterTableUtilityContext *context)
 {
     int            pass;
     ListCell   *ltab;
@@ -4189,7 +4242,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
             foreach(lcmd, subcmds)
                 ATExecCmd(wqueue, tab, rel,
                           castNode(AlterTableCmd, lfirst(lcmd)),
-                          lockmode);
+                          lockmode, pass, context);

             /*
              * After the ALTER TYPE pass, do cleanup work (this is not done in
@@ -4226,7 +4279,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
  */
 static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-          AlterTableCmd *cmd, LOCKMODE lockmode)
+          AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass,
+          AlterTableUtilityContext *context)
 {
     ObjectAddress address = InvalidObjectAddress;

@@ -4234,22 +4288,28 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
     {
         case AT_AddColumn:        /* ADD COLUMN */
         case AT_AddColumnToView:    /* add column via CREATE OR REPLACE VIEW */
-            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+            address = ATExecAddColumn(wqueue, tab, rel, &cmd,
                                       false, false,
-                                      cmd->missing_ok, lockmode);
+                                      lockmode, cur_pass, context);
             break;
         case AT_AddColumnRecurse:
-            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+            address = ATExecAddColumn(wqueue, tab, rel, &cmd,
                                       true, false,
-                                      cmd->missing_ok, lockmode);
+                                      lockmode, cur_pass, context);
             break;
         case AT_ColumnDefault:    /* ALTER COLUMN DEFAULT */
             address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_AddIdentity:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_SetIdentity:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_DropIdentity:
@@ -4297,14 +4357,24 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                      lockmode);
             break;
         case AT_AddConstraint:    /* ADD CONSTRAINT */
-            address =
-                ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                                    false, false, lockmode);
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            /* Might not have gotten AddConstraint back from parse transform */
+            if (cmd != NULL)
+                address =
+                    ATExecAddConstraint(wqueue, tab, rel,
+                                        (Constraint *) cmd->def,
+                                        false, false, lockmode);
             break;
         case AT_AddConstraintRecurse:    /* ADD CONSTRAINT with recursion */
-            address =
-                ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                                    true, false, lockmode);
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, true, lockmode,
+                                      cur_pass, context);
+            /* Might not have gotten AddConstraint back from parse transform */
+            if (cmd != NULL)
+                address =
+                    ATExecAddConstraint(wqueue, tab, rel,
+                                        (Constraint *) cmd->def,
+                                        true, false, lockmode);
             break;
         case AT_ReAddConstraint:    /* Re-add pre-existing check constraint */
             address =
@@ -4348,6 +4418,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                  cmd->missing_ok, lockmode);
             break;
         case AT_AlterColumnType:    /* ALTER COLUMN TYPE */
+            /* parse transformation was done earlier */
             address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
             break;
         case AT_AlterColumnGenericOptions:    /* ALTER COLUMN OPTIONS */
@@ -4470,6 +4541,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
             ATExecGenericOptions(rel, (List *) cmd->def);
             break;
         case AT_AttachPartition:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
                 ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
             else
@@ -4477,6 +4551,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                          ((PartitionCmd *) cmd->def)->name);
             break;
         case AT_DetachPartition:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             /* ATPrepCmd ensures it must be a table */
             Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
             ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
@@ -4490,7 +4567,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
     /*
      * Report the subcommand to interested event triggers.
      */
-    EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
+    if (cmd)
+        EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);

     /*
      * Bump the command counter to ensure the next subcommand in the sequence
@@ -4500,10 +4578,143 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 }

 /*
+ * ATParseTransformCmd: perform parse transformation for one subcommand
+ *
+ * Returns the transformed subcommand tree, if there is one, else NULL.
+ *
+ * The parser may hand back additional AlterTableCmd(s) and/or other
+ * utility statements, either before or after the original subcommand.
+ * Other AlterTableCmds are scheduled into the appropriate slot of the
+ * AlteredTableInfo (they had better be for later passes than the current one).
+ * Utility statements that are supposed to happen before the AlterTableCmd
+ * are executed immediately.  Those that are supposed to happen afterwards
+ * are added to the tab->afterStmts list to be done at the very end.
+ */
+static AlterTableCmd *
+ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
+                    AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                    int cur_pass, AlterTableUtilityContext *context)
+{
+    AlterTableCmd *newcmd = NULL;
+    AlterTableStmt *atstmt = makeNode(AlterTableStmt);
+    List       *beforeStmts;
+    List       *afterStmts;
+    ListCell   *lc;
+
+    /* Gin up an AlterTableStmt with just this subcommand and this table */
+    atstmt->relation =
+        makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+                     pstrdup(RelationGetRelationName(rel)),
+                     -1);
+    atstmt->relation->inh = recurse;
+    atstmt->cmds = list_make1(cmd);
+    atstmt->relkind = OBJECT_TABLE; /* needn't be picky here */
+    atstmt->missing_ok = false;
+
+    /* Transform the AlterTableStmt */
+    atstmt = transformAlterTableStmt(RelationGetRelid(rel),
+                                     atstmt,
+                                     context->queryString,
+                                     &beforeStmts,
+                                     &afterStmts);
+
+    /* Execute any statements that should happen before these subcommand(s) */
+    foreach(lc, beforeStmts)
+    {
+        Node       *stmt = (Node *) lfirst(lc);
+
+        ProcessUtilityForAlterTable(stmt, context);
+        CommandCounterIncrement();
+    }
+
+    /* Examine the transformed subcommands and schedule them appropriately */
+    foreach(lc, atstmt->cmds)
+    {
+        AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc);
+
+        if (newcmd == NULL &&
+            (cmd->subtype == cmd2->subtype ||
+             (cmd->subtype == AT_AddConstraintRecurse &&
+              cmd2->subtype == AT_AddConstraint)))
+        {
+            /* Found the transformed version of our subcommand */
+            cmd2->subtype = cmd->subtype;    /* copy recursion flag */
+            newcmd = cmd2;
+        }
+        else
+        {
+            int            pass;
+
+            /*
+             * Schedule added subcommand appropriately.  We assume we needn't
+             * do any phase-1 checks for it.  This switch only has to cover
+             * the subcommand types that can be added by parse_utilcmd.c.
+             */
+            switch (cmd2->subtype)
+            {
+                case AT_SetNotNull:
+                    /* Need command-specific recursion decision */
+                    ATPrepSetNotNull(wqueue, rel, cmd2,
+                                     recurse, false,
+                                     lockmode, context);
+                    pass = AT_PASS_COL_ATTRS;
+                    break;
+                case AT_AddIndex:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_ADD_INDEX;
+                    break;
+                case AT_AddIndexConstraint:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_ADD_INDEXCONSTR;
+                    break;
+                case AT_AddConstraint:
+                    /* Recursion occurs during execution phase */
+                    if (recurse)
+                        cmd2->subtype = AT_AddConstraintRecurse;
+                    switch (castNode(Constraint, cmd2->def)->contype)
+                    {
+                        case CONSTR_PRIMARY:
+                        case CONSTR_UNIQUE:
+                        case CONSTR_EXCLUSION:
+                            pass = AT_PASS_ADD_INDEXCONSTR;
+                            break;
+                        default:
+                            pass = AT_PASS_ADD_OTHERCONSTR;
+                            break;
+                    }
+                    break;
+                case AT_AlterColumnGenericOptions:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_MISC;
+                    break;
+                default:
+                    elog(ERROR, "unexpected AlterTableType: %d",
+                         (int) cmd2->subtype);
+                    pass = AT_PASS_UNSET;
+                    break;
+            }
+            /* Must be for a later pass than we're currently doing */
+            if (pass <= cur_pass)
+                elog(ERROR, "ALTER TABLE scheduling failure");
+            tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2);
+        }
+    }
+
+    /* Queue up any after-statements to happen at the end */
+    tab->afterStmts = list_concat(tab->afterStmts, afterStmts);
+
+    return newcmd;
+}
+
+/*
  * ATRewriteTables: ALTER TABLE phase 3
  */
 static void
-ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
+ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
+                AlterTableUtilityContext *context)
 {
     ListCell   *ltab;

@@ -4730,6 +4941,21 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
         if (rel)
             table_close(rel, NoLock);
     }
+
+    /* Finally, run any afterStmts that were queued up */
+    foreach(ltab, *wqueue)
+    {
+        AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+        ListCell   *lc;
+
+        foreach(lc, tab->afterStmts)
+        {
+            Node       *stmt = (Node *) lfirst(lc);
+
+            ProcessUtilityForAlterTable(stmt, context);
+            CommandCounterIncrement();
+        }
+    }
 }

 /*
@@ -4901,8 +5127,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)

             /*
              * Set all columns in the new slot to NULL initially, to ensure
-             * columns added as part of the rewrite are initialized to
-             * NULL. That is necessary as tab->newvals will not contain an
+             * columns added as part of the rewrite are initialized to NULL.
+             * That is necessary as tab->newvals will not contain an
              * expression for columns with a NULL default, e.g. when adding a
              * column without a default together with a column with a default
              * requiring an actual rewrite.
@@ -5241,7 +5467,8 @@ ATWrongRelkindError(Relation rel, int allowed_targets)
  */
 static void
 ATSimpleRecursion(List **wqueue, Relation rel,
-                  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode)
+                  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                  AlterTableUtilityContext *context)
 {
     /*
      * Propagate to children if desired.  Only plain tables, foreign tables
@@ -5274,7 +5501,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
             /* find_all_inheritors already got lock */
             childrel = relation_open(childrelid, NoLock);
             CheckTableNotInUse(childrel, "ALTER TABLE");
-            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
+            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
             relation_close(childrel, NoLock);
         }
     }
@@ -5319,7 +5546,7 @@ ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
  */
 static void
 ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                      LOCKMODE lockmode)
+                      LOCKMODE lockmode, AlterTableUtilityContext *context)
 {
     ListCell   *child;
     List       *children;
@@ -5337,7 +5564,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,

         childrel = relation_open(childrelid, lockmode);
         CheckTableNotInUse(childrel, "ALTER TABLE");
-        ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode);
+        ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
         relation_close(childrel, NoLock);
     }
 }
@@ -5574,7 +5801,8 @@ check_of_type(HeapTuple typetuple)
  */
 static void
 ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode)
+                bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
+                AlterTableUtilityContext *context)
 {
     if (rel->rd_rel->reloftype && !recursing)
         ereport(ERROR,
@@ -5582,7 +5810,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
                  errmsg("cannot add column to typed table")));

     if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

     if (recurse && !is_view)
         cmd->subtype = AT_AddColumnRecurse;
@@ -5591,14 +5819,20 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 /*
  * Add a column to a table.  The return value is the address of the
  * new column in the parent relation.
+ *
+ * cmd is pass-by-ref so that we can replace it with the parse-transformed
+ * copy (but that happens only after we check for IF NOT EXISTS).
  */
 static ObjectAddress
 ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                ColumnDef *colDef,
+                AlterTableCmd **cmd,
                 bool recurse, bool recursing,
-                bool if_not_exists, LOCKMODE lockmode)
+                LOCKMODE lockmode, int cur_pass,
+                AlterTableUtilityContext *context)
 {
     Oid            myrelid = RelationGetRelid(rel);
+    ColumnDef  *colDef = castNode(ColumnDef, (*cmd)->def);
+    bool        if_not_exists = (*cmd)->missing_ok;
     Relation    pgclass,
                 attrdesc;
     HeapTuple    reltup;
@@ -5613,6 +5847,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
     Expr       *defval;
     List       *children;
     ListCell   *child;
+    AlterTableCmd *childcmd;
     AclResult    aclresult;
     ObjectAddress address;

@@ -5680,12 +5915,31 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
         }
     }

-    pgclass = table_open(RelationRelationId, RowExclusiveLock);
+    /* skip if the name already exists and if_not_exists is true */
+    if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
+    {
+        table_close(attrdesc, RowExclusiveLock);
+        return InvalidObjectAddress;
+    }

-    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
-    if (!HeapTupleIsValid(reltup))
-        elog(ERROR, "cache lookup failed for relation %u", myrelid);
-    relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
+    /*
+     * Okay, we need to add the column, so go ahead and do parse
+     * transformation.  This can result in queueing up, or even immediately
+     * executing, subsidiary operations (such as creation of unique indexes);
+     * so we mustn't do it until we have made the if_not_exists check.
+     *
+     * When recursing, the command was already transformed and we needn't do
+     * so again.  Also, if context isn't given we can't transform.  (That
+     * currently happens only for AT_AddColumnToView; we expect that view.c
+     * passed us a ColumnDef that doesn't need work.)
+     */
+    if (context != NULL && !recursing)
+    {
+        *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode,
+                                   cur_pass, context);
+        Assert(*cmd != NULL);
+        colDef = castNode(ColumnDef, (*cmd)->def);
+    }

     /*
      * Cannot add identity column if table has children, because identity does
@@ -5698,14 +5952,12 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
                 (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                  errmsg("cannot recursively add identity column to table that has child tables")));

-    /* skip if the name already exists and if_not_exists is true */
-    if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
-    {
-        table_close(attrdesc, RowExclusiveLock);
-        heap_freetuple(reltup);
-        table_close(pgclass, RowExclusiveLock);
-        return InvalidObjectAddress;
-    }
+    pgclass = table_open(RelationRelationId, RowExclusiveLock);
+
+    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
+    if (!HeapTupleIsValid(reltup))
+        elog(ERROR, "cache lookup failed for relation %u", myrelid);
+    relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;

     /* Determine the new attribute's number */
     newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1;
@@ -5935,10 +6187,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
     /* Children should see column as singly inherited */
     if (!recursing)
     {
-        colDef = copyObject(colDef);
+        childcmd = copyObject(*cmd);
+        colDef = castNode(ColumnDef, childcmd->def);
         colDef->inhcount = 1;
         colDef->is_local = false;
     }
+    else
+        childcmd = *cmd;        /* no need to copy again */

     foreach(child, children)
     {
@@ -5955,8 +6210,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,

         /* Recurse to child; return value is ignored */
         ATExecAddColumn(wqueue, childtab, childrel,
-                        colDef, recurse, true,
-                        if_not_exists, lockmode);
+                        &childcmd, recurse, true,
+                        lockmode, cur_pass, context);

         table_close(childrel, NoLock);
     }
@@ -6215,7 +6470,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 static void
 ATPrepSetNotNull(List **wqueue, Relation rel,
                  AlterTableCmd *cmd, bool recurse, bool recursing,
-                 LOCKMODE lockmode)
+                 LOCKMODE lockmode, AlterTableUtilityContext *context)
 {
     /*
      * If we're already recursing, there's nothing to do; the topmost
@@ -6236,10 +6491,10 @@ ATPrepSetNotNull(List **wqueue, Relation rel,

         newcmd->subtype = AT_CheckNotNull;
         newcmd->name = pstrdup(cmd->name);
-        ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode);
+        ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode, context);
     }
     else
-        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
 }

 /*
@@ -6979,7 +7234,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
  */
 static void
 ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                 AlterTableCmd *cmd, LOCKMODE lockmode)
+                 AlterTableCmd *cmd, LOCKMODE lockmode,
+                 AlterTableUtilityContext *context)
 {
     if (rel->rd_rel->reloftype && !recursing)
         ereport(ERROR,
@@ -6987,7 +7243,7 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
                  errmsg("cannot drop column from typed table")));

     if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

     if (recurse)
         cmd->subtype = AT_DropColumnRecurse;
@@ -10239,12 +10495,27 @@ ATExecDropConstraint(Relation rel, const char *constrName,

 /*
  * ALTER COLUMN TYPE
+ *
+ * Unlike other subcommand types, we do parse transformation for ALTER COLUMN
+ * TYPE during phase 1 --- the AlterTableCmd passed in here is already
+ * transformed (and must be, because we rely on some transformed fields).
+ *
+ * The point of this is that the execution of all ALTER COLUMN TYPEs for a
+ * table will be done "in parallel" during phase 3, so all the USING
+ * expressions should be parsed assuming the original column types.  Also,
+ * this allows a USING expression to refer to a field that will be dropped.
+ *
+ * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be
+ * the first two execution steps in phase 2; they must not see the effects
+ * of any other subcommand types, since the USING expressions are parsed
+ * against the unmodified table's state.
  */
 static void
 ATPrepAlterColumnType(List **wqueue,
                       AlteredTableInfo *tab, Relation rel,
                       bool recurse, bool recursing,
-                      AlterTableCmd *cmd, LOCKMODE lockmode)
+                      AlterTableCmd *cmd, LOCKMODE lockmode,
+                      AlterTableUtilityContext *context)
 {
     char       *colName = cmd->name;
     ColumnDef  *def = (ColumnDef *) cmd->def;
@@ -10490,7 +10761,7 @@ ATPrepAlterColumnType(List **wqueue,
                              errdetail("USING expression contains a whole-row table reference.")));
                 pfree(attmap);
             }
-            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
+            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
             relation_close(childrel, NoLock);
         }
     }
@@ -10502,7 +10773,7 @@ ATPrepAlterColumnType(List **wqueue,
                         colName)));

     if (tab->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
 }

 /*
@@ -11281,10 +11552,19 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
                                                         (IndexStmt *) stmt,
                                                         cmd));
         else if (IsA(stmt, AlterTableStmt))
-            querytree_list = list_concat(querytree_list,
-                                         transformAlterTableStmt(oldRelId,
-                                                                 (AlterTableStmt *) stmt,
-                                                                 cmd));
+        {
+            List       *beforeStmts;
+            List       *afterStmts;
+
+            stmt = (Node *) transformAlterTableStmt(oldRelId,
+                                                    (AlterTableStmt *) stmt,
+                                                    cmd,
+                                                    &beforeStmts,
+                                                    &afterStmts);
+            querytree_list = list_concat(querytree_list, beforeStmts);
+            querytree_list = lappend(querytree_list, stmt);
+            querytree_list = list_concat(querytree_list, afterStmts);
+        }
         else
             querytree_list = lappend(querytree_list, stmt);
     }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 9b51480..73b3e60 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -145,6 +145,10 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
          * Note that we must do this before updating the query for the view,
          * since the rules system requires that the correct view columns be in
          * place when defining the new rules.
+         *
+         * Also note that ALTER TABLE doesn't run parse transformation on
+         * AT_AddColumnToView commands.  The ColumnDef we supply must be ready
+         * to execute as-is.
          */
         if (list_length(attrList) > rel->rd_att->natts)
         {
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b761fdf..77c4fc8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -347,7 +347,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
  */
 static void
 generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
-                         Oid seqtypid, List *seqoptions, bool for_identity,
+                         Oid seqtypid, List *seqoptions,
+                         bool for_identity, bool col_exists,
                          char **snamespace_p, char **sname_p)
 {
     ListCell   *option;
@@ -472,8 +473,12 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,

     /*
      * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as
-     * owned by this column, and add it to the list of things to be done after
-     * this CREATE/ALTER TABLE.
+     * owned by this column, and add it to the appropriate list of things to
+     * be done along with this CREATE/ALTER TABLE.  In a CREATE or ALTER ADD
+     * COLUMN, it must be done after the statement because we don't know the
+     * column's attnum yet.  But if we do have the attnum (in AT_AddIdentity),
+     * we can do the marking immediately, which improves some ALTER TABLE
+     * behaviors.
      */
     altseqstmt = makeNode(AlterSeqStmt);
     altseqstmt->sequence = makeRangeVar(snamespace, sname, -1);
@@ -484,7 +489,10 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
                                                  (Node *) attnamelist, -1));
     altseqstmt->for_identity = for_identity;

-    cxt->alist = lappend(cxt->alist, altseqstmt);
+    if (col_exists)
+        cxt->blist = lappend(cxt->blist, altseqstmt);
+    else
+        cxt->alist = lappend(cxt->alist, altseqstmt);

     if (snamespace_p)
         *snamespace_p = snamespace;
@@ -568,7 +576,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
         Constraint *constraint;

         generateSerialExtraStmts(cxt, column,
-                                 column->typeName->typeOid, NIL, false,
+                                 column->typeName->typeOid, NIL,
+                                 false, false,
                                  &snamespace, &sname);

         /*
@@ -684,7 +693,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
                                                     constraint->location)));

                     generateSerialExtraStmts(cxt, column,
-                                             typeOid, constraint->options, true,
+                                             typeOid, constraint->options,
+                                             true, false,
                                              NULL, NULL);

                     column->identity = constraint->generated_when;
@@ -1086,7 +1096,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
             seq_relid = getIdentitySequence(RelationGetRelid(relation), attribute->attnum, false);
             seq_options = sequence_options(seq_relid);
             generateSerialExtraStmts(cxt, def,
-                                     InvalidOid, seq_options, true,
+                                     InvalidOid, seq_options,
+                                     true, false,
                                      NULL, NULL);
             def->identity = attribute->attidentity;
         }
@@ -2572,7 +2583,7 @@ transformFKConstraints(CreateStmtContext *cxt,
             Constraint *constraint = (Constraint *) lfirst(fkclist);
             AlterTableCmd *altercmd = makeNode(AlterTableCmd);

-            altercmd->subtype = AT_ProcessedConstraint;
+            altercmd->subtype = AT_AddConstraint;
             altercmd->name = NULL;
             altercmd->def = (Node *) constraint;
             alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
@@ -3002,23 +3013,23 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  * transformAlterTableStmt -
  *        parse analysis for ALTER TABLE
  *
- * Returns a List of utility commands to be done in sequence.  One of these
- * will be the transformed AlterTableStmt, but there may be additional actions
- * to be done before and after the actual AlterTable() call.
+ * Returns the transformed AlterTableStmt.  There may be additional actions
+ * to be done before and after the transformed statement, which are returned
+ * in *beforeStmts and *afterStmts as lists of utility command parsetrees.
  *
  * To avoid race conditions, it's important that this function rely only on
  * the passed-in relid (and not on stmt->relation) to determine the target
  * relation.
  */
-List *
+AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-                        const char *queryString)
+                        const char *queryString,
+                        List **beforeStmts, List **afterStmts)
 {
     Relation    rel;
     TupleDesc    tupdesc;
     ParseState *pstate;
     CreateStmtContext cxt;
-    List       *result;
     List       *save_alist;
     ListCell   *lcmd,
                *l;
@@ -3050,7 +3061,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,

     /* Set up CreateStmtContext */
     cxt.pstate = pstate;
-    if (stmt->relkind == OBJECT_FOREIGN_TABLE)
+    if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
     {
         cxt.stmtType = "ALTER FOREIGN TABLE";
         cxt.isforeign = true;
@@ -3078,9 +3089,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
     cxt.ofType = false;

     /*
-     * The only subtypes that currently require parse transformation handling
-     * are ADD COLUMN, ADD CONSTRAINT and SET DATA TYPE.  These largely re-use
-     * code from CREATE TABLE.
+     * Transform ALTER subcommands that need it (most don't).  These largely
+     * re-use code from CREATE TABLE.
      */
     foreach(lcmd, stmt->cmds)
     {
@@ -3089,7 +3099,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
         switch (cmd->subtype)
         {
             case AT_AddColumn:
-            case AT_AddColumnToView:
+            case AT_AddColumnRecurse:
                 {
                     ColumnDef  *def = castNode(ColumnDef, cmd->def);

@@ -3113,6 +3123,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                 }

             case AT_AddConstraint:
+            case AT_AddConstraintRecurse:

                 /*
                  * The original AddConstraint cmd node doesn't go to newcmds
@@ -3128,19 +3139,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                          (int) nodeTag(cmd->def));
                 break;

-            case AT_ProcessedConstraint:
-
-                /*
-                 * Already-transformed ADD CONSTRAINT, so just make it look
-                 * like the standard case.
-                 */
-                cmd->subtype = AT_AddConstraint;
-                newcmds = lappend(newcmds, cmd);
-                break;
-
             case AT_AlterColumnType:
                 {
-                    ColumnDef  *def = (ColumnDef *) cmd->def;
+                    ColumnDef  *def = castNode(ColumnDef, cmd->def);
                     AttrNumber    attnum;

                     /*
@@ -3159,13 +3160,13 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                      * change the data type of the sequence.
                      */
                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    /*
-                     * if attribute not found, something will error about it
-                     * later
-                     */
-                    if (attnum != InvalidAttrNumber &&
-                        TupleDescAttr(tupdesc, attnum - 1)->attidentity)
+                    if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
                     {
                         Oid            seq_relid = getIdentitySequence(relid, attnum, false);
                         Oid            typeOid = typenameTypeId(pstate, def->typeName);
@@ -3194,16 +3195,16 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     cmd->def = (Node *) newdef;

                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    /*
-                     * if attribute not found, something will error about it
-                     * later
-                     */
-                    if (attnum != InvalidAttrNumber)
-                        generateSerialExtraStmts(&cxt, newdef,
-                                                 get_atttype(relid, attnum),
-                                                 def->options, true,
-                                                 NULL, NULL);
+                    generateSerialExtraStmts(&cxt, newdef,
+                                             get_atttype(relid, attnum),
+                                             def->options, true, true,
+                                             NULL, NULL);

                     newcmds = lappend(newcmds, cmd);
                     break;
@@ -3219,6 +3220,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     List       *newseqopts = NIL;
                     List       *newdef = NIL;
                     AttrNumber    attnum;
+                    Oid            seq_relid;

                     /*
                      * Split options into those handled by ALTER SEQUENCE and
@@ -3235,29 +3237,34 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     }

                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    if (attnum)
-                    {
-                        Oid            seq_relid = getIdentitySequence(relid, attnum, true);
+                    seq_relid = getIdentitySequence(relid, attnum, true);

-                        if (seq_relid)
-                        {
-                            AlterSeqStmt *seqstmt;
+                    if (seq_relid)
+                    {
+                        AlterSeqStmt *seqstmt;

-                            seqstmt = makeNode(AlterSeqStmt);
-                            seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
-                                                             get_rel_name(seq_relid), -1);
-                            seqstmt->options = newseqopts;
-                            seqstmt->for_identity = true;
-                            seqstmt->missing_ok = false;
+                        seqstmt = makeNode(AlterSeqStmt);
+                        seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
+                                                         get_rel_name(seq_relid), -1);
+                        seqstmt->options = newseqopts;
+                        seqstmt->for_identity = true;
+                        seqstmt->missing_ok = false;

-                            cxt.alist = lappend(cxt.alist, seqstmt);
-                        }
+                        cxt.blist = lappend(cxt.blist, seqstmt);
                     }

                     /*
-                     * If column was not found or was not an identity column,
-                     * we just let the ALTER TABLE command error out later.
+                     * If column was not an identity column, we just let the
+                     * ALTER TABLE command error out later.  (There are cases
+                     * this fails to cover, but we'll need to restructure
+                     * where creation of the sequence dependency linkage
+                     * happens before we can fix it.)
                      */

                     cmd->def = (Node *) newdef;
@@ -3279,6 +3286,12 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                 break;

             default:
+
+                /*
+                 * Currently, we shouldn't actually get here for subcommand
+                 * types that don't require transformation; but if we do, just
+                 * emit them unchanged.
+                 */
                 newcmds = lappend(newcmds, cmd);
                 break;
         }
@@ -3359,11 +3372,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
      */
     stmt->cmds = newcmds;

-    result = lappend(cxt.blist, stmt);
-    result = list_concat(result, cxt.alist);
-    result = list_concat(result, save_alist);
+    *beforeStmts = cxt.blist;
+    *afterStmts = list_concat(cxt.alist, save_alist);

-    return result;
+    return stmt;
 }


diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3a03ca7..ad16f8a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1092,8 +1092,6 @@ ProcessUtilitySlow(ParseState *pstate,
                 {
                     AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
                     Oid            relid;
-                    List       *stmts;
-                    ListCell   *l;
                     LOCKMODE    lockmode;

                     /*
@@ -1107,59 +1105,21 @@ ProcessUtilitySlow(ParseState *pstate,

                     if (OidIsValid(relid))
                     {
-                        /* Run parse analysis ... */
-                        stmts = transformAlterTableStmt(relid, atstmt,
-                                                        queryString);
+                        AlterTableUtilityContext atcontext;
+
+                        /* Set up info needed for recursive callbacks ... */
+                        atcontext.pstmt = pstmt;
+                        atcontext.queryString = queryString;
+                        atcontext.relid = relid;
+                        atcontext.params = params;
+                        atcontext.queryEnv = queryEnv;

                         /* ... ensure we have an event trigger context ... */
                         EventTriggerAlterTableStart(parsetree);
                         EventTriggerAlterTableRelid(relid);

                         /* ... and do it */
-                        foreach(l, stmts)
-                        {
-                            Node       *stmt = (Node *) lfirst(l);
-
-                            if (IsA(stmt, AlterTableStmt))
-                            {
-                                /* Do the table alteration proper */
-                                AlterTable(relid, lockmode,
-                                           (AlterTableStmt *) stmt);
-                            }
-                            else
-                            {
-                                /*
-                                 * Recurse for anything else.  If we need to
-                                 * do so, "close" the current complex-command
-                                 * set, and start a new one at the bottom;
-                                 * this is needed to ensure the ordering of
-                                 * queued commands is consistent with the way
-                                 * they are executed here.
-                                 */
-                                PlannedStmt *wrapper;
-
-                                EventTriggerAlterTableEnd();
-                                wrapper = makeNode(PlannedStmt);
-                                wrapper->commandType = CMD_UTILITY;
-                                wrapper->canSetTag = false;
-                                wrapper->utilityStmt = stmt;
-                                wrapper->stmt_location = pstmt->stmt_location;
-                                wrapper->stmt_len = pstmt->stmt_len;
-                                ProcessUtility(wrapper,
-                                               queryString,
-                                               PROCESS_UTILITY_SUBCOMMAND,
-                                               params,
-                                               NULL,
-                                               None_Receiver,
-                                               NULL);
-                                EventTriggerAlterTableStart(parsetree);
-                                EventTriggerAlterTableRelid(relid);
-                            }
-
-                            /* Need CCI between commands */
-                            if (lnext(stmts, l) != NULL)
-                                CommandCounterIncrement();
-                        }
+                        AlterTable(atstmt, lockmode, &atcontext);

                         /* done */
                         EventTriggerAlterTableEnd();
@@ -1718,6 +1678,52 @@ ProcessUtilitySlow(ParseState *pstate,
 }

 /*
+ * ProcessUtilityForAlterTable
+ *        Recursive entry from ALTER TABLE
+ *
+ * ALTER TABLE sometimes generates subcommands such as CREATE INDEX.
+ * It calls this, not the main entry point ProcessUtility, to execute
+ * such subcommands.
+ *
+ * stmt: the utility command to execute
+ * context: opaque passthrough struct with the info we need
+ *
+ * It's caller's responsibility to do CommandCounterIncrement after
+ * calling this, if needed.
+ */
+void
+ProcessUtilityForAlterTable(Node *stmt, AlterTableUtilityContext *context)
+{
+    PlannedStmt *wrapper;
+
+    /*
+     * For event triggers, we must "close" the current complex-command set,
+     * and start a new one afterwards; this is needed to ensure the ordering
+     * of command events is consistent with the way they were executed.
+     */
+    EventTriggerAlterTableEnd();
+
+    /* Create a suitable wrapper */
+    wrapper = makeNode(PlannedStmt);
+    wrapper->commandType = CMD_UTILITY;
+    wrapper->canSetTag = false;
+    wrapper->utilityStmt = stmt;
+    wrapper->stmt_location = context->pstmt->stmt_location;
+    wrapper->stmt_len = context->pstmt->stmt_len;
+
+    ProcessUtility(wrapper,
+                   context->queryString,
+                   PROCESS_UTILITY_SUBCOMMAND,
+                   context->params,
+                   context->queryEnv,
+                   None_Receiver,
+                   NULL);
+
+    EventTriggerAlterTableStart(context->pstmt->utilityStmt);
+    EventTriggerAlterTableRelid(context->relid);
+}
+
+/*
  * Dispatch function for DropStmt
  */
 static void
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 9c25a80..7245f83 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -21,6 +21,8 @@
 #include "storage/lock.h"
 #include "utils/relcache.h"

+struct AlterTableUtilityContext;    /* avoid including tcop/utility.h here */
+

 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
                                     ObjectAddress *typaddress, const char *queryString);
@@ -29,7 +31,8 @@ extern void RemoveRelations(DropStmt *drop);

 extern Oid    AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);

-extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt);
+extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+                       struct AlterTableUtilityContext *context);

 extern LOCKMODE AlterTableGetLockLevel(List *cmds);

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff626cb..c5ea5c9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1780,8 +1780,6 @@ typedef enum AlterTableType
     AT_AlterConstraint,            /* alter constraint */
     AT_ValidateConstraint,        /* validate constraint */
     AT_ValidateConstraintRecurse,    /* internal to commands/tablecmds.c */
-    AT_ProcessedConstraint,        /* pre-processed add constraint (local in
-                                 * parser/parse_utilcmd.c) */
     AT_AddIndexConstraint,        /* add constraint using existing index */
     AT_DropConstraint,            /* drop constraint */
     AT_DropConstraintRecurse,    /* internal to commands/tablecmds.c */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 1348064..7d640cf 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -18,8 +18,10 @@


 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
-extern List *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-                                     const char *queryString);
+extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
+                                               const char *queryString,
+                                               List **beforeStmts,
+                                               List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
                                      const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 5abcacf..d8f5d4f 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -25,6 +25,16 @@ typedef enum
     PROCESS_UTILITY_SUBCOMMAND    /* a portion of a query */
 } ProcessUtilityContext;

+/* Info needed when recursing from ALTER TABLE */
+typedef struct AlterTableUtilityContext
+{
+    PlannedStmt *pstmt;            /* PlannedStmt for outer ALTER TABLE command */
+    const char *queryString;    /* its query string */
+    Oid            relid;            /* OID of ALTER's target table */
+    ParamListInfo params;        /* any parameters available to ALTER TABLE */
+    QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+} AlterTableUtilityContext;
+
 /* Hook for plugins to get control in ProcessUtility() */
 typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
                                           const char *queryString, ProcessUtilityContext context,
@@ -42,6 +52,9 @@ extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
                                     QueryEnvironment *queryEnv,
                                     DestReceiver *dest, char *completionTag);

+extern void ProcessUtilityForAlterTable(Node *stmt,
+                                        AlterTableUtilityContext *context);
+
 extern bool UtilityReturnsTuples(Node *parsetree);

 extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 7f77f19..04d963b 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -162,9 +162,6 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
             case AT_ValidateConstraintRecurse:
                 strtype = "VALIDATE CONSTRAINT (and recurse)";
                 break;
-            case AT_ProcessedConstraint:
-                strtype = "ADD (processed) CONSTRAINT";
-                break;
             case AT_AddIndexConstraint:
                 strtype = "ADD CONSTRAINT (using index)";
                 break;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index b492c60..45217d0 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1958,27 +1958,29 @@ Indexes:
     "anothertab_f4_idx" UNIQUE, btree (f4)

 drop table anothertab;
-create table another (f1 int, f2 text);
-insert into another values(1, 'one');
-insert into another values(2, 'two');
-insert into another values(3, 'three');
+-- test that USING expressions are parsed before column alter type / drop steps
+create table another (f1 int, f2 text, f3 text);
+insert into another values(1, 'one', 'uno');
+insert into another values(2, 'two', 'due');
+insert into another values(3, 'three', 'tre');
 select * from another;
- f1 |  f2
-----+-------
-  1 | one
-  2 | two
-  3 | three
+ f1 |  f2   | f3
+----+-------+-----
+  1 | one   | uno
+  2 | two   | due
+  3 | three | tre
 (3 rows)

 alter table another
-  alter f1 type text using f2 || ' more',
-  alter f2 type bigint using f1 * 10;
+  alter f1 type text using f2 || ' and ' || f3 || ' more',
+  alter f2 type bigint using f1 * 10,
+  drop column f3;
 select * from another;
-     f1     | f2
-------------+----
- one more   | 10
- two more   | 20
- three more | 30
+         f1         | f2
+--------------------+----
+ one and uno more   | 10
+ two and due more   | 20
+ three and tre more | 30
 (3 rows)

 drop table another;
@@ -3469,7 +3471,7 @@ NOTICE:  column "c2" of relation "test_add_column" already exists, skipping

 ALTER TABLE test_add_column
     ADD COLUMN c2 integer, -- fail because c2 already exists
-    ADD COLUMN c3 integer;
+    ADD COLUMN c3 integer primary key;
 ERROR:  column "c2" of relation "test_add_column" already exists
 \d test_add_column
           Table "public.test_add_column"
@@ -3480,7 +3482,7 @@ ERROR:  column "c2" of relation "test_add_column" already exists

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN c3 integer; -- fail because c3 already exists
+    ADD COLUMN c3 integer primary key;
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 \d test_add_column
           Table "public.test_add_column"
@@ -3488,11 +3490,13 @@ NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
+    ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 \d test_add_column
@@ -3501,12 +3505,14 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
     ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
-    ADD COLUMN c4 integer;
+    ADD COLUMN c4 integer REFERENCES test_add_column;
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 \d test_add_column
@@ -3515,10 +3521,84 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
+NOTICE:  column "c4" of relation "test_add_column" already exists, skipping
+\d test_add_column
+          Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL;
+\d test_add_column
+                            Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable |                   Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
  c4     | integer |           |          |
+ c5     | integer |           | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL;
+NOTICE:  column "c5" of relation "test_add_column" already exists, skipping
+\d test_add_column*
+                            Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable |                   Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+ c5     | integer |           | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+               Sequence "public.test_add_column_c5_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |         1 | no      |     1
+Owned by: public.test_add_column.c5
+
+ Index "public.test_add_column_pkey"
+ Column |  Type   | Key? | Definition
+--------+---------+------+------------
+ c3     | integer | yes  | c3
+primary key, btree, for table "public.test_add_column"

 DROP TABLE test_add_column;
+\d test_add_column*
 -- unsupported constraint types for partitioned tables
 CREATE TABLE partitioned (
     a int,
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 36a2393..7cf4696 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -387,6 +387,68 @@ SELECT * FROM itest8;
 RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;
+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+INSERT INTO itest8 VALUES(0), (1);
+TABLE itest8;
+ f1 | f2 | f3 | f4 | f5
+----+----+----+----+----
+  0 |  1 |  1 |  1 |
+  1 |  2 | 11 |  2 |
+(2 rows)
+
+\d+ itest8
+                                               Table "public.itest8"
+ Column |  Type   | Collation | Nullable |             Default              | Storage | Stats target | Description
+--------+---------+-----------+----------+----------------------------------+---------+--------------+-------------
+ f1     | integer |           |          |                                  | plain   |              |
+ f2     | integer |           | not null | generated always as identity     | plain   |              |
+ f3     | integer |           | not null | generated by default as identity | plain   |              |
+ f4     | bigint  |           | not null | generated always as identity     | plain   |              |
+ f5     | bigint  |           |          |                                  | plain   |              |
+
+\d itest8_f2_seq
+                   Sequence "public.itest8_f2_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |         1 | no      |     1
+Sequence for identity column: public.itest8.f2
+
+\d itest8_f3_seq
+                   Sequence "public.itest8_f3_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |        10 | no      |     1
+Sequence for identity column: public.itest8.f3
+
+\d itest8_f4_seq
+                       Sequence "public.itest8_f4_seq"
+  Type  | Start | Minimum |       Maximum       | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint |     1 |       1 | 9223372036854775807 |         1 | no      |     1
+Sequence for identity column: public.itest8.f4
+
+\d itest8_f5_seq
+DROP TABLE itest8;
 -- typed tables (currently not supported)
 CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint);
 CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index abe7be3..25d9717 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1342,17 +1342,19 @@ alter table anothertab alter column f5 type bigint;

 drop table anothertab;

-create table another (f1 int, f2 text);
+-- test that USING expressions are parsed before column alter type / drop steps
+create table another (f1 int, f2 text, f3 text);

-insert into another values(1, 'one');
-insert into another values(2, 'two');
-insert into another values(3, 'three');
+insert into another values(1, 'one', 'uno');
+insert into another values(2, 'two', 'due');
+insert into another values(3, 'three', 'tre');

 select * from another;

 alter table another
-  alter f1 type text using f2 || ' more',
-  alter f2 type bigint using f1 * 10;
+  alter f1 type text using f2 || ' and ' || f3 || ' more',
+  alter f2 type bigint using f1 * 10,
+  drop column f3;

 select * from another;

@@ -2170,22 +2172,32 @@ ALTER TABLE ONLY test_add_column
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN c2 integer, -- fail because c2 already exists
-    ADD COLUMN c3 integer;
+    ADD COLUMN c3 integer primary key;
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN c3 integer; -- fail because c3 already exists
+    ADD COLUMN c3 integer primary key;
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
+    ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
     ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
-    ADD COLUMN c4 integer;
+    ADD COLUMN c4 integer REFERENCES test_add_column;
 \d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
+\d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL;
+\d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL;
+\d test_add_column*
 DROP TABLE test_add_column;
+\d test_add_column*

 -- unsupported constraint types for partitioned tables
 CREATE TABLE partitioned (
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index 4b03d24..685607c 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -239,6 +239,44 @@ RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;

+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+
+INSERT INTO itest8 VALUES(0), (1);
+
+TABLE itest8;
+\d+ itest8
+\d itest8_f2_seq
+\d itest8_f3_seq
+\d itest8_f4_seq
+\d itest8_f5_seq
+DROP TABLE itest8;
+

 -- typed tables (currently not supported)


Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
I wrote:
>> [ fix-alter-table-order-of-operations-1.patch ]
> The cfbot noticed that this failed to apply over a recent commit,
> so here's v2.  No substantive changes.

Another rebase required :-(.  Still no code changes from v1, but this
time I remembered to add a couple more test cases that I'd been
meaning to put in, mostly based on bug reports from Manuel Rigger.

I'd kind of like to get this cleared out of my queue soon.
Does anyone intend to review it further?

            regards, tom lane

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e19772a..2824be4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -85,6 +85,7 @@
 #include "storage/lock.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
+#include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -142,11 +143,13 @@ static List *on_commits = NIL;
 #define AT_PASS_OLD_CONSTR        3    /* re-add existing constraints */
 /* We could support a RENAME COLUMN pass here, but not currently used */
 #define AT_PASS_ADD_COL            4    /* ADD COLUMN */
-#define AT_PASS_COL_ATTRS        5    /* set other column attributes */
-#define AT_PASS_ADD_INDEX        6    /* ADD indexes */
-#define AT_PASS_ADD_CONSTR        7    /* ADD constraints, defaults */
-#define AT_PASS_MISC            8    /* other stuff */
-#define AT_NUM_PASSES            9
+#define AT_PASS_ADD_CONSTR        5    /* ADD constraints (initial examination) */
+#define AT_PASS_COL_ATTRS        6    /* set column attributes, eg NOT NULL */
+#define AT_PASS_ADD_INDEXCONSTR    7    /* ADD index-based constraints */
+#define AT_PASS_ADD_INDEX        8    /* ADD indexes */
+#define AT_PASS_ADD_OTHERCONSTR    9    /* ADD other constraints, defaults */
+#define AT_PASS_MISC            10    /* other stuff */
+#define AT_NUM_PASSES            11

 typedef struct AlteredTableInfo
 {
@@ -159,6 +162,7 @@ typedef struct AlteredTableInfo
     /* Information saved by Phases 1/2 for Phase 3: */
     List       *constraints;    /* List of NewConstraint */
     List       *newvals;        /* List of NewColumnValue */
+    List       *afterStmts;        /* List of utility command parsetrees */
     bool        verify_new_notnull; /* T if we should recheck NOT NULL */
     int            rewrite;        /* Reason for forced rewrite, if any */
     Oid            newTableSpace;    /* new tablespace; 0 means no change */
@@ -338,31 +342,45 @@ static void validateForeignKeyConstraint(char *conname,
                                          Relation rel, Relation pkrel,
                                          Oid pkindOid, Oid constraintOid);
 static void ATController(AlterTableStmt *parsetree,
-                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
+                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+                         AlterTableUtilityContext *context);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                      bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode);
+                      bool recurse, bool recursing, LOCKMODE lockmode,
+                      AlterTableUtilityContext *context);
+static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                              AlterTableUtilityContext *context);
 static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                      AlterTableCmd *cmd, LOCKMODE lockmode);
+                      AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass,
+                      AlterTableUtilityContext *context);
+static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
+                                          Relation rel, AlterTableCmd *cmd,
+                                          bool recurse, LOCKMODE lockmode,
+                                          int cur_pass,
+                                          AlterTableUtilityContext *context);
 static void ATRewriteTables(AlterTableStmt *parsetree,
-                            List **wqueue, LOCKMODE lockmode);
+                            List **wqueue, LOCKMODE lockmode,
+                            AlterTableUtilityContext *context);
 static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
 static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
 static void ATSimplePermissions(Relation rel, int allowed_targets);
 static void ATWrongRelkindError(Relation rel, int allowed_targets);
 static void ATSimpleRecursion(List **wqueue, Relation rel,
-                              AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
+                              AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                              AlterTableUtilityContext *context);
 static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
 static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                                  LOCKMODE lockmode);
+                                  LOCKMODE lockmode,
+                                  AlterTableUtilityContext *context);
 static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
                                            DropBehavior behavior);
 static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                            bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode);
+                            bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
+                            AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
-                                     Relation rel, ColumnDef *colDef,
+                                     Relation rel, AlterTableCmd **cmd,
                                      bool recurse, bool recursing,
-                                     bool if_not_exists, LOCKMODE lockmode);
+                                     LOCKMODE lockmode, int cur_pass,
+                                     AlterTableUtilityContext *context);
 static bool check_for_column_name_collision(Relation rel, const char *colname,
                                             bool if_not_exists);
 static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
@@ -371,7 +389,8 @@ static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
 static void ATPrepSetNotNull(List **wqueue, Relation rel,
                              AlterTableCmd *cmd, bool recurse, bool recursing,
-                             LOCKMODE lockmode);
+                             LOCKMODE lockmode,
+                             AlterTableUtilityContext *context);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
                                       const char *colName, LOCKMODE lockmode);
 static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel,
@@ -393,7 +412,8 @@ static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
 static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
                                       Node *newValue, LOCKMODE lockmode);
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                             AlterTableCmd *cmd, LOCKMODE lockmode);
+                             AlterTableCmd *cmd, LOCKMODE lockmode,
+                             AlterTableUtilityContext *context);
 static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
                                       DropBehavior behavior,
                                       bool recurse, bool recursing,
@@ -450,7 +470,8 @@ static void ATExecDropConstraint(Relation rel, const char *constrName,
 static void ATPrepAlterColumnType(List **wqueue,
                                   AlteredTableInfo *tab, Relation rel,
                                   bool recurse, bool recursing,
-                                  AlterTableCmd *cmd, LOCKMODE lockmode);
+                                  AlterTableCmd *cmd, LOCKMODE lockmode,
+                                  AlterTableUtilityContext *context);
 static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
 static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                            AlterTableCmd *cmd, LOCKMODE lockmode);
@@ -3458,7 +3479,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  *
  * ALTER TABLE is performed in three phases:
  *        1. Examine subcommands and perform pre-transformation checking.
- *        2. Update system catalogs.
+ *        2. Validate and transform subcommands, and update system catalogs.
  *        3. Scan table(s) to check new constraints, and optionally recopy
  *           the data into new table(s).
  * Phase 3 is not performed unless one or more of the subcommands requires
@@ -3469,9 +3490,10 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * ATPrepCmd performs phase 1.  A "work queue" entry is created for
  * each table to be affected (there may be multiple affected tables if the
  * commands traverse a table inheritance hierarchy).  Also we do preliminary
- * validation of the subcommands, including parse transformation of those
- * expressions that need to be evaluated with respect to the old table
- * schema.
+ * validation of the subcommands.  Because earlier subcommands may change
+ * the catalog state seen by later commands, there are limits to what can
+ * be done in this phase.  Generally, this phase acquires table locks,
+ * checks permissions and relkind, and recurses to find child tables.
  *
  * ATRewriteCatalogs performs phase 2 for each affected table.  (Note that
  * phases 2 and 3 normally do no explicit recursion, since phase 1 already
@@ -3493,18 +3515,23 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
  * rather than reassess it at lower levels.
+ *
+ * The caller also provides a "context" which is to be passed back to
+ * utility.c when we need to execute a subcommand such as CREATE INDEX.
+ * Some of the fields therein, such as the relid, are used here as well.
  */
 void
-AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
+AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+           AlterTableUtilityContext *context)
 {
     Relation    rel;

     /* Caller is required to provide an adequate lock. */
-    rel = relation_open(relid, NoLock);
+    rel = relation_open(context->relid, NoLock);

     CheckTableNotInUse(rel, "ALTER TABLE");

-    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
+    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
 }

 /*
@@ -3517,6 +3544,10 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
  * is unsafe to use this entry point for alterations that could break
  * existing query plans.  On the assumption it's not used for such, we
  * don't have to reject pending AFTER triggers, either.
+ *
+ * Also, since we don't have an AlterTableUtilityContext, this cannot be
+ * used for any subcommand types that require parse transformation or
+ * could generate subcommands that have to be passed to ProcessUtility.
  */
 void
 AlterTableInternal(Oid relid, List *cmds, bool recurse)
@@ -3528,7 +3559,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)

     EventTriggerAlterTableRelid(relid);

-    ATController(NULL, rel, cmds, recurse, lockmode);
+    ATController(NULL, rel, cmds, recurse, lockmode, NULL);
 }

 /*
@@ -3673,7 +3704,6 @@ AlterTableGetLockLevel(List *cmds)
                 break;

             case AT_AddConstraint:
-            case AT_ProcessedConstraint:    /* becomes AT_AddConstraint */
             case AT_AddConstraintRecurse:    /* becomes AT_AddConstraint */
             case AT_ReAddConstraint:    /* becomes AT_AddConstraint */
             case AT_ReAddDomainConstraint:    /* becomes AT_AddConstraint */
@@ -3826,7 +3856,8 @@ AlterTableGetLockLevel(List *cmds)
  */
 static void
 ATController(AlterTableStmt *parsetree,
-             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
+             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+             AlterTableUtilityContext *context)
 {
     List       *wqueue = NIL;
     ListCell   *lcmd;
@@ -3836,17 +3867,17 @@ ATController(AlterTableStmt *parsetree,
     {
         AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);

-        ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode);
+        ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context);
     }

     /* Close the relation, but keep lock until commit */
     relation_close(rel, NoLock);

     /* Phase 2: update system catalogs */
-    ATRewriteCatalogs(&wqueue, lockmode);
+    ATRewriteCatalogs(&wqueue, lockmode, context);

-    /* Phase 3: scan/rewrite tables as needed */
-    ATRewriteTables(parsetree, &wqueue, lockmode);
+    /* Phase 3: scan/rewrite tables as needed, and run afterStmts */
+    ATRewriteTables(parsetree, &wqueue, lockmode, context);
 }

 /*
@@ -3860,7 +3891,8 @@ ATController(AlterTableStmt *parsetree,
  */
 static void
 ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
-          bool recurse, bool recursing, LOCKMODE lockmode)
+          bool recurse, bool recursing, LOCKMODE lockmode,
+          AlterTableUtilityContext *context)
 {
     AlteredTableInfo *tab;
     int            pass = AT_PASS_UNSET;
@@ -3872,13 +3904,17 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
      * Copy the original subcommand for each table.  This avoids conflicts
      * when different child tables need to make different parse
      * transformations (for example, the same column may have different column
-     * numbers in different children).
+     * numbers in different children).  It also ensures that we don't corrupt
+     * the original parse tree, in case it is saved in plancache.
      */
     cmd = copyObject(cmd);

     /*
-     * Do permissions checking, recursion to child tables if needed, and any
-     * additional phase-1 processing needed.
+     * Do permissions and relkind checking, recursion to child tables if
+     * needed, and any additional phase-1 processing needed.  (But beware of
+     * adding any processing that looks at table details that another
+     * subcommand could change.  In some cases we reject multiple subcommands
+     * that could try to change the same state in contrary ways.)
      */
     switch (cmd->subtype)
     {
@@ -3886,14 +3922,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
             ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
-                            lockmode);
+                            lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_ADD_COL;
             break;
         case AT_AddColumnToView:    /* add column via CREATE OR REPLACE VIEW */
             ATSimplePermissions(rel, ATT_VIEW);
             ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
-                            lockmode);
+                            lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_ADD_COL;
             break;
@@ -3906,19 +3942,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
              * rules.
              */
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
-            pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
+            pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
             break;
         case AT_AddIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
             /* This command never recurses */
-            pass = AT_PASS_ADD_CONSTR;
+            pass = AT_PASS_ADD_OTHERCONSTR;
             break;
         case AT_SetIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
             /* This command never recurses */
-            pass = AT_PASS_COL_ATTRS;
+            /* This should run after AddIdentity, so do it in MISC pass */
+            pass = AT_PASS_MISC;
             break;
         case AT_DropIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
@@ -3928,24 +3965,25 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
         case AT_DropNotNull:    /* ALTER COLUMN DROP NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
             ATPrepDropNotNull(rel, recurse, recursing);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             pass = AT_PASS_DROP;
             break;
         case AT_SetNotNull:        /* ALTER COLUMN SET NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
             /* Need command-specific recursion decision */
-            ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing, lockmode);
+            ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing,
+                             lockmode, context);
             pass = AT_PASS_COL_ATTRS;
             break;
         case AT_CheckNotNull:    /* check column is already marked NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_COL_ATTRS;
             break;
         case AT_SetStatistics:    /* ALTER COLUMN SET STATISTICS */
             ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_MISC;
             break;
@@ -3957,14 +3995,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetStorage:        /* ALTER COLUMN SET STORAGE */
             ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_MISC;
             break;
         case AT_DropColumn:        /* DROP COLUMN */
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
-            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
+            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
+                             lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_DROP;
             break;
@@ -3986,7 +4025,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             ATSimplePermissions(rel, ATT_TABLE);
             /* This command never recurses */
             /* No command-specific prep needed */
-            pass = AT_PASS_ADD_CONSTR;
+            pass = AT_PASS_ADD_INDEXCONSTR;
             break;
         case AT_DropConstraint: /* DROP CONSTRAINT */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
@@ -4000,8 +4039,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
         case AT_AlterColumnType:    /* ALTER COLUMN TYPE */
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
+            /* See comments for ATPrepAlterColumnType */
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
+                                      AT_PASS_UNSET, context);
+            Assert(cmd != NULL);
             /* Performs own recursion */
-            ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
+            ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
+                                  lockmode, context);
             pass = AT_PASS_ALTER_TYPE;
             break;
         case AT_AlterColumnGenericOptions:
@@ -4024,6 +4068,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetLogged:        /* SET LOGGED */
             ATSimplePermissions(rel, ATT_TABLE);
+            if (tab->chgPersistence)
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("cannot change persistence setting twice")));
             tab->chgPersistence = ATPrepChangePersistence(rel, true);
             /* force rewrite if necessary; see comment in ATRewriteTables */
             if (tab->chgPersistence)
@@ -4035,6 +4083,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetUnLogged:    /* SET UNLOGGED */
             ATSimplePermissions(rel, ATT_TABLE);
+            if (tab->chgPersistence)
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("cannot change persistence setting twice")));
             tab->chgPersistence = ATPrepChangePersistence(rel, false);
             /* force rewrite if necessary; see comment in ATRewriteTables */
             if (tab->chgPersistence)
@@ -4154,7 +4206,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  * conflicts).
  */
 static void
-ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
+ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                  AlterTableUtilityContext *context)
 {
     int            pass;
     ListCell   *ltab;
@@ -4187,7 +4240,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
             foreach(lcmd, subcmds)
                 ATExecCmd(wqueue, tab, rel,
                           castNode(AlterTableCmd, lfirst(lcmd)),
-                          lockmode);
+                          lockmode, pass, context);

             /*
              * After the ALTER TYPE pass, do cleanup work (this is not done in
@@ -4224,7 +4277,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
  */
 static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-          AlterTableCmd *cmd, LOCKMODE lockmode)
+          AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass,
+          AlterTableUtilityContext *context)
 {
     ObjectAddress address = InvalidObjectAddress;

@@ -4232,22 +4286,28 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
     {
         case AT_AddColumn:        /* ADD COLUMN */
         case AT_AddColumnToView:    /* add column via CREATE OR REPLACE VIEW */
-            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+            address = ATExecAddColumn(wqueue, tab, rel, &cmd,
                                       false, false,
-                                      cmd->missing_ok, lockmode);
+                                      lockmode, cur_pass, context);
             break;
         case AT_AddColumnRecurse:
-            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+            address = ATExecAddColumn(wqueue, tab, rel, &cmd,
                                       true, false,
-                                      cmd->missing_ok, lockmode);
+                                      lockmode, cur_pass, context);
             break;
         case AT_ColumnDefault:    /* ALTER COLUMN DEFAULT */
             address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_AddIdentity:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_SetIdentity:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_DropIdentity:
@@ -4295,14 +4355,24 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                      lockmode);
             break;
         case AT_AddConstraint:    /* ADD CONSTRAINT */
-            address =
-                ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                                    false, false, lockmode);
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            /* Might not have gotten AddConstraint back from parse transform */
+            if (cmd != NULL)
+                address =
+                    ATExecAddConstraint(wqueue, tab, rel,
+                                        (Constraint *) cmd->def,
+                                        false, false, lockmode);
             break;
         case AT_AddConstraintRecurse:    /* ADD CONSTRAINT with recursion */
-            address =
-                ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                                    true, false, lockmode);
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, true, lockmode,
+                                      cur_pass, context);
+            /* Might not have gotten AddConstraint back from parse transform */
+            if (cmd != NULL)
+                address =
+                    ATExecAddConstraint(wqueue, tab, rel,
+                                        (Constraint *) cmd->def,
+                                        true, false, lockmode);
             break;
         case AT_ReAddConstraint:    /* Re-add pre-existing check constraint */
             address =
@@ -4346,6 +4416,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                  cmd->missing_ok, lockmode);
             break;
         case AT_AlterColumnType:    /* ALTER COLUMN TYPE */
+            /* parse transformation was done earlier */
             address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
             break;
         case AT_AlterColumnGenericOptions:    /* ALTER COLUMN OPTIONS */
@@ -4468,6 +4539,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
             ATExecGenericOptions(rel, (List *) cmd->def);
             break;
         case AT_AttachPartition:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
                 ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
             else
@@ -4475,6 +4549,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                          ((PartitionCmd *) cmd->def)->name);
             break;
         case AT_DetachPartition:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             /* ATPrepCmd ensures it must be a table */
             Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
             ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
@@ -4488,7 +4565,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
     /*
      * Report the subcommand to interested event triggers.
      */
-    EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
+    if (cmd)
+        EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);

     /*
      * Bump the command counter to ensure the next subcommand in the sequence
@@ -4498,10 +4576,143 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 }

 /*
+ * ATParseTransformCmd: perform parse transformation for one subcommand
+ *
+ * Returns the transformed subcommand tree, if there is one, else NULL.
+ *
+ * The parser may hand back additional AlterTableCmd(s) and/or other
+ * utility statements, either before or after the original subcommand.
+ * Other AlterTableCmds are scheduled into the appropriate slot of the
+ * AlteredTableInfo (they had better be for later passes than the current one).
+ * Utility statements that are supposed to happen before the AlterTableCmd
+ * are executed immediately.  Those that are supposed to happen afterwards
+ * are added to the tab->afterStmts list to be done at the very end.
+ */
+static AlterTableCmd *
+ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
+                    AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                    int cur_pass, AlterTableUtilityContext *context)
+{
+    AlterTableCmd *newcmd = NULL;
+    AlterTableStmt *atstmt = makeNode(AlterTableStmt);
+    List       *beforeStmts;
+    List       *afterStmts;
+    ListCell   *lc;
+
+    /* Gin up an AlterTableStmt with just this subcommand and this table */
+    atstmt->relation =
+        makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+                     pstrdup(RelationGetRelationName(rel)),
+                     -1);
+    atstmt->relation->inh = recurse;
+    atstmt->cmds = list_make1(cmd);
+    atstmt->relkind = OBJECT_TABLE; /* needn't be picky here */
+    atstmt->missing_ok = false;
+
+    /* Transform the AlterTableStmt */
+    atstmt = transformAlterTableStmt(RelationGetRelid(rel),
+                                     atstmt,
+                                     context->queryString,
+                                     &beforeStmts,
+                                     &afterStmts);
+
+    /* Execute any statements that should happen before these subcommand(s) */
+    foreach(lc, beforeStmts)
+    {
+        Node       *stmt = (Node *) lfirst(lc);
+
+        ProcessUtilityForAlterTable(stmt, context);
+        CommandCounterIncrement();
+    }
+
+    /* Examine the transformed subcommands and schedule them appropriately */
+    foreach(lc, atstmt->cmds)
+    {
+        AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc);
+
+        if (newcmd == NULL &&
+            (cmd->subtype == cmd2->subtype ||
+             (cmd->subtype == AT_AddConstraintRecurse &&
+              cmd2->subtype == AT_AddConstraint)))
+        {
+            /* Found the transformed version of our subcommand */
+            cmd2->subtype = cmd->subtype;    /* copy recursion flag */
+            newcmd = cmd2;
+        }
+        else
+        {
+            int            pass;
+
+            /*
+             * Schedule added subcommand appropriately.  We assume we needn't
+             * do any phase-1 checks for it.  This switch only has to cover
+             * the subcommand types that can be added by parse_utilcmd.c.
+             */
+            switch (cmd2->subtype)
+            {
+                case AT_SetNotNull:
+                    /* Need command-specific recursion decision */
+                    ATPrepSetNotNull(wqueue, rel, cmd2,
+                                     recurse, false,
+                                     lockmode, context);
+                    pass = AT_PASS_COL_ATTRS;
+                    break;
+                case AT_AddIndex:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_ADD_INDEX;
+                    break;
+                case AT_AddIndexConstraint:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_ADD_INDEXCONSTR;
+                    break;
+                case AT_AddConstraint:
+                    /* Recursion occurs during execution phase */
+                    if (recurse)
+                        cmd2->subtype = AT_AddConstraintRecurse;
+                    switch (castNode(Constraint, cmd2->def)->contype)
+                    {
+                        case CONSTR_PRIMARY:
+                        case CONSTR_UNIQUE:
+                        case CONSTR_EXCLUSION:
+                            pass = AT_PASS_ADD_INDEXCONSTR;
+                            break;
+                        default:
+                            pass = AT_PASS_ADD_OTHERCONSTR;
+                            break;
+                    }
+                    break;
+                case AT_AlterColumnGenericOptions:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_MISC;
+                    break;
+                default:
+                    elog(ERROR, "unexpected AlterTableType: %d",
+                         (int) cmd2->subtype);
+                    pass = AT_PASS_UNSET;
+                    break;
+            }
+            /* Must be for a later pass than we're currently doing */
+            if (pass <= cur_pass)
+                elog(ERROR, "ALTER TABLE scheduling failure");
+            tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2);
+        }
+    }
+
+    /* Queue up any after-statements to happen at the end */
+    tab->afterStmts = list_concat(tab->afterStmts, afterStmts);
+
+    return newcmd;
+}
+
+/*
  * ATRewriteTables: ALTER TABLE phase 3
  */
 static void
-ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
+ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
+                AlterTableUtilityContext *context)
 {
     ListCell   *ltab;

@@ -4728,6 +4939,21 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
         if (rel)
             table_close(rel, NoLock);
     }
+
+    /* Finally, run any afterStmts that were queued up */
+    foreach(ltab, *wqueue)
+    {
+        AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+        ListCell   *lc;
+
+        foreach(lc, tab->afterStmts)
+        {
+            Node       *stmt = (Node *) lfirst(lc);
+
+            ProcessUtilityForAlterTable(stmt, context);
+            CommandCounterIncrement();
+        }
+    }
 }

 /*
@@ -5239,7 +5465,8 @@ ATWrongRelkindError(Relation rel, int allowed_targets)
  */
 static void
 ATSimpleRecursion(List **wqueue, Relation rel,
-                  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode)
+                  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                  AlterTableUtilityContext *context)
 {
     /*
      * Propagate to children if desired.  Only plain tables, foreign tables
@@ -5272,7 +5499,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
             /* find_all_inheritors already got lock */
             childrel = relation_open(childrelid, NoLock);
             CheckTableNotInUse(childrel, "ALTER TABLE");
-            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
+            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
             relation_close(childrel, NoLock);
         }
     }
@@ -5317,7 +5544,7 @@ ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
  */
 static void
 ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                      LOCKMODE lockmode)
+                      LOCKMODE lockmode, AlterTableUtilityContext *context)
 {
     ListCell   *child;
     List       *children;
@@ -5335,7 +5562,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,

         childrel = relation_open(childrelid, lockmode);
         CheckTableNotInUse(childrel, "ALTER TABLE");
-        ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode);
+        ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
         relation_close(childrel, NoLock);
     }
 }
@@ -5572,7 +5799,8 @@ check_of_type(HeapTuple typetuple)
  */
 static void
 ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode)
+                bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
+                AlterTableUtilityContext *context)
 {
     if (rel->rd_rel->reloftype && !recursing)
         ereport(ERROR,
@@ -5580,7 +5808,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
                  errmsg("cannot add column to typed table")));

     if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

     if (recurse && !is_view)
         cmd->subtype = AT_AddColumnRecurse;
@@ -5589,14 +5817,20 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 /*
  * Add a column to a table.  The return value is the address of the
  * new column in the parent relation.
+ *
+ * cmd is pass-by-ref so that we can replace it with the parse-transformed
+ * copy (but that happens only after we check for IF NOT EXISTS).
  */
 static ObjectAddress
 ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                ColumnDef *colDef,
+                AlterTableCmd **cmd,
                 bool recurse, bool recursing,
-                bool if_not_exists, LOCKMODE lockmode)
+                LOCKMODE lockmode, int cur_pass,
+                AlterTableUtilityContext *context)
 {
     Oid            myrelid = RelationGetRelid(rel);
+    ColumnDef  *colDef = castNode(ColumnDef, (*cmd)->def);
+    bool        if_not_exists = (*cmd)->missing_ok;
     Relation    pgclass,
                 attrdesc;
     HeapTuple    reltup;
@@ -5611,6 +5845,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
     Expr       *defval;
     List       *children;
     ListCell   *child;
+    AlterTableCmd *childcmd;
     AclResult    aclresult;
     ObjectAddress address;

@@ -5678,12 +5913,31 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
         }
     }

-    pgclass = table_open(RelationRelationId, RowExclusiveLock);
+    /* skip if the name already exists and if_not_exists is true */
+    if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
+    {
+        table_close(attrdesc, RowExclusiveLock);
+        return InvalidObjectAddress;
+    }

-    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
-    if (!HeapTupleIsValid(reltup))
-        elog(ERROR, "cache lookup failed for relation %u", myrelid);
-    relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
+    /*
+     * Okay, we need to add the column, so go ahead and do parse
+     * transformation.  This can result in queueing up, or even immediately
+     * executing, subsidiary operations (such as creation of unique indexes);
+     * so we mustn't do it until we have made the if_not_exists check.
+     *
+     * When recursing, the command was already transformed and we needn't do
+     * so again.  Also, if context isn't given we can't transform.  (That
+     * currently happens only for AT_AddColumnToView; we expect that view.c
+     * passed us a ColumnDef that doesn't need work.)
+     */
+    if (context != NULL && !recursing)
+    {
+        *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode,
+                                   cur_pass, context);
+        Assert(*cmd != NULL);
+        colDef = castNode(ColumnDef, (*cmd)->def);
+    }

     /*
      * Cannot add identity column if table has children, because identity does
@@ -5696,14 +5950,12 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
                 (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                  errmsg("cannot recursively add identity column to table that has child tables")));

-    /* skip if the name already exists and if_not_exists is true */
-    if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
-    {
-        table_close(attrdesc, RowExclusiveLock);
-        heap_freetuple(reltup);
-        table_close(pgclass, RowExclusiveLock);
-        return InvalidObjectAddress;
-    }
+    pgclass = table_open(RelationRelationId, RowExclusiveLock);
+
+    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
+    if (!HeapTupleIsValid(reltup))
+        elog(ERROR, "cache lookup failed for relation %u", myrelid);
+    relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;

     /* Determine the new attribute's number */
     newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1;
@@ -5933,10 +6185,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
     /* Children should see column as singly inherited */
     if (!recursing)
     {
-        colDef = copyObject(colDef);
+        childcmd = copyObject(*cmd);
+        colDef = castNode(ColumnDef, childcmd->def);
         colDef->inhcount = 1;
         colDef->is_local = false;
     }
+    else
+        childcmd = *cmd;        /* no need to copy again */

     foreach(child, children)
     {
@@ -5953,8 +6208,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,

         /* Recurse to child; return value is ignored */
         ATExecAddColumn(wqueue, childtab, childrel,
-                        colDef, recurse, true,
-                        if_not_exists, lockmode);
+                        &childcmd, recurse, true,
+                        lockmode, cur_pass, context);

         table_close(childrel, NoLock);
     }
@@ -6213,7 +6468,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 static void
 ATPrepSetNotNull(List **wqueue, Relation rel,
                  AlterTableCmd *cmd, bool recurse, bool recursing,
-                 LOCKMODE lockmode)
+                 LOCKMODE lockmode, AlterTableUtilityContext *context)
 {
     /*
      * If we're already recursing, there's nothing to do; the topmost
@@ -6234,10 +6489,10 @@ ATPrepSetNotNull(List **wqueue, Relation rel,

         newcmd->subtype = AT_CheckNotNull;
         newcmd->name = pstrdup(cmd->name);
-        ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode);
+        ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode, context);
     }
     else
-        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
 }

 /*
@@ -6977,7 +7232,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
  */
 static void
 ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                 AlterTableCmd *cmd, LOCKMODE lockmode)
+                 AlterTableCmd *cmd, LOCKMODE lockmode,
+                 AlterTableUtilityContext *context)
 {
     if (rel->rd_rel->reloftype && !recursing)
         ereport(ERROR,
@@ -6985,7 +7241,7 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
                  errmsg("cannot drop column from typed table")));

     if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

     if (recurse)
         cmd->subtype = AT_DropColumnRecurse;
@@ -10238,12 +10494,27 @@ ATExecDropConstraint(Relation rel, const char *constrName,

 /*
  * ALTER COLUMN TYPE
+ *
+ * Unlike other subcommand types, we do parse transformation for ALTER COLUMN
+ * TYPE during phase 1 --- the AlterTableCmd passed in here is already
+ * transformed (and must be, because we rely on some transformed fields).
+ *
+ * The point of this is that the execution of all ALTER COLUMN TYPEs for a
+ * table will be done "in parallel" during phase 3, so all the USING
+ * expressions should be parsed assuming the original column types.  Also,
+ * this allows a USING expression to refer to a field that will be dropped.
+ *
+ * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be
+ * the first two execution steps in phase 2; they must not see the effects
+ * of any other subcommand types, since the USING expressions are parsed
+ * against the unmodified table's state.
  */
 static void
 ATPrepAlterColumnType(List **wqueue,
                       AlteredTableInfo *tab, Relation rel,
                       bool recurse, bool recursing,
-                      AlterTableCmd *cmd, LOCKMODE lockmode)
+                      AlterTableCmd *cmd, LOCKMODE lockmode,
+                      AlterTableUtilityContext *context)
 {
     char       *colName = cmd->name;
     ColumnDef  *def = (ColumnDef *) cmd->def;
@@ -10489,7 +10760,7 @@ ATPrepAlterColumnType(List **wqueue,
                              errdetail("USING expression contains a whole-row table reference.")));
                 pfree(attmap);
             }
-            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
+            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
             relation_close(childrel, NoLock);
         }
     }
@@ -10501,7 +10772,7 @@ ATPrepAlterColumnType(List **wqueue,
                         colName)));

     if (tab->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
 }

 /*
@@ -11280,10 +11551,19 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
                                                         (IndexStmt *) stmt,
                                                         cmd));
         else if (IsA(stmt, AlterTableStmt))
-            querytree_list = list_concat(querytree_list,
-                                         transformAlterTableStmt(oldRelId,
-                                                                 (AlterTableStmt *) stmt,
-                                                                 cmd));
+        {
+            List       *beforeStmts;
+            List       *afterStmts;
+
+            stmt = (Node *) transformAlterTableStmt(oldRelId,
+                                                    (AlterTableStmt *) stmt,
+                                                    cmd,
+                                                    &beforeStmts,
+                                                    &afterStmts);
+            querytree_list = list_concat(querytree_list, beforeStmts);
+            querytree_list = lappend(querytree_list, stmt);
+            querytree_list = list_concat(querytree_list, afterStmts);
+        }
         else
             querytree_list = lappend(querytree_list, stmt);
     }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 9b51480..73b3e60 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -145,6 +145,10 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
          * Note that we must do this before updating the query for the view,
          * since the rules system requires that the correct view columns be in
          * place when defining the new rules.
+         *
+         * Also note that ALTER TABLE doesn't run parse transformation on
+         * AT_AddColumnToView commands.  The ColumnDef we supply must be ready
+         * to execute as-is.
          */
         if (list_length(attrList) > rel->rd_att->natts)
         {
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 3fafa92..55792f7 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -347,7 +347,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
  */
 static void
 generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
-                         Oid seqtypid, List *seqoptions, bool for_identity,
+                         Oid seqtypid, List *seqoptions,
+                         bool for_identity, bool col_exists,
                          char **snamespace_p, char **sname_p)
 {
     ListCell   *option;
@@ -472,8 +473,12 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,

     /*
      * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as
-     * owned by this column, and add it to the list of things to be done after
-     * this CREATE/ALTER TABLE.
+     * owned by this column, and add it to the appropriate list of things to
+     * be done along with this CREATE/ALTER TABLE.  In a CREATE or ALTER ADD
+     * COLUMN, it must be done after the statement because we don't know the
+     * column's attnum yet.  But if we do have the attnum (in AT_AddIdentity),
+     * we can do the marking immediately, which improves some ALTER TABLE
+     * behaviors.
      */
     altseqstmt = makeNode(AlterSeqStmt);
     altseqstmt->sequence = makeRangeVar(snamespace, sname, -1);
@@ -484,7 +489,10 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
                                                  (Node *) attnamelist, -1));
     altseqstmt->for_identity = for_identity;

-    cxt->alist = lappend(cxt->alist, altseqstmt);
+    if (col_exists)
+        cxt->blist = lappend(cxt->blist, altseqstmt);
+    else
+        cxt->alist = lappend(cxt->alist, altseqstmt);

     if (snamespace_p)
         *snamespace_p = snamespace;
@@ -568,7 +576,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
         Constraint *constraint;

         generateSerialExtraStmts(cxt, column,
-                                 column->typeName->typeOid, NIL, false,
+                                 column->typeName->typeOid, NIL,
+                                 false, false,
                                  &snamespace, &sname);

         /*
@@ -684,7 +693,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
                                                     constraint->location)));

                     generateSerialExtraStmts(cxt, column,
-                                             typeOid, constraint->options, true,
+                                             typeOid, constraint->options,
+                                             true, false,
                                              NULL, NULL);

                     column->identity = constraint->generated_when;
@@ -1086,7 +1096,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
             seq_relid = getIdentitySequence(RelationGetRelid(relation), attribute->attnum, false);
             seq_options = sequence_options(seq_relid);
             generateSerialExtraStmts(cxt, def,
-                                     InvalidOid, seq_options, true,
+                                     InvalidOid, seq_options,
+                                     true, false,
                                      NULL, NULL);
             def->identity = attribute->attidentity;
         }
@@ -2572,7 +2583,7 @@ transformFKConstraints(CreateStmtContext *cxt,
             Constraint *constraint = (Constraint *) lfirst(fkclist);
             AlterTableCmd *altercmd = makeNode(AlterTableCmd);

-            altercmd->subtype = AT_ProcessedConstraint;
+            altercmd->subtype = AT_AddConstraint;
             altercmd->name = NULL;
             altercmd->def = (Node *) constraint;
             alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
@@ -3002,23 +3013,23 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  * transformAlterTableStmt -
  *        parse analysis for ALTER TABLE
  *
- * Returns a List of utility commands to be done in sequence.  One of these
- * will be the transformed AlterTableStmt, but there may be additional actions
- * to be done before and after the actual AlterTable() call.
+ * Returns the transformed AlterTableStmt.  There may be additional actions
+ * to be done before and after the transformed statement, which are returned
+ * in *beforeStmts and *afterStmts as lists of utility command parsetrees.
  *
  * To avoid race conditions, it's important that this function rely only on
  * the passed-in relid (and not on stmt->relation) to determine the target
  * relation.
  */
-List *
+AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-                        const char *queryString)
+                        const char *queryString,
+                        List **beforeStmts, List **afterStmts)
 {
     Relation    rel;
     TupleDesc    tupdesc;
     ParseState *pstate;
     CreateStmtContext cxt;
-    List       *result;
     List       *save_alist;
     ListCell   *lcmd,
                *l;
@@ -3050,7 +3061,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,

     /* Set up CreateStmtContext */
     cxt.pstate = pstate;
-    if (stmt->relkind == OBJECT_FOREIGN_TABLE)
+    if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
     {
         cxt.stmtType = "ALTER FOREIGN TABLE";
         cxt.isforeign = true;
@@ -3078,9 +3089,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
     cxt.ofType = false;

     /*
-     * The only subtypes that currently require parse transformation handling
-     * are ADD COLUMN, ADD CONSTRAINT and SET DATA TYPE.  These largely re-use
-     * code from CREATE TABLE.
+     * Transform ALTER subcommands that need it (most don't).  These largely
+     * re-use code from CREATE TABLE.
      */
     foreach(lcmd, stmt->cmds)
     {
@@ -3089,7 +3099,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
         switch (cmd->subtype)
         {
             case AT_AddColumn:
-            case AT_AddColumnToView:
+            case AT_AddColumnRecurse:
                 {
                     ColumnDef  *def = castNode(ColumnDef, cmd->def);

@@ -3113,6 +3123,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                 }

             case AT_AddConstraint:
+            case AT_AddConstraintRecurse:

                 /*
                  * The original AddConstraint cmd node doesn't go to newcmds
@@ -3128,19 +3139,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                          (int) nodeTag(cmd->def));
                 break;

-            case AT_ProcessedConstraint:
-
-                /*
-                 * Already-transformed ADD CONSTRAINT, so just make it look
-                 * like the standard case.
-                 */
-                cmd->subtype = AT_AddConstraint;
-                newcmds = lappend(newcmds, cmd);
-                break;
-
             case AT_AlterColumnType:
                 {
-                    ColumnDef  *def = (ColumnDef *) cmd->def;
+                    ColumnDef  *def = castNode(ColumnDef, cmd->def);
                     AttrNumber    attnum;

                     /*
@@ -3159,13 +3160,13 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                      * change the data type of the sequence.
                      */
                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    /*
-                     * if attribute not found, something will error about it
-                     * later
-                     */
-                    if (attnum != InvalidAttrNumber &&
-                        TupleDescAttr(tupdesc, attnum - 1)->attidentity)
+                    if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
                     {
                         Oid            seq_relid = getIdentitySequence(relid, attnum, false);
                         Oid            typeOid = typenameTypeId(pstate, def->typeName);
@@ -3194,16 +3195,16 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     cmd->def = (Node *) newdef;

                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    /*
-                     * if attribute not found, something will error about it
-                     * later
-                     */
-                    if (attnum != InvalidAttrNumber)
-                        generateSerialExtraStmts(&cxt, newdef,
-                                                 get_atttype(relid, attnum),
-                                                 def->options, true,
-                                                 NULL, NULL);
+                    generateSerialExtraStmts(&cxt, newdef,
+                                             get_atttype(relid, attnum),
+                                             def->options, true, true,
+                                             NULL, NULL);

                     newcmds = lappend(newcmds, cmd);
                     break;
@@ -3219,6 +3220,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     List       *newseqopts = NIL;
                     List       *newdef = NIL;
                     AttrNumber    attnum;
+                    Oid            seq_relid;

                     /*
                      * Split options into those handled by ALTER SEQUENCE and
@@ -3235,29 +3237,34 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     }

                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    if (attnum)
-                    {
-                        Oid            seq_relid = getIdentitySequence(relid, attnum, true);
+                    seq_relid = getIdentitySequence(relid, attnum, true);

-                        if (seq_relid)
-                        {
-                            AlterSeqStmt *seqstmt;
+                    if (seq_relid)
+                    {
+                        AlterSeqStmt *seqstmt;

-                            seqstmt = makeNode(AlterSeqStmt);
-                            seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
-                                                             get_rel_name(seq_relid), -1);
-                            seqstmt->options = newseqopts;
-                            seqstmt->for_identity = true;
-                            seqstmt->missing_ok = false;
+                        seqstmt = makeNode(AlterSeqStmt);
+                        seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
+                                                         get_rel_name(seq_relid), -1);
+                        seqstmt->options = newseqopts;
+                        seqstmt->for_identity = true;
+                        seqstmt->missing_ok = false;

-                            cxt.alist = lappend(cxt.alist, seqstmt);
-                        }
+                        cxt.blist = lappend(cxt.blist, seqstmt);
                     }

                     /*
-                     * If column was not found or was not an identity column,
-                     * we just let the ALTER TABLE command error out later.
+                     * If column was not an identity column, we just let the
+                     * ALTER TABLE command error out later.  (There are cases
+                     * this fails to cover, but we'll need to restructure
+                     * where creation of the sequence dependency linkage
+                     * happens before we can fix it.)
                      */

                     cmd->def = (Node *) newdef;
@@ -3279,6 +3286,12 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                 break;

             default:
+
+                /*
+                 * Currently, we shouldn't actually get here for subcommand
+                 * types that don't require transformation; but if we do, just
+                 * emit them unchanged.
+                 */
                 newcmds = lappend(newcmds, cmd);
                 break;
         }
@@ -3359,11 +3372,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
      */
     stmt->cmds = newcmds;

-    result = lappend(cxt.blist, stmt);
-    result = list_concat(result, cxt.alist);
-    result = list_concat(result, save_alist);
+    *beforeStmts = cxt.blist;
+    *afterStmts = list_concat(cxt.alist, save_alist);

-    return result;
+    return stmt;
 }


diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3a03ca7..ad16f8a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1092,8 +1092,6 @@ ProcessUtilitySlow(ParseState *pstate,
                 {
                     AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
                     Oid            relid;
-                    List       *stmts;
-                    ListCell   *l;
                     LOCKMODE    lockmode;

                     /*
@@ -1107,59 +1105,21 @@ ProcessUtilitySlow(ParseState *pstate,

                     if (OidIsValid(relid))
                     {
-                        /* Run parse analysis ... */
-                        stmts = transformAlterTableStmt(relid, atstmt,
-                                                        queryString);
+                        AlterTableUtilityContext atcontext;
+
+                        /* Set up info needed for recursive callbacks ... */
+                        atcontext.pstmt = pstmt;
+                        atcontext.queryString = queryString;
+                        atcontext.relid = relid;
+                        atcontext.params = params;
+                        atcontext.queryEnv = queryEnv;

                         /* ... ensure we have an event trigger context ... */
                         EventTriggerAlterTableStart(parsetree);
                         EventTriggerAlterTableRelid(relid);

                         /* ... and do it */
-                        foreach(l, stmts)
-                        {
-                            Node       *stmt = (Node *) lfirst(l);
-
-                            if (IsA(stmt, AlterTableStmt))
-                            {
-                                /* Do the table alteration proper */
-                                AlterTable(relid, lockmode,
-                                           (AlterTableStmt *) stmt);
-                            }
-                            else
-                            {
-                                /*
-                                 * Recurse for anything else.  If we need to
-                                 * do so, "close" the current complex-command
-                                 * set, and start a new one at the bottom;
-                                 * this is needed to ensure the ordering of
-                                 * queued commands is consistent with the way
-                                 * they are executed here.
-                                 */
-                                PlannedStmt *wrapper;
-
-                                EventTriggerAlterTableEnd();
-                                wrapper = makeNode(PlannedStmt);
-                                wrapper->commandType = CMD_UTILITY;
-                                wrapper->canSetTag = false;
-                                wrapper->utilityStmt = stmt;
-                                wrapper->stmt_location = pstmt->stmt_location;
-                                wrapper->stmt_len = pstmt->stmt_len;
-                                ProcessUtility(wrapper,
-                                               queryString,
-                                               PROCESS_UTILITY_SUBCOMMAND,
-                                               params,
-                                               NULL,
-                                               None_Receiver,
-                                               NULL);
-                                EventTriggerAlterTableStart(parsetree);
-                                EventTriggerAlterTableRelid(relid);
-                            }
-
-                            /* Need CCI between commands */
-                            if (lnext(stmts, l) != NULL)
-                                CommandCounterIncrement();
-                        }
+                        AlterTable(atstmt, lockmode, &atcontext);

                         /* done */
                         EventTriggerAlterTableEnd();
@@ -1718,6 +1678,52 @@ ProcessUtilitySlow(ParseState *pstate,
 }

 /*
+ * ProcessUtilityForAlterTable
+ *        Recursive entry from ALTER TABLE
+ *
+ * ALTER TABLE sometimes generates subcommands such as CREATE INDEX.
+ * It calls this, not the main entry point ProcessUtility, to execute
+ * such subcommands.
+ *
+ * stmt: the utility command to execute
+ * context: opaque passthrough struct with the info we need
+ *
+ * It's caller's responsibility to do CommandCounterIncrement after
+ * calling this, if needed.
+ */
+void
+ProcessUtilityForAlterTable(Node *stmt, AlterTableUtilityContext *context)
+{
+    PlannedStmt *wrapper;
+
+    /*
+     * For event triggers, we must "close" the current complex-command set,
+     * and start a new one afterwards; this is needed to ensure the ordering
+     * of command events is consistent with the way they were executed.
+     */
+    EventTriggerAlterTableEnd();
+
+    /* Create a suitable wrapper */
+    wrapper = makeNode(PlannedStmt);
+    wrapper->commandType = CMD_UTILITY;
+    wrapper->canSetTag = false;
+    wrapper->utilityStmt = stmt;
+    wrapper->stmt_location = context->pstmt->stmt_location;
+    wrapper->stmt_len = context->pstmt->stmt_len;
+
+    ProcessUtility(wrapper,
+                   context->queryString,
+                   PROCESS_UTILITY_SUBCOMMAND,
+                   context->params,
+                   context->queryEnv,
+                   None_Receiver,
+                   NULL);
+
+    EventTriggerAlterTableStart(context->pstmt->utilityStmt);
+    EventTriggerAlterTableRelid(context->relid);
+}
+
+/*
  * Dispatch function for DropStmt
  */
 static void
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 9c25a80..7245f83 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -21,6 +21,8 @@
 #include "storage/lock.h"
 #include "utils/relcache.h"

+struct AlterTableUtilityContext;    /* avoid including tcop/utility.h here */
+

 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
                                     ObjectAddress *typaddress, const char *queryString);
@@ -29,7 +31,8 @@ extern void RemoveRelations(DropStmt *drop);

 extern Oid    AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);

-extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt);
+extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+                       struct AlterTableUtilityContext *context);

 extern LOCKMODE AlterTableGetLockLevel(List *cmds);

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff626cb..c5ea5c9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1780,8 +1780,6 @@ typedef enum AlterTableType
     AT_AlterConstraint,            /* alter constraint */
     AT_ValidateConstraint,        /* validate constraint */
     AT_ValidateConstraintRecurse,    /* internal to commands/tablecmds.c */
-    AT_ProcessedConstraint,        /* pre-processed add constraint (local in
-                                 * parser/parse_utilcmd.c) */
     AT_AddIndexConstraint,        /* add constraint using existing index */
     AT_DropConstraint,            /* drop constraint */
     AT_DropConstraintRecurse,    /* internal to commands/tablecmds.c */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index ba3f42b..0b14bd0 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -20,8 +20,10 @@ struct AttrMap;                    /* avoid including attmap.h here */


 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
-extern List *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-                                     const char *queryString);
+extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
+                                               const char *queryString,
+                                               List **beforeStmts,
+                                               List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
                                      const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 5abcacf..d8f5d4f 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -25,6 +25,16 @@ typedef enum
     PROCESS_UTILITY_SUBCOMMAND    /* a portion of a query */
 } ProcessUtilityContext;

+/* Info needed when recursing from ALTER TABLE */
+typedef struct AlterTableUtilityContext
+{
+    PlannedStmt *pstmt;            /* PlannedStmt for outer ALTER TABLE command */
+    const char *queryString;    /* its query string */
+    Oid            relid;            /* OID of ALTER's target table */
+    ParamListInfo params;        /* any parameters available to ALTER TABLE */
+    QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+} AlterTableUtilityContext;
+
 /* Hook for plugins to get control in ProcessUtility() */
 typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
                                           const char *queryString, ProcessUtilityContext context,
@@ -42,6 +52,9 @@ extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
                                     QueryEnvironment *queryEnv,
                                     DestReceiver *dest, char *completionTag);

+extern void ProcessUtilityForAlterTable(Node *stmt,
+                                        AlterTableUtilityContext *context);
+
 extern bool UtilityReturnsTuples(Node *parsetree);

 extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 7f77f19..04d963b 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -162,9 +162,6 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
             case AT_ValidateConstraintRecurse:
                 strtype = "VALIDATE CONSTRAINT (and recurse)";
                 break;
-            case AT_ProcessedConstraint:
-                strtype = "ADD (processed) CONSTRAINT";
-                break;
             case AT_AddIndexConstraint:
                 strtype = "ADD CONSTRAINT (using index)";
                 break;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index b492c60..4dd3507 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1958,27 +1958,29 @@ Indexes:
     "anothertab_f4_idx" UNIQUE, btree (f4)

 drop table anothertab;
-create table another (f1 int, f2 text);
-insert into another values(1, 'one');
-insert into another values(2, 'two');
-insert into another values(3, 'three');
+-- test that USING expressions are parsed before column alter type / drop steps
+create table another (f1 int, f2 text, f3 text);
+insert into another values(1, 'one', 'uno');
+insert into another values(2, 'two', 'due');
+insert into another values(3, 'three', 'tre');
 select * from another;
- f1 |  f2
-----+-------
-  1 | one
-  2 | two
-  3 | three
+ f1 |  f2   | f3
+----+-------+-----
+  1 | one   | uno
+  2 | two   | due
+  3 | three | tre
 (3 rows)

 alter table another
-  alter f1 type text using f2 || ' more',
-  alter f2 type bigint using f1 * 10;
+  alter f1 type text using f2 || ' and ' || f3 || ' more',
+  alter f2 type bigint using f1 * 10,
+  drop column f3;
 select * from another;
-     f1     | f2
-------------+----
- one more   | 10
- two more   | 20
- three more | 30
+         f1         | f2
+--------------------+----
+ one and uno more   | 10
+ two and due more   | 20
+ three and tre more | 30
 (3 rows)

 drop table another;
@@ -3469,7 +3471,7 @@ NOTICE:  column "c2" of relation "test_add_column" already exists, skipping

 ALTER TABLE test_add_column
     ADD COLUMN c2 integer, -- fail because c2 already exists
-    ADD COLUMN c3 integer;
+    ADD COLUMN c3 integer primary key;
 ERROR:  column "c2" of relation "test_add_column" already exists
 \d test_add_column
           Table "public.test_add_column"
@@ -3480,7 +3482,7 @@ ERROR:  column "c2" of relation "test_add_column" already exists

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN c3 integer; -- fail because c3 already exists
+    ADD COLUMN c3 integer primary key;
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 \d test_add_column
           Table "public.test_add_column"
@@ -3488,11 +3490,13 @@ NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
+    ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 \d test_add_column
@@ -3501,12 +3505,14 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
     ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
-    ADD COLUMN c4 integer;
+    ADD COLUMN c4 integer REFERENCES test_add_column;
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 \d test_add_column
@@ -3515,10 +3521,118 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
+NOTICE:  column "c4" of relation "test_add_column" already exists, skipping
+\d test_add_column
+          Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8);
+\d test_add_column
+                            Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable |                   Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+ c5     | integer |           | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Check constraints:
+    "test_add_column_c5_check" CHECK (c5 > 8)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10);
+NOTICE:  column "c5" of relation "test_add_column" already exists, skipping
+\d test_add_column*
+                            Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable |                   Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
  c4     | integer |           |          |
+ c5     | integer |           | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Check constraints:
+    "test_add_column_c5_check" CHECK (c5 > 8)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+               Sequence "public.test_add_column_c5_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |         1 | no      |     1
+Owned by: public.test_add_column.c5
+
+ Index "public.test_add_column_pkey"
+ Column |  Type   | Key? | Definition
+--------+---------+------+------------
+ c3     | integer | yes  | c3
+primary key, btree, for table "public.test_add_column"

 DROP TABLE test_add_column;
+\d test_add_column*
+-- assorted cases with multiple ALTER TABLE steps
+CREATE TABLE ataddindex(f1 INT);
+INSERT INTO ataddindex VALUES (42), (43);
+CREATE UNIQUE INDEX ataddindexi0 ON ataddindex(f1);
+ALTER TABLE ataddindex
+  ADD PRIMARY KEY USING INDEX ataddindexi0,
+  ALTER f1 TYPE BIGINT;
+\d ataddindex
+            Table "public.ataddindex"
+ Column |  Type  | Collation | Nullable | Default
+--------+--------+-----------+----------+---------
+ f1     | bigint |           | not null |
+Indexes:
+    "ataddindexi0" PRIMARY KEY, btree (f1)
+
+DROP TABLE ataddindex;
+CREATE TABLE ataddindex(f1 VARCHAR(10));
+INSERT INTO ataddindex(f1) VALUES ('foo'), ('a');
+ALTER TABLE ataddindex
+  ALTER f1 SET DATA TYPE TEXT,
+  ADD EXCLUDE ((f1 LIKE 'a') WITH =);
+\d ataddindex
+           Table "public.ataddindex"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ f1     | text |           |          |
+Indexes:
+    "ataddindex_expr_excl" EXCLUDE USING btree ((f1 ~~ 'a'::text) WITH =)
+
+DROP TABLE ataddindex;
 -- unsupported constraint types for partitioned tables
 CREATE TABLE partitioned (
     a int,
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 36a2393..7cf4696 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -387,6 +387,68 @@ SELECT * FROM itest8;
 RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;
+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+INSERT INTO itest8 VALUES(0), (1);
+TABLE itest8;
+ f1 | f2 | f3 | f4 | f5
+----+----+----+----+----
+  0 |  1 |  1 |  1 |
+  1 |  2 | 11 |  2 |
+(2 rows)
+
+\d+ itest8
+                                               Table "public.itest8"
+ Column |  Type   | Collation | Nullable |             Default              | Storage | Stats target | Description
+--------+---------+-----------+----------+----------------------------------+---------+--------------+-------------
+ f1     | integer |           |          |                                  | plain   |              |
+ f2     | integer |           | not null | generated always as identity     | plain   |              |
+ f3     | integer |           | not null | generated by default as identity | plain   |              |
+ f4     | bigint  |           | not null | generated always as identity     | plain   |              |
+ f5     | bigint  |           |          |                                  | plain   |              |
+
+\d itest8_f2_seq
+                   Sequence "public.itest8_f2_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |         1 | no      |     1
+Sequence for identity column: public.itest8.f2
+
+\d itest8_f3_seq
+                   Sequence "public.itest8_f3_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |        10 | no      |     1
+Sequence for identity column: public.itest8.f3
+
+\d itest8_f4_seq
+                       Sequence "public.itest8_f4_seq"
+  Type  | Start | Minimum |       Maximum       | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint |     1 |       1 | 9223372036854775807 |         1 | no      |     1
+Sequence for identity column: public.itest8.f4
+
+\d itest8_f5_seq
+DROP TABLE itest8;
 -- typed tables (currently not supported)
 CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint);
 CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index abe7be3..a16e4c9 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1342,17 +1342,19 @@ alter table anothertab alter column f5 type bigint;

 drop table anothertab;

-create table another (f1 int, f2 text);
+-- test that USING expressions are parsed before column alter type / drop steps
+create table another (f1 int, f2 text, f3 text);

-insert into another values(1, 'one');
-insert into another values(2, 'two');
-insert into another values(3, 'three');
+insert into another values(1, 'one', 'uno');
+insert into another values(2, 'two', 'due');
+insert into another values(3, 'three', 'tre');

 select * from another;

 alter table another
-  alter f1 type text using f2 || ' more',
-  alter f2 type bigint using f1 * 10;
+  alter f1 type text using f2 || ' and ' || f3 || ' more',
+  alter f2 type bigint using f1 * 10,
+  drop column f3;

 select * from another;

@@ -2170,22 +2172,50 @@ ALTER TABLE ONLY test_add_column
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN c2 integer, -- fail because c2 already exists
-    ADD COLUMN c3 integer;
+    ADD COLUMN c3 integer primary key;
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN c3 integer; -- fail because c3 already exists
+    ADD COLUMN c3 integer primary key;
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
+    ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
     ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
-    ADD COLUMN c4 integer;
+    ADD COLUMN c4 integer REFERENCES test_add_column;
 \d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
+\d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8);
+\d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10);
+\d test_add_column*
 DROP TABLE test_add_column;
+\d test_add_column*
+
+-- assorted cases with multiple ALTER TABLE steps
+CREATE TABLE ataddindex(f1 INT);
+INSERT INTO ataddindex VALUES (42), (43);
+CREATE UNIQUE INDEX ataddindexi0 ON ataddindex(f1);
+ALTER TABLE ataddindex
+  ADD PRIMARY KEY USING INDEX ataddindexi0,
+  ALTER f1 TYPE BIGINT;
+\d ataddindex
+DROP TABLE ataddindex;
+
+CREATE TABLE ataddindex(f1 VARCHAR(10));
+INSERT INTO ataddindex(f1) VALUES ('foo'), ('a');
+ALTER TABLE ataddindex
+  ALTER f1 SET DATA TYPE TEXT,
+  ADD EXCLUDE ((f1 LIKE 'a') WITH =);
+\d ataddindex
+DROP TABLE ataddindex;

 -- unsupported constraint types for partitioned tables
 CREATE TABLE partitioned (
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index 4b03d24..685607c 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -239,6 +239,44 @@ RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;

+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+
+INSERT INTO itest8 VALUES(0), (1);
+
+TABLE itest8;
+\d+ itest8
+\d itest8_f2_seq
+\d itest8_f3_seq
+\d itest8_f4_seq
+\d itest8_f5_seq
+DROP TABLE itest8;
+

 -- typed tables (currently not supported)


Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
I wrote:
> [ fix-alter-table-order-of-operations-3.patch ]

Rebased again, fixing a minor conflict with f595117e2.

> I'd kind of like to get this cleared out of my queue soon.
> Does anyone intend to review it further?

If I don't hear objections pretty darn quick, I'm going to
go ahead and push this.

            regards, tom lane

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2ec3fc5..4e44ebb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -85,6 +85,7 @@
 #include "storage/lock.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
+#include "tcop/utility.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -142,11 +143,13 @@ static List *on_commits = NIL;
 #define AT_PASS_OLD_CONSTR        3    /* re-add existing constraints */
 /* We could support a RENAME COLUMN pass here, but not currently used */
 #define AT_PASS_ADD_COL            4    /* ADD COLUMN */
-#define AT_PASS_COL_ATTRS        5    /* set other column attributes */
-#define AT_PASS_ADD_INDEX        6    /* ADD indexes */
-#define AT_PASS_ADD_CONSTR        7    /* ADD constraints, defaults */
-#define AT_PASS_MISC            8    /* other stuff */
-#define AT_NUM_PASSES            9
+#define AT_PASS_ADD_CONSTR        5    /* ADD constraints (initial examination) */
+#define AT_PASS_COL_ATTRS        6    /* set column attributes, eg NOT NULL */
+#define AT_PASS_ADD_INDEXCONSTR    7    /* ADD index-based constraints */
+#define AT_PASS_ADD_INDEX        8    /* ADD indexes */
+#define AT_PASS_ADD_OTHERCONSTR    9    /* ADD other constraints, defaults */
+#define AT_PASS_MISC            10    /* other stuff */
+#define AT_NUM_PASSES            11

 typedef struct AlteredTableInfo
 {
@@ -159,6 +162,7 @@ typedef struct AlteredTableInfo
     /* Information saved by Phases 1/2 for Phase 3: */
     List       *constraints;    /* List of NewConstraint */
     List       *newvals;        /* List of NewColumnValue */
+    List       *afterStmts;        /* List of utility command parsetrees */
     bool        verify_new_notnull; /* T if we should recheck NOT NULL */
     int            rewrite;        /* Reason for forced rewrite, if any */
     Oid            newTableSpace;    /* new tablespace; 0 means no change */
@@ -340,31 +344,45 @@ static void validateForeignKeyConstraint(char *conname,
                                          Relation rel, Relation pkrel,
                                          Oid pkindOid, Oid constraintOid);
 static void ATController(AlterTableStmt *parsetree,
-                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
+                         Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+                         AlterTableUtilityContext *context);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                      bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode);
+                      bool recurse, bool recursing, LOCKMODE lockmode,
+                      AlterTableUtilityContext *context);
+static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                              AlterTableUtilityContext *context);
 static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                      AlterTableCmd *cmd, LOCKMODE lockmode);
+                      AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass,
+                      AlterTableUtilityContext *context);
+static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab,
+                                          Relation rel, AlterTableCmd *cmd,
+                                          bool recurse, LOCKMODE lockmode,
+                                          int cur_pass,
+                                          AlterTableUtilityContext *context);
 static void ATRewriteTables(AlterTableStmt *parsetree,
-                            List **wqueue, LOCKMODE lockmode);
+                            List **wqueue, LOCKMODE lockmode,
+                            AlterTableUtilityContext *context);
 static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
 static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
 static void ATSimplePermissions(Relation rel, int allowed_targets);
 static void ATWrongRelkindError(Relation rel, int allowed_targets);
 static void ATSimpleRecursion(List **wqueue, Relation rel,
-                              AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
+                              AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                              AlterTableUtilityContext *context);
 static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
 static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                                  LOCKMODE lockmode);
+                                  LOCKMODE lockmode,
+                                  AlterTableUtilityContext *context);
 static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
                                            DropBehavior behavior);
 static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                            bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode);
+                            bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
+                            AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
-                                     Relation rel, ColumnDef *colDef,
+                                     Relation rel, AlterTableCmd **cmd,
                                      bool recurse, bool recursing,
-                                     bool if_not_exists, LOCKMODE lockmode);
+                                     LOCKMODE lockmode, int cur_pass,
+                                     AlterTableUtilityContext *context);
 static bool check_for_column_name_collision(Relation rel, const char *colname,
                                             bool if_not_exists);
 static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
@@ -373,7 +391,8 @@ static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing);
 static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
 static void ATPrepSetNotNull(List **wqueue, Relation rel,
                              AlterTableCmd *cmd, bool recurse, bool recursing,
-                             LOCKMODE lockmode);
+                             LOCKMODE lockmode,
+                             AlterTableUtilityContext *context);
 static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
                                       const char *colName, LOCKMODE lockmode);
 static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel,
@@ -397,7 +416,8 @@ static ObjectAddress ATExecSetOptions(Relation rel, const char *colName,
 static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
                                       Node *newValue, LOCKMODE lockmode);
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                             AlterTableCmd *cmd, LOCKMODE lockmode);
+                             AlterTableCmd *cmd, LOCKMODE lockmode,
+                             AlterTableUtilityContext *context);
 static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
                                       DropBehavior behavior,
                                       bool recurse, bool recursing,
@@ -454,7 +474,8 @@ static void ATExecDropConstraint(Relation rel, const char *constrName,
 static void ATPrepAlterColumnType(List **wqueue,
                                   AlteredTableInfo *tab, Relation rel,
                                   bool recurse, bool recursing,
-                                  AlterTableCmd *cmd, LOCKMODE lockmode);
+                                  AlterTableCmd *cmd, LOCKMODE lockmode,
+                                  AlterTableUtilityContext *context);
 static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
 static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                            AlterTableCmd *cmd, LOCKMODE lockmode);
@@ -3463,7 +3484,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  *
  * ALTER TABLE is performed in three phases:
  *        1. Examine subcommands and perform pre-transformation checking.
- *        2. Update system catalogs.
+ *        2. Validate and transform subcommands, and update system catalogs.
  *        3. Scan table(s) to check new constraints, and optionally recopy
  *           the data into new table(s).
  * Phase 3 is not performed unless one or more of the subcommands requires
@@ -3474,9 +3495,10 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * ATPrepCmd performs phase 1.  A "work queue" entry is created for
  * each table to be affected (there may be multiple affected tables if the
  * commands traverse a table inheritance hierarchy).  Also we do preliminary
- * validation of the subcommands, including parse transformation of those
- * expressions that need to be evaluated with respect to the old table
- * schema.
+ * validation of the subcommands.  Because earlier subcommands may change
+ * the catalog state seen by later commands, there are limits to what can
+ * be done in this phase.  Generally, this phase acquires table locks,
+ * checks permissions and relkind, and recurses to find child tables.
  *
  * ATRewriteCatalogs performs phase 2 for each affected table.  (Note that
  * phases 2 and 3 normally do no explicit recursion, since phase 1 already
@@ -3498,18 +3520,23 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
  * lock level we want as we recurse might well be higher than required for
  * that specific subcommand. So we pass down the overall lock requirement,
  * rather than reassess it at lower levels.
+ *
+ * The caller also provides a "context" which is to be passed back to
+ * utility.c when we need to execute a subcommand such as CREATE INDEX.
+ * Some of the fields therein, such as the relid, are used here as well.
  */
 void
-AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
+AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+           AlterTableUtilityContext *context)
 {
     Relation    rel;

     /* Caller is required to provide an adequate lock. */
-    rel = relation_open(relid, NoLock);
+    rel = relation_open(context->relid, NoLock);

     CheckTableNotInUse(rel, "ALTER TABLE");

-    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
+    ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
 }

 /*
@@ -3522,6 +3549,10 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
  * is unsafe to use this entry point for alterations that could break
  * existing query plans.  On the assumption it's not used for such, we
  * don't have to reject pending AFTER triggers, either.
+ *
+ * Also, since we don't have an AlterTableUtilityContext, this cannot be
+ * used for any subcommand types that require parse transformation or
+ * could generate subcommands that have to be passed to ProcessUtility.
  */
 void
 AlterTableInternal(Oid relid, List *cmds, bool recurse)
@@ -3533,7 +3564,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)

     EventTriggerAlterTableRelid(relid);

-    ATController(NULL, rel, cmds, recurse, lockmode);
+    ATController(NULL, rel, cmds, recurse, lockmode, NULL);
 }

 /*
@@ -3679,7 +3710,6 @@ AlterTableGetLockLevel(List *cmds)
                 break;

             case AT_AddConstraint:
-            case AT_ProcessedConstraint:    /* becomes AT_AddConstraint */
             case AT_AddConstraintRecurse:    /* becomes AT_AddConstraint */
             case AT_ReAddConstraint:    /* becomes AT_AddConstraint */
             case AT_ReAddDomainConstraint:    /* becomes AT_AddConstraint */
@@ -3832,7 +3862,8 @@ AlterTableGetLockLevel(List *cmds)
  */
 static void
 ATController(AlterTableStmt *parsetree,
-             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
+             Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
+             AlterTableUtilityContext *context)
 {
     List       *wqueue = NIL;
     ListCell   *lcmd;
@@ -3842,17 +3873,17 @@ ATController(AlterTableStmt *parsetree,
     {
         AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd);

-        ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode);
+        ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context);
     }

     /* Close the relation, but keep lock until commit */
     relation_close(rel, NoLock);

     /* Phase 2: update system catalogs */
-    ATRewriteCatalogs(&wqueue, lockmode);
+    ATRewriteCatalogs(&wqueue, lockmode, context);

-    /* Phase 3: scan/rewrite tables as needed */
-    ATRewriteTables(parsetree, &wqueue, lockmode);
+    /* Phase 3: scan/rewrite tables as needed, and run afterStmts */
+    ATRewriteTables(parsetree, &wqueue, lockmode, context);
 }

 /*
@@ -3866,7 +3897,8 @@ ATController(AlterTableStmt *parsetree,
  */
 static void
 ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
-          bool recurse, bool recursing, LOCKMODE lockmode)
+          bool recurse, bool recursing, LOCKMODE lockmode,
+          AlterTableUtilityContext *context)
 {
     AlteredTableInfo *tab;
     int            pass = AT_PASS_UNSET;
@@ -3878,13 +3910,17 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
      * Copy the original subcommand for each table.  This avoids conflicts
      * when different child tables need to make different parse
      * transformations (for example, the same column may have different column
-     * numbers in different children).
+     * numbers in different children).  It also ensures that we don't corrupt
+     * the original parse tree, in case it is saved in plancache.
      */
     cmd = copyObject(cmd);

     /*
-     * Do permissions checking, recursion to child tables if needed, and any
-     * additional phase-1 processing needed.
+     * Do permissions and relkind checking, recursion to child tables if
+     * needed, and any additional phase-1 processing needed.  (But beware of
+     * adding any processing that looks at table details that another
+     * subcommand could change.  In some cases we reject multiple subcommands
+     * that could try to change the same state in contrary ways.)
      */
     switch (cmd->subtype)
     {
@@ -3892,14 +3928,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
             ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
-                            lockmode);
+                            lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_ADD_COL;
             break;
         case AT_AddColumnToView:    /* add column via CREATE OR REPLACE VIEW */
             ATSimplePermissions(rel, ATT_VIEW);
             ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
-                            lockmode);
+                            lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_ADD_COL;
             break;
@@ -3912,19 +3948,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
              * rules.
              */
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
-            pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
+            pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP;
             break;
         case AT_AddIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
             /* This command never recurses */
-            pass = AT_PASS_ADD_CONSTR;
+            pass = AT_PASS_ADD_OTHERCONSTR;
             break;
         case AT_SetIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
             /* This command never recurses */
-            pass = AT_PASS_COL_ATTRS;
+            /* This should run after AddIdentity, so do it in MISC pass */
+            pass = AT_PASS_MISC;
             break;
         case AT_DropIdentity:
             ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
@@ -3934,30 +3971,31 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
         case AT_DropNotNull:    /* ALTER COLUMN DROP NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
             ATPrepDropNotNull(rel, recurse, recursing);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             pass = AT_PASS_DROP;
             break;
         case AT_SetNotNull:        /* ALTER COLUMN SET NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
             /* Need command-specific recursion decision */
-            ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing, lockmode);
+            ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing,
+                             lockmode, context);
             pass = AT_PASS_COL_ATTRS;
             break;
         case AT_CheckNotNull:    /* check column is already marked NOT NULL */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_COL_ATTRS;
             break;
         case AT_DropExpression:    /* ALTER COLUMN DROP EXPRESSION */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             ATPrepDropExpression(rel, cmd, recursing);
             pass = AT_PASS_DROP;
             break;
         case AT_SetStatistics:    /* ALTER COLUMN SET STATISTICS */
             ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_MISC;
             break;
@@ -3969,14 +4007,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetStorage:        /* ALTER COLUMN SET STORAGE */
             ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
-            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
             /* No command-specific prep needed */
             pass = AT_PASS_MISC;
             break;
         case AT_DropColumn:        /* DROP COLUMN */
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
-            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode);
+            ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd,
+                             lockmode, context);
             /* Recursion occurs during execution phase */
             pass = AT_PASS_DROP;
             break;
@@ -3998,7 +4037,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             ATSimplePermissions(rel, ATT_TABLE);
             /* This command never recurses */
             /* No command-specific prep needed */
-            pass = AT_PASS_ADD_CONSTR;
+            pass = AT_PASS_ADD_INDEXCONSTR;
             break;
         case AT_DropConstraint: /* DROP CONSTRAINT */
             ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
@@ -4012,8 +4051,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
         case AT_AlterColumnType:    /* ALTER COLUMN TYPE */
             ATSimplePermissions(rel,
                                 ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
+            /* See comments for ATPrepAlterColumnType */
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode,
+                                      AT_PASS_UNSET, context);
+            Assert(cmd != NULL);
             /* Performs own recursion */
-            ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode);
+            ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd,
+                                  lockmode, context);
             pass = AT_PASS_ALTER_TYPE;
             break;
         case AT_AlterColumnGenericOptions:
@@ -4036,6 +4080,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetLogged:        /* SET LOGGED */
             ATSimplePermissions(rel, ATT_TABLE);
+            if (tab->chgPersistence)
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("cannot change persistence setting twice")));
             tab->chgPersistence = ATPrepChangePersistence(rel, true);
             /* force rewrite if necessary; see comment in ATRewriteTables */
             if (tab->chgPersistence)
@@ -4047,6 +4095,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
             break;
         case AT_SetUnLogged:    /* SET UNLOGGED */
             ATSimplePermissions(rel, ATT_TABLE);
+            if (tab->chgPersistence)
+                ereport(ERROR,
+                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                         errmsg("cannot change persistence setting twice")));
             tab->chgPersistence = ATPrepChangePersistence(rel, false);
             /* force rewrite if necessary; see comment in ATRewriteTables */
             if (tab->chgPersistence)
@@ -4166,7 +4218,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
  * conflicts).
  */
 static void
-ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
+ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+                  AlterTableUtilityContext *context)
 {
     int            pass;
     ListCell   *ltab;
@@ -4199,7 +4252,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
             foreach(lcmd, subcmds)
                 ATExecCmd(wqueue, tab, rel,
                           castNode(AlterTableCmd, lfirst(lcmd)),
-                          lockmode);
+                          lockmode, pass, context);

             /*
              * After the ALTER TYPE pass, do cleanup work (this is not done in
@@ -4236,7 +4289,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
  */
 static void
 ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
-          AlterTableCmd *cmd, LOCKMODE lockmode)
+          AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass,
+          AlterTableUtilityContext *context)
 {
     ObjectAddress address = InvalidObjectAddress;

@@ -4244,22 +4298,28 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
     {
         case AT_AddColumn:        /* ADD COLUMN */
         case AT_AddColumnToView:    /* add column via CREATE OR REPLACE VIEW */
-            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+            address = ATExecAddColumn(wqueue, tab, rel, &cmd,
                                       false, false,
-                                      cmd->missing_ok, lockmode);
+                                      lockmode, cur_pass, context);
             break;
         case AT_AddColumnRecurse:
-            address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def,
+            address = ATExecAddColumn(wqueue, tab, rel, &cmd,
                                       true, false,
-                                      cmd->missing_ok, lockmode);
+                                      lockmode, cur_pass, context);
             break;
         case AT_ColumnDefault:    /* ALTER COLUMN DEFAULT */
             address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_AddIdentity:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_SetIdentity:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode);
             break;
         case AT_DropIdentity:
@@ -4310,14 +4370,24 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                      lockmode);
             break;
         case AT_AddConstraint:    /* ADD CONSTRAINT */
-            address =
-                ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                                    false, false, lockmode);
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            /* Might not have gotten AddConstraint back from parse transform */
+            if (cmd != NULL)
+                address =
+                    ATExecAddConstraint(wqueue, tab, rel,
+                                        (Constraint *) cmd->def,
+                                        false, false, lockmode);
             break;
         case AT_AddConstraintRecurse:    /* ADD CONSTRAINT with recursion */
-            address =
-                ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def,
-                                    true, false, lockmode);
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, true, lockmode,
+                                      cur_pass, context);
+            /* Might not have gotten AddConstraint back from parse transform */
+            if (cmd != NULL)
+                address =
+                    ATExecAddConstraint(wqueue, tab, rel,
+                                        (Constraint *) cmd->def,
+                                        true, false, lockmode);
             break;
         case AT_ReAddConstraint:    /* Re-add pre-existing check constraint */
             address =
@@ -4361,6 +4431,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                  cmd->missing_ok, lockmode);
             break;
         case AT_AlterColumnType:    /* ALTER COLUMN TYPE */
+            /* parse transformation was done earlier */
             address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
             break;
         case AT_AlterColumnGenericOptions:    /* ALTER COLUMN OPTIONS */
@@ -4483,6 +4554,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
             ATExecGenericOptions(rel, (List *) cmd->def);
             break;
         case AT_AttachPartition:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
                 ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
             else
@@ -4490,6 +4564,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
                                          ((PartitionCmd *) cmd->def)->name);
             break;
         case AT_DetachPartition:
+            cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
+                                      cur_pass, context);
+            Assert(cmd != NULL);
             /* ATPrepCmd ensures it must be a table */
             Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
             ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
@@ -4503,7 +4580,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
     /*
      * Report the subcommand to interested event triggers.
      */
-    EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);
+    if (cmd)
+        EventTriggerCollectAlterTableSubcmd((Node *) cmd, address);

     /*
      * Bump the command counter to ensure the next subcommand in the sequence
@@ -4513,10 +4591,143 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 }

 /*
+ * ATParseTransformCmd: perform parse transformation for one subcommand
+ *
+ * Returns the transformed subcommand tree, if there is one, else NULL.
+ *
+ * The parser may hand back additional AlterTableCmd(s) and/or other
+ * utility statements, either before or after the original subcommand.
+ * Other AlterTableCmds are scheduled into the appropriate slot of the
+ * AlteredTableInfo (they had better be for later passes than the current one).
+ * Utility statements that are supposed to happen before the AlterTableCmd
+ * are executed immediately.  Those that are supposed to happen afterwards
+ * are added to the tab->afterStmts list to be done at the very end.
+ */
+static AlterTableCmd *
+ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
+                    AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                    int cur_pass, AlterTableUtilityContext *context)
+{
+    AlterTableCmd *newcmd = NULL;
+    AlterTableStmt *atstmt = makeNode(AlterTableStmt);
+    List       *beforeStmts;
+    List       *afterStmts;
+    ListCell   *lc;
+
+    /* Gin up an AlterTableStmt with just this subcommand and this table */
+    atstmt->relation =
+        makeRangeVar(get_namespace_name(RelationGetNamespace(rel)),
+                     pstrdup(RelationGetRelationName(rel)),
+                     -1);
+    atstmt->relation->inh = recurse;
+    atstmt->cmds = list_make1(cmd);
+    atstmt->relkind = OBJECT_TABLE; /* needn't be picky here */
+    atstmt->missing_ok = false;
+
+    /* Transform the AlterTableStmt */
+    atstmt = transformAlterTableStmt(RelationGetRelid(rel),
+                                     atstmt,
+                                     context->queryString,
+                                     &beforeStmts,
+                                     &afterStmts);
+
+    /* Execute any statements that should happen before these subcommand(s) */
+    foreach(lc, beforeStmts)
+    {
+        Node       *stmt = (Node *) lfirst(lc);
+
+        ProcessUtilityForAlterTable(stmt, context);
+        CommandCounterIncrement();
+    }
+
+    /* Examine the transformed subcommands and schedule them appropriately */
+    foreach(lc, atstmt->cmds)
+    {
+        AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc);
+
+        if (newcmd == NULL &&
+            (cmd->subtype == cmd2->subtype ||
+             (cmd->subtype == AT_AddConstraintRecurse &&
+              cmd2->subtype == AT_AddConstraint)))
+        {
+            /* Found the transformed version of our subcommand */
+            cmd2->subtype = cmd->subtype;    /* copy recursion flag */
+            newcmd = cmd2;
+        }
+        else
+        {
+            int            pass;
+
+            /*
+             * Schedule added subcommand appropriately.  We assume we needn't
+             * do any phase-1 checks for it.  This switch only has to cover
+             * the subcommand types that can be added by parse_utilcmd.c.
+             */
+            switch (cmd2->subtype)
+            {
+                case AT_SetNotNull:
+                    /* Need command-specific recursion decision */
+                    ATPrepSetNotNull(wqueue, rel, cmd2,
+                                     recurse, false,
+                                     lockmode, context);
+                    pass = AT_PASS_COL_ATTRS;
+                    break;
+                case AT_AddIndex:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_ADD_INDEX;
+                    break;
+                case AT_AddIndexConstraint:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_ADD_INDEXCONSTR;
+                    break;
+                case AT_AddConstraint:
+                    /* Recursion occurs during execution phase */
+                    if (recurse)
+                        cmd2->subtype = AT_AddConstraintRecurse;
+                    switch (castNode(Constraint, cmd2->def)->contype)
+                    {
+                        case CONSTR_PRIMARY:
+                        case CONSTR_UNIQUE:
+                        case CONSTR_EXCLUSION:
+                            pass = AT_PASS_ADD_INDEXCONSTR;
+                            break;
+                        default:
+                            pass = AT_PASS_ADD_OTHERCONSTR;
+                            break;
+                    }
+                    break;
+                case AT_AlterColumnGenericOptions:
+                    /* This command never recurses */
+                    /* No command-specific prep needed */
+                    pass = AT_PASS_MISC;
+                    break;
+                default:
+                    elog(ERROR, "unexpected AlterTableType: %d",
+                         (int) cmd2->subtype);
+                    pass = AT_PASS_UNSET;
+                    break;
+            }
+            /* Must be for a later pass than we're currently doing */
+            if (pass <= cur_pass)
+                elog(ERROR, "ALTER TABLE scheduling failure");
+            tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2);
+        }
+    }
+
+    /* Queue up any after-statements to happen at the end */
+    tab->afterStmts = list_concat(tab->afterStmts, afterStmts);
+
+    return newcmd;
+}
+
+/*
  * ATRewriteTables: ALTER TABLE phase 3
  */
 static void
-ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
+ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
+                AlterTableUtilityContext *context)
 {
     ListCell   *ltab;

@@ -4743,6 +4954,21 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
         if (rel)
             table_close(rel, NoLock);
     }
+
+    /* Finally, run any afterStmts that were queued up */
+    foreach(ltab, *wqueue)
+    {
+        AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
+        ListCell   *lc;
+
+        foreach(lc, tab->afterStmts)
+        {
+            Node       *stmt = (Node *) lfirst(lc);
+
+            ProcessUtilityForAlterTable(stmt, context);
+            CommandCounterIncrement();
+        }
+    }
 }

 /*
@@ -5279,7 +5505,8 @@ ATWrongRelkindError(Relation rel, int allowed_targets)
  */
 static void
 ATSimpleRecursion(List **wqueue, Relation rel,
-                  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode)
+                  AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode,
+                  AlterTableUtilityContext *context)
 {
     /*
      * Propagate to children if desired.  Only plain tables, foreign tables
@@ -5312,7 +5539,7 @@ ATSimpleRecursion(List **wqueue, Relation rel,
             /* find_all_inheritors already got lock */
             childrel = relation_open(childrelid, NoLock);
             CheckTableNotInUse(childrel, "ALTER TABLE");
-            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
+            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
             relation_close(childrel, NoLock);
         }
     }
@@ -5357,7 +5584,7 @@ ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
  */
 static void
 ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
-                      LOCKMODE lockmode)
+                      LOCKMODE lockmode, AlterTableUtilityContext *context)
 {
     ListCell   *child;
     List       *children;
@@ -5375,7 +5602,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,

         childrel = relation_open(childrelid, lockmode);
         CheckTableNotInUse(childrel, "ALTER TABLE");
-        ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode);
+        ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
         relation_close(childrel, NoLock);
     }
 }
@@ -5612,7 +5839,8 @@ check_of_type(HeapTuple typetuple)
  */
 static void
 ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode)
+                bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode,
+                AlterTableUtilityContext *context)
 {
     if (rel->rd_rel->reloftype && !recursing)
         ereport(ERROR,
@@ -5620,7 +5848,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
                  errmsg("cannot add column to typed table")));

     if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

     if (recurse && !is_view)
         cmd->subtype = AT_AddColumnRecurse;
@@ -5629,14 +5857,20 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 /*
  * Add a column to a table.  The return value is the address of the
  * new column in the parent relation.
+ *
+ * cmd is pass-by-ref so that we can replace it with the parse-transformed
+ * copy (but that happens only after we check for IF NOT EXISTS).
  */
 static ObjectAddress
 ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
-                ColumnDef *colDef,
+                AlterTableCmd **cmd,
                 bool recurse, bool recursing,
-                bool if_not_exists, LOCKMODE lockmode)
+                LOCKMODE lockmode, int cur_pass,
+                AlterTableUtilityContext *context)
 {
     Oid            myrelid = RelationGetRelid(rel);
+    ColumnDef  *colDef = castNode(ColumnDef, (*cmd)->def);
+    bool        if_not_exists = (*cmd)->missing_ok;
     Relation    pgclass,
                 attrdesc;
     HeapTuple    reltup;
@@ -5651,6 +5885,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
     Expr       *defval;
     List       *children;
     ListCell   *child;
+    AlterTableCmd *childcmd;
     AclResult    aclresult;
     ObjectAddress address;

@@ -5718,12 +5953,31 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
         }
     }

-    pgclass = table_open(RelationRelationId, RowExclusiveLock);
+    /* skip if the name already exists and if_not_exists is true */
+    if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
+    {
+        table_close(attrdesc, RowExclusiveLock);
+        return InvalidObjectAddress;
+    }

-    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
-    if (!HeapTupleIsValid(reltup))
-        elog(ERROR, "cache lookup failed for relation %u", myrelid);
-    relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
+    /*
+     * Okay, we need to add the column, so go ahead and do parse
+     * transformation.  This can result in queueing up, or even immediately
+     * executing, subsidiary operations (such as creation of unique indexes);
+     * so we mustn't do it until we have made the if_not_exists check.
+     *
+     * When recursing, the command was already transformed and we needn't do
+     * so again.  Also, if context isn't given we can't transform.  (That
+     * currently happens only for AT_AddColumnToView; we expect that view.c
+     * passed us a ColumnDef that doesn't need work.)
+     */
+    if (context != NULL && !recursing)
+    {
+        *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode,
+                                   cur_pass, context);
+        Assert(*cmd != NULL);
+        colDef = castNode(ColumnDef, (*cmd)->def);
+    }

     /*
      * Cannot add identity column if table has children, because identity does
@@ -5736,14 +5990,12 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
                 (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
                  errmsg("cannot recursively add identity column to table that has child tables")));

-    /* skip if the name already exists and if_not_exists is true */
-    if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
-    {
-        table_close(attrdesc, RowExclusiveLock);
-        heap_freetuple(reltup);
-        table_close(pgclass, RowExclusiveLock);
-        return InvalidObjectAddress;
-    }
+    pgclass = table_open(RelationRelationId, RowExclusiveLock);
+
+    reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
+    if (!HeapTupleIsValid(reltup))
+        elog(ERROR, "cache lookup failed for relation %u", myrelid);
+    relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;

     /* Determine the new attribute's number */
     newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1;
@@ -5974,10 +6226,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
     /* Children should see column as singly inherited */
     if (!recursing)
     {
-        colDef = copyObject(colDef);
+        childcmd = copyObject(*cmd);
+        colDef = castNode(ColumnDef, childcmd->def);
         colDef->inhcount = 1;
         colDef->is_local = false;
     }
+    else
+        childcmd = *cmd;        /* no need to copy again */

     foreach(child, children)
     {
@@ -5994,8 +6249,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,

         /* Recurse to child; return value is ignored */
         ATExecAddColumn(wqueue, childtab, childrel,
-                        colDef, recurse, true,
-                        if_not_exists, lockmode);
+                        &childcmd, recurse, true,
+                        lockmode, cur_pass, context);

         table_close(childrel, NoLock);
     }
@@ -6254,7 +6509,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 static void
 ATPrepSetNotNull(List **wqueue, Relation rel,
                  AlterTableCmd *cmd, bool recurse, bool recursing,
-                 LOCKMODE lockmode)
+                 LOCKMODE lockmode, AlterTableUtilityContext *context)
 {
     /*
      * If we're already recursing, there's nothing to do; the topmost
@@ -6275,10 +6530,10 @@ ATPrepSetNotNull(List **wqueue, Relation rel,

         newcmd->subtype = AT_CheckNotNull;
         newcmd->name = pstrdup(cmd->name);
-        ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode);
+        ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode, context);
     }
     else
-        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
+        ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
 }

 /*
@@ -7165,7 +7420,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
  */
 static void
 ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
-                 AlterTableCmd *cmd, LOCKMODE lockmode)
+                 AlterTableCmd *cmd, LOCKMODE lockmode,
+                 AlterTableUtilityContext *context)
 {
     if (rel->rd_rel->reloftype && !recursing)
         ereport(ERROR,
@@ -7173,7 +7429,7 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
                  errmsg("cannot drop column from typed table")));

     if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);

     if (recurse)
         cmd->subtype = AT_DropColumnRecurse;
@@ -10426,12 +10682,27 @@ ATExecDropConstraint(Relation rel, const char *constrName,

 /*
  * ALTER COLUMN TYPE
+ *
+ * Unlike other subcommand types, we do parse transformation for ALTER COLUMN
+ * TYPE during phase 1 --- the AlterTableCmd passed in here is already
+ * transformed (and must be, because we rely on some transformed fields).
+ *
+ * The point of this is that the execution of all ALTER COLUMN TYPEs for a
+ * table will be done "in parallel" during phase 3, so all the USING
+ * expressions should be parsed assuming the original column types.  Also,
+ * this allows a USING expression to refer to a field that will be dropped.
+ *
+ * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be
+ * the first two execution steps in phase 2; they must not see the effects
+ * of any other subcommand types, since the USING expressions are parsed
+ * against the unmodified table's state.
  */
 static void
 ATPrepAlterColumnType(List **wqueue,
                       AlteredTableInfo *tab, Relation rel,
                       bool recurse, bool recursing,
-                      AlterTableCmd *cmd, LOCKMODE lockmode)
+                      AlterTableCmd *cmd, LOCKMODE lockmode,
+                      AlterTableUtilityContext *context)
 {
     char       *colName = cmd->name;
     ColumnDef  *def = (ColumnDef *) cmd->def;
@@ -10678,7 +10949,7 @@ ATPrepAlterColumnType(List **wqueue,
                              errdetail("USING expression contains a whole-row table reference.")));
                 pfree(attmap);
             }
-            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode);
+            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
             relation_close(childrel, NoLock);
         }
     }
@@ -10690,7 +10961,7 @@ ATPrepAlterColumnType(List **wqueue,
                         colName)));

     if (tab->relkind == RELKIND_COMPOSITE_TYPE)
-        ATTypedTableRecursion(wqueue, rel, cmd, lockmode);
+        ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
 }

 /*
@@ -11469,10 +11740,19 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
                                                         (IndexStmt *) stmt,
                                                         cmd));
         else if (IsA(stmt, AlterTableStmt))
-            querytree_list = list_concat(querytree_list,
-                                         transformAlterTableStmt(oldRelId,
-                                                                 (AlterTableStmt *) stmt,
-                                                                 cmd));
+        {
+            List       *beforeStmts;
+            List       *afterStmts;
+
+            stmt = (Node *) transformAlterTableStmt(oldRelId,
+                                                    (AlterTableStmt *) stmt,
+                                                    cmd,
+                                                    &beforeStmts,
+                                                    &afterStmts);
+            querytree_list = list_concat(querytree_list, beforeStmts);
+            querytree_list = lappend(querytree_list, stmt);
+            querytree_list = list_concat(querytree_list, afterStmts);
+        }
         else
             querytree_list = lappend(querytree_list, stmt);
     }
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 06bc2b7..6e65103 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -145,6 +145,10 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
          * Note that we must do this before updating the query for the view,
          * since the rules system requires that the correct view columns be in
          * place when defining the new rules.
+         *
+         * Also note that ALTER TABLE doesn't run parse transformation on
+         * AT_AddColumnToView commands.  The ColumnDef we supply must be ready
+         * to execute as-is.
          */
         if (list_length(attrList) > rel->rd_att->natts)
         {
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 42095ab..ee2d2b5 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -347,7 +347,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
  */
 static void
 generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
-                         Oid seqtypid, List *seqoptions, bool for_identity,
+                         Oid seqtypid, List *seqoptions,
+                         bool for_identity, bool col_exists,
                          char **snamespace_p, char **sname_p)
 {
     ListCell   *option;
@@ -472,8 +473,12 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,

     /*
      * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as
-     * owned by this column, and add it to the list of things to be done after
-     * this CREATE/ALTER TABLE.
+     * owned by this column, and add it to the appropriate list of things to
+     * be done along with this CREATE/ALTER TABLE.  In a CREATE or ALTER ADD
+     * COLUMN, it must be done after the statement because we don't know the
+     * column's attnum yet.  But if we do have the attnum (in AT_AddIdentity),
+     * we can do the marking immediately, which improves some ALTER TABLE
+     * behaviors.
      */
     altseqstmt = makeNode(AlterSeqStmt);
     altseqstmt->sequence = makeRangeVar(snamespace, sname, -1);
@@ -484,7 +489,10 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
                                                  (Node *) attnamelist, -1));
     altseqstmt->for_identity = for_identity;

-    cxt->alist = lappend(cxt->alist, altseqstmt);
+    if (col_exists)
+        cxt->blist = lappend(cxt->blist, altseqstmt);
+    else
+        cxt->alist = lappend(cxt->alist, altseqstmt);

     if (snamespace_p)
         *snamespace_p = snamespace;
@@ -568,7 +576,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
         Constraint *constraint;

         generateSerialExtraStmts(cxt, column,
-                                 column->typeName->typeOid, NIL, false,
+                                 column->typeName->typeOid, NIL,
+                                 false, false,
                                  &snamespace, &sname);

         /*
@@ -684,7 +693,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
                                                     constraint->location)));

                     generateSerialExtraStmts(cxt, column,
-                                             typeOid, constraint->options, true,
+                                             typeOid, constraint->options,
+                                             true, false,
                                              NULL, NULL);

                     column->identity = constraint->generated_when;
@@ -1086,7 +1096,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
             seq_relid = getIdentitySequence(RelationGetRelid(relation), attribute->attnum, false);
             seq_options = sequence_options(seq_relid);
             generateSerialExtraStmts(cxt, def,
-                                     InvalidOid, seq_options, true,
+                                     InvalidOid, seq_options,
+                                     true, false,
                                      NULL, NULL);
             def->identity = attribute->attidentity;
         }
@@ -2572,7 +2583,7 @@ transformFKConstraints(CreateStmtContext *cxt,
             Constraint *constraint = (Constraint *) lfirst(fkclist);
             AlterTableCmd *altercmd = makeNode(AlterTableCmd);

-            altercmd->subtype = AT_ProcessedConstraint;
+            altercmd->subtype = AT_AddConstraint;
             altercmd->name = NULL;
             altercmd->def = (Node *) constraint;
             alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
@@ -3004,23 +3015,23 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  * transformAlterTableStmt -
  *        parse analysis for ALTER TABLE
  *
- * Returns a List of utility commands to be done in sequence.  One of these
- * will be the transformed AlterTableStmt, but there may be additional actions
- * to be done before and after the actual AlterTable() call.
+ * Returns the transformed AlterTableStmt.  There may be additional actions
+ * to be done before and after the transformed statement, which are returned
+ * in *beforeStmts and *afterStmts as lists of utility command parsetrees.
  *
  * To avoid race conditions, it's important that this function rely only on
  * the passed-in relid (and not on stmt->relation) to determine the target
  * relation.
  */
-List *
+AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-                        const char *queryString)
+                        const char *queryString,
+                        List **beforeStmts, List **afterStmts)
 {
     Relation    rel;
     TupleDesc    tupdesc;
     ParseState *pstate;
     CreateStmtContext cxt;
-    List       *result;
     List       *save_alist;
     ListCell   *lcmd,
                *l;
@@ -3052,7 +3063,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,

     /* Set up CreateStmtContext */
     cxt.pstate = pstate;
-    if (stmt->relkind == OBJECT_FOREIGN_TABLE)
+    if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
     {
         cxt.stmtType = "ALTER FOREIGN TABLE";
         cxt.isforeign = true;
@@ -3080,9 +3091,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
     cxt.ofType = false;

     /*
-     * The only subtypes that currently require parse transformation handling
-     * are ADD COLUMN, ADD CONSTRAINT and SET DATA TYPE.  These largely re-use
-     * code from CREATE TABLE.
+     * Transform ALTER subcommands that need it (most don't).  These largely
+     * re-use code from CREATE TABLE.
      */
     foreach(lcmd, stmt->cmds)
     {
@@ -3091,7 +3101,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
         switch (cmd->subtype)
         {
             case AT_AddColumn:
-            case AT_AddColumnToView:
+            case AT_AddColumnRecurse:
                 {
                     ColumnDef  *def = castNode(ColumnDef, cmd->def);

@@ -3115,6 +3125,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                 }

             case AT_AddConstraint:
+            case AT_AddConstraintRecurse:

                 /*
                  * The original AddConstraint cmd node doesn't go to newcmds
@@ -3130,19 +3141,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                          (int) nodeTag(cmd->def));
                 break;

-            case AT_ProcessedConstraint:
-
-                /*
-                 * Already-transformed ADD CONSTRAINT, so just make it look
-                 * like the standard case.
-                 */
-                cmd->subtype = AT_AddConstraint;
-                newcmds = lappend(newcmds, cmd);
-                break;
-
             case AT_AlterColumnType:
                 {
-                    ColumnDef  *def = (ColumnDef *) cmd->def;
+                    ColumnDef  *def = castNode(ColumnDef, cmd->def);
                     AttrNumber    attnum;

                     /*
@@ -3161,13 +3162,13 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                      * change the data type of the sequence.
                      */
                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    /*
-                     * if attribute not found, something will error about it
-                     * later
-                     */
-                    if (attnum != InvalidAttrNumber &&
-                        TupleDescAttr(tupdesc, attnum - 1)->attidentity)
+                    if (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
                     {
                         Oid            seq_relid = getIdentitySequence(relid, attnum, false);
                         Oid            typeOid = typenameTypeId(pstate, def->typeName);
@@ -3196,16 +3197,16 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     cmd->def = (Node *) newdef;

                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    /*
-                     * if attribute not found, something will error about it
-                     * later
-                     */
-                    if (attnum != InvalidAttrNumber)
-                        generateSerialExtraStmts(&cxt, newdef,
-                                                 get_atttype(relid, attnum),
-                                                 def->options, true,
-                                                 NULL, NULL);
+                    generateSerialExtraStmts(&cxt, newdef,
+                                             get_atttype(relid, attnum),
+                                             def->options, true, true,
+                                             NULL, NULL);

                     newcmds = lappend(newcmds, cmd);
                     break;
@@ -3221,6 +3222,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     List       *newseqopts = NIL;
                     List       *newdef = NIL;
                     AttrNumber    attnum;
+                    Oid            seq_relid;

                     /*
                      * Split options into those handled by ALTER SEQUENCE and
@@ -3237,29 +3239,34 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                     }

                     attnum = get_attnum(relid, cmd->name);
+                    if (attnum == InvalidAttrNumber)
+                        ereport(ERROR,
+                                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
+                                        cmd->name, RelationGetRelationName(rel))));

-                    if (attnum)
-                    {
-                        Oid            seq_relid = getIdentitySequence(relid, attnum, true);
+                    seq_relid = getIdentitySequence(relid, attnum, true);

-                        if (seq_relid)
-                        {
-                            AlterSeqStmt *seqstmt;
+                    if (seq_relid)
+                    {
+                        AlterSeqStmt *seqstmt;

-                            seqstmt = makeNode(AlterSeqStmt);
-                            seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
-                                                             get_rel_name(seq_relid), -1);
-                            seqstmt->options = newseqopts;
-                            seqstmt->for_identity = true;
-                            seqstmt->missing_ok = false;
+                        seqstmt = makeNode(AlterSeqStmt);
+                        seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
+                                                         get_rel_name(seq_relid), -1);
+                        seqstmt->options = newseqopts;
+                        seqstmt->for_identity = true;
+                        seqstmt->missing_ok = false;

-                            cxt.alist = lappend(cxt.alist, seqstmt);
-                        }
+                        cxt.blist = lappend(cxt.blist, seqstmt);
                     }

                     /*
-                     * If column was not found or was not an identity column,
-                     * we just let the ALTER TABLE command error out later.
+                     * If column was not an identity column, we just let the
+                     * ALTER TABLE command error out later.  (There are cases
+                     * this fails to cover, but we'll need to restructure
+                     * where creation of the sequence dependency linkage
+                     * happens before we can fix it.)
                      */

                     cmd->def = (Node *) newdef;
@@ -3281,6 +3288,12 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
                 break;

             default:
+
+                /*
+                 * Currently, we shouldn't actually get here for subcommand
+                 * types that don't require transformation; but if we do, just
+                 * emit them unchanged.
+                 */
                 newcmds = lappend(newcmds, cmd);
                 break;
         }
@@ -3361,11 +3374,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
      */
     stmt->cmds = newcmds;

-    result = lappend(cxt.blist, stmt);
-    result = list_concat(result, cxt.alist);
-    result = list_concat(result, save_alist);
+    *beforeStmts = cxt.blist;
+    *afterStmts = list_concat(cxt.alist, save_alist);

-    return result;
+    return stmt;
 }


diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b2c58bf..696c92f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1093,8 +1093,6 @@ ProcessUtilitySlow(ParseState *pstate,
                 {
                     AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
                     Oid            relid;
-                    List       *stmts;
-                    ListCell   *l;
                     LOCKMODE    lockmode;

                     /*
@@ -1108,59 +1106,21 @@ ProcessUtilitySlow(ParseState *pstate,

                     if (OidIsValid(relid))
                     {
-                        /* Run parse analysis ... */
-                        stmts = transformAlterTableStmt(relid, atstmt,
-                                                        queryString);
+                        AlterTableUtilityContext atcontext;
+
+                        /* Set up info needed for recursive callbacks ... */
+                        atcontext.pstmt = pstmt;
+                        atcontext.queryString = queryString;
+                        atcontext.relid = relid;
+                        atcontext.params = params;
+                        atcontext.queryEnv = queryEnv;

                         /* ... ensure we have an event trigger context ... */
                         EventTriggerAlterTableStart(parsetree);
                         EventTriggerAlterTableRelid(relid);

                         /* ... and do it */
-                        foreach(l, stmts)
-                        {
-                            Node       *stmt = (Node *) lfirst(l);
-
-                            if (IsA(stmt, AlterTableStmt))
-                            {
-                                /* Do the table alteration proper */
-                                AlterTable(relid, lockmode,
-                                           (AlterTableStmt *) stmt);
-                            }
-                            else
-                            {
-                                /*
-                                 * Recurse for anything else.  If we need to
-                                 * do so, "close" the current complex-command
-                                 * set, and start a new one at the bottom;
-                                 * this is needed to ensure the ordering of
-                                 * queued commands is consistent with the way
-                                 * they are executed here.
-                                 */
-                                PlannedStmt *wrapper;
-
-                                EventTriggerAlterTableEnd();
-                                wrapper = makeNode(PlannedStmt);
-                                wrapper->commandType = CMD_UTILITY;
-                                wrapper->canSetTag = false;
-                                wrapper->utilityStmt = stmt;
-                                wrapper->stmt_location = pstmt->stmt_location;
-                                wrapper->stmt_len = pstmt->stmt_len;
-                                ProcessUtility(wrapper,
-                                               queryString,
-                                               PROCESS_UTILITY_SUBCOMMAND,
-                                               params,
-                                               NULL,
-                                               None_Receiver,
-                                               NULL);
-                                EventTriggerAlterTableStart(parsetree);
-                                EventTriggerAlterTableRelid(relid);
-                            }
-
-                            /* Need CCI between commands */
-                            if (lnext(stmts, l) != NULL)
-                                CommandCounterIncrement();
-                        }
+                        AlterTable(atstmt, lockmode, &atcontext);

                         /* done */
                         EventTriggerAlterTableEnd();
@@ -1718,6 +1678,52 @@ ProcessUtilitySlow(ParseState *pstate,
 }

 /*
+ * ProcessUtilityForAlterTable
+ *        Recursive entry from ALTER TABLE
+ *
+ * ALTER TABLE sometimes generates subcommands such as CREATE INDEX.
+ * It calls this, not the main entry point ProcessUtility, to execute
+ * such subcommands.
+ *
+ * stmt: the utility command to execute
+ * context: opaque passthrough struct with the info we need
+ *
+ * It's caller's responsibility to do CommandCounterIncrement after
+ * calling this, if needed.
+ */
+void
+ProcessUtilityForAlterTable(Node *stmt, AlterTableUtilityContext *context)
+{
+    PlannedStmt *wrapper;
+
+    /*
+     * For event triggers, we must "close" the current complex-command set,
+     * and start a new one afterwards; this is needed to ensure the ordering
+     * of command events is consistent with the way they were executed.
+     */
+    EventTriggerAlterTableEnd();
+
+    /* Create a suitable wrapper */
+    wrapper = makeNode(PlannedStmt);
+    wrapper->commandType = CMD_UTILITY;
+    wrapper->canSetTag = false;
+    wrapper->utilityStmt = stmt;
+    wrapper->stmt_location = context->pstmt->stmt_location;
+    wrapper->stmt_len = context->pstmt->stmt_len;
+
+    ProcessUtility(wrapper,
+                   context->queryString,
+                   PROCESS_UTILITY_SUBCOMMAND,
+                   context->params,
+                   context->queryEnv,
+                   None_Receiver,
+                   NULL);
+
+    EventTriggerAlterTableStart(context->pstmt->utilityStmt);
+    EventTriggerAlterTableRelid(context->relid);
+}
+
+/*
  * Dispatch function for DropStmt
  */
 static void
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 2e04b82..c1581ad 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -21,6 +21,8 @@
 #include "storage/lock.h"
 #include "utils/relcache.h"

+struct AlterTableUtilityContext;    /* avoid including tcop/utility.h here */
+

 extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
                                     ObjectAddress *typaddress, const char *queryString);
@@ -29,7 +31,8 @@ extern void RemoveRelations(DropStmt *drop);

 extern Oid    AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);

-extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt);
+extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
+                       struct AlterTableUtilityContext *context);

 extern LOCKMODE AlterTableGetLockLevel(List *cmds);

diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 28d837b..da0706a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1802,8 +1802,6 @@ typedef enum AlterTableType
     AT_AlterConstraint,            /* alter constraint */
     AT_ValidateConstraint,        /* validate constraint */
     AT_ValidateConstraintRecurse,    /* internal to commands/tablecmds.c */
-    AT_ProcessedConstraint,        /* pre-processed add constraint (local in
-                                 * parser/parse_utilcmd.c) */
     AT_AddIndexConstraint,        /* add constraint using existing index */
     AT_DropConstraint,            /* drop constraint */
     AT_DropConstraintRecurse,    /* internal to commands/tablecmds.c */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index eb73acd..1a5e0b8 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -20,8 +20,10 @@ struct AttrMap;                    /* avoid including attmap.h here */


 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
-extern List *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-                                     const char *queryString);
+extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
+                                               const char *queryString,
+                                               List **beforeStmts,
+                                               List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
                                      const char *queryString);
 extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 40551c4..f62bfc4 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -25,6 +25,16 @@ typedef enum
     PROCESS_UTILITY_SUBCOMMAND    /* a portion of a query */
 } ProcessUtilityContext;

+/* Info needed when recursing from ALTER TABLE */
+typedef struct AlterTableUtilityContext
+{
+    PlannedStmt *pstmt;            /* PlannedStmt for outer ALTER TABLE command */
+    const char *queryString;    /* its query string */
+    Oid            relid;            /* OID of ALTER's target table */
+    ParamListInfo params;        /* any parameters available to ALTER TABLE */
+    QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+} AlterTableUtilityContext;
+
 /* Hook for plugins to get control in ProcessUtility() */
 typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
                                           const char *queryString, ProcessUtilityContext context,
@@ -42,6 +52,9 @@ extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
                                     QueryEnvironment *queryEnv,
                                     DestReceiver *dest, char *completionTag);

+extern void ProcessUtilityForAlterTable(Node *stmt,
+                                        AlterTableUtilityContext *context);
+
 extern bool UtilityReturnsTuples(Node *parsetree);

 extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index 5ef04b7..e1629ec 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -162,9 +162,6 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
             case AT_ValidateConstraintRecurse:
                 strtype = "VALIDATE CONSTRAINT (and recurse)";
                 break;
-            case AT_ProcessedConstraint:
-                strtype = "ADD (processed) CONSTRAINT";
-                break;
             case AT_AddIndexConstraint:
                 strtype = "ADD CONSTRAINT (using index)";
                 break;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index b492c60..4dd3507 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -1958,27 +1958,29 @@ Indexes:
     "anothertab_f4_idx" UNIQUE, btree (f4)

 drop table anothertab;
-create table another (f1 int, f2 text);
-insert into another values(1, 'one');
-insert into another values(2, 'two');
-insert into another values(3, 'three');
+-- test that USING expressions are parsed before column alter type / drop steps
+create table another (f1 int, f2 text, f3 text);
+insert into another values(1, 'one', 'uno');
+insert into another values(2, 'two', 'due');
+insert into another values(3, 'three', 'tre');
 select * from another;
- f1 |  f2
-----+-------
-  1 | one
-  2 | two
-  3 | three
+ f1 |  f2   | f3
+----+-------+-----
+  1 | one   | uno
+  2 | two   | due
+  3 | three | tre
 (3 rows)

 alter table another
-  alter f1 type text using f2 || ' more',
-  alter f2 type bigint using f1 * 10;
+  alter f1 type text using f2 || ' and ' || f3 || ' more',
+  alter f2 type bigint using f1 * 10,
+  drop column f3;
 select * from another;
-     f1     | f2
-------------+----
- one more   | 10
- two more   | 20
- three more | 30
+         f1         | f2
+--------------------+----
+ one and uno more   | 10
+ two and due more   | 20
+ three and tre more | 30
 (3 rows)

 drop table another;
@@ -3469,7 +3471,7 @@ NOTICE:  column "c2" of relation "test_add_column" already exists, skipping

 ALTER TABLE test_add_column
     ADD COLUMN c2 integer, -- fail because c2 already exists
-    ADD COLUMN c3 integer;
+    ADD COLUMN c3 integer primary key;
 ERROR:  column "c2" of relation "test_add_column" already exists
 \d test_add_column
           Table "public.test_add_column"
@@ -3480,7 +3482,7 @@ ERROR:  column "c2" of relation "test_add_column" already exists

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN c3 integer; -- fail because c3 already exists
+    ADD COLUMN c3 integer primary key;
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 \d test_add_column
           Table "public.test_add_column"
@@ -3488,11 +3490,13 @@ NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
+    ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 \d test_add_column
@@ -3501,12 +3505,14 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)

 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
     ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
-    ADD COLUMN c4 integer;
+    ADD COLUMN c4 integer REFERENCES test_add_column;
 NOTICE:  column "c2" of relation "test_add_column" already exists, skipping
 NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 \d test_add_column
@@ -3515,10 +3521,118 @@ NOTICE:  column "c3" of relation "test_add_column" already exists, skipping
 --------+---------+-----------+----------+---------
  c1     | integer |           |          |
  c2     | integer |           |          |
- c3     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
+NOTICE:  column "c4" of relation "test_add_column" already exists, skipping
+\d test_add_column
+          Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8);
+\d test_add_column
+                            Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable |                   Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
+ c4     | integer |           |          |
+ c5     | integer |           | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Check constraints:
+    "test_add_column_c5_check" CHECK (c5 > 8)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10);
+NOTICE:  column "c5" of relation "test_add_column" already exists, skipping
+\d test_add_column*
+                            Table "public.test_add_column"
+ Column |  Type   | Collation | Nullable |                   Default
+--------+---------+-----------+----------+---------------------------------------------
+ c1     | integer |           |          |
+ c2     | integer |           |          |
+ c3     | integer |           | not null |
  c4     | integer |           |          |
+ c5     | integer |           | not null | nextval('test_add_column_c5_seq'::regclass)
+Indexes:
+    "test_add_column_pkey" PRIMARY KEY, btree (c3)
+Check constraints:
+    "test_add_column_c5_check" CHECK (c5 > 8)
+Foreign-key constraints:
+    "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+Referenced by:
+    TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
+
+               Sequence "public.test_add_column_c5_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |         1 | no      |     1
+Owned by: public.test_add_column.c5
+
+ Index "public.test_add_column_pkey"
+ Column |  Type   | Key? | Definition
+--------+---------+------+------------
+ c3     | integer | yes  | c3
+primary key, btree, for table "public.test_add_column"

 DROP TABLE test_add_column;
+\d test_add_column*
+-- assorted cases with multiple ALTER TABLE steps
+CREATE TABLE ataddindex(f1 INT);
+INSERT INTO ataddindex VALUES (42), (43);
+CREATE UNIQUE INDEX ataddindexi0 ON ataddindex(f1);
+ALTER TABLE ataddindex
+  ADD PRIMARY KEY USING INDEX ataddindexi0,
+  ALTER f1 TYPE BIGINT;
+\d ataddindex
+            Table "public.ataddindex"
+ Column |  Type  | Collation | Nullable | Default
+--------+--------+-----------+----------+---------
+ f1     | bigint |           | not null |
+Indexes:
+    "ataddindexi0" PRIMARY KEY, btree (f1)
+
+DROP TABLE ataddindex;
+CREATE TABLE ataddindex(f1 VARCHAR(10));
+INSERT INTO ataddindex(f1) VALUES ('foo'), ('a');
+ALTER TABLE ataddindex
+  ALTER f1 SET DATA TYPE TEXT,
+  ADD EXCLUDE ((f1 LIKE 'a') WITH =);
+\d ataddindex
+           Table "public.ataddindex"
+ Column | Type | Collation | Nullable | Default
+--------+------+-----------+----------+---------
+ f1     | text |           |          |
+Indexes:
+    "ataddindex_expr_excl" EXCLUDE USING btree ((f1 ~~ 'a'::text) WITH =)
+
+DROP TABLE ataddindex;
 -- unsupported constraint types for partitioned tables
 CREATE TABLE partitioned (
     a int,
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 36a2393..7cf4696 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -387,6 +387,68 @@ SELECT * FROM itest8;
 RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;
+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+INSERT INTO itest8 VALUES(0), (1);
+TABLE itest8;
+ f1 | f2 | f3 | f4 | f5
+----+----+----+----+----
+  0 |  1 |  1 |  1 |
+  1 |  2 | 11 |  2 |
+(2 rows)
+
+\d+ itest8
+                                               Table "public.itest8"
+ Column |  Type   | Collation | Nullable |             Default              | Storage | Stats target | Description
+--------+---------+-----------+----------+----------------------------------+---------+--------------+-------------
+ f1     | integer |           |          |                                  | plain   |              |
+ f2     | integer |           | not null | generated always as identity     | plain   |              |
+ f3     | integer |           | not null | generated by default as identity | plain   |              |
+ f4     | bigint  |           | not null | generated always as identity     | plain   |              |
+ f5     | bigint  |           |          |                                  | plain   |              |
+
+\d itest8_f2_seq
+                   Sequence "public.itest8_f2_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |         1 | no      |     1
+Sequence for identity column: public.itest8.f2
+
+\d itest8_f3_seq
+                   Sequence "public.itest8_f3_seq"
+  Type   | Start | Minimum |  Maximum   | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer |     1 |       1 | 2147483647 |        10 | no      |     1
+Sequence for identity column: public.itest8.f3
+
+\d itest8_f4_seq
+                       Sequence "public.itest8_f4_seq"
+  Type  | Start | Minimum |       Maximum       | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint |     1 |       1 | 9223372036854775807 |         1 | no      |     1
+Sequence for identity column: public.itest8.f4
+
+\d itest8_f5_seq
+DROP TABLE itest8;
 -- typed tables (currently not supported)
 CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint);
 CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error
diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql
index abe7be3..a16e4c9 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -1342,17 +1342,19 @@ alter table anothertab alter column f5 type bigint;

 drop table anothertab;

-create table another (f1 int, f2 text);
+-- test that USING expressions are parsed before column alter type / drop steps
+create table another (f1 int, f2 text, f3 text);

-insert into another values(1, 'one');
-insert into another values(2, 'two');
-insert into another values(3, 'three');
+insert into another values(1, 'one', 'uno');
+insert into another values(2, 'two', 'due');
+insert into another values(3, 'three', 'tre');

 select * from another;

 alter table another
-  alter f1 type text using f2 || ' more',
-  alter f2 type bigint using f1 * 10;
+  alter f1 type text using f2 || ' and ' || f3 || ' more',
+  alter f2 type bigint using f1 * 10,
+  drop column f3;

 select * from another;

@@ -2170,22 +2172,50 @@ ALTER TABLE ONLY test_add_column
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN c2 integer, -- fail because c2 already exists
-    ADD COLUMN c3 integer;
+    ADD COLUMN c3 integer primary key;
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN c3 integer; -- fail because c3 already exists
+    ADD COLUMN c3 integer primary key;
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
-    ADD COLUMN IF NOT EXISTS c3 integer; -- skipping because c3 already exists
+    ADD COLUMN IF NOT EXISTS c3 integer primary key; -- skipping because c3 already exists
 \d test_add_column
 ALTER TABLE test_add_column
     ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists
     ADD COLUMN IF NOT EXISTS c3 integer, -- skipping because c3 already exists
-    ADD COLUMN c4 integer;
+    ADD COLUMN c4 integer REFERENCES test_add_column;
 \d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
+\d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8);
+\d test_add_column
+ALTER TABLE test_add_column
+    ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10);
+\d test_add_column*
 DROP TABLE test_add_column;
+\d test_add_column*
+
+-- assorted cases with multiple ALTER TABLE steps
+CREATE TABLE ataddindex(f1 INT);
+INSERT INTO ataddindex VALUES (42), (43);
+CREATE UNIQUE INDEX ataddindexi0 ON ataddindex(f1);
+ALTER TABLE ataddindex
+  ADD PRIMARY KEY USING INDEX ataddindexi0,
+  ALTER f1 TYPE BIGINT;
+\d ataddindex
+DROP TABLE ataddindex;
+
+CREATE TABLE ataddindex(f1 VARCHAR(10));
+INSERT INTO ataddindex(f1) VALUES ('foo'), ('a');
+ALTER TABLE ataddindex
+  ALTER f1 SET DATA TYPE TEXT,
+  ADD EXCLUDE ((f1 LIKE 'a') WITH =);
+\d ataddindex
+DROP TABLE ataddindex;

 -- unsupported constraint types for partitioned tables
 CREATE TABLE partitioned (
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index 4b03d24..685607c 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -239,6 +239,44 @@ RESET ROLE;
 DROP TABLE itest8;
 DROP USER regress_identity_user1;

+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+
+ALTER TABLE itest8
+  ADD COLUMN f2 int NOT NULL,
+  ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ADD COLUMN f3 int NOT NULL,
+  ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+
+ALTER TABLE itest8
+  ADD COLUMN f4 int;
+
+ALTER TABLE itest8
+  ALTER COLUMN f4 SET NOT NULL,
+  ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+  ALTER COLUMN f4 SET DATA TYPE bigint;
+
+ALTER TABLE itest8
+  ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+  ALTER COLUMN f5 DROP IDENTITY,
+  ALTER COLUMN f5 DROP NOT NULL,
+  ALTER COLUMN f5 SET DATA TYPE bigint;
+
+INSERT INTO itest8 VALUES(0), (1);
+
+TABLE itest8;
+\d+ itest8
+\d itest8_f2_seq
+\d itest8_f3_seq
+\d itest8_f4_seq
+\d itest8_f5_seq
+DROP TABLE itest8;
+

 -- typed tables (currently not supported)


Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Sergei Kornilov
Дата:
Hello

Thank you!

I am clearly not a good reviewer for such changes... But for a note: I read the v4 patch and have no useful comments.
Goodnew tests, reasonable code changes to fix multiple bug reports.
 

The patch is proposed only for the master branch, right?

regards, Sergei



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
Sergei Kornilov <sk@zsrv.org> writes:
> I am clearly not a good reviewer for such changes... But for a note: I read the v4 patch and have no useful comments.
Goodnew tests, reasonable code changes to fix multiple bug reports. 

Thanks for looking!

> The patch is proposed only for the master branch, right?

Yes, it seems far too risky for the back branches.

            regards, tom lane



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Alvaro Herrera
Дата:
On 2020-Jan-14, Tom Lane wrote:

> I wrote:
> > [ fix-alter-table-order-of-operations-3.patch ]
> 
> Rebased again, fixing a minor conflict with f595117e2.
> 
> > I'd kind of like to get this cleared out of my queue soon.
> > Does anyone intend to review it further?
> 
> If I don't hear objections pretty darn quick, I'm going to
> go ahead and push this.

I didn't review in detail, but it seems good to me.  I especially liked
getting rid of the ProcessedConstraint code, and the additional test
cases.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
> I didn't review in detail, but it seems good to me.  I especially liked
> getting rid of the ProcessedConstraint code, and the additional test
> cases.

Thanks for looking!

Yeah, all those test cases expose situations where we misbehave
today :-(.  I wish this were small enough to be back-patchable,
but it's not feasible.

            regards, tom lane



Re: Rearranging ALTER TABLE to avoid multi-operations bugs

От
Tom Lane
Дата:
I wrote:
> The squirrely-ness around identity is that while this now works:

> regression=# CREATE TABLE itest8 (f1 int);
> CREATE TABLE
> regression=# ALTER TABLE itest8
> regression-#   ADD COLUMN f2 int NOT NULL,
> regression-#   ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
> ALTER TABLE

> it doesn't work if there's rows in the table:

> regression=# CREATE TABLE itest8 (f1 int);
> CREATE TABLE
> regression=# insert into itest8 default values;
> INSERT 0 1
> regression=# ALTER TABLE itest8
>   ADD COLUMN f2 int NOT NULL,
>   ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
> ERROR:  column "f2" contains null values

> The same would be true if you tried to do the ALTER as two separate
> operations (because the ADD ... NOT NULL, without a default, will
> naturally fail on a nonempty table).  So I don't feel *too* awful
> about that.  But it'd be better if this worked.

After further poking at that, I've concluded that maybe this is not
a bug but operating as designed.  Adding the GENERATED property in a
separate step is arguably equivalent to setting a plain default in
a separate step, and look at how we handle that:

regression=# create table t1(x int);
CREATE TABLE
regression=# insert into t1 values(1);
INSERT 0 1
regression=# alter table t1 add column y int default 11,
  alter column y set default 12;
ALTER TABLE
regression=# table t1;
 x | y  
---+----
 1 | 11
(1 row)

This is documented, rather opaquely perhaps, for the SET DEFAULT
case:

SET/DROP DEFAULT

    These forms set or remove the default value for a column. Default
    values only apply in subsequent INSERT or UPDATE commands; they do not
    cause rows already in the table to change.

So the design principle here seems to be that we fill the column
using whatever is specified *in the ADD COLUMN subcommand*, and
any screwing-about in other subcommands just affects what the
behavior will be in subsequent INSERT commands.  That's a little
weird but it has potential use-cases.  If we attempt to apply the
"new" default immediately then this syntax devolves to having the
same effects as a simple ADD-COLUMN-with-default.  There's not
a lot of reason to write the longer form if that's what you wanted.

So I'm now inclined to think that the code is all right.  We could
improve the documentation, perhaps, with an explicit example.
Also, the man page's entry for SET GENERATED says nothing of this,
but it likely ought to say the same thing as SET DEFAULT.

Also, we don't really have any test cases proving it works that way.
Simple tests, such as the one above, are not too trustworthy because
the attmissingval optimization tends to hide what's really happening.
(I found this out the hard way while messing with a patch to change
the behavior --- which I now think we shouldn't do, anyhow.)

            regards, tom lane