Restructure ALTER TABLE execution to fix assorted bugs.

We've had numerous bug reports about how (1) IF NOT EXISTS clauses in
ALTER TABLE don't behave as-expected, and (2) combining certain actions
into one ALTER TABLE doesn't work, though executing the same actions as
separate statements does.  This patch cleans up all of the cases so far
reported from the field, though there are still some oddities associated
with identity columns.

The core problem behind all of these bugs is that we do parse analysis
of ALTER TABLE subcommands too soon, before starting execution of the
statement.  The root of the bugs in group (1) is that parse analysis
schedules derived commands (such as a CREATE SEQUENCE for a serial
column) before it's known whether the IF NOT EXISTS clause should cause
a subcommand to be skipped.  The root of the bugs in group (2) is that
earlier subcommands may change the catalog state that later subcommands
need to be parsed against.

Hence, postpone parse analysis of ALTER TABLE's subcommands, and do
that one subcommand at a time, during "phase 2" of ALTER TABLE which
is the phase that does catalog rewrites.  Thus the catalog effects
of earlier subcommands are already visible when we analyze later ones.
(The sole exception is that we do parse analysis for ALTER COLUMN TYPE
subcommands during phase 1, so that their USING expressions can be
parsed against the table's original state, which is what we need.
Arguably, these bugs stem from falsely concluding that because ALTER
COLUMN TYPE must do early parse analysis, every other command subtype
can too.)

This means that ALTER TABLE itself must deal with execution of any
non-ALTER-TABLE derived statements that are generated by parse analysis.
Add a suitable entry point to utility.c to accept those recursive
calls, and create a struct to pass through the information needed by
the recursive call, rather than making the argument lists of
AlterTable() and friends even longer.

Getting this to work correctly required a little bit of fiddling
with the subcommand pass structure, in particular breaking up
AT_PASS_ADD_CONSTR into multiple passes.  But otherwise it's mostly
a pretty straightforward application of the above ideas.

Fixing the residual issues for identity columns requires refactoring of
where the dependency link from an identity column to its sequence gets
set up.  So that seems like suitable material for a separate patch,
especially since this one is pretty big already.

Discussion: https://postgr.es/m/10365.1558909428@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2020-01-15 18:49:24 -05:00
parent a166d408eb
commit 1281a5c907
13 changed files with 827 additions and 267 deletions

View File

@ -85,6 +85,7 @@
#include "storage/lock.h" #include "storage/lock.h"
#include "storage/predicate.h" #include "storage/predicate.h"
#include "storage/smgr.h" #include "storage/smgr.h"
#include "tcop/utility.h"
#include "utils/acl.h" #include "utils/acl.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/fmgroids.h" #include "utils/fmgroids.h"
@ -142,11 +143,13 @@ static List *on_commits = NIL;
#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ #define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */
/* We could support a RENAME COLUMN pass here, but not currently used */ /* We could support a RENAME COLUMN pass here, but not currently used */
#define AT_PASS_ADD_COL 4 /* ADD COLUMN */ #define AT_PASS_ADD_COL 4 /* ADD COLUMN */
#define AT_PASS_COL_ATTRS 5 /* set other column attributes */ #define AT_PASS_ADD_CONSTR 5 /* ADD constraints (initial examination) */
#define AT_PASS_ADD_INDEX 6 /* ADD indexes */ #define AT_PASS_COL_ATTRS 6 /* set column attributes, eg NOT NULL */
#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */ #define AT_PASS_ADD_INDEXCONSTR 7 /* ADD index-based constraints */
#define AT_PASS_MISC 8 /* other stuff */ #define AT_PASS_ADD_INDEX 8 /* ADD indexes */
#define AT_NUM_PASSES 9 #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 typedef struct AlteredTableInfo
{ {
@ -159,6 +162,7 @@ typedef struct AlteredTableInfo
/* Information saved by Phases 1/2 for Phase 3: */ /* Information saved by Phases 1/2 for Phase 3: */
List *constraints; /* List of NewConstraint */ List *constraints; /* List of NewConstraint */
List *newvals; /* List of NewColumnValue */ List *newvals; /* List of NewColumnValue */
List *afterStmts; /* List of utility command parsetrees */
bool verify_new_notnull; /* T if we should recheck NOT NULL */ bool verify_new_notnull; /* T if we should recheck NOT NULL */
int rewrite; /* Reason for forced rewrite, if any */ int rewrite; /* Reason for forced rewrite, if any */
Oid newTableSpace; /* new tablespace; 0 means no change */ Oid newTableSpace; /* new tablespace; 0 means no change */
@ -340,31 +344,45 @@ static void validateForeignKeyConstraint(char *conname,
Relation rel, Relation pkrel, Relation rel, Relation pkrel,
Oid pkindOid, Oid constraintOid); Oid pkindOid, Oid constraintOid);
static void ATController(AlterTableStmt *parsetree, 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, static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing, LOCKMODE lockmode); bool recurse, bool recursing, LOCKMODE lockmode,
static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode); AlterTableUtilityContext *context);
static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
AlterTableUtilityContext *context);
static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, 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, 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 void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel); static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
static void ATSimplePermissions(Relation rel, int allowed_targets); static void ATSimplePermissions(Relation rel, int allowed_targets);
static void ATWrongRelkindError(Relation rel, int allowed_targets); static void ATWrongRelkindError(Relation rel, int allowed_targets);
static void ATSimpleRecursion(List **wqueue, Relation rel, 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 ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode);
static void ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, 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, static List *find_typed_table_dependencies(Oid typeOid, const char *typeName,
DropBehavior behavior); DropBehavior behavior);
static void ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, 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, static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
Relation rel, ColumnDef *colDef, Relation rel, AlterTableCmd **cmd,
bool recurse, bool recursing, 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, static bool check_for_column_name_collision(Relation rel, const char *colname,
bool if_not_exists); bool if_not_exists);
static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); 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 ObjectAddress ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode);
static void ATPrepSetNotNull(List **wqueue, Relation rel, static void ATPrepSetNotNull(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, bool recursing, AlterTableCmd *cmd, bool recurse, bool recursing,
LOCKMODE lockmode); LOCKMODE lockmode,
AlterTableUtilityContext *context);
static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode); const char *colName, LOCKMODE lockmode);
static void ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel, 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, static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
Node *newValue, LOCKMODE lockmode); Node *newValue, LOCKMODE lockmode);
static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, 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, static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
DropBehavior behavior, DropBehavior behavior,
bool recurse, bool recursing, bool recurse, bool recursing,
@ -454,7 +474,8 @@ static void ATExecDropConstraint(Relation rel, const char *constrName,
static void ATPrepAlterColumnType(List **wqueue, static void ATPrepAlterColumnType(List **wqueue,
AlteredTableInfo *tab, Relation rel, AlteredTableInfo *tab, Relation rel,
bool recurse, bool recursing, bool recurse, bool recursing,
AlterTableCmd *cmd, LOCKMODE lockmode); AlterTableCmd *cmd, LOCKMODE lockmode,
AlterTableUtilityContext *context);
static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno); static bool ATColumnChangeRequiresRewrite(Node *expr, AttrNumber varattno);
static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, static ObjectAddress ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode); AlterTableCmd *cmd, LOCKMODE lockmode);
@ -3463,7 +3484,7 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
* *
* ALTER TABLE is performed in three phases: * ALTER TABLE is performed in three phases:
* 1. Examine subcommands and perform pre-transformation checking. * 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 * 3. Scan table(s) to check new constraints, and optionally recopy
* the data into new table(s). * the data into new table(s).
* Phase 3 is not performed unless one or more of the subcommands requires * 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 * ATPrepCmd performs phase 1. A "work queue" entry is created for
* each table to be affected (there may be multiple affected tables if the * each table to be affected (there may be multiple affected tables if the
* commands traverse a table inheritance hierarchy). Also we do preliminary * commands traverse a table inheritance hierarchy). Also we do preliminary
* validation of the subcommands, including parse transformation of those * validation of the subcommands. Because earlier subcommands may change
* expressions that need to be evaluated with respect to the old table * the catalog state seen by later commands, there are limits to what can
* schema. * 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 * ATRewriteCatalogs performs phase 2 for each affected table. (Note that
* phases 2 and 3 normally do no explicit recursion, since phase 1 already * 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 * 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, * that specific subcommand. So we pass down the overall lock requirement,
* rather than reassess it at lower levels. * 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 void
AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt) AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{ {
Relation rel; Relation rel;
/* Caller is required to provide an adequate lock. */ /* Caller is required to provide an adequate lock. */
rel = relation_open(relid, NoLock); rel = relation_open(context->relid, NoLock);
CheckTableNotInUse(rel, "ALTER TABLE"); 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 * 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 * existing query plans. On the assumption it's not used for such, we
* don't have to reject pending AFTER triggers, either. * 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 void
AlterTableInternal(Oid relid, List *cmds, bool recurse) AlterTableInternal(Oid relid, List *cmds, bool recurse)
@ -3533,7 +3564,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
EventTriggerAlterTableRelid(relid); EventTriggerAlterTableRelid(relid);
ATController(NULL, rel, cmds, recurse, lockmode); ATController(NULL, rel, cmds, recurse, lockmode, NULL);
} }
/* /*
@ -3679,7 +3710,6 @@ AlterTableGetLockLevel(List *cmds)
break; break;
case AT_AddConstraint: case AT_AddConstraint:
case AT_ProcessedConstraint: /* becomes AT_AddConstraint */
case AT_AddConstraintRecurse: /* becomes AT_AddConstraint */ case AT_AddConstraintRecurse: /* becomes AT_AddConstraint */
case AT_ReAddConstraint: /* becomes AT_AddConstraint */ case AT_ReAddConstraint: /* becomes AT_AddConstraint */
case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */ case AT_ReAddDomainConstraint: /* becomes AT_AddConstraint */
@ -3832,7 +3862,8 @@ AlterTableGetLockLevel(List *cmds)
*/ */
static void static void
ATController(AlterTableStmt *parsetree, ATController(AlterTableStmt *parsetree,
Relation rel, List *cmds, bool recurse, LOCKMODE lockmode) Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{ {
List *wqueue = NIL; List *wqueue = NIL;
ListCell *lcmd; ListCell *lcmd;
@ -3842,17 +3873,17 @@ ATController(AlterTableStmt *parsetree,
{ {
AlterTableCmd *cmd = (AlterTableCmd *) lfirst(lcmd); 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 */ /* Close the relation, but keep lock until commit */
relation_close(rel, NoLock); relation_close(rel, NoLock);
/* Phase 2: update system catalogs */ /* Phase 2: update system catalogs */
ATRewriteCatalogs(&wqueue, lockmode); ATRewriteCatalogs(&wqueue, lockmode, context);
/* Phase 3: scan/rewrite tables as needed */ /* Phase 3: scan/rewrite tables as needed, and run afterStmts */
ATRewriteTables(parsetree, &wqueue, lockmode); ATRewriteTables(parsetree, &wqueue, lockmode, context);
} }
/* /*
@ -3866,7 +3897,8 @@ ATController(AlterTableStmt *parsetree,
*/ */
static void static void
ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing, LOCKMODE lockmode) bool recurse, bool recursing, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{ {
AlteredTableInfo *tab; AlteredTableInfo *tab;
int pass = AT_PASS_UNSET; 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 * Copy the original subcommand for each table. This avoids conflicts
* when different child tables need to make different parse * when different child tables need to make different parse
* transformations (for example, the same column may have different column * 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); cmd = copyObject(cmd);
/* /*
* Do permissions checking, recursion to child tables if needed, and any * Do permissions and relkind checking, recursion to child tables if
* additional phase-1 processing needed. * 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) switch (cmd->subtype)
{ {
@ -3892,14 +3928,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
ATSimplePermissions(rel, ATSimplePermissions(rel,
ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE);
ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd, ATPrepAddColumn(wqueue, rel, recurse, recursing, false, cmd,
lockmode); lockmode, context);
/* Recursion occurs during execution phase */ /* Recursion occurs during execution phase */
pass = AT_PASS_ADD_COL; pass = AT_PASS_ADD_COL;
break; break;
case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
ATSimplePermissions(rel, ATT_VIEW); ATSimplePermissions(rel, ATT_VIEW);
ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd, ATPrepAddColumn(wqueue, rel, recurse, recursing, true, cmd,
lockmode); lockmode, context);
/* Recursion occurs during execution phase */ /* Recursion occurs during execution phase */
pass = AT_PASS_ADD_COL; pass = AT_PASS_ADD_COL;
break; break;
@ -3912,19 +3948,20 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
* rules. * rules.
*/ */
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); 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 */ /* 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; break;
case AT_AddIdentity: case AT_AddIdentity:
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
/* This command never recurses */ /* This command never recurses */
pass = AT_PASS_ADD_CONSTR; pass = AT_PASS_ADD_OTHERCONSTR;
break; break;
case AT_SetIdentity: case AT_SetIdentity:
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
/* This command never recurses */ /* 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; break;
case AT_DropIdentity: case AT_DropIdentity:
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); 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 */ case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
ATPrepDropNotNull(rel, recurse, recursing); ATPrepDropNotNull(rel, recurse, recursing);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
pass = AT_PASS_DROP; pass = AT_PASS_DROP;
break; break;
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* Need command-specific recursion decision */ /* 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; pass = AT_PASS_COL_ATTRS;
break; break;
case AT_CheckNotNull: /* check column is already marked NOT NULL */ case AT_CheckNotNull: /* check column is already marked NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); 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 */ /* No command-specific prep needed */
pass = AT_PASS_COL_ATTRS; pass = AT_PASS_COL_ATTRS;
break; break;
case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */ case AT_DropExpression: /* ALTER COLUMN DROP EXPRESSION */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
ATPrepDropExpression(rel, cmd, recursing); ATPrepDropExpression(rel, cmd, recursing);
pass = AT_PASS_DROP; pass = AT_PASS_DROP;
break; break;
case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_PARTITIONED_INDEX | ATT_FOREIGN_TABLE); 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 */ /* No command-specific prep needed */
pass = AT_PASS_MISC; pass = AT_PASS_MISC;
break; break;
@ -3969,14 +4007,15 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break; break;
case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ case AT_SetStorage: /* ALTER COLUMN SET STORAGE */
ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE); 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 */ /* No command-specific prep needed */
pass = AT_PASS_MISC; pass = AT_PASS_MISC;
break; break;
case AT_DropColumn: /* DROP COLUMN */ case AT_DropColumn: /* DROP COLUMN */
ATSimplePermissions(rel, ATSimplePermissions(rel,
ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); 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 */ /* Recursion occurs during execution phase */
pass = AT_PASS_DROP; pass = AT_PASS_DROP;
break; break;
@ -3998,7 +4037,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
ATSimplePermissions(rel, ATT_TABLE); ATSimplePermissions(rel, ATT_TABLE);
/* This command never recurses */ /* This command never recurses */
/* No command-specific prep needed */ /* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR; pass = AT_PASS_ADD_INDEXCONSTR;
break; break;
case AT_DropConstraint: /* DROP CONSTRAINT */ case AT_DropConstraint: /* DROP CONSTRAINT */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE); ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
@ -4012,8 +4051,13 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
case AT_AlterColumnType: /* ALTER COLUMN TYPE */ case AT_AlterColumnType: /* ALTER COLUMN TYPE */
ATSimplePermissions(rel, ATSimplePermissions(rel,
ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE); 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 */ /* 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; pass = AT_PASS_ALTER_TYPE;
break; break;
case AT_AlterColumnGenericOptions: case AT_AlterColumnGenericOptions:
@ -4036,6 +4080,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break; break;
case AT_SetLogged: /* SET LOGGED */ case AT_SetLogged: /* SET LOGGED */
ATSimplePermissions(rel, ATT_TABLE); 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); tab->chgPersistence = ATPrepChangePersistence(rel, true);
/* force rewrite if necessary; see comment in ATRewriteTables */ /* force rewrite if necessary; see comment in ATRewriteTables */
if (tab->chgPersistence) if (tab->chgPersistence)
@ -4047,6 +4095,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break; break;
case AT_SetUnLogged: /* SET UNLOGGED */ case AT_SetUnLogged: /* SET UNLOGGED */
ATSimplePermissions(rel, ATT_TABLE); 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); tab->chgPersistence = ATPrepChangePersistence(rel, false);
/* force rewrite if necessary; see comment in ATRewriteTables */ /* force rewrite if necessary; see comment in ATRewriteTables */
if (tab->chgPersistence) if (tab->chgPersistence)
@ -4166,7 +4218,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
* conflicts). * conflicts).
*/ */
static void static void
ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{ {
int pass; int pass;
ListCell *ltab; ListCell *ltab;
@ -4199,7 +4252,7 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
foreach(lcmd, subcmds) foreach(lcmd, subcmds)
ATExecCmd(wqueue, tab, rel, ATExecCmd(wqueue, tab, rel,
castNode(AlterTableCmd, lfirst(lcmd)), castNode(AlterTableCmd, lfirst(lcmd)),
lockmode); lockmode, pass, context);
/* /*
* After the ALTER TYPE pass, do cleanup work (this is not done in * After the ALTER TYPE pass, do cleanup work (this is not done in
@ -4236,7 +4289,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
*/ */
static void static void
ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode) AlterTableCmd *cmd, LOCKMODE lockmode, int cur_pass,
AlterTableUtilityContext *context)
{ {
ObjectAddress address = InvalidObjectAddress; ObjectAddress address = InvalidObjectAddress;
@ -4244,22 +4298,28 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
{ {
case AT_AddColumn: /* ADD COLUMN */ case AT_AddColumn: /* ADD COLUMN */
case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */ 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, false, false,
cmd->missing_ok, lockmode); lockmode, cur_pass, context);
break; break;
case AT_AddColumnRecurse: case AT_AddColumnRecurse:
address = ATExecAddColumn(wqueue, tab, rel, (ColumnDef *) cmd->def, address = ATExecAddColumn(wqueue, tab, rel, &cmd,
true, false, true, false,
cmd->missing_ok, lockmode); lockmode, cur_pass, context);
break; break;
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */ case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode); address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
break; break;
case AT_AddIdentity: 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); address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
break; break;
case AT_SetIdentity: 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); address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode);
break; break;
case AT_DropIdentity: case AT_DropIdentity:
@ -4310,14 +4370,24 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
lockmode); lockmode);
break; break;
case AT_AddConstraint: /* ADD CONSTRAINT */ case AT_AddConstraint: /* ADD CONSTRAINT */
address = cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, cur_pass, context);
false, false, lockmode); /* Might not have gotten AddConstraint back from parse transform */
if (cmd != NULL)
address =
ATExecAddConstraint(wqueue, tab, rel,
(Constraint *) cmd->def,
false, false, lockmode);
break; break;
case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion */ case AT_AddConstraintRecurse: /* ADD CONSTRAINT with recursion */
address = cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, true, lockmode,
ATExecAddConstraint(wqueue, tab, rel, (Constraint *) cmd->def, cur_pass, context);
true, false, lockmode); /* Might not have gotten AddConstraint back from parse transform */
if (cmd != NULL)
address =
ATExecAddConstraint(wqueue, tab, rel,
(Constraint *) cmd->def,
true, false, lockmode);
break; break;
case AT_ReAddConstraint: /* Re-add pre-existing check constraint */ case AT_ReAddConstraint: /* Re-add pre-existing check constraint */
address = address =
@ -4361,6 +4431,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
cmd->missing_ok, lockmode); cmd->missing_ok, lockmode);
break; break;
case AT_AlterColumnType: /* ALTER COLUMN TYPE */ case AT_AlterColumnType: /* ALTER COLUMN TYPE */
/* parse transformation was done earlier */
address = ATExecAlterColumnType(tab, rel, cmd, lockmode); address = ATExecAlterColumnType(tab, rel, cmd, lockmode);
break; break;
case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */ case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */
@ -4483,6 +4554,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecGenericOptions(rel, (List *) cmd->def); ATExecGenericOptions(rel, (List *) cmd->def);
break; break;
case AT_AttachPartition: 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) if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def); ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
else else
@ -4490,6 +4564,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
((PartitionCmd *) cmd->def)->name); ((PartitionCmd *) cmd->def)->name);
break; break;
case AT_DetachPartition: case AT_DetachPartition:
cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode,
cur_pass, context);
Assert(cmd != NULL);
/* ATPrepCmd ensures it must be a table */ /* ATPrepCmd ensures it must be a table */
Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name); ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
@ -4503,7 +4580,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* /*
* Report the subcommand to interested event triggers. * 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 * Bump the command counter to ensure the next subcommand in the sequence
@ -4512,11 +4590,144 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
CommandCounterIncrement(); CommandCounterIncrement();
} }
/*
* 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 * ATRewriteTables: ALTER TABLE phase 3
*/ */
static void static void
ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{ {
ListCell *ltab; ListCell *ltab;
@ -4743,6 +4954,21 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
if (rel) if (rel)
table_close(rel, NoLock); 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 static void
ATSimpleRecursion(List **wqueue, Relation rel, 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 * 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 */ /* find_all_inheritors already got lock */
childrel = relation_open(childrelid, NoLock); childrel = relation_open(childrelid, NoLock);
CheckTableNotInUse(childrel, "ALTER TABLE"); CheckTableNotInUse(childrel, "ALTER TABLE");
ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode); ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
relation_close(childrel, NoLock); relation_close(childrel, NoLock);
} }
} }
@ -5357,7 +5584,7 @@ ATCheckPartitionsNotInUse(Relation rel, LOCKMODE lockmode)
*/ */
static void static void
ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
LOCKMODE lockmode) LOCKMODE lockmode, AlterTableUtilityContext *context)
{ {
ListCell *child; ListCell *child;
List *children; List *children;
@ -5375,7 +5602,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd,
childrel = relation_open(childrelid, lockmode); childrel = relation_open(childrelid, lockmode);
CheckTableNotInUse(childrel, "ALTER TABLE"); CheckTableNotInUse(childrel, "ALTER TABLE");
ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode); ATPrepCmd(wqueue, childrel, cmd, true, true, lockmode, context);
relation_close(childrel, NoLock); relation_close(childrel, NoLock);
} }
} }
@ -5612,7 +5839,8 @@ check_of_type(HeapTuple typetuple)
*/ */
static void static void
ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing, 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) if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR, ereport(ERROR,
@ -5620,7 +5848,7 @@ ATPrepAddColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
errmsg("cannot add column to typed table"))); errmsg("cannot add column to typed table")));
if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
ATTypedTableRecursion(wqueue, rel, cmd, lockmode); ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
if (recurse && !is_view) if (recurse && !is_view)
cmd->subtype = AT_AddColumnRecurse; 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 * Add a column to a table. The return value is the address of the
* new column in the parent relation. * 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 static ObjectAddress
ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
ColumnDef *colDef, AlterTableCmd **cmd,
bool recurse, bool recursing, bool recurse, bool recursing,
bool if_not_exists, LOCKMODE lockmode) LOCKMODE lockmode, int cur_pass,
AlterTableUtilityContext *context)
{ {
Oid myrelid = RelationGetRelid(rel); Oid myrelid = RelationGetRelid(rel);
ColumnDef *colDef = castNode(ColumnDef, (*cmd)->def);
bool if_not_exists = (*cmd)->missing_ok;
Relation pgclass, Relation pgclass,
attrdesc; attrdesc;
HeapTuple reltup; HeapTuple reltup;
@ -5651,6 +5885,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
Expr *defval; Expr *defval;
List *children; List *children;
ListCell *child; ListCell *child;
AlterTableCmd *childcmd;
AclResult aclresult; AclResult aclresult;
ObjectAddress address; 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)) * Okay, we need to add the column, so go ahead and do parse
elog(ERROR, "cache lookup failed for relation %u", myrelid); * transformation. This can result in queueing up, or even immediately
relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind; * 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 * 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), (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot recursively add identity column to table that has child tables"))); errmsg("cannot recursively add identity column to table that has child tables")));
/* skip if the name already exists and if_not_exists is true */ pgclass = table_open(RelationRelationId, RowExclusiveLock);
if (!check_for_column_name_collision(rel, colDef->colname, if_not_exists))
{ reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(myrelid));
table_close(attrdesc, RowExclusiveLock); if (!HeapTupleIsValid(reltup))
heap_freetuple(reltup); elog(ERROR, "cache lookup failed for relation %u", myrelid);
table_close(pgclass, RowExclusiveLock); relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
return InvalidObjectAddress;
}
/* Determine the new attribute's number */ /* Determine the new attribute's number */
newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1; 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 */ /* Children should see column as singly inherited */
if (!recursing) if (!recursing)
{ {
colDef = copyObject(colDef); childcmd = copyObject(*cmd);
colDef = castNode(ColumnDef, childcmd->def);
colDef->inhcount = 1; colDef->inhcount = 1;
colDef->is_local = false; colDef->is_local = false;
} }
else
childcmd = *cmd; /* no need to copy again */
foreach(child, children) foreach(child, children)
{ {
@ -5994,8 +6249,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* Recurse to child; return value is ignored */ /* Recurse to child; return value is ignored */
ATExecAddColumn(wqueue, childtab, childrel, ATExecAddColumn(wqueue, childtab, childrel,
colDef, recurse, true, &childcmd, recurse, true,
if_not_exists, lockmode); lockmode, cur_pass, context);
table_close(childrel, NoLock); table_close(childrel, NoLock);
} }
@ -6254,7 +6509,7 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
static void static void
ATPrepSetNotNull(List **wqueue, Relation rel, ATPrepSetNotNull(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, bool recursing, AlterTableCmd *cmd, bool recurse, bool recursing,
LOCKMODE lockmode) LOCKMODE lockmode, AlterTableUtilityContext *context)
{ {
/* /*
* If we're already recursing, there's nothing to do; the topmost * 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->subtype = AT_CheckNotNull;
newcmd->name = pstrdup(cmd->name); newcmd->name = pstrdup(cmd->name);
ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode); ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode, context);
} }
else 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 static void
ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing, 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) if (rel->rd_rel->reloftype && !recursing)
ereport(ERROR, ereport(ERROR,
@ -7173,7 +7429,7 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
errmsg("cannot drop column from typed table"))); errmsg("cannot drop column from typed table")));
if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
ATTypedTableRecursion(wqueue, rel, cmd, lockmode); ATTypedTableRecursion(wqueue, rel, cmd, lockmode, context);
if (recurse) if (recurse)
cmd->subtype = AT_DropColumnRecurse; cmd->subtype = AT_DropColumnRecurse;
@ -10426,12 +10682,27 @@ ATExecDropConstraint(Relation rel, const char *constrName,
/* /*
* ALTER COLUMN TYPE * 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 static void
ATPrepAlterColumnType(List **wqueue, ATPrepAlterColumnType(List **wqueue,
AlteredTableInfo *tab, Relation rel, AlteredTableInfo *tab, Relation rel,
bool recurse, bool recursing, bool recurse, bool recursing,
AlterTableCmd *cmd, LOCKMODE lockmode) AlterTableCmd *cmd, LOCKMODE lockmode,
AlterTableUtilityContext *context)
{ {
char *colName = cmd->name; char *colName = cmd->name;
ColumnDef *def = (ColumnDef *) cmd->def; ColumnDef *def = (ColumnDef *) cmd->def;
@ -10678,7 +10949,7 @@ ATPrepAlterColumnType(List **wqueue,
errdetail("USING expression contains a whole-row table reference."))); errdetail("USING expression contains a whole-row table reference.")));
pfree(attmap); pfree(attmap);
} }
ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode); ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, context);
relation_close(childrel, NoLock); relation_close(childrel, NoLock);
} }
} }
@ -10690,7 +10961,7 @@ ATPrepAlterColumnType(List **wqueue,
colName))); colName)));
if (tab->relkind == RELKIND_COMPOSITE_TYPE) 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, (IndexStmt *) stmt,
cmd)); cmd));
else if (IsA(stmt, AlterTableStmt)) else if (IsA(stmt, AlterTableStmt))
querytree_list = list_concat(querytree_list, {
transformAlterTableStmt(oldRelId, List *beforeStmts;
(AlterTableStmt *) stmt, List *afterStmts;
cmd));
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 else
querytree_list = lappend(querytree_list, stmt); querytree_list = lappend(querytree_list, stmt);
} }
@ -16133,7 +16413,7 @@ isPartitionTrigger(Oid trigger_oid)
{ {
Relation pg_depend; Relation pg_depend;
ScanKeyData key[2]; ScanKeyData key[2];
SysScanDesc scan; SysScanDesc scan;
HeapTuple tup; HeapTuple tup;
bool found = false; bool found = false;
@ -16152,7 +16432,7 @@ isPartitionTrigger(Oid trigger_oid)
true, NULL, 2, key); true, NULL, 2, key);
while ((tup = systable_getnext(scan)) != NULL) 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) if (dep->refclassid == TriggerRelationId)
{ {
@ -16212,8 +16492,8 @@ CloneRowTriggersToPartition(Relation parent, Relation partition)
* clone them. * clone them.
* *
* However, if our parent is a partitioned relation, there might be * However, if our parent is a partitioned relation, there might be
* internal triggers that need cloning. In that case, we must * internal triggers that need cloning. In that case, we must skip
* skip clone it if the trigger on parent depends on another trigger. * clone it if the trigger on parent depends on another trigger.
* *
* Note we dare not verify that the other trigger belongs to an * Note we dare not verify that the other trigger belongs to an
* ancestor relation of our parent, because that creates deadlock * ancestor relation of our parent, because that creates deadlock

View File

@ -145,6 +145,10 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
* Note that we must do this before updating the query for the view, * 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 * since the rules system requires that the correct view columns be in
* place when defining the new rules. * 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) if (list_length(attrList) > rel->rd_att->natts)
{ {

View File

@ -347,7 +347,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
*/ */
static void static void
generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, 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) char **snamespace_p, char **sname_p)
{ {
ListCell *option; ListCell *option;
@ -472,8 +473,12 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
/* /*
* Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as * 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 * owned by this column, and add it to the appropriate list of things to
* this CREATE/ALTER TABLE. * 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 = makeNode(AlterSeqStmt);
altseqstmt->sequence = makeRangeVar(snamespace, sname, -1); altseqstmt->sequence = makeRangeVar(snamespace, sname, -1);
@ -484,7 +489,10 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
(Node *) attnamelist, -1)); (Node *) attnamelist, -1));
altseqstmt->for_identity = for_identity; 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) if (snamespace_p)
*snamespace_p = snamespace; *snamespace_p = snamespace;
@ -568,7 +576,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
Constraint *constraint; Constraint *constraint;
generateSerialExtraStmts(cxt, column, generateSerialExtraStmts(cxt, column,
column->typeName->typeOid, NIL, false, column->typeName->typeOid, NIL,
false, false,
&snamespace, &sname); &snamespace, &sname);
/* /*
@ -684,7 +693,8 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
constraint->location))); constraint->location)));
generateSerialExtraStmts(cxt, column, generateSerialExtraStmts(cxt, column,
typeOid, constraint->options, true, typeOid, constraint->options,
true, false,
NULL, NULL); NULL, NULL);
column->identity = constraint->generated_when; 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_relid = getIdentitySequence(RelationGetRelid(relation), attribute->attnum, false);
seq_options = sequence_options(seq_relid); seq_options = sequence_options(seq_relid);
generateSerialExtraStmts(cxt, def, generateSerialExtraStmts(cxt, def,
InvalidOid, seq_options, true, InvalidOid, seq_options,
true, false,
NULL, NULL); NULL, NULL);
def->identity = attribute->attidentity; def->identity = attribute->attidentity;
} }
@ -2572,7 +2583,7 @@ transformFKConstraints(CreateStmtContext *cxt,
Constraint *constraint = (Constraint *) lfirst(fkclist); Constraint *constraint = (Constraint *) lfirst(fkclist);
AlterTableCmd *altercmd = makeNode(AlterTableCmd); AlterTableCmd *altercmd = makeNode(AlterTableCmd);
altercmd->subtype = AT_ProcessedConstraint; altercmd->subtype = AT_AddConstraint;
altercmd->name = NULL; altercmd->name = NULL;
altercmd->def = (Node *) constraint; altercmd->def = (Node *) constraint;
alterstmt->cmds = lappend(alterstmt->cmds, altercmd); alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
@ -3004,23 +3015,23 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
* transformAlterTableStmt - * transformAlterTableStmt -
* parse analysis for ALTER TABLE * parse analysis for ALTER TABLE
* *
* Returns a List of utility commands to be done in sequence. One of these * Returns the transformed AlterTableStmt. There may be additional actions
* will be the transformed AlterTableStmt, but there may be additional actions * to be done before and after the transformed statement, which are returned
* to be done before and after the actual AlterTable() call. * in *beforeStmts and *afterStmts as lists of utility command parsetrees.
* *
* To avoid race conditions, it's important that this function rely only on * 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 * the passed-in relid (and not on stmt->relation) to determine the target
* relation. * relation.
*/ */
List * AlterTableStmt *
transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
const char *queryString) const char *queryString,
List **beforeStmts, List **afterStmts)
{ {
Relation rel; Relation rel;
TupleDesc tupdesc; TupleDesc tupdesc;
ParseState *pstate; ParseState *pstate;
CreateStmtContext cxt; CreateStmtContext cxt;
List *result;
List *save_alist; List *save_alist;
ListCell *lcmd, ListCell *lcmd,
*l; *l;
@ -3052,7 +3063,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
/* Set up CreateStmtContext */ /* Set up CreateStmtContext */
cxt.pstate = pstate; cxt.pstate = pstate;
if (stmt->relkind == OBJECT_FOREIGN_TABLE) if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
{ {
cxt.stmtType = "ALTER FOREIGN TABLE"; cxt.stmtType = "ALTER FOREIGN TABLE";
cxt.isforeign = true; cxt.isforeign = true;
@ -3080,9 +3091,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.ofType = false; cxt.ofType = false;
/* /*
* The only subtypes that currently require parse transformation handling * Transform ALTER subcommands that need it (most don't). These largely
* are ADD COLUMN, ADD CONSTRAINT and SET DATA TYPE. These largely re-use * re-use code from CREATE TABLE.
* code from CREATE TABLE.
*/ */
foreach(lcmd, stmt->cmds) foreach(lcmd, stmt->cmds)
{ {
@ -3091,7 +3101,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
switch (cmd->subtype) switch (cmd->subtype)
{ {
case AT_AddColumn: case AT_AddColumn:
case AT_AddColumnToView: case AT_AddColumnRecurse:
{ {
ColumnDef *def = castNode(ColumnDef, cmd->def); ColumnDef *def = castNode(ColumnDef, cmd->def);
@ -3115,6 +3125,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
} }
case AT_AddConstraint: case AT_AddConstraint:
case AT_AddConstraintRecurse:
/* /*
* The original AddConstraint cmd node doesn't go to newcmds * The original AddConstraint cmd node doesn't go to newcmds
@ -3130,19 +3141,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
(int) nodeTag(cmd->def)); (int) nodeTag(cmd->def));
break; 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: case AT_AlterColumnType:
{ {
ColumnDef *def = (ColumnDef *) cmd->def; ColumnDef *def = castNode(ColumnDef, cmd->def);
AttrNumber attnum; AttrNumber attnum;
/* /*
@ -3161,13 +3162,13 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
* change the data type of the sequence. * change the data type of the sequence.
*/ */
attnum = get_attnum(relid, cmd->name); 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 (TupleDescAttr(tupdesc, attnum - 1)->attidentity)
* if attribute not found, something will error about it
* later
*/
if (attnum != InvalidAttrNumber &&
TupleDescAttr(tupdesc, attnum - 1)->attidentity)
{ {
Oid seq_relid = getIdentitySequence(relid, attnum, false); Oid seq_relid = getIdentitySequence(relid, attnum, false);
Oid typeOid = typenameTypeId(pstate, def->typeName); Oid typeOid = typenameTypeId(pstate, def->typeName);
@ -3196,16 +3197,16 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cmd->def = (Node *) newdef; cmd->def = (Node *) newdef;
attnum = get_attnum(relid, cmd->name); 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))));
/* generateSerialExtraStmts(&cxt, newdef,
* if attribute not found, something will error about it get_atttype(relid, attnum),
* later def->options, true, true,
*/ NULL, NULL);
if (attnum != InvalidAttrNumber)
generateSerialExtraStmts(&cxt, newdef,
get_atttype(relid, attnum),
def->options, true,
NULL, NULL);
newcmds = lappend(newcmds, cmd); newcmds = lappend(newcmds, cmd);
break; break;
@ -3221,6 +3222,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List *newseqopts = NIL; List *newseqopts = NIL;
List *newdef = NIL; List *newdef = NIL;
AttrNumber attnum; AttrNumber attnum;
Oid seq_relid;
/* /*
* Split options into those handled by ALTER SEQUENCE and * Split options into those handled by ALTER SEQUENCE and
@ -3237,29 +3239,34 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
} }
attnum = get_attnum(relid, cmd->name); 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) seq_relid = getIdentitySequence(relid, attnum, true);
if (seq_relid)
{ {
Oid seq_relid = getIdentitySequence(relid, attnum, true); AlterSeqStmt *seqstmt;
if (seq_relid) seqstmt = makeNode(AlterSeqStmt);
{ seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
AlterSeqStmt *seqstmt; get_rel_name(seq_relid), -1);
seqstmt->options = newseqopts;
seqstmt->for_identity = true;
seqstmt->missing_ok = false;
seqstmt = makeNode(AlterSeqStmt); cxt.blist = lappend(cxt.blist, seqstmt);
seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
get_rel_name(seq_relid), -1);
seqstmt->options = newseqopts;
seqstmt->for_identity = true;
seqstmt->missing_ok = false;
cxt.alist = lappend(cxt.alist, seqstmt);
}
} }
/* /*
* If column was not found or was not an identity column, * If column was not an identity column, we just let the
* we just let the ALTER TABLE command error out later. * 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; cmd->def = (Node *) newdef;
@ -3281,6 +3288,12 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break; break;
default: 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); newcmds = lappend(newcmds, cmd);
break; break;
} }
@ -3361,11 +3374,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
*/ */
stmt->cmds = newcmds; stmt->cmds = newcmds;
result = lappend(cxt.blist, stmt); *beforeStmts = cxt.blist;
result = list_concat(result, cxt.alist); *afterStmts = list_concat(cxt.alist, save_alist);
result = list_concat(result, save_alist);
return result; return stmt;
} }

View File

@ -1093,8 +1093,6 @@ ProcessUtilitySlow(ParseState *pstate,
{ {
AlterTableStmt *atstmt = (AlterTableStmt *) parsetree; AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
Oid relid; Oid relid;
List *stmts;
ListCell *l;
LOCKMODE lockmode; LOCKMODE lockmode;
/* /*
@ -1108,59 +1106,21 @@ ProcessUtilitySlow(ParseState *pstate,
if (OidIsValid(relid)) if (OidIsValid(relid))
{ {
/* Run parse analysis ... */ AlterTableUtilityContext atcontext;
stmts = transformAlterTableStmt(relid, atstmt,
queryString); /* 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 ... */ /* ... ensure we have an event trigger context ... */
EventTriggerAlterTableStart(parsetree); EventTriggerAlterTableStart(parsetree);
EventTriggerAlterTableRelid(relid); EventTriggerAlterTableRelid(relid);
/* ... and do it */ /* ... and do it */
foreach(l, stmts) AlterTable(atstmt, lockmode, &atcontext);
{
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();
}
/* done */ /* done */
EventTriggerAlterTableEnd(); EventTriggerAlterTableEnd();
@ -1717,6 +1677,52 @@ ProcessUtilitySlow(ParseState *pstate,
PG_END_TRY(); PG_END_TRY();
} }
/*
* 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 * Dispatch function for DropStmt
*/ */
@ -2394,14 +2400,15 @@ CreateCommandTag(Node *parsetree)
break; break;
case T_RenameStmt: case T_RenameStmt:
/* /*
* When the column is renamed, the command tag is created * When the column is renamed, the command tag is created from its
* from its relation type * relation type
*/ */
tag = AlterObjectTypeCommandTag( tag = AlterObjectTypeCommandTag(
((RenameStmt *) parsetree)->renameType == OBJECT_COLUMN ? ((RenameStmt *) parsetree)->renameType == OBJECT_COLUMN ?
((RenameStmt *) parsetree)->relationType : ((RenameStmt *) parsetree)->relationType :
((RenameStmt *) parsetree)->renameType); ((RenameStmt *) parsetree)->renameType);
break; break;
case T_AlterObjectDependsStmt: case T_AlterObjectDependsStmt:

View File

@ -21,6 +21,8 @@
#include "storage/lock.h" #include "storage/lock.h"
#include "utils/relcache.h" #include "utils/relcache.h"
struct AlterTableUtilityContext; /* avoid including tcop/utility.h here */
extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
ObjectAddress *typaddress, const char *queryString); ObjectAddress *typaddress, const char *queryString);
@ -29,7 +31,8 @@ extern void RemoveRelations(DropStmt *drop);
extern Oid AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode); 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); extern LOCKMODE AlterTableGetLockLevel(List *cmds);

View File

@ -1802,8 +1802,6 @@ typedef enum AlterTableType
AT_AlterConstraint, /* alter constraint */ AT_AlterConstraint, /* alter constraint */
AT_ValidateConstraint, /* validate constraint */ AT_ValidateConstraint, /* validate constraint */
AT_ValidateConstraintRecurse, /* internal to commands/tablecmds.c */ 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_AddIndexConstraint, /* add constraint using existing index */
AT_DropConstraint, /* drop constraint */ AT_DropConstraint, /* drop constraint */
AT_DropConstraintRecurse, /* internal to commands/tablecmds.c */ AT_DropConstraintRecurse, /* internal to commands/tablecmds.c */

View File

@ -20,8 +20,10 @@ struct AttrMap; /* avoid including attmap.h here */
extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString); extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
extern List *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
const char *queryString); const char *queryString,
List **beforeStmts,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString); const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,

View File

@ -25,6 +25,16 @@ typedef enum
PROCESS_UTILITY_SUBCOMMAND /* a portion of a query */ PROCESS_UTILITY_SUBCOMMAND /* a portion of a query */
} ProcessUtilityContext; } 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() */ /* Hook for plugins to get control in ProcessUtility() */
typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt, typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
const char *queryString, ProcessUtilityContext context, const char *queryString, ProcessUtilityContext context,
@ -42,6 +52,9 @@ extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
QueryEnvironment *queryEnv, QueryEnvironment *queryEnv,
DestReceiver *dest, char *completionTag); DestReceiver *dest, char *completionTag);
extern void ProcessUtilityForAlterTable(Node *stmt,
AlterTableUtilityContext *context);
extern bool UtilityReturnsTuples(Node *parsetree); extern bool UtilityReturnsTuples(Node *parsetree);
extern TupleDesc UtilityTupleDescriptor(Node *parsetree); extern TupleDesc UtilityTupleDescriptor(Node *parsetree);

View File

@ -162,9 +162,6 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_ValidateConstraintRecurse: case AT_ValidateConstraintRecurse:
strtype = "VALIDATE CONSTRAINT (and recurse)"; strtype = "VALIDATE CONSTRAINT (and recurse)";
break; break;
case AT_ProcessedConstraint:
strtype = "ADD (processed) CONSTRAINT";
break;
case AT_AddIndexConstraint: case AT_AddIndexConstraint:
strtype = "ADD CONSTRAINT (using index)"; strtype = "ADD CONSTRAINT (using index)";
break; break;

View File

@ -1958,27 +1958,29 @@ Indexes:
"anothertab_f4_idx" UNIQUE, btree (f4) "anothertab_f4_idx" UNIQUE, btree (f4)
drop table anothertab; drop table anothertab;
create table another (f1 int, f2 text); -- test that USING expressions are parsed before column alter type / drop steps
insert into another values(1, 'one'); create table another (f1 int, f2 text, f3 text);
insert into another values(2, 'two'); insert into another values(1, 'one', 'uno');
insert into another values(3, 'three'); insert into another values(2, 'two', 'due');
insert into another values(3, 'three', 'tre');
select * from another; select * from another;
f1 | f2 f1 | f2 | f3
----+------- ----+-------+-----
1 | one 1 | one | uno
2 | two 2 | two | due
3 | three 3 | three | tre
(3 rows) (3 rows)
alter table another alter table another
alter f1 type text using f2 || ' more', alter f1 type text using f2 || ' and ' || f3 || ' more',
alter f2 type bigint using f1 * 10; alter f2 type bigint using f1 * 10,
drop column f3;
select * from another; select * from another;
f1 | f2 f1 | f2
------------+---- --------------------+----
one more | 10 one and uno more | 10
two more | 20 two and due more | 20
three more | 30 three and tre more | 30
(3 rows) (3 rows)
drop table another; drop table another;
@ -3469,7 +3471,7 @@ NOTICE: column "c2" of relation "test_add_column" already exists, skipping
ALTER TABLE test_add_column ALTER TABLE test_add_column
ADD COLUMN c2 integer, -- fail because c2 already exists 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 ERROR: column "c2" of relation "test_add_column" already exists
\d test_add_column \d test_add_column
Table "public.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 ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c2 integer, -- skipping because c2 already exists 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 NOTICE: column "c2" of relation "test_add_column" already exists, skipping
\d test_add_column \d test_add_column
Table "public.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 | | | c1 | integer | | |
c2 | integer | | | c2 | integer | | |
c3 | integer | | | c3 | integer | | not null |
Indexes:
"test_add_column_pkey" PRIMARY KEY, btree (c3)
ALTER TABLE 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 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 "c2" of relation "test_add_column" already exists, skipping
NOTICE: column "c3" of relation "test_add_column" already exists, skipping NOTICE: column "c3" of relation "test_add_column" already exists, skipping
\d test_add_column \d test_add_column
@ -3501,12 +3505,14 @@ NOTICE: column "c3" of relation "test_add_column" already exists, skipping
--------+---------+-----------+----------+--------- --------+---------+-----------+----------+---------
c1 | integer | | | c1 | integer | | |
c2 | integer | | | c2 | integer | | |
c3 | integer | | | c3 | integer | | not null |
Indexes:
"test_add_column_pkey" PRIMARY KEY, btree (c3)
ALTER TABLE 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 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, -- 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 "c2" of relation "test_add_column" already exists, skipping
NOTICE: column "c3" of relation "test_add_column" already exists, skipping NOTICE: column "c3" of relation "test_add_column" already exists, skipping
\d test_add_column \d test_add_column
@ -3515,10 +3521,118 @@ NOTICE: column "c3" of relation "test_add_column" already exists, skipping
--------+---------+-----------+----------+--------- --------+---------+-----------+----------+---------
c1 | integer | | | c1 | integer | | |
c2 | integer | | | c2 | integer | | |
c3 | integer | | | c3 | integer | | not null |
c4 | integer | | | c4 | integer | | |
Indexes:
"test_add_column_pkey" PRIMARY KEY, btree (c3)
Foreign-key constraints:
"test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
Referenced by:
TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
NOTICE: column "c4" of relation "test_add_column" already exists, skipping
\d test_add_column
Table "public.test_add_column"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
c1 | integer | | |
c2 | integer | | |
c3 | integer | | not null |
c4 | integer | | |
Indexes:
"test_add_column_pkey" PRIMARY KEY, btree (c3)
Foreign-key constraints:
"test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
Referenced by:
TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8);
\d test_add_column
Table "public.test_add_column"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------------------------------------------
c1 | integer | | |
c2 | integer | | |
c3 | integer | | not null |
c4 | integer | | |
c5 | integer | | not null | nextval('test_add_column_c5_seq'::regclass)
Indexes:
"test_add_column_pkey" PRIMARY KEY, btree (c3)
Check constraints:
"test_add_column_c5_check" CHECK (c5 > 8)
Foreign-key constraints:
"test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
Referenced by:
TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10);
NOTICE: column "c5" of relation "test_add_column" already exists, skipping
\d test_add_column*
Table "public.test_add_column"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------------------------------------------
c1 | integer | | |
c2 | integer | | |
c3 | integer | | not null |
c4 | integer | | |
c5 | integer | | not null | nextval('test_add_column_c5_seq'::regclass)
Indexes:
"test_add_column_pkey" PRIMARY KEY, btree (c3)
Check constraints:
"test_add_column_c5_check" CHECK (c5 > 8)
Foreign-key constraints:
"test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
Referenced by:
TABLE "test_add_column" CONSTRAINT "test_add_column_c4_fkey" FOREIGN KEY (c4) REFERENCES test_add_column(c3)
Sequence "public.test_add_column_c5_seq"
Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
---------+-------+---------+------------+-----------+---------+-------
integer | 1 | 1 | 2147483647 | 1 | no | 1
Owned by: public.test_add_column.c5
Index "public.test_add_column_pkey"
Column | Type | Key? | Definition
--------+---------+------+------------
c3 | integer | yes | c3
primary key, btree, for table "public.test_add_column"
DROP TABLE test_add_column; DROP TABLE test_add_column;
\d test_add_column*
-- assorted cases with multiple ALTER TABLE steps
CREATE TABLE ataddindex(f1 INT);
INSERT INTO ataddindex VALUES (42), (43);
CREATE UNIQUE INDEX ataddindexi0 ON ataddindex(f1);
ALTER TABLE ataddindex
ADD PRIMARY KEY USING INDEX ataddindexi0,
ALTER f1 TYPE BIGINT;
\d ataddindex
Table "public.ataddindex"
Column | Type | Collation | Nullable | Default
--------+--------+-----------+----------+---------
f1 | bigint | | not null |
Indexes:
"ataddindexi0" PRIMARY KEY, btree (f1)
DROP TABLE ataddindex;
CREATE TABLE ataddindex(f1 VARCHAR(10));
INSERT INTO ataddindex(f1) VALUES ('foo'), ('a');
ALTER TABLE ataddindex
ALTER f1 SET DATA TYPE TEXT,
ADD EXCLUDE ((f1 LIKE 'a') WITH =);
\d ataddindex
Table "public.ataddindex"
Column | Type | Collation | Nullable | Default
--------+------+-----------+----------+---------
f1 | text | | |
Indexes:
"ataddindex_expr_excl" EXCLUDE USING btree ((f1 ~~ 'a'::text) WITH =)
DROP TABLE ataddindex;
-- unsupported constraint types for partitioned tables -- unsupported constraint types for partitioned tables
CREATE TABLE partitioned ( CREATE TABLE partitioned (
a int, a int,

View File

@ -387,6 +387,68 @@ SELECT * FROM itest8;
RESET ROLE; RESET ROLE;
DROP TABLE itest8; DROP TABLE itest8;
DROP USER regress_identity_user1; 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) -- typed tables (currently not supported)
CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint); 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 CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error

View File

@ -1342,17 +1342,19 @@ alter table anothertab alter column f5 type bigint;
drop table anothertab; 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(1, 'one', 'uno');
insert into another values(2, 'two'); insert into another values(2, 'two', 'due');
insert into another values(3, 'three'); insert into another values(3, 'three', 'tre');
select * from another; select * from another;
alter table another alter table another
alter f1 type text using f2 || ' more', alter f1 type text using f2 || ' and ' || f3 || ' more',
alter f2 type bigint using f1 * 10; alter f2 type bigint using f1 * 10,
drop column f3;
select * from another; select * from another;
@ -2170,22 +2172,50 @@ ALTER TABLE ONLY test_add_column
\d test_add_column \d test_add_column
ALTER TABLE test_add_column ALTER TABLE test_add_column
ADD COLUMN c2 integer, -- fail because c2 already exists ADD COLUMN c2 integer, -- fail because c2 already exists
ADD COLUMN c3 integer; ADD COLUMN c3 integer primary key;
\d test_add_column \d test_add_column
ALTER TABLE 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 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 \d test_add_column
ALTER TABLE 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 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 \d test_add_column
ALTER TABLE 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 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, -- skipping because c3 already exists
ADD COLUMN c4 integer; ADD COLUMN c4 integer REFERENCES test_add_column;
\d test_add_column \d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c4 integer REFERENCES test_add_column;
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 8);
\d test_add_column
ALTER TABLE test_add_column
ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10);
\d test_add_column*
DROP TABLE test_add_column; DROP TABLE test_add_column;
\d test_add_column*
-- assorted cases with multiple ALTER TABLE steps
CREATE TABLE ataddindex(f1 INT);
INSERT INTO ataddindex VALUES (42), (43);
CREATE UNIQUE INDEX ataddindexi0 ON ataddindex(f1);
ALTER TABLE ataddindex
ADD PRIMARY KEY USING INDEX ataddindexi0,
ALTER f1 TYPE BIGINT;
\d ataddindex
DROP TABLE ataddindex;
CREATE TABLE ataddindex(f1 VARCHAR(10));
INSERT INTO ataddindex(f1) VALUES ('foo'), ('a');
ALTER TABLE ataddindex
ALTER f1 SET DATA TYPE TEXT,
ADD EXCLUDE ((f1 LIKE 'a') WITH =);
\d ataddindex
DROP TABLE ataddindex;
-- unsupported constraint types for partitioned tables -- unsupported constraint types for partitioned tables
CREATE TABLE partitioned ( CREATE TABLE partitioned (

View File

@ -239,6 +239,44 @@ RESET ROLE;
DROP TABLE itest8; DROP TABLE itest8;
DROP USER regress_identity_user1; 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) -- typed tables (currently not supported)