deparse: Support ALTER TABLE
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Fri, 25 Apr 2014 19:32:20 +0000 (16:32 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 7 Apr 2015 17:09:37 +0000 (14:09 -0300)
With help from Andres Freund

src/backend/commands/event_trigger.c
src/backend/commands/tablecmds.c
src/backend/tcop/deparse_utility.c
src/backend/tcop/utility.c
src/include/commands/event_trigger.h
src/include/tcop/deparse_utility.h

index df346f9a0946218821562cfeb1f32c7cf712d052..f4eb2f43d66a6527e147851fb17eac2f5853509a 100644 (file)
@@ -58,6 +58,7 @@ typedef struct EventTriggerQueryState
 
    bool        commandCollectionInhibited;
    MemoryContext cxt;
+   StashedCommand *curcmd;
    List       *stash;      /* list of StashedCommand; see deparse_utility.h */
    struct EventTriggerQueryState *previous;
 } EventTriggerQueryState;
@@ -1220,8 +1221,10 @@ EventTriggerBeginCompleteQuery(void)
    slist_init(&(state->SQLDropList));
    state->in_sql_drop = false;
    state->table_rewrite_oid = InvalidOid;
+
    state->commandCollectionInhibited = currentEventTriggerState ?
        currentEventTriggerState->commandCollectionInhibited : false;
+   state->curcmd = NULL;
    state->stash = NIL;
    state->previous = currentEventTriggerState;
    currentEventTriggerState = state;
@@ -1647,6 +1650,129 @@ EventTriggerUndoInhibitCommandCollection(void)
    currentEventTriggerState->commandCollectionInhibited = false;
 }
 
+/*
+ * EventTriggerAlterTableStart
+ *     Prepare to receive data on an ALTER TABLE command about to be executed
+ *
+ * Note we don't actually stash the object we create here into the "stashed"
+ * list; instead we keep it in curcmd, and only when we're done processing the
+ * subcommands we will add it to the actual stash.
+ *
+ * XXX -- this API isn't considering the possibility of an ALTER TABLE command
+ * being called reentrantly by an event trigger function.  Do we need stackable
+ * commands at this level?  Perhaps at least we should detect the condition and
+ * raise an error.
+ */
+void
+EventTriggerAlterTableStart(Node *parsetree)
+{
+   MemoryContext   oldcxt;
+   StashedCommand *stashed;
+
+   if (currentEventTriggerState->commandCollectionInhibited)
+       return;
+
+   oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+   stashed = palloc(sizeof(StashedCommand));
+
+   stashed->type = SCT_AlterTable;
+   stashed->in_extension = creating_extension;
+
+   stashed->d.alterTable.classId = RelationRelationId;
+   stashed->d.alterTable.objectId = InvalidOid;
+   stashed->d.alterTable.subcmds = NIL;
+   stashed->parsetree = copyObject(parsetree);
+
+   currentEventTriggerState->curcmd = stashed;
+
+   MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * Remember the OID of the object being affected by a complex command.
+ *
+ * This is needed because in some cases we don't know the OID until later.
+ */
+void
+EventTriggerAlterTableRelid(Oid objectId)
+{
+   if (currentEventTriggerState->commandCollectionInhibited)
+       return;
+
+   currentEventTriggerState->curcmd->d.alterTable.objectId = objectId;
+}
+
+/*
+ * EventTriggerAlterTableStashSubcmd
+ *         Save data about a single part of a complex DDL command
+ *
+ * Several different commands go through this path, but apart from ALTER TABLE
+ * itself, they are all concerned with AlterTableCmd nodes that are generated
+ * internally, so that's all that this code needs to handle at the moment.
+ */
+void
+EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
+                                 ObjectAddress address)
+{
+   MemoryContext   oldcxt;
+   StashedATSubcmd *newsub;
+
+   if (currentEventTriggerState->commandCollectionInhibited)
+       return;
+
+   Assert(IsA(subcmd, AlterTableCmd));
+   Assert(OidIsValid(currentEventTriggerState->curcmd->d.alterTable.objectId));
+
+   /*
+    * If we receive a subcommand intended for a relation other than the one
+    * we've started the complex command for, ignore it.  This is chiefly
+    * concerned with inheritance situations: in such cases, alter table
+    * would dispatch multiple copies of the same command for various things,
+    * but we're only concerned with the one for the main table.
+    */
+   if (relid != currentEventTriggerState->curcmd->d.alterTable.objectId)
+       return;
+
+   oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+   newsub = palloc(sizeof(StashedATSubcmd));
+   newsub->address = address;
+   newsub->parsetree = copyObject(subcmd);
+
+   currentEventTriggerState->curcmd->d.alterTable.subcmds =
+       lappend(currentEventTriggerState->curcmd->d.alterTable.subcmds, newsub);
+
+   MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * EventTriggerAlterTableEnd
+ *         Finish up saving an ALTER TABLE command, and add it to stash
+ *
+ * FIXME this API isn't considering the possibility that a xact/subxact is
+ * aborted partway through.  Probably it's best to add an
+ * AtEOSubXact_EventTriggers() to fix this.
+ */
+void
+EventTriggerAlterTableEnd(void)
+{
+   if (currentEventTriggerState->commandCollectionInhibited)
+       return;
+
+   /* If no subcommands, don't stash anything */
+   if (list_length(currentEventTriggerState->curcmd->d.alterTable.subcmds) != 0)
+   {
+       currentEventTriggerState->stash =
+           lappend(currentEventTriggerState->stash,
+                   currentEventTriggerState->curcmd);
+   }
+   else
+       pfree(currentEventTriggerState->curcmd);
+
+   currentEventTriggerState->curcmd = NULL;
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1725,7 +1851,8 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
        MemSet(nulls, 0, sizeof(nulls));
 
-       if (cmd->type == SCT_Simple)
+       if (cmd->type == SCT_Simple ||
+           cmd->type == SCT_AlterTable)
        {
            const char *tag;
            char       *identity;
@@ -1734,6 +1861,10 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 
            if (cmd->type == SCT_Simple)
                addr = cmd->d.simple.address;
+           else if (cmd->type == SCT_AlterTable)
+               ObjectAddressSet(addr,
+                                cmd->d.alterTable.classId,
+                                cmd->d.alterTable.objectId);
 
            tag = CreateCommandTag(cmd->parsetree);
 
index 780075a5c9cc123189dcd1bfab1d757bf80e1516..6d5de1139584a8a2064866586ca3d11e3e5a80d8 100644 (file)
@@ -2781,6 +2781,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
 
    rel = relation_open(relid, lockmode);
 
+   EventTriggerAlterTableRelid(relid);
+
    ATController(NULL, rel, cmds, recurse, lockmode);
 }
 
@@ -3667,6 +3669,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
    /* supress compiler warning until we have some use for the address */
    (void) address;
 
+   /*
+    * Report the subcommand to interested event triggers.
+    */
+   EventTriggerAlterTableStashSubcmd((Node *) cmd, RelationGetRelid(rel),
+                                     address);
+
    /*
     * Bump the command counter to ensure the next subcommand in the sequence
     * can see the changes so far
@@ -9703,7 +9711,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
 
        cmds = lappend(cmds, cmd);
 
+       EventTriggerAlterTableStart((Node *) stmt);
+       /* OID is set by AlterTableInternal */
        AlterTableInternal(lfirst_oid(l), cmds, false);
+       EventTriggerAlterTableEnd();
    }
 
    return new_tablespaceoid;
index 8e0a460f0939ececf212d03dc7604303e85ce142..ac3175bebf812892a02a38c82acdaeab7968780c 100644 (file)
@@ -1168,7 +1168,7 @@ deparse_CreateTrigStmt(Oid objectId, Node *parsetree)
  */
 static ObjTree *
 deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
-                 ColumnDef *coldef)
+                 ColumnDef *coldef, bool is_alter)
 {
    ObjTree    *column;
    ObjTree    *tmp;
@@ -1236,6 +1236,9 @@ deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
         * we scan the list of constraints attached to this column to determine
         * whether we need to emit anything.
         * (Fortunately, NOT NULL constraints cannot be table constraints.)
+        *
+        * In the ALTER TABLE cases, we also add a NOT NULL if the colDef is
+        * marked is_not_null.
         */
        saw_notnull = false;
        foreach(cell, coldef->constraints)
@@ -1245,6 +1248,8 @@ deparse_ColumnDef(Relation relation, List *dpcontext, bool composite,
            if (constr->contype == CONSTR_NOTNULL)
                saw_notnull = true;
        }
+       if (is_alter && coldef->is_not_null)
+           saw_notnull = true;
 
        if (saw_notnull)
            append_string_object(column, "not_null", "NOT NULL");
@@ -1309,6 +1314,7 @@ deparse_ColumnDef_typed(Relation relation, List *dpcontext, ColumnDef *coldef)
    /*
     * Search for a NOT NULL declaration.  As in deparse_ColumnDef, we rely on
     * finding a constraint on the column rather than coldef->is_not_null.
+    * (This routine is never used for ALTER cases.)
     */
    saw_notnull = false;
    foreach(cell, coldef->constraints)
@@ -1362,7 +1368,8 @@ deparseTableElements(Relation relation, List *tableElements, List *dpcontext,
                        deparse_ColumnDef_typed(relation, dpcontext,
                                                (ColumnDef *) elt) :
                        deparse_ColumnDef(relation, dpcontext,
-                                         composite, (ColumnDef *) elt);
+                                         composite, (ColumnDef *) elt,
+                                         false);
                    if (tree != NULL)
                    {
                        ObjElem    *column;
@@ -1552,6 +1559,70 @@ deparse_DefElem(DefElem *elem, bool is_reset)
    return set;
 }
 
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_ColumnSetOptions(AlterTableCmd *subcmd)
+{
+   List       *sets = NIL;
+   ListCell   *cell;
+   ObjTree    *tmp;
+   bool        is_reset = subcmd->subtype == AT_ResetOptions;
+
+   if (is_reset)
+       tmp = new_objtree_VA("ALTER COLUMN %{column}I RESET (%{options:, }s)", 0);
+   else
+       tmp = new_objtree_VA("ALTER COLUMN %{column}I SET (%{options:, }s)", 0);
+
+   append_string_object(tmp, "column", subcmd->name);
+
+   foreach(cell, (List *) subcmd->def)
+   {
+       DefElem    *elem;
+       ObjTree    *set;
+
+       elem = (DefElem *) lfirst(cell);
+       set = deparse_DefElem(elem, is_reset);
+       sets = lappend(sets, new_object_object(set));
+   }
+
+   append_array_object(tmp, "options", sets);
+
+   return tmp;
+}
+
+/*
+ * ... ALTER COLUMN ... SET/RESET (...)
+ */
+static ObjTree *
+deparse_RelSetOptions(AlterTableCmd *subcmd)
+{
+   List       *sets = NIL;
+   ListCell   *cell;
+   ObjTree    *tmp;
+   bool        is_reset = subcmd->subtype == AT_ResetRelOptions;
+
+   if (is_reset)
+       tmp = new_objtree_VA("RESET (%{options:, }s)", 0);
+   else
+       tmp = new_objtree_VA("SET (%{options:, }s)", 0);
+
+   foreach(cell, (List *) subcmd->def)
+   {
+       DefElem    *elem;
+       ObjTree    *set;
+
+       elem = (DefElem *) lfirst(cell);
+       set = deparse_DefElem(elem, is_reset);
+       sets = lappend(sets, new_object_object(set));
+   }
+
+   append_array_object(tmp, "options", sets);
+
+   return tmp;
+}
+
 /*
  * deparse_CreateStmt
  *     Deparse a CreateStmt (CREATE TABLE)
@@ -3427,6 +3498,567 @@ deparse_AlterEnumStmt(Oid objectId, Node *parsetree)
    return alterEnum;
 }
 
+static ObjTree *
+deparse_AlterTableStmt(StashedCommand *cmd)
+{
+   ObjTree    *alterTableStmt;
+   ObjTree    *tmp;
+   ObjTree    *tmp2;
+   List       *dpcontext;
+   Relation    rel;
+   List       *subcmds = NIL;
+   ListCell   *cell;
+   char       *fmtstr;
+   const char *reltype;
+   bool        istype = false;
+
+   Assert(cmd->type == SCT_AlterTable);
+
+   rel = relation_open(cmd->d.alterTable.objectId, AccessShareLock);
+   dpcontext = deparse_context_for(RelationGetRelationName(rel),
+                                   cmd->d.alterTable.objectId);
+
+   switch (rel->rd_rel->relkind)
+   {
+       case RELKIND_RELATION:
+           reltype = "TABLE";
+           break;
+       case RELKIND_INDEX:
+           reltype = "INDEX";
+           break;
+       case RELKIND_VIEW:
+           reltype = "VIEW";
+           break;
+       case RELKIND_COMPOSITE_TYPE:
+           reltype = "TYPE";
+           istype = true;
+           break;
+       case RELKIND_FOREIGN_TABLE:
+           reltype = "FOREIGN TABLE";
+           break;
+
+       default:
+           elog(ERROR, "unexpected relkind %d", rel->rd_rel->relkind);
+           reltype = NULL;;
+   }
+
+   fmtstr = psprintf("ALTER %s %%{identity}D %%{subcmds:, }s", reltype);
+   alterTableStmt = new_objtree_VA(fmtstr, 0);
+
+   tmp = new_objtree_for_qualname(rel->rd_rel->relnamespace,
+                                  RelationGetRelationName(rel));
+   append_object_object(alterTableStmt, "identity", tmp);
+
+   foreach(cell, cmd->d.alterTable.subcmds)
+   {
+       StashedATSubcmd *substashed = (StashedATSubcmd *) lfirst(cell);
+       AlterTableCmd   *subcmd = (AlterTableCmd *) substashed->parsetree;
+       ObjTree    *tree;
+
+       Assert(IsA(subcmd, AlterTableCmd));
+
+       switch (subcmd->subtype)
+       {
+           case AT_AddColumn:
+           case AT_AddColumnRecurse:
+               /* XXX need to set the "recurse" bit somewhere? */
+               Assert(IsA(subcmd->def, ColumnDef));
+               tree = deparse_ColumnDef(rel, dpcontext, false,
+                                        (ColumnDef *) subcmd->def, true);
+               fmtstr = psprintf("ADD %s %%{definition}s",
+                                 istype ? "ATTRIBUTE" : "COLUMN");
+               tmp = new_objtree_VA(fmtstr, 2,
+                                    "type", ObjTypeString, "add column",
+                                    "definition", ObjTypeObject, tree);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_AddIndexConstraint:
+           case AT_ReAddIndex:
+           case AT_ReAddConstraint:
+           case AT_ProcessedConstraint:
+           case AT_ReplaceRelOptions:
+               /* Subtypes used for internal operations; nothing to do here */
+               break;
+
+           case AT_AddColumnToView:
+               /* CREATE OR REPLACE VIEW -- nothing to do here */
+               break;
+
+           case AT_ColumnDefault:
+               if (subcmd->def == NULL)
+               {
+                   tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP DEFAULT",
+                                        1, "type", ObjTypeString, "drop default");
+               }
+               else
+               {
+                   List       *dpcontext;
+                   HeapTuple   attrtup;
+                   AttrNumber  attno;
+
+                   tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DEFAULT %{definition}s",
+                                        1, "type", ObjTypeString, "set default");
+
+                   dpcontext = deparse_context_for(RelationGetRelationName(rel),
+                                                   RelationGetRelid(rel));
+                   attrtup = SearchSysCacheAttName(RelationGetRelid(rel), subcmd->name);
+                   attno = ((Form_pg_attribute) GETSTRUCT(attrtup))->attnum;
+                   append_string_object(tmp, "definition",
+                                        RelationGetColumnDefault(rel, attno, dpcontext));
+                   ReleaseSysCache(attrtup);
+               }
+               append_string_object(tmp, "column", subcmd->name);
+
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DropNotNull:
+               tmp = new_objtree_VA("ALTER COLUMN %{column}I DROP NOT NULL",
+                                    1, "type", ObjTypeString, "drop not null");
+               append_string_object(tmp, "column", subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_SetNotNull:
+               tmp = new_objtree_VA("ALTER COLUMN %{column}I SET NOT NULL",
+                                    1, "type", ObjTypeString, "set not null");
+               append_string_object(tmp, "column", subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_SetStatistics:
+               {
+                   Assert(IsA(subcmd->def, Integer));
+                   tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STATISTICS %{statistics}n",
+                                        3, "type", ObjTypeString, "set statistics",
+                                        "column", ObjTypeString, subcmd->name,
+                                        "statistics", ObjTypeInteger,
+                                        intVal((Value *) subcmd->def));
+                   subcmds = lappend(subcmds, new_object_object(tmp));
+               }
+               break;
+
+           case AT_SetOptions:
+           case AT_ResetOptions:
+               subcmds = lappend(subcmds, new_object_object(
+                                     deparse_ColumnSetOptions(subcmd)));
+               break;
+
+           case AT_SetStorage:
+               Assert(IsA(subcmd->def, String));
+               tmp = new_objtree_VA("ALTER COLUMN %{column}I SET STORAGE %{storage}s",
+                                    3, "type", ObjTypeString, "set storage",
+                                    "column", ObjTypeString, subcmd->name,
+                                    "storage", ObjTypeString,
+                                    strVal((Value *) subcmd->def));
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DropColumnRecurse:
+           case AT_DropColumn:
+               fmtstr = psprintf("DROP %s %%{column}I %%{cascade}s",
+                                 istype ? "ATTRIBUTE" : "COLUMN");
+               tmp = new_objtree_VA(fmtstr, 2,
+                                    "type", ObjTypeString, "drop column",
+                                    "column", ObjTypeString, subcmd->name);
+               tmp2 = new_objtree_VA("CASCADE", 1,
+                                     "present", ObjTypeBool, subcmd->behavior);
+               append_object_object(tmp, "cascade", tmp2);
+
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_AddIndex:
+               {
+                   Oid         idxOid = substashed->address.objectId;
+                   IndexStmt  *istmt;
+                   Relation    idx;
+                   const char *idxname;
+                   Oid         constrOid;
+
+                   Assert(IsA(subcmd->def, IndexStmt));
+                   istmt = (IndexStmt *) subcmd->def;
+
+                   if (!istmt->isconstraint)
+                       break;
+
+                   idx = relation_open(idxOid, AccessShareLock);
+                   idxname = RelationGetRelationName(idx);
+
+                   constrOid = get_relation_constraint_oid(
+                       cmd->d.alterTable.objectId, idxname, false);
+
+                   tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s",
+                                        3, "type", ObjTypeString, "add constraint",
+                                        "name", ObjTypeString, idxname,
+                                        "definition", ObjTypeString,
+                                        pg_get_constraintdef_string(constrOid, false));
+                   subcmds = lappend(subcmds, new_object_object(tmp));
+
+                   relation_close(idx, AccessShareLock);
+               }
+               break;
+
+           case AT_AddConstraint:
+           case AT_AddConstraintRecurse:
+               {
+                   /* XXX need to set the "recurse" bit somewhere? */
+                   Oid         constrOid = substashed->address.objectId;
+
+                   tmp = new_objtree_VA("ADD CONSTRAINT %{name}I %{definition}s",
+                                        3, "type", ObjTypeString, "add constraint",
+                                        "name", ObjTypeString, get_constraint_name(constrOid),
+                                        "definition", ObjTypeString,
+                                        pg_get_constraintdef_string(constrOid, false));
+                   subcmds = lappend(subcmds, new_object_object(tmp));
+               }
+               break;
+
+           case AT_AlterConstraint:
+               {
+                   Oid     constrOid = substashed->address.objectId;
+                   Constraint *c = (Constraint *) subcmd->def;
+
+                   /* if no constraint was altered, silently skip it */
+                   if (!OidIsValid(constrOid))
+                       break;
+
+                   Assert(IsA(c, Constraint));
+                   tmp = new_objtree_VA("ALTER CONSTRAINT %{name}I %{deferrable}s %{init_deferred}s",
+                                        2, "type", ObjTypeString, "alter constraint",
+                                        "name", ObjTypeString, get_constraint_name(constrOid));
+                   append_string_object(tmp, "deferrable", c->deferrable ?
+                                        "DEFERRABLE" : "NOT DEFERRABLE");
+                   append_string_object(tmp, "init_deferred", c->initdeferred ?
+                                        "INITIALLY DEFERRED" : "INITIALLY IMMEDIATE");
+                   subcmds = lappend(subcmds, new_object_object(tmp));
+               }
+               break;
+
+           case AT_ValidateConstraintRecurse:
+           case AT_ValidateConstraint:
+               tmp = new_objtree_VA("VALIDATE CONSTRAINT %{constraint}I", 2,
+                                    "type", ObjTypeString, "validate constraint",
+                                    "constraint", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DropConstraintRecurse:
+           case AT_DropConstraint:
+               tmp = new_objtree_VA("DROP CONSTRAINT %{constraint}I", 2,
+                                    "type", ObjTypeString, "drop constraint",
+                                    "constraint", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_AlterColumnType:
+               {
+                   TupleDesc tupdesc = RelationGetDescr(rel);
+                   Form_pg_attribute att;
+                   ColumnDef      *def;
+
+                   att = tupdesc->attrs[substashed->address.objectSubId - 1];
+                   def = (ColumnDef *) subcmd->def;
+                   Assert(IsA(def, ColumnDef));
+
+                   fmtstr = psprintf("ALTER %s %%{column}I SET DATA TYPE %%{datatype}T %%{collation}s %s",
+                                     istype ? "ATTRIBUTE" : "COLUMN",
+                                     istype ? "%{cascade}s" : "%{using}s");
+
+                   tmp = new_objtree_VA(fmtstr, 2,
+                                        "type", ObjTypeString, "alter column type",
+                                        "column", ObjTypeString, subcmd->name);
+                   /* add the TYPE clause */
+                   append_object_object(tmp, "datatype",
+                                        new_objtree_for_type(att->atttypid,
+                                                             att->atttypmod));
+
+                   /* add a COLLATE clause, if needed */
+                   tmp2 = new_objtree_VA("COLLATE %{name}D", 0);
+                   if (OidIsValid(att->attcollation))
+                   {
+                       ObjTree *collname;
+
+                       collname = new_objtree_for_qualname_id(CollationRelationId,
+                                                              att->attcollation);
+                       append_object_object(tmp2, "name", collname);
+                   }
+                   else
+                       append_bool_object(tmp2, "present", false);
+                   append_object_object(tmp, "collation", tmp2);
+
+                   /* if not a composite type, add the USING clause */
+                   if (!istype)
+                   {
+                       /*
+                        * If there's a USING clause, transformAlterTableStmt
+                        * ran it through transformExpr and stored the
+                        * resulting node in cooked_default, which we can use
+                        * here.
+                        */
+                       tmp2 = new_objtree_VA("USING %{expression}s", 0);
+                       if (def->raw_default)
+                       {
+                           Datum   deparsed;
+                           char   *defexpr;
+
+                           defexpr = nodeToString(def->cooked_default);
+                           deparsed = DirectFunctionCall2(pg_get_expr,
+                                                          CStringGetTextDatum(defexpr),
+                                                          RelationGetRelid(rel));
+                           append_string_object(tmp2, "expression",
+                                                TextDatumGetCString(deparsed));
+                       }
+                       else
+                           append_bool_object(tmp2, "present", false);
+                       append_object_object(tmp, "using", tmp2);
+                   }
+
+                   /* if it's a composite type, add the CASCADE clause */
+                   if (istype)
+                   {
+                       tmp2 = new_objtree_VA("CASCADE", 0);
+                       if (subcmd->behavior != DROP_CASCADE)
+                           append_bool_object(tmp2, "present", false);
+                       append_object_object(tmp, "cascade", tmp2);
+                   }
+
+                   subcmds = lappend(subcmds, new_object_object(tmp));
+               }
+               break;
+
+           case AT_AlterColumnGenericOptions:
+               elog(ERROR, "unimplemented deparse of ALTER TABLE ALTER COLUMN OPTIONS");
+               break;
+
+           case AT_ChangeOwner:
+               tmp = new_objtree_VA("OWNER TO %{owner}I",
+                                    2, "type", ObjTypeString, "change owner",
+                                    "owner",  ObjTypeString,
+                                    get_rolespec_name(subcmd->newowner));
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_ClusterOn:
+               tmp = new_objtree_VA("CLUSTER ON %{index}I", 2,
+                                    "type", ObjTypeString, "cluster on",
+                                    "index", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DropCluster:
+               tmp = new_objtree_VA("SET WITHOUT CLUSTER", 1,
+                                    "type", ObjTypeString, "set without cluster");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_SetLogged:
+               tmp = new_objtree_VA("SET LOGGED", 1,
+                                    "type", ObjTypeString, "set logged");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_SetUnLogged:
+               tmp = new_objtree_VA("SET UNLOGGED", 1,
+                                    "type", ObjTypeString, "set unlogged");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_AddOidsRecurse:
+           case AT_AddOids:
+               tmp = new_objtree_VA("SET WITH OIDS", 1,
+                                    "type", ObjTypeString, "set with oids");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DropOids:
+               tmp = new_objtree_VA("SET WITHOUT OIDS", 1,
+                                    "type", ObjTypeString, "set without oids");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_SetTableSpace:
+               tmp = new_objtree_VA("SET TABLESPACE %{tablespace}I", 2,
+                                    "type", ObjTypeString, "set tablespace",
+                                    "tablespace", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_SetRelOptions:
+           case AT_ResetRelOptions:
+               subcmds = lappend(subcmds, new_object_object(
+                                     deparse_RelSetOptions(subcmd)));
+               break;
+
+           case AT_EnableTrig:
+               tmp = new_objtree_VA("ENABLE TRIGGER %{trigger}I", 2,
+                                    "type", ObjTypeString, "enable trigger",
+                                    "trigger", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_EnableAlwaysTrig:
+               tmp = new_objtree_VA("ENABLE ALWAYS TRIGGER %{trigger}I", 2,
+                                    "type", ObjTypeString, "enable always trigger",
+                                    "trigger", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_EnableReplicaTrig:
+               tmp = new_objtree_VA("ENABLE REPLICA TRIGGER %{trigger}I", 2,
+                                    "type", ObjTypeString, "enable replica trigger",
+                                    "trigger", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DisableTrig:
+               tmp = new_objtree_VA("DISABLE TRIGGER %{trigger}I", 2,
+                                    "type", ObjTypeString, "disable trigger",
+                                    "trigger", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_EnableTrigAll:
+               tmp = new_objtree_VA("ENABLE TRIGGER ALL", 1,
+                                    "type", ObjTypeString, "enable trigger all");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DisableTrigAll:
+               tmp = new_objtree_VA("DISABLE TRIGGER ALL", 1,
+                                    "type", ObjTypeString, "disable trigger all");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_EnableTrigUser:
+               tmp = new_objtree_VA("ENABLE TRIGGER USER", 1,
+                                    "type", ObjTypeString, "enable trigger user");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DisableTrigUser:
+               tmp = new_objtree_VA("DISABLE TRIGGER USER", 1,
+                                    "type", ObjTypeString, "disable trigger user");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_EnableRule:
+               tmp = new_objtree_VA("ENABLE RULE %{rule}I", 2,
+                                    "type", ObjTypeString, "enable rule",
+                                    "rule", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_EnableAlwaysRule:
+               tmp = new_objtree_VA("ENABLE ALWAYS RULE %{rule}I", 2,
+                                    "type", ObjTypeString, "enable always rule",
+                                    "rule", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_EnableReplicaRule:
+               tmp = new_objtree_VA("ENABLE REPLICA RULE %{rule}I", 2,
+                                    "type", ObjTypeString, "enable replica rule",
+                                    "rule", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DisableRule:
+               tmp = new_objtree_VA("DISABLE RULE %{rule}I", 2,
+                                    "type", ObjTypeString, "disable rule",
+                                    "rule", ObjTypeString, subcmd->name);
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_AddInherit:
+               tmp = new_objtree_VA("INHERIT %{parent}D",
+                                    2, "type", ObjTypeString, "inherit",
+                                    "parent", ObjTypeObject,
+                                    new_objtree_for_qualname_id(RelationRelationId,
+                                                                substashed->address.objectId));
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DropInherit:
+               tmp = new_objtree_VA("NO INHERIT %{parent}D",
+                                    2, "type", ObjTypeString, "drop inherit",
+                                    "parent", ObjTypeObject,
+                                    new_objtree_for_qualname_id(RelationRelationId,
+                                                                substashed->address.objectId));
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_AddOf:
+               tmp = new_objtree_VA("OF %{type_of}T",
+                                    2, "type", ObjTypeString, "add of",
+                                    "type_of", ObjTypeObject,
+                                    new_objtree_for_type(substashed->address.objectId, -1));
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DropOf:
+               tmp = new_objtree_VA("NOT OF",
+                                    1, "type", ObjTypeString, "not of");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_ReplicaIdentity:
+               tmp = new_objtree_VA("REPLICA IDENTITY %{ident}s", 1,
+                                    "type", ObjTypeString, "replica identity");
+               switch (((ReplicaIdentityStmt *) subcmd->def)->identity_type)
+               {
+                   case REPLICA_IDENTITY_DEFAULT:
+                       append_string_object(tmp, "ident", "DEFAULT");
+                       break;
+                   case REPLICA_IDENTITY_FULL:
+                       append_string_object(tmp, "ident", "FULL");
+                       break;
+                   case REPLICA_IDENTITY_NOTHING:
+                       append_string_object(tmp, "ident", "NOTHING");
+                       break;
+                   case REPLICA_IDENTITY_INDEX:
+                       tmp2 = new_objtree_VA("USING INDEX %{index}I", 1,
+                                             "index", ObjTypeString,
+                                             ((ReplicaIdentityStmt *) subcmd->def)->name);
+                       append_object_object(tmp, "ident", tmp2);
+                       break;
+               }
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_EnableRowSecurity:
+               tmp = new_objtree_VA("ENABLE ROW LEVEL SECURITY", 1,
+                                    "type", ObjTypeString, "enable row security");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_DisableRowSecurity:
+               tmp = new_objtree_VA("DISABLE ROW LEVEL SECURITY", 1,
+                                    "type", ObjTypeString, "disable row security");
+               subcmds = lappend(subcmds, new_object_object(tmp));
+               break;
+
+           case AT_GenericOptions:
+               elog(ERROR, "unimplemented deparse of ALTER TABLE OPTIONS (...)");
+               break;
+
+           default:
+               elog(WARNING, "unsupported alter table subtype %d",
+                    subcmd->subtype);
+               break;
+       }
+   }
+
+   heap_close(rel, AccessShareLock);
+
+   if (list_length(subcmds) == 0)
+       return NULL;
+
+   append_array_object(alterTableStmt, "subcmds", subcmds);
+   return alterTableStmt;
+}
+
 /*
  * Handle deparsing of simple commands.
  *
@@ -3722,6 +4354,9 @@ deparse_utility_command(StashedCommand *cmd)
        case SCT_Simple:
            tree = deparse_simple_command(cmd);
            break;
+       case SCT_AlterTable:
+           tree = deparse_AlterTableStmt(cmd);
+           break;
        default:
            elog(ERROR, "unexpected deparse node type %d", cmd->type);
    }
index fde1ddbdb878da397486747ddb9c76491d0cc504..0bd239c6aa7f5ff31c88b8391f143a0dce80f406 100644 (file)
@@ -1030,6 +1030,10 @@ ProcessUtilitySlow(Node *parsetree,
                        stmts = transformAlterTableStmt(relid, atstmt,
                                                        queryString);
 
+                       /* ... ensure we have an event trigger context ... */
+                       EventTriggerAlterTableStart(parsetree);
+                       EventTriggerAlterTableRelid(relid);
+
                        /* ... and do it */
                        foreach(l, stmts)
                        {
@@ -1043,19 +1047,32 @@ ProcessUtilitySlow(Node *parsetree,
                            }
                            else
                            {
-                               /* Recurse for anything 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.
+                                */
+                               EventTriggerAlterTableEnd();
                                ProcessUtility(stmt,
                                               queryString,
                                               PROCESS_UTILITY_SUBCOMMAND,
                                               params,
                                               None_Receiver,
                                               NULL);
+                               EventTriggerAlterTableStart(parsetree);
+                               EventTriggerAlterTableRelid(relid);
                            }
 
                            /* Need CCI between commands */
                            if (lnext(l) != NULL)
                                CommandCounterIncrement();
                        }
+
+                       /* done */
+                       EventTriggerAlterTableEnd();
                    }
                    else
                        ereport(NOTICE,
@@ -1213,6 +1230,7 @@ ProcessUtilitySlow(Node *parsetree,
                    stmt = transformIndexStmt(relid, stmt, queryString);
 
                    /* ... and do it */
+                   EventTriggerAlterTableStart(parsetree);
                    address =
                        DefineIndex(relid,  /* OID of heap relation */
                                    stmt,
@@ -1229,6 +1247,7 @@ ProcessUtilitySlow(Node *parsetree,
                    EventTriggerStashCommand(address, secondaryObject,
                                             parsetree);
                    commandStashed = true;
+                   EventTriggerAlterTableEnd();
                }
                break;
 
@@ -1303,10 +1322,12 @@ ProcessUtilitySlow(Node *parsetree,
                break;
 
            case T_ViewStmt:    /* CREATE VIEW */
+               EventTriggerAlterTableStart(parsetree);
                address = DefineView((ViewStmt *) parsetree, queryString);
                EventTriggerStashCommand(address, secondaryObject, parsetree);
                /* stashed internally */
                commandStashed = true;
+               EventTriggerAlterTableEnd();
                break;
 
            case T_CreateFunctionStmt:  /* CREATE FUNCTION */
index a38d5aaf24f5e6c447e9bba72488b8e83c072af2..7a449cc521906d5a76f667366a149da43d2d7f5a 100644 (file)
@@ -66,4 +66,10 @@ extern void EventTriggerUndoInhibitCommandCollection(void);
 extern void EventTriggerStashCommand(ObjectAddress address,
                         ObjectAddress secondaryObject, Node *parsetree);
 
+extern void EventTriggerAlterTableStart(Node *parsetree);
+extern void EventTriggerAlterTableRelid(Oid objectId);
+extern void EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
+                                 ObjectAddress address);
+extern void EventTriggerAlterTableEnd(void);
+
 #endif   /* EVENT_TRIGGER_H */
index 7f355cbefb76253a6003ad54d94784fd8cb5f861..3b682901bdb611dfb41864a2c0beef2a8d3c7e9f 100644 (file)
@@ -26,6 +26,7 @@
 typedef enum StashedCommandType
 {
    SCT_Simple,
+   SCT_AlterTable
 } StashedCommandType;
 
 /*
@@ -33,8 +34,7 @@ typedef enum StashedCommandType
  */
 typedef struct StashedATSubcmd
 {
-   AttrNumber      attnum; /* affected column number */
-   Oid             oid;    /* affected constraint, default value or index */
+   ObjectAddress   address; /* affected column, constraint, index, ... */
    Node           *parsetree;
 } StashedATSubcmd;
 
@@ -43,6 +43,7 @@ typedef struct StashedCommand
    StashedCommandType type;
    bool        in_extension;
    Node       *parsetree;
+   slist_node  link;
 
    union
    {
@@ -52,6 +53,14 @@ typedef struct StashedCommand
            ObjectAddress address;
            ObjectAddress secondaryObject;
        } simple;
+
+       /* ALTER TABLE, and internal uses thereof */
+       struct
+       {
+           Oid     objectId;
+           Oid     classId;
+           List   *subcmds;
+       } alterTable;
    } d;
 } StashedCommand;