Re: Rearranging ALTER TABLE to avoid multi-operations bugs - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Rearranging ALTER TABLE to avoid multi-operations bugs |
Date | |
Msg-id | 19348.1576267346@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Rearranging ALTER TABLE to avoid multi-operations bugs (Tom Lane <tgl@sss.pgh.pa.us>) |
Responses |
Re: Rearranging ALTER TABLE to avoid multi-operations bugs
|
List | pgsql-hackers |
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)
pgsql-hackers by date: