diff options
Diffstat (limited to 'src/backend/commands/tablecmds.c')
-rw-r--r-- | src/backend/commands/tablecmds.c | 494 |
1 files changed, 387 insertions, 107 deletions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2ec3fc50145..30b72b62971 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -85,6 +85,7 @@ #include "storage/lock.h" #include "storage/predicate.h" #include "storage/smgr.h" +#include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -142,11 +143,13 @@ static List *on_commits = NIL; #define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ /* We could support a RENAME COLUMN pass here, but not currently used */ #define AT_PASS_ADD_COL 4 /* ADD COLUMN */ -#define AT_PASS_COL_ATTRS 5 /* set other column attributes */ -#define AT_PASS_ADD_INDEX 6 /* ADD indexes */ -#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */ -#define AT_PASS_MISC 8 /* other stuff */ -#define AT_NUM_PASSES 9 +#define AT_PASS_ADD_CONSTR 5 /* ADD constraints (initial examination) */ +#define AT_PASS_COL_ATTRS 6 /* set column attributes, eg NOT NULL */ +#define AT_PASS_ADD_INDEXCONSTR 7 /* ADD index-based constraints */ +#define AT_PASS_ADD_INDEX 8 /* ADD indexes */ +#define AT_PASS_ADD_OTHERCONSTR 9 /* ADD other constraints, defaults */ +#define AT_PASS_MISC 10 /* other stuff */ +#define AT_NUM_PASSES 11 typedef struct AlteredTableInfo { @@ -159,6 +162,7 @@ typedef struct AlteredTableInfo /* Information saved by Phases 1/2 for Phase 3: */ List *constraints; /* List of NewConstraint */ List *newvals; /* List of NewColumnValue */ + List *afterStmts; /* List of utility command parsetrees */ bool verify_new_notnull; /* T if we should recheck NOT NULL */ int rewrite; /* Reason for forced rewrite, if any */ Oid newTableSpace; /* new tablespace; 0 means no change */ @@ -340,31 +344,45 @@ static void validateForeignKeyConstraint(char *conname, Relation rel, Relation pkrel, Oid pkindOid, Oid constraintOid); static void ATController(AlterTableStmt *parsetree, - Relation rel, List *cmds, bool recurse, LOCKMODE lockmode); + Relation rel, List *cmds, bool recurse, LOCKMODE lockmode, + AlterTableUtilityContext *context); static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, - bool recurse, bool recursing, LOCKMODE lockmode); -static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode); + bool recurse, bool recursing, LOCKMODE lockmode, + AlterTableUtilityContext *context); +static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode, + AlterTableUtilityContext *context); static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, - AlterTableCmd *cmd, LOCKMODE lockmode); + AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass, + AlterTableUtilityContext *context); +static AlterTableCmd *ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, + Relation rel, AlterTableCmd *cmd, + bool recurse, LOCKMODE lockmode, + int cur_pass, + AlterTableUtilityContext *context); static void ATRewriteTables(AlterTableStmt *parsetree, - List **wqueue, LOCKMODE lockmode); + List **wqueue, LOCKMODE lockmode, + AlterTableUtilityContext *context); static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode); static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel); static void ATSimplePermissions(Relation rel, int allowed_targets); static void ATWrongRelkindError(Relation rel, int allowed_targets); static void ATSimpleRecursion(List **wqueue, Relation rel, - AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode); + AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode, + AlterTableUtilityContext *context); static void ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode); static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, - LOCKMODE lockmode); + LOCKMODE lockmode, + AlterTableUtilityContext *context); static List *find_typed_table_dependencies(Oid typeOid, const char *typeName, DropBehavior behavior); static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, - bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode); + bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode, + AlterTableUtilityContext *context); static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, - Relation rel, ColumnDef *colDef, + Relation rel, AlterTableCmd **cmd, bool recurse, bool recursing, - bool if_not_exists, LOCKMODE lockmode); + LOCKMODE lockmode, int cur_pass, + AlterTableUtilityContext *context); static bool check_for_column_name_collision(Relation rel, const char *colname, bool if_not_exists); static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); @@ -373,7 +391,8 @@ static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing); static ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode); static void ATPrepSetNotNull(List **wqueue, Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, - LOCKMODE lockmode); + LOCKMODE lockmode, + AlterTableUtilityContext *context); static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, const char *colName, LOCKMODE lockmode); static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel, @@ -397,7 +416,8 @@ static ObjectAddress ATExecSetOptions(Relation rel, const char *colName, static ObjectAddress ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE lockmode); static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, - AlterTableCmd *cmd, LOCKMODE lockmode); + AlterTableCmd *cmd, LOCKMODE lockmode, + AlterTableUtilityContext *context); static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName, DropBehavior behavior, bool recurse, bool recursing, @@ -454,7 +474,8 @@ static void ATExecDropConstraint(Relation rel, const char *constrName, static void ATPrepAlterColumnType(List **wqueue, AlteredTableInfo *tab, Relation rel, bool recurse, bool recursing, - AlterTableCmd *cmd, LOCKMODE lockmode); + AlterTableCmd *cmd, LOCKMODE lockmode, + AlterTableUtilityContext *context); static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno); static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, AlterTableCmd *cmd, LOCKMODE lockmode); @@ -3463,7 +3484,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode) * * ALTER TABLE is performed in three phases: * 1. Examine subcommands and perform pre-transformation checking. - * 2. Update system catalogs. + * 2. Validate and transform subcommands, and update system catalogs. * 3. Scan table(s) to check new constraints, and optionally recopy * the data into new table(s). * Phase 3 is not performed unless one or more of the subcommands requires @@ -3474,9 +3495,10 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode) * ATPrepCmd performs phase 1. A "work queue" entry is created for * each table to be affected (there may be multiple affected tables if the * commands traverse a table inheritance hierarchy). Also we do preliminary - * validation of the subcommands, including parse transformation of those - * expressions that need to be evaluated with respect to the old table - * schema. + * validation of the subcommands. Because earlier subcommands may change + * the catalog state seen by later commands, there are limits to what can + * be done in this phase. Generally, this phase acquires table locks, + * checks permissions and relkind, and recurses to find child tables. * * ATRewriteCatalogs performs phase 2 for each affected table. (Note that * phases 2 and 3 normally do no explicit recursion, since phase 1 already @@ -3498,18 +3520,23 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode) * lock level we want as we recurse might well be higher than required for * that specific subcommand. So we pass down the overall lock requirement, * rather than reassess it at lower levels. + * + * The caller also provides a "context" which is to be passed back to + * utility.c when we need to execute a subcommand such as CREATE INDEX. + * Some of the fields therein, such as the relid, are used here as well. */ void -AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt) +AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, + AlterTableUtilityContext *context) { Relation rel; /* Caller is required to provide an adequate lock. */ - rel = relation_open(relid, NoLock); + rel = relation_open(context->relid, NoLock); CheckTableNotInUse(rel, "ALTER TABLE"); - ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode); + ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context); } /* @@ -3522,6 +3549,10 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt) * is unsafe to use this entry point for alterations that could break * existing query plans. On the assumption it's not used for such, we * don't have to reject pending AFTER triggers, either. + * + * Also, since we don't have an AlterTableUtilityContext, this cannot be + * used for any subcommand types that require parse transformation or + * could generate subcommands that have to be passed to ProcessUtility. */ void AlterTableInternal(Oid relid, List *cmds, bool recurse) @@ -3533,7 +3564,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse) EventTriggerAlterTableRelid(relid); - ATController(NULL, rel, cmds, recurse, lockmode); + ATController(NULL, rel, cmds, recurse, lockmode, NULL); } /* @@ -3679,7 +3710,6 @@ AlterTableGetLockLevel(List *cmds) break; case AT_AddConstraint: - case AT_ProcessedConstraint: /* becomes AT_AddConstraint */ case AT_AddConstraintRecurse: /* becomes AT_AddConstraint */ case AT_ReAddConstraint: /* becomes AT_AddConstraint */ case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */ @@ -3832,7 +3862,8 @@ AlterTableGetLockLevel(List *cmds) */ static void ATController(AlterTableStmt *parsetree, - Relation rel, List *cmds, bool recurse, LOCKMODE lockmode) + Relation rel, List *cmds, bool recurse, LOCKMODE lockmode, + AlterTableUtilityContext *context) { List *wqueue = NIL; ListCell *lcmd; @@ -3842,17 +3873,17 @@ ATController(AlterTableStmt *parsetree, { AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); - ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode); + ATPrepCmd(&wqueue, rel, cmd, recurse, false, lockmode, context); } /* Close the relation, but keep lock until commit */ relation_close(rel, NoLock); /* Phase 2: update system catalogs */ - ATRewriteCatalogs(&wqueue, lockmode); + ATRewriteCatalogs(&wqueue, lockmode, context); - /* Phase 3: scan/rewrite tables as needed */ - ATRewriteTables(parsetree, &wqueue, lockmode); + /* Phase 3: scan/rewrite tables as needed, and run afterStmts */ + ATRewriteTables(parsetree, &wqueue, lockmode, context); } /* @@ -3866,7 +3897,8 @@ ATController(AlterTableStmt *parsetree, */ static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, - bool recurse, bool recursing, LOCKMODE lockmode) + bool recurse, bool recursing, LOCKMODE lockmode, + AlterTableUtilityContext *context) { AlteredTableInfo *tab; int pass = AT_PASS_UNSET; @@ -3878,13 +3910,17 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, * Copy the original subcommand for each table. This avoids conflicts * when different child tables need to make different parse * transformations (for example, the same column may have different column - * numbers in different children). + * numbers in different children). It also ensures that we don't corrupt + * the original parse tree, in case it is saved in plancache. */ cmd = copyObject(cmd); /* - * Do permissions checking, recursion to child tables if needed, and any - * additional phase-1 processing needed. + * Do permissions and relkind checking, recursion to child tables if + * needed, and any additional phase-1 processing needed. (But beware of + * adding any processing that looks at table details that another + * subcommand could change. In some cases we reject multiple subcommands + * that could try to change the same state in contrary ways.) */ switch (cmd->subtype) { @@ -3892,14 +3928,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ATSimplePermissions(rel, ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd, - lockmode); + lockmode, context); /* Recursion occurs during execution phase */ pass = AT_PASS_ADD_COL; break; case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ ATSimplePermissions(rel, ATT_VIEW); ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd, - lockmode); + lockmode, context); /* Recursion occurs during execution phase */ pass = AT_PASS_ADD_COL; break; @@ -3912,19 +3948,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, * rules. */ ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); /* No command-specific prep needed */ - pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP; + pass = cmd->def ? AT_PASS_ADD_OTHERCONSTR : AT_PASS_DROP; break; case AT_AddIdentity: ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); /* This command never recurses */ - pass = AT_PASS_ADD_CONSTR; + pass = AT_PASS_ADD_OTHERCONSTR; break; case AT_SetIdentity: ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); /* This command never recurses */ - pass = AT_PASS_COL_ATTRS; + /* This should run after AddIdentity, so do it in MISC pass */ + pass = AT_PASS_MISC; break; case AT_DropIdentity: ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); @@ -3934,30 +3971,31 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ATPrepDropNotNull(rel, recurse, recursing); - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); pass = AT_PASS_DROP; break; case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); /* Need command-specific recursion decision */ - ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing, lockmode); + ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing, + lockmode, context); pass = AT_PASS_COL_ATTRS; break; case AT_CheckNotNull: /* check column is already marked NOT NULL */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); /* No command-specific prep needed */ pass = AT_PASS_COL_ATTRS; break; - case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */ + case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); ATPrepDropExpression(rel, cmd, recursing); pass = AT_PASS_DROP; break; case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE); - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); /* No command-specific prep needed */ pass = AT_PASS_MISC; break; @@ -3969,14 +4007,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE); - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); /* No command-specific prep needed */ pass = AT_PASS_MISC; break; case AT_DropColumn: /* DROP COLUMN */ ATSimplePermissions(rel, ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); - ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, lockmode); + ATPrepDropColumn(wqueue, rel, recurse, recursing, cmd, + lockmode, context); /* Recursion occurs during execution phase */ pass = AT_PASS_DROP; break; @@ -3998,7 +4037,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ATSimplePermissions(rel, ATT_TABLE); /* This command never recurses */ /* No command-specific prep needed */ - pass = AT_PASS_ADD_CONSTR; + pass = AT_PASS_ADD_INDEXCONSTR; break; case AT_DropConstraint: /* DROP CONSTRAINT */ ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); @@ -4012,8 +4051,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_AlterColumnType: /* ALTER COLUMN TYPE */ ATSimplePermissions(rel, ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); + /* See comments for ATPrepAlterColumnType */ + cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode, + AT_PASS_UNSET, context); + Assert(cmd != NULL); /* Performs own recursion */ - ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode); + ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, + lockmode, context); pass = AT_PASS_ALTER_TYPE; break; case AT_AlterColumnGenericOptions: @@ -4036,6 +4080,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_SetLogged: /* SET LOGGED */ ATSimplePermissions(rel, ATT_TABLE); + if (tab->chgPersistence) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot change persistence setting twice"))); tab->chgPersistence = ATPrepChangePersistence(rel, true); /* force rewrite if necessary; see comment in ATRewriteTables */ if (tab->chgPersistence) @@ -4047,6 +4095,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_SetUnLogged: /* SET UNLOGGED */ ATSimplePermissions(rel, ATT_TABLE); + if (tab->chgPersistence) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot change persistence setting twice"))); tab->chgPersistence = ATPrepChangePersistence(rel, false); /* force rewrite if necessary; see comment in ATRewriteTables */ if (tab->chgPersistence) @@ -4166,7 +4218,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, * conflicts). */ static void -ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) +ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode, + AlterTableUtilityContext *context) { int pass; ListCell *ltab; @@ -4199,7 +4252,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) foreach(lcmd, subcmds) ATExecCmd(wqueue, tab, rel, castNode(AlterTableCmd, lfirst(lcmd)), - lockmode); + lockmode, pass, context); /* * After the ALTER TYPE pass, do cleanup work (this is not done in @@ -4236,7 +4289,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) */ static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, - AlterTableCmd *cmd, LOCKMODE lockmode) + AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass, + AlterTableUtilityContext *context) { ObjectAddress address = InvalidObjectAddress; @@ -4244,22 +4298,28 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, { case AT_AddColumn: /* ADD COLUMN */ case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ - address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, + address = ATExecAddColumn(wqueue, tab, rel, &cmd, false, false, - cmd->missing_ok, lockmode); + lockmode, cur_pass, context); break; case AT_AddColumnRecurse: - address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, + address = ATExecAddColumn(wqueue, tab, rel, &cmd, true, false, - cmd->missing_ok, lockmode); + lockmode, cur_pass, context); break; case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode); break; case AT_AddIdentity: + cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, + cur_pass, context); + Assert(cmd != NULL); address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode); break; case AT_SetIdentity: + cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, + cur_pass, context); + Assert(cmd != NULL); address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode); break; case AT_DropIdentity: @@ -4310,14 +4370,24 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, lockmode); break; case AT_AddConstraint: /* ADD CONSTRAINT */ - address = - ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, - false, false, lockmode); + cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, + cur_pass, context); + /* Might not have gotten AddConstraint back from parse transform */ + if (cmd != NULL) + address = + ATExecAddConstraint(wqueue, tab, rel, + (Constraint *) cmd->def, + false, false, lockmode); break; case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion */ - address = - ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, - true, false, lockmode); + cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, true, lockmode, + cur_pass, context); + /* Might not have gotten AddConstraint back from parse transform */ + if (cmd != NULL) + address = + ATExecAddConstraint(wqueue, tab, rel, + (Constraint *) cmd->def, + true, false, lockmode); break; case AT_ReAddConstraint: /* Re-add pre-existing check constraint */ address = @@ -4361,6 +4431,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, cmd->missing_ok, lockmode); break; case AT_AlterColumnType: /* ALTER COLUMN TYPE */ + /* parse transformation was done earlier */ address = ATExecAlterColumnType(tab, rel, cmd, lockmode); break; case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */ @@ -4483,6 +4554,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, ATExecGenericOptions(rel, (List *) cmd->def); break; case AT_AttachPartition: + cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, + cur_pass, context); + Assert(cmd != NULL); if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def); else @@ -4490,6 +4564,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, ((PartitionCmd *) cmd->def)->name); break; case AT_DetachPartition: + cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, + cur_pass, context); + Assert(cmd != NULL); /* ATPrepCmd ensures it must be a table */ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name); @@ -4503,7 +4580,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, /* * Report the subcommand to interested event triggers. */ - EventTriggerCollectAlterTableSubcmd((Node *) cmd, address); + if (cmd) + EventTriggerCollectAlterTableSubcmd((Node *) cmd, address); /* * Bump the command counter to ensure the next subcommand in the sequence @@ -4513,10 +4591,143 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, } /* + * ATParseTransformCmd: perform parse transformation for one subcommand + * + * Returns the transformed subcommand tree, if there is one, else NULL. + * + * The parser may hand back additional AlterTableCmd(s) and/or other + * utility statements, either before or after the original subcommand. + * Other AlterTableCmds are scheduled into the appropriate slot of the + * AlteredTableInfo (they had better be for later passes than the current one). + * Utility statements that are supposed to happen before the AlterTableCmd + * are executed immediately. Those that are supposed to happen afterwards + * are added to the tab->afterStmts list to be done at the very end. + */ +static AlterTableCmd * +ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, + AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode, + int cur_pass, AlterTableUtilityContext *context) +{ + AlterTableCmd *newcmd = NULL; + AlterTableStmt *atstmt = makeNode(AlterTableStmt); + List *beforeStmts; + List *afterStmts; + ListCell *lc; + + /* Gin up an AlterTableStmt with just this subcommand and this table */ + atstmt->relation = + makeRangeVar(get_namespace_name(RelationGetNamespace(rel)), + pstrdup(RelationGetRelationName(rel)), + -1); + atstmt->relation->inh = recurse; + atstmt->cmds = list_make1(cmd); + atstmt->relkind = OBJECT_TABLE; /* needn't be picky here */ + atstmt->missing_ok = false; + + /* Transform the AlterTableStmt */ + atstmt = transformAlterTableStmt(RelationGetRelid(rel), + atstmt, + context->queryString, + &beforeStmts, + &afterStmts); + + /* Execute any statements that should happen before these subcommand(s) */ + foreach(lc, beforeStmts) + { + Node *stmt = (Node *) lfirst(lc); + + ProcessUtilityForAlterTable(stmt, context); + CommandCounterIncrement(); + } + + /* Examine the transformed subcommands and schedule them appropriately */ + foreach(lc, atstmt->cmds) + { + AlterTableCmd *cmd2 = lfirst_node(AlterTableCmd, lc); + + if (newcmd == NULL && + (cmd->subtype == cmd2->subtype || + (cmd->subtype == AT_AddConstraintRecurse && + cmd2->subtype == AT_AddConstraint))) + { + /* Found the transformed version of our subcommand */ + cmd2->subtype = cmd->subtype; /* copy recursion flag */ + newcmd = cmd2; + } + else + { + int pass; + + /* + * Schedule added subcommand appropriately. We assume we needn't + * do any phase-1 checks for it. This switch only has to cover + * the subcommand types that can be added by parse_utilcmd.c. + */ + switch (cmd2->subtype) + { + case AT_SetNotNull: + /* Need command-specific recursion decision */ + ATPrepSetNotNull(wqueue, rel, cmd2, + recurse, false, + lockmode, context); + pass = AT_PASS_COL_ATTRS; + break; + case AT_AddIndex: + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_ADD_INDEX; + break; + case AT_AddIndexConstraint: + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_ADD_INDEXCONSTR; + break; + case AT_AddConstraint: + /* Recursion occurs during execution phase */ + if (recurse) + cmd2->subtype = AT_AddConstraintRecurse; + switch (castNode(Constraint, cmd2->def)->contype) + { + case CONSTR_PRIMARY: + case CONSTR_UNIQUE: + case CONSTR_EXCLUSION: + pass = AT_PASS_ADD_INDEXCONSTR; + break; + default: + pass = AT_PASS_ADD_OTHERCONSTR; + break; + } + break; + case AT_AlterColumnGenericOptions: + /* This command never recurses */ + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; + default: + elog(ERROR, "unexpected AlterTableType: %d", + (int) cmd2->subtype); + pass = AT_PASS_UNSET; + break; + } + /* Must be for a later pass than we're currently doing */ + if (pass <= cur_pass) + elog(ERROR, "ALTER TABLE scheduling failure"); + tab->subcmds[pass] = lappend(tab->subcmds[pass], cmd2); + } + } + + /* Queue up any after-statements to happen at the end */ + tab->afterStmts = list_concat(tab->afterStmts, afterStmts); + + return newcmd; +} + +/* * ATRewriteTables: ALTER TABLE phase 3 */ static void -ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) +ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, + AlterTableUtilityContext *context) { ListCell *ltab; @@ -4743,6 +4954,21 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) if (rel) table_close(rel, NoLock); } + + /* Finally, run any afterStmts that were queued up */ + foreach(ltab, *wqueue) + { + AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); + ListCell *lc; + + foreach(lc, tab->afterStmts) + { + Node *stmt = (Node *) lfirst(lc); + + ProcessUtilityForAlterTable(stmt, context); + CommandCounterIncrement(); + } + } } /* @@ -5279,7 +5505,8 @@ ATWrongRelkindError(Relation rel, int allowed_targets) */ static void ATSimpleRecursion(List **wqueue, Relation rel, - AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode) + AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode, + AlterTableUtilityContext *context) { /* * Propagate to children if desired. Only plain tables, foreign tables @@ -5312,7 +5539,7 @@ ATSimpleRecursion(List **wqueue, Relation rel, /* find_all_inheritors already got lock */ childrel = relation_open(childrelid, NoLock); CheckTableNotInUse(childrel, "ALTER TABLE"); - ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode); + ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context); relation_close(childrel, NoLock); } } @@ -5357,7 +5584,7 @@ ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode) */ static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, - LOCKMODE lockmode) + LOCKMODE lockmode, AlterTableUtilityContext *context) { ListCell *child; List *children; @@ -5375,7 +5602,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, childrel = relation_open(childrelid, lockmode); CheckTableNotInUse(childrel, "ALTER TABLE"); - ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode); + ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context); relation_close(childrel, NoLock); } } @@ -5612,7 +5839,8 @@ check_of_type(HeapTuple typetuple) */ static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, - bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode) + bool is_view, AlterTableCmd *cmd, LOCKMODE lockmode, + AlterTableUtilityContext *context) { if (rel->rd_rel->reloftype && !recursing) ereport(ERROR, @@ -5620,7 +5848,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, errmsg("cannot add column to typed table"))); if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) - ATTypedTableRecursion(wqueue, rel, cmd, lockmode); + ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context); if (recurse && !is_view) cmd->subtype = AT_AddColumnRecurse; @@ -5629,14 +5857,20 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, /* * Add a column to a table. The return value is the address of the * new column in the parent relation. + * + * cmd is pass-by-ref so that we can replace it with the parse-transformed + * copy (but that happens only after we check for IF NOT EXISTS). */ static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, - ColumnDef *colDef, + AlterTableCmd **cmd, bool recurse, bool recursing, - bool if_not_exists, LOCKMODE lockmode) + LOCKMODE lockmode, int cur_pass, + AlterTableUtilityContext *context) { Oid myrelid = RelationGetRelid(rel); + ColumnDef *colDef = castNode(ColumnDef, (*cmd)->def); + bool if_not_exists = (*cmd)->missing_ok; Relation pgclass, attrdesc; HeapTuple reltup; @@ -5651,6 +5885,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, Expr *defval; List *children; ListCell *child; + AlterTableCmd *childcmd; AclResult aclresult; ObjectAddress address; @@ -5718,12 +5953,31 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, } } - pgclass = table_open(RelationRelationId, RowExclusiveLock); + /* skip if the name already exists and if_not_exists is true */ + if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists)) + { + table_close(attrdesc, RowExclusiveLock); + return InvalidObjectAddress; + } - reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid)); - if (!HeapTupleIsValid(reltup)) - elog(ERROR, "cache lookup failed for relation %u", myrelid); - relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind; + /* + * Okay, we need to add the column, so go ahead and do parse + * transformation. This can result in queueing up, or even immediately + * executing, subsidiary operations (such as creation of unique indexes); + * so we mustn't do it until we have made the if_not_exists check. + * + * When recursing, the command was already transformed and we needn't do + * so again. Also, if context isn't given we can't transform. (That + * currently happens only for AT_AddColumnToView; we expect that view.c + * passed us a ColumnDef that doesn't need work.) + */ + if (context != NULL && !recursing) + { + *cmd = ATParseTransformCmd(wqueue, tab, rel, *cmd, recurse, lockmode, + cur_pass, context); + Assert(*cmd != NULL); + colDef = castNode(ColumnDef, (*cmd)->def); + } /* * Cannot add identity column if table has children, because identity does @@ -5736,14 +5990,12 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot recursively add identity column to table that has child tables"))); - /* skip if the name already exists and if_not_exists is true */ - if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists)) - { - table_close(attrdesc, RowExclusiveLock); - heap_freetuple(reltup); - table_close(pgclass, RowExclusiveLock); - return InvalidObjectAddress; - } + pgclass = table_open(RelationRelationId, RowExclusiveLock); + + reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid)); + if (!HeapTupleIsValid(reltup)) + elog(ERROR, "cache lookup failed for relation %u", myrelid); + relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind; /* Determine the new attribute's number */ newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1; @@ -5974,10 +6226,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Children should see column as singly inherited */ if (!recursing) { - colDef = copyObject(colDef); + childcmd = copyObject(*cmd); + colDef = castNode(ColumnDef, childcmd->def); colDef->inhcount = 1; colDef->is_local = false; } + else + childcmd = *cmd; /* no need to copy again */ foreach(child, children) { @@ -5994,8 +6249,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Recurse to child; return value is ignored */ ATExecAddColumn(wqueue, childtab, childrel, - colDef, recurse, true, - if_not_exists, lockmode); + &childcmd, recurse, true, + lockmode, cur_pass, context); table_close(childrel, NoLock); } @@ -6254,7 +6509,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) static void ATPrepSetNotNull(List **wqueue, Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, - LOCKMODE lockmode) + LOCKMODE lockmode, AlterTableUtilityContext *context) { /* * If we're already recursing, there's nothing to do; the topmost @@ -6275,10 +6530,10 @@ ATPrepSetNotNull(List **wqueue, Relation rel, newcmd->subtype = AT_CheckNotNull; newcmd->name = pstrdup(cmd->name); - ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode); + ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode, context); } else - ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); + ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); } /* @@ -7165,7 +7420,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc */ static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, - AlterTableCmd *cmd, LOCKMODE lockmode) + AlterTableCmd *cmd, LOCKMODE lockmode, + AlterTableUtilityContext *context) { if (rel->rd_rel->reloftype && !recursing) ereport(ERROR, @@ -7173,7 +7429,7 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, errmsg("cannot drop column from typed table"))); if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) - ATTypedTableRecursion(wqueue, rel, cmd, lockmode); + ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context); if (recurse) cmd->subtype = AT_DropColumnRecurse; @@ -10426,12 +10682,27 @@ ATExecDropConstraint(Relation rel, const char *constrName, /* * ALTER COLUMN TYPE + * + * Unlike other subcommand types, we do parse transformation for ALTER COLUMN + * TYPE during phase 1 --- the AlterTableCmd passed in here is already + * transformed (and must be, because we rely on some transformed fields). + * + * The point of this is that the execution of all ALTER COLUMN TYPEs for a + * table will be done "in parallel" during phase 3, so all the USING + * expressions should be parsed assuming the original column types. Also, + * this allows a USING expression to refer to a field that will be dropped. + * + * To make this work safely, AT_PASS_DROP then AT_PASS_ALTER_TYPE must be + * the first two execution steps in phase 2; they must not see the effects + * of any other subcommand types, since the USING expressions are parsed + * against the unmodified table's state. */ static void ATPrepAlterColumnType(List **wqueue, AlteredTableInfo *tab, Relation rel, bool recurse, bool recursing, - AlterTableCmd *cmd, LOCKMODE lockmode) + AlterTableCmd *cmd, LOCKMODE lockmode, + AlterTableUtilityContext *context) { char *colName = cmd->name; ColumnDef *def = (ColumnDef *) cmd->def; @@ -10678,7 +10949,7 @@ ATPrepAlterColumnType(List **wqueue, errdetail("USING expression contains a whole-row table reference."))); pfree(attmap); } - ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode); + ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context); relation_close(childrel, NoLock); } } @@ -10690,7 +10961,7 @@ ATPrepAlterColumnType(List **wqueue, colName))); if (tab->relkind == RELKIND_COMPOSITE_TYPE) - ATTypedTableRecursion(wqueue, rel, cmd, lockmode); + ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context); } /* @@ -11469,10 +11740,19 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, (IndexStmt *) stmt, cmd)); else if (IsA(stmt, AlterTableStmt)) - querytree_list = list_concat(querytree_list, - transformAlterTableStmt(oldRelId, - (AlterTableStmt *) stmt, - cmd)); + { + List *beforeStmts; + List *afterStmts; + + stmt = (Node *) transformAlterTableStmt(oldRelId, + (AlterTableStmt *) stmt, + cmd, + &beforeStmts, + &afterStmts); + querytree_list = list_concat(querytree_list, beforeStmts); + querytree_list = lappend(querytree_list, stmt); + querytree_list = list_concat(querytree_list, afterStmts); + } else querytree_list = lappend(querytree_list, stmt); } @@ -16133,7 +16413,7 @@ isPartitionTrigger(Oid trigger_oid) { Relation pg_depend; ScanKeyData key[2]; - SysScanDesc scan; + SysScanDesc scan; HeapTuple tup; bool found = false; @@ -16152,7 +16432,7 @@ isPartitionTrigger(Oid trigger_oid) true, NULL, 2, key); while ((tup = systable_getnext(scan)) != NULL) { - Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(tup); + Form_pg_depend dep = (Form_pg_depend) GETSTRUCT(tup); if (dep->refclassid == TriggerRelationId) { @@ -16212,8 +16492,8 @@ CloneRowTriggersToPartition(Relation parent, Relation partition) * clone them. * * However, if our parent is a partitioned relation, there might be - * internal triggers that need cloning. In that case, we must - * skip clone it if the trigger on parent depends on another trigger. + * internal triggers that need cloning. In that case, we must skip + * clone it if the trigger on parent depends on another trigger. * * Note we dare not verify that the other trigger belongs to an * ancestor relation of our parent, because that creates deadlock |