From d56122ce643cc28e4970e9800d48233a7e7aaf9b Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 25 Apr 2014 16:32:20 -0300 Subject: [PATCH] deparse: Support ALTER TABLE --- src/backend/commands/event_trigger.c | 118 +++++++- src/backend/commands/tablecmds.c | 9 + src/backend/tcop/deparse_utility.c | 386 +++++++++++++++++++++++++++ src/backend/tcop/utility.c | 23 +- src/include/commands/event_trigger.h | 5 + src/include/tcop/deparse_utility.h | 8 + 6 files changed, 547 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 48ba3ec091..b66301ffe7 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -52,6 +52,7 @@ typedef struct EventTriggerQueryState slist_head SQLDropList; bool in_sql_drop; MemoryContext cxt; + StashedCommand *curcmd; List *stash; /* list of StashedCommand; see deparse_utility.h */ struct EventTriggerQueryState *previous; } EventTriggerQueryState; @@ -1073,6 +1074,7 @@ EventTriggerBeginCompleteQuery(void) state->cxt = cxt; slist_init(&(state->SQLDropList)); state->in_sql_drop = false; + state->curcmd = NULL; state->stash = NIL; state->previous = currentEventTriggerState; @@ -1360,6 +1362,113 @@ EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype, MemoryContextSwitchTo(oldcxt); } +/* + * EventTriggerStartRecordingSubcmds + * Prepare to receive data on a complex DDL 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. + * + * FIXME -- 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? + */ +void +EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype) +{ + MemoryContext oldcxt; + StashedCommand *stashed; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + stashed = palloc(sizeof(StashedCommand)); + + stashed->type = SCT_AlterTable; + stashed->in_extension = creating_extension; + + stashed->d.alterTable.objectId = InvalidOid; + stashed->d.alterTable.objtype = objtype; + stashed->d.alterTable.subcmds = NIL; + /* XXX is it necessary to have the whole parsetree? probably not ... */ + stashed->parsetree = copyObject(parsetree); + + currentEventTriggerState->curcmd = stashed; + + MemoryContextSwitchTo(oldcxt); +} + +void +EventTriggerComplexCmdSetOid(Oid objectId) +{ + currentEventTriggerState->curcmd->d.alterTable.objectId = objectId; +} + +/* + * EventTriggerRecordSubcmd + * Save data about a single part of a complex DDL command + * + * Right now we only support ALTER TABLE; there are no other DDL commands that + * require this. (ALTER TYPE can also generate multiple subcommands, but it's + * actually parsed as ALTER TABLE, so there is no difference at this level.) + */ +void +EventTriggerRecordSubcmd(Node *subcmd, Oid relid, AttrNumber attnum, + Oid newoid) +{ + MemoryContext oldcxt; + StashedATSubcmd *newsub; + + 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->attnum = attnum; + newsub->oid = newoid; + newsub->parsetree = copyObject(subcmd); + + currentEventTriggerState->curcmd->d.alterTable.subcmds = + lappend(currentEventTriggerState->curcmd->d.alterTable.subcmds, newsub); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerEndRecordingSubcmds + * Finish up saving a complex DDL command + * + * 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 +EventTriggerComplexCmdEnd(void) +{ + /* 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) { @@ -1439,7 +1548,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) { Oid classId; Oid objId; @@ -1455,6 +1565,12 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS) objId = cmd->d.simple.objectId; objSubId = cmd->d.simple.objectSubId; } + else if (cmd->type == SCT_AlterTable) + { + classId = get_objtype_catalog_oid(cmd->d.alterTable.objtype); + objId = cmd->d.alterTable.objectId; + objSubId = 0; + } tag = CreateCommandTag(cmd->parsetree); addr.classId = classId; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 4855c24a4e..b7eea2e9d6 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2757,6 +2757,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse) rel = relation_open(relid, lockmode); + EventTriggerComplexCmdSetOid(relid); + ATController(rel, cmds, recurse, lockmode); } @@ -3622,6 +3624,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, break; } + EventTriggerRecordSubcmd((Node *) cmd, RelationGetRelid(rel), + colno, newoid); + /* * Bump the command counter to ensure the next subcommand in the sequence * can see the changes so far @@ -5726,6 +5731,8 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName, * There is no such command in the grammar, but parse_utilcmd.c converts * UNIQUE and PRIMARY KEY constraints into AT_AddIndex subcommands. This lets * us schedule creation of the index at the appropriate time during ALTER. + * + * Return value is the OID of the new index. */ static Oid ATExecAddIndex(AlteredTableInfo *tab, Relation rel, @@ -9565,7 +9572,9 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt) cmds = lappend(cmds, cmd); + EventTriggerComplexCmdStart((Node *) stmt, OBJECT_TABLE); AlterTableInternal(lfirst_oid(l), cmds, false); + EventTriggerComplexCmdEnd(); } return new_tablespaceoid; diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c index 07820a0837..013ab51d70 100644 --- a/src/backend/tcop/deparse_utility.c +++ b/src/backend/tcop/deparse_utility.c @@ -2719,6 +2719,389 @@ 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; + + Assert(cmd->type == SCT_AlterTable); + + rel = heap_open(cmd->d.alterTable.objectId, AccessShareLock); + dpcontext = deparse_context_for(RelationGetRelationName(rel), + cmd->d.alterTable.objectId); + + alterTableStmt = + new_objtree_VA("ALTER TABLE %{identity}D %{subcmds:, }s", 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); + tmp = new_objtree_VA("ADD COLUMN %{definition}s", + 2, "type", ObjTypeString, "add column", + "definition", ObjTypeObject, tree); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_DropColumnRecurse: + case AT_ValidateConstraintRecurse: + case AT_DropConstraintRecurse: + case AT_AddOidsRecurse: + 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: + /* not yet */ + break; + + case AT_SetOptions: + /* not yet */ + break; + + case AT_ResetOptions: + /* not yet */ + 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_DropColumn: + tmp = new_objtree_VA("DROP COLUMN %{column}I", + 2, "type", ObjTypeString, "drop column", + "column", ObjTypeString, subcmd->name); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AddIndex: + { + Oid idxOid = substashed->oid; + 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->oid; + + 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: + break; + + 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_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: + tmp = new_objtree_VA("ALTER COLUMN %{column}I SET DATA TYPE %{datatype}T collate_clause using_clause", + 2, "type", ObjTypeString, "alter column type", + "column", ObjTypeString, subcmd->name); + /* FIXME figure out correct typid/typmod , collate clause, using_clause */ + append_object_object(tmp, "datatype", + new_objtree_for_type(INT4OID, -1)); + subcmds = lappend(subcmds, new_object_object(tmp)); + break; + + case AT_AlterColumnGenericOptions: + break; + + case AT_ChangeOwner: + tmp = new_objtree_VA("OWNER TO %{owner}I", + 2, "type", ObjTypeString, "change owner", + "owner", ObjTypeString, subcmd->name); + 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_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: + break; + + case AT_ResetRelOptions: + break; + + /* + * FIXME --- should we unify representation of all these + * ENABLE/DISABLE TRIGGER commands?? + */ + 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: + break; + + case AT_EnableAlwaysRule: + break; + + case AT_EnableReplicaRule: + break; + + case AT_DisableRule: + break; + + case AT_AddInherit: + /* + * XXX this case is interesting: we cannot rely on parse node + * because parent name might be unqualified; but there's no way + * to extract it from catalog either, since we don't know which + * of the parents is the new one. + */ + break; + + case AT_DropInherit: + /* XXX ditto ... */ + break; + + case AT_AddOf: + break; + + case AT_DropOf: + 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_GenericOptions: + 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. * @@ -3005,6 +3388,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); } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 6732715a14..38ee200047 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1007,6 +1007,10 @@ ProcessUtilitySlow(Node *parsetree, stmts = transformAlterTableStmt(relid, atstmt, queryString); + /* ... ensure we have an event trigger context ... */ + EventTriggerComplexCmdStart(parsetree, atstmt->relkind); + EventTriggerComplexCmdSetOid(relid); + /* ... and do it */ foreach(l, stmts) { @@ -1020,19 +1024,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. + */ + EventTriggerComplexCmdEnd(); ProcessUtility(stmt, queryString, PROCESS_UTILITY_SUBCOMMAND, params, None_Receiver, NULL); + EventTriggerComplexCmdStart(parsetree, atstmt->relkind); + EventTriggerComplexCmdSetOid(relid); } /* Need CCI between commands */ if (lnext(l) != NULL) CommandCounterIncrement(); } + + /* done */ + EventTriggerComplexCmdEnd(); } else ereport(NOTICE, @@ -1189,6 +1206,7 @@ ProcessUtilitySlow(Node *parsetree, stmt = transformIndexStmt(relid, stmt, queryString); /* ... and do it */ + EventTriggerComplexCmdStart(parsetree, OBJECT_INDEX); /* relkind? */ objectId = DefineIndex(relid, /* OID of heap relation */ stmt, @@ -1199,6 +1217,7 @@ ProcessUtilitySlow(Node *parsetree, false); /* quiet */ EventTriggerStashCommand(objectId, 0, OBJECT_INDEX, parsetree); + EventTriggerComplexCmdEnd(); } break; @@ -1287,8 +1306,10 @@ ProcessUtilitySlow(Node *parsetree, break; case T_ViewStmt: /* CREATE VIEW */ + EventTriggerComplexCmdStart(parsetree, OBJECT_VIEW); /* XXX relkind? */ objectId = DefineView((ViewStmt *) parsetree, queryString); EventTriggerStashCommand(objectId, 0, OBJECT_VIEW, parsetree); + EventTriggerComplexCmdEnd(); break; case T_CreateFunctionStmt: /* CREATE FUNCTION */ diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index da7f449ae1..2a23edd599 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -55,5 +55,10 @@ extern void EventTriggerSQLDropAddObject(ObjectAddress *object); extern void EventTriggerStashCommand(Oid objectId, uint32 objectSubId, ObjectType objtype, Node *parsetree); +extern void EventTriggerComplexCmdStart(Node *parsetree, ObjectType objtype); +extern void EventTriggerComplexCmdSetOid(Oid objectId); +extern void EventTriggerRecordSubcmd(Node *subcmd, Oid relid, + AttrNumber attnum, Oid newoid); +extern void EventTriggerComplexCmdEnd(void); #endif /* EVENT_TRIGGER_H */ diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h index 1278df9b32..910c716553 100644 --- a/src/include/tcop/deparse_utility.h +++ b/src/include/tcop/deparse_utility.h @@ -26,6 +26,7 @@ typedef enum StashedCommandType { SCT_Simple, + SCT_AlterTable } StashedCommandType; /* @@ -52,6 +53,13 @@ typedef struct StashedCommand uint32 objectSubId; ObjectType objtype; } simple; + + struct AlterTableCommand + { + Oid objectId; + ObjectType objtype; + List *subcmds; + } alterTable; } d; } StashedCommand; -- 2.39.5