alter-table-with-recursive-process-utility-calls-wip.patch
text/x-diff
Filename: alter-table-with-recursive-process-utility-calls-wip.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| src/backend/commands/tablecmds.c | 90 | 27 |
| src/backend/parser/parse_utilcmd.c | 64 | 90 |
| src/backend/tcop/utility.c | 58 | 28 |
| src/include/commands/tablecmds.h | 4 | 1 |
| src/include/parser/parse_utilcmd.h | 3 | 0 |
| src/include/tcop/utility.h | 13 | 0 |
| src/test/regress/expected/identity.out | 63 | 1 |
| src/test/regress/sql/identity.sql | 39 | 1 |
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e34d4cc..73c56ed 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -86,6 +86,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"
@@ -340,12 +341,15 @@ 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,
+ ProcessUtilityForAlterTableContext *pcontext);
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing, LOCKMODE lockmode);
-static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode);
+static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+ ProcessUtilityForAlterTableContext *pcontext);
static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
- AlterTableCmd *cmd, LOCKMODE lockmode);
+ AlterTableCmd *cmd, LOCKMODE lockmode,
+ ProcessUtilityForAlterTableContext *pcontext);
static void ATRewriteTables(AlterTableStmt *parsetree,
List **wqueue, LOCKMODE lockmode);
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
@@ -383,9 +387,11 @@ static bool ConstraintImpliedByRelConstraint(Relation scanrel,
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
Node *newDefault, LOCKMODE lockmode);
static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
- Node *def, LOCKMODE lockmode);
+ Node *def, LOCKMODE lockmode,
+ ProcessUtilityForAlterTableContext *pcontext);
static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
- Node *def, LOCKMODE lockmode);
+ Node *def, LOCKMODE lockmode,
+ ProcessUtilityForAlterTableContext *pcontext);
static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode);
static void ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum,
Node *newValue, LOCKMODE lockmode);
@@ -3467,7 +3473,8 @@ AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode)
* rather than reassess it at lower levels.
*/
void
-AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
+AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt,
+ ProcessUtilityForAlterTableContext *pcontext)
{
Relation rel;
@@ -3476,7 +3483,8 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
- ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
+ ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode,
+ pcontext);
}
/*
@@ -3489,6 +3497,9 @@ 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.
+ *
+ * This also doesn't support subcommands that need to recursively call
+ * ProcessUtility, so no pcontext argument is needed.
*/
void
AlterTableInternal(Oid relid, List *cmds, bool recurse)
@@ -3500,7 +3511,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
EventTriggerAlterTableRelid(relid);
- ATController(NULL, rel, cmds, recurse, lockmode);
+ ATController(NULL, rel, cmds, recurse, lockmode, NULL);
}
/*
@@ -3798,7 +3809,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,
+ ProcessUtilityForAlterTableContext *pcontext)
{
List *wqueue = NIL;
ListCell *lcmd;
@@ -3815,7 +3827,7 @@ ATController(AlterTableStmt *parsetree,
relation_close(rel, NoLock);
/* Phase 2: update system catalogs */
- ATRewriteCatalogs(&wqueue, lockmode);
+ ATRewriteCatalogs(&wqueue, lockmode, pcontext);
/* Phase 3: scan/rewrite tables as needed */
ATRewriteTables(parsetree, &wqueue, lockmode);
@@ -3884,7 +3896,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_AddIdentity:
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
- pass = AT_PASS_ADD_CONSTR;
+ pass = AT_PASS_COL_ATTRS;
break;
case AT_DropIdentity:
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
@@ -4122,7 +4134,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
* conflicts).
*/
static void
-ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
+ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
+ ProcessUtilityForAlterTableContext *pcontext)
{
int pass;
ListCell *ltab;
@@ -4153,9 +4166,12 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
rel = relation_open(tab->relid, NoLock);
foreach(lcmd, subcmds)
+ {
ATExecCmd(wqueue, tab, rel,
castNode(AlterTableCmd, lfirst(lcmd)),
- lockmode);
+ lockmode,
+ pcontext);
+ }
/*
* After the ALTER TYPE pass, do cleanup work (this is not done in
@@ -4192,7 +4208,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode)
*/
static void
ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
- AlterTableCmd *cmd, LOCKMODE lockmode)
+ AlterTableCmd *cmd, LOCKMODE lockmode,
+ ProcessUtilityForAlterTableContext *pcontext)
{
ObjectAddress address = InvalidObjectAddress;
@@ -4213,10 +4230,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
break;
case AT_AddIdentity:
- address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
+ address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode,
+ pcontext);
break;
case AT_SetIdentity:
- address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode);
+ address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode,
+ pcontext);
break;
case AT_DropIdentity:
address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode);
@@ -6398,14 +6417,17 @@ ATExecColumnDefault(Relation rel, const char *colName,
*/
static ObjectAddress
ATExecAddIdentity(Relation rel, const char *colName,
- Node *def, LOCKMODE lockmode)
+ Node *def, LOCKMODE lockmode,
+ ProcessUtilityForAlterTableContext *pcontext)
{
+ Constraint *condef = castNode(Constraint, def);
Relation attrelation;
HeapTuple tuple;
Form_pg_attribute attTup;
AttrNumber attnum;
+ List *seqcmds;
+ ListCell *lc;
ObjectAddress address;
- ColumnDef *cdef = castNode(ColumnDef, def);
attrelation = table_open(AttributeRelationId, RowExclusiveLock);
@@ -6448,9 +6470,22 @@ ATExecAddIdentity(Relation rel, const char *colName,
errmsg("column \"%s\" of relation \"%s\" already has a default value",
colName, RelationGetRelationName(rel))));
- attTup->attidentity = cdef->identity;
+ /* Update column's attidentity state */
+ attTup->attidentity = condef->generated_when;
CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
+ /* Create the required supporting sequence */
+ seqcmds = transformIdentityColumnSerialOptions(rel,
+ NameStr(attTup->attname),
+ attTup->atttypid,
+ condef->options);
+ foreach(lc, seqcmds)
+ {
+ Node *stmt = lfirst(lc);
+
+ ProcessUtilityForAlterTable(stmt, pcontext);
+ }
+
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel),
attTup->attnum);
@@ -6469,17 +6504,21 @@ ATExecAddIdentity(Relation rel, const char *colName,
* Return the address of the affected column.
*/
static ObjectAddress
-ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode)
+ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode,
+ ProcessUtilityForAlterTableContext *pcontext)
{
+ List *options = castNode(List, def);
ListCell *option;
DefElem *generatedEl = NULL;
+ List *seqoptions = NIL;
HeapTuple tuple;
Form_pg_attribute attTup;
AttrNumber attnum;
Relation attrelation;
ObjectAddress address;
- foreach(option, castNode(List, def))
+ /* Examine options */
+ foreach(option, options)
{
DefElem *defel = lfirst_node(DefElem, option);
@@ -6492,14 +6531,15 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
generatedEl = defel;
}
else
- elog(ERROR, "option \"%s\" not recognized",
- defel->defname);
+ {
+ /* Assume it is an option for ALTER SEQUENCE */
+ seqoptions = lappend(seqoptions, defel);
+ }
}
/*
- * Even if there is nothing to change here, we run all the checks. There
- * will be a subsequent ALTER SEQUENCE that relies on everything being
- * there.
+ * Even if there is nothing to change, verify that target column is valid
+ * for the command.
*/
attrelation = table_open(AttributeRelationId, RowExclusiveLock);
@@ -6525,6 +6565,7 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
errmsg("column \"%s\" of relation \"%s\" is not an identity column",
colName, RelationGetRelationName(rel))));
+ /* Apply attidentity change if given */
if (generatedEl)
{
attTup->attidentity = defGetInt32(generatedEl);
@@ -6532,13 +6573,35 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
InvokeObjectPostAlterHook(RelationRelationId,
RelationGetRelid(rel),
- attTup->attnum);
+ attnum);
ObjectAddressSubSet(address, RelationRelationId,
RelationGetRelid(rel), attnum);
}
else
address = InvalidObjectAddress;
+ /* Apply sequence options if given */
+ if (seqoptions)
+ {
+ List *seqlist = getOwnedSequences(RelationGetRelid(rel), attnum);
+ ListCell *seqcell;
+
+ foreach(seqcell, seqlist)
+ {
+ Oid seq_relid = lfirst_oid(seqcell);
+ AlterSeqStmt *seqstmt;
+
+ seqstmt = makeNode(AlterSeqStmt);
+ seqstmt->sequence = makeRangeVar(get_namespace_name(get_rel_namespace(seq_relid)),
+ get_rel_name(seq_relid), -1);
+ seqstmt->options = seqoptions;
+ seqstmt->for_identity = true;
+ seqstmt->missing_ok = false;
+
+ ProcessUtilityForAlterTable((Node *) seqstmt, pcontext);
+ }
+ }
+
heap_freetuple(tuple);
table_close(attrelation, RowExclusiveLock);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 7450d74..a0526d4 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -113,6 +113,10 @@ typedef struct
} CreateSchemaStmtContext;
+static void generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
+ Oid seqtypid, List *seqoptions,
+ bool for_identity,
+ char **snamespace_p, char **sname_p);
static void transformColumnDefinition(CreateStmtContext *cxt,
ColumnDef *column);
static void transformTableConstraint(CreateStmtContext *cxt,
@@ -337,6 +341,38 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
}
/*
+ * transformIdentityColumnSerialOptions
+ * Generate CREATE SEQUENCE and ALTER SEQUENCE ... OWNED BY statements
+ * to create the sequence for an identity column.
+ *
+ * This is used during ALTER TABLE ADD IDENTITY. We don't need to separate
+ * the execution of the two commands, because the column already exists and
+ * doesn't need its default expression set. So just pass them back as a
+ * single List.
+ */
+List *
+transformIdentityColumnSerialOptions(Relation rel,
+ char *colName, Oid colTypeOid,
+ List *seqoptions)
+{
+ CreateStmtContext cxt;
+ ColumnDef *column = makeNode(ColumnDef);
+
+ /* Set up just enough of cxt for generateSerialExtraStmts() */
+ memset(&cxt, 0, sizeof(cxt));
+ cxt.stmtType = "ALTER TABLE";
+ cxt.rel = rel;
+
+ /* Need a mostly-dummy ColumnDef, too */
+ column->colname = colName;
+
+ generateSerialExtraStmts(&cxt, column, colTypeOid, seqoptions, true,
+ NULL, NULL);
+
+ return list_concat(cxt.blist, cxt.alist);
+}
+
+/*
* generateSerialExtraStmts
* Generate CREATE SEQUENCE and ALTER SEQUENCE ... OWNED BY statements
* to create the sequence for a serial or identity column.
@@ -350,7 +386,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
Oid seqtypid, List *seqoptions, bool for_identity,
char **snamespace_p, char **sname_p)
{
- ListCell *option;
+ char *relname;
DefElem *nameEl = NULL;
Oid snamespaceid;
char *snamespace;
@@ -358,6 +394,14 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
CreateSeqStmt *seqstmt;
AlterSeqStmt *altseqstmt;
List *attnamelist;
+ ListCell *option;
+
+ /*
+ * Get name of relation. Note: this function mustn't access cxt->relation
+ * when cxt->rel is set, because transformIdentityColumnSerialOptions()
+ * only provides the latter.
+ */
+ relname = cxt->rel ? RelationGetRelationName(cxt->rel) : cxt->relation->relname;
/*
* Determine namespace and name to use for the sequence.
@@ -415,7 +459,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
RangeVarAdjustRelationPersistence(cxt->relation, snamespaceid);
}
snamespace = get_namespace_name(snamespaceid);
- sname = ChooseRelationName(cxt->relation->relname,
+ sname = ChooseRelationName(relname,
column->colname,
"seq",
snamespaceid,
@@ -425,7 +469,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
ereport(DEBUG1,
(errmsg("%s will create implicit sequence \"%s\" for serial column \"%s.%s\"",
cxt->stmtType, sname,
- cxt->relation->relname, column->colname)));
+ relname, column->colname)));
/*
* Build a CREATE SEQUENCE command to create the sequence object, and add
@@ -478,7 +522,7 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
altseqstmt = makeNode(AlterSeqStmt);
altseqstmt->sequence = makeRangeVar(snamespace, sname, -1);
attnamelist = list_make3(makeString(snamespace),
- makeString(cxt->relation->relname),
+ makeString(relname),
makeString(column->colname));
altseqstmt->options = list_make1(makeDefElem("owned_by",
(Node *) attnamelist, -1));
@@ -3077,8 +3121,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
/*
* The only subtypes that currently require parse transformation handling
- * are ADD COLUMN, ADD CONSTRAINT and SET DATA TYPE. These largely re-use
- * code from CREATE TABLE.
+ * are ADD COLUMN, ADD CONSTRAINT, SET DATA TYPE, and ATTACH/DETACH
+ * PARTITION. These largely re-use code from CREATE TABLE.
+ *
+ * NOW HEAR THIS: you can NOT put code here that examines the current
+ * properties of the target table or anything associated with it. Such
+ * code will do the wrong thing if any preceding ALTER TABLE subcommand
+ * changes the property in question. Wait till runtime to look at the
+ * table.
*/
foreach(lcmd, stmt->cmds)
{
@@ -3155,6 +3205,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
/*
* For identity column, create ALTER SEQUENCE command to
* change the data type of the sequence.
+ *
+ * XXX This is a direct violation of the advice given
+ * above to not look at the table's properties yet. It
+ * accidentally works (at least for most cases) because of
+ * the ordering of operations in ALTER TABLE --- note in
+ * particular that we must add the new command to blist
+ * not alist. But we ought to move this to be done at
+ * execution.
*/
attnum = get_attnum(relid, cmd->name);
@@ -3181,90 +3239,6 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
break;
}
- case AT_AddIdentity:
- {
- Constraint *def = castNode(Constraint, cmd->def);
- ColumnDef *newdef = makeNode(ColumnDef);
- AttrNumber attnum;
-
- newdef->colname = cmd->name;
- newdef->identity = def->generated_when;
- cmd->def = (Node *) newdef;
-
- attnum = get_attnum(relid, cmd->name);
-
- /*
- * if attribute not found, something will error about it
- * later
- */
- if (attnum != InvalidAttrNumber)
- generateSerialExtraStmts(&cxt, newdef,
- get_atttype(relid, attnum),
- def->options, true,
- NULL, NULL);
-
- newcmds = lappend(newcmds, cmd);
- break;
- }
-
- case AT_SetIdentity:
- {
- /*
- * Create an ALTER SEQUENCE statement for the internal
- * sequence of the identity column.
- */
- ListCell *lc;
- List *newseqopts = NIL;
- List *newdef = NIL;
- List *seqlist;
- AttrNumber attnum;
-
- /*
- * Split options into those handled by ALTER SEQUENCE and
- * those for ALTER TABLE proper.
- */
- foreach(lc, castNode(List, cmd->def))
- {
- DefElem *def = lfirst_node(DefElem, lc);
-
- if (strcmp(def->defname, "generated") == 0)
- newdef = lappend(newdef, def);
- else
- newseqopts = lappend(newseqopts, def);
- }
-
- attnum = get_attnum(relid, cmd->name);
-
- if (attnum)
- {
- seqlist = getOwnedSequences(relid, attnum);
- if (seqlist)
- {
- AlterSeqStmt *seqstmt;
- Oid seq_relid;
-
- seqstmt = makeNode(AlterSeqStmt);
- seq_relid = linitial_oid(seqlist);
- 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,
- * we just let the ALTER TABLE command error out later.
- */
-
- cmd->def = (Node *) newdef;
- newcmds = lappend(newcmds, cmd);
- break;
- }
-
case AT_AttachPartition:
case AT_DetachPartition:
{
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9578b5c..937a9b7 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1075,7 +1075,7 @@ ProcessUtilitySlow(ParseState *pstate,
queryString,
PROCESS_UTILITY_SUBCOMMAND,
params,
- NULL,
+ queryEnv,
None_Receiver,
NULL);
}
@@ -1097,8 +1097,6 @@ ProcessUtilitySlow(ParseState *pstate,
{
AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
Oid relid;
- List *stmts;
- ListCell *l;
LOCKMODE lockmode;
/*
@@ -1112,10 +1110,21 @@ ProcessUtilitySlow(ParseState *pstate,
if (OidIsValid(relid))
{
+ List *stmts;
+ ProcessUtilityForAlterTableContext pcontext;
+ ListCell *l;
+
/* Run parse analysis ... */
stmts = transformAlterTableStmt(relid, atstmt,
queryString);
+ /* ... set up info for possible recursion ... */
+ pcontext.pstmt = pstmt;
+ pcontext.queryString = queryString;
+ pcontext.params = params;
+ pcontext.queryEnv = queryEnv;
+ pcontext.relid = relid;
+
/* ... ensure we have an event trigger context ... */
EventTriggerAlterTableStart(parsetree);
EventTriggerAlterTableRelid(relid);
@@ -1129,36 +1138,21 @@ ProcessUtilitySlow(ParseState *pstate,
{
/* Do the table alteration proper */
AlterTable(relid, lockmode,
- (AlterTableStmt *) stmt);
+ (AlterTableStmt *) stmt,
+ &pcontext);
}
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.
+ * Recurse for anything else. We get here if
+ * transformAlterTableStmt() tacked extra
+ * commands onto its output, but it's also
+ * possible for AlterTable() to generate extra
+ * commands on-the-fly, in which case it will
+ * call ProcessUtilityForAlterTable directly.
*/
- 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);
+ ProcessUtilityForAlterTable(stmt,
+ &pcontext);
}
/* Need CCI between commands */
@@ -1719,6 +1713,42 @@ ProcessUtilitySlow(ParseState *pstate,
}
/*
+ * ProcessUtilityForAlterTable
+ * Recursively process an arbitrary utility command as a subcommand
+ * of ALTER TABLE.
+ */
+void
+ProcessUtilityForAlterTable(Node *stmt,
+ ProcessUtilityForAlterTableContext *pcontext)
+{
+ PlannedStmt *wrapper = makeNode(PlannedStmt);
+
+ /*
+ * When recursing, "close" the current complex-command set, and start a
+ * new one afterwards; this is needed to ensure the ordering of queued
+ * commands is consistent with the way they are executed here.
+ */
+ EventTriggerAlterTableEnd();
+
+ wrapper->commandType = CMD_UTILITY;
+ wrapper->canSetTag = false;
+ wrapper->utilityStmt = stmt;
+ wrapper->stmt_location = pcontext->pstmt->stmt_location;
+ wrapper->stmt_len = pcontext->pstmt->stmt_len;
+
+ ProcessUtility(wrapper,
+ pcontext->queryString,
+ PROCESS_UTILITY_SUBCOMMAND,
+ pcontext->params,
+ pcontext->queryEnv,
+ None_Receiver,
+ NULL);
+
+ EventTriggerAlterTableStart(pcontext->pstmt->utilityStmt);
+ EventTriggerAlterTableRelid(pcontext->relid);
+}
+
+/*
* Dispatch function for DropStmt
*/
static void
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index b09afa2..25282c0 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -21,6 +21,8 @@
#include "storage/lock.h"
#include "utils/relcache.h"
+struct ProcessUtilityForAlterTableContext; /* avoid including utility.h */
+
extern ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
ObjectAddress *typaddress, const char *queryString);
@@ -29,7 +31,8 @@ extern void RemoveRelations(DropStmt *drop);
extern Oid AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode);
-extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt);
+extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt,
+ struct ProcessUtilityForAlterTableContext *pcontext);
extern LOCKMODE AlterTableGetLockLevel(List *cmds);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 1348064..3f30fc6 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -27,6 +27,9 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
PartitionBoundSpec *spec);
+extern List *transformIdentityColumnSerialOptions(Relation rel,
+ char *colName, Oid colTypeOid,
+ List *seqoptions);
extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel,
Relation source_idx,
const AttrNumber *attmap, int attmap_length,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 5abcacf..34390e6 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -25,6 +25,16 @@ typedef enum
PROCESS_UTILITY_SUBCOMMAND /* a portion of a query */
} ProcessUtilityContext;
+typedef struct ProcessUtilityForAlterTableContext
+{
+ /* Info that has to be passed through an ALTER TABLE */
+ PlannedStmt *pstmt;
+ const char *queryString;
+ ParamListInfo params;
+ QueryEnvironment *queryEnv;
+ Oid relid;
+} ProcessUtilityForAlterTableContext;
+
/* Hook for plugins to get control in ProcessUtility() */
typedef void (*ProcessUtility_hook_type) (PlannedStmt *pstmt,
const char *queryString, ProcessUtilityContext context,
@@ -42,6 +52,9 @@ extern void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
QueryEnvironment *queryEnv,
DestReceiver *dest, char *completionTag);
+extern void ProcessUtilityForAlterTable(Node *stmt,
+ ProcessUtilityForAlterTableContext *pcontext);
+
extern bool UtilityReturnsTuples(Node *parsetree);
extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out
index 2286519..b72c9d0 100644
--- a/src/test/regress/expected/identity.out
+++ b/src/test/regress/expected/identity.out
@@ -39,7 +39,7 @@ SELECT pg_get_serial_sequence('itest1', 'a');
integer | 1 | 1 | 2147483647 | 1 | no | 1
Sequence for identity column: public.itest1.a
-CREATE TABLE itest4 (a int, b text);
+CREATE TABLE itest4 (a int, b text not null);
ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, requires NOT NULL
ERROR: column "a" of relation "itest4" must be declared NOT NULL before identity can be added
ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL;
@@ -387,6 +387,68 @@ SELECT * FROM itest8;
RESET ROLE;
DROP TABLE itest8;
DROP USER regress_identity_user1;
+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+ALTER TABLE itest8
+ ADD COLUMN f2 int NOT NULL,
+ ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+ ADD COLUMN f3 int NOT NULL,
+ ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+ ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+ALTER TABLE itest8
+ ADD COLUMN f4 int;
+ALTER TABLE itest8
+ ALTER COLUMN f4 SET NOT NULL,
+ ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+ ALTER COLUMN f4 SET DATA TYPE bigint;
+ALTER TABLE itest8
+ ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+ALTER TABLE itest8
+ ALTER COLUMN f5 DROP IDENTITY,
+ ALTER COLUMN f5 DROP NOT NULL,
+ ALTER COLUMN f5 SET DATA TYPE bigint;
+INSERT INTO itest8 VALUES(0), (1);
+TABLE itest8;
+ f1 | f2 | f3 | f4 | f5
+----+----+----+----+----
+ 0 | 1 | 1 | 1 |
+ 1 | 2 | 11 | 2 |
+(2 rows)
+
+\d+ itest8
+ Table "public.itest8"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+--------+---------+-----------+----------+----------------------------------+---------+--------------+-------------
+ f1 | integer | | | | plain | |
+ f2 | integer | | not null | generated always as identity | plain | |
+ f3 | integer | | not null | generated by default as identity | plain | |
+ f4 | bigint | | not null | generated always as identity | plain | |
+ f5 | bigint | | | | plain | |
+
+\d itest8_f2_seq
+ Sequence "public.itest8_f2_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer | 1 | 1 | 2147483647 | 1 | no | 1
+Sequence for identity column: public.itest8.f2
+
+\d itest8_f3_seq
+ Sequence "public.itest8_f3_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+---------+-------+---------+------------+-----------+---------+-------
+ integer | 1 | 1 | 2147483647 | 10 | no | 1
+Sequence for identity column: public.itest8.f3
+
+\d itest8_f4_seq
+ Sequence "public.itest8_f4_seq"
+ Type | Start | Minimum | Maximum | Increment | Cycles? | Cache
+--------+-------+---------+---------------------+-----------+---------+-------
+ bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1
+Sequence for identity column: public.itest8.f4
+
+\d itest8_f5_seq
+DROP TABLE itest8;
-- typed tables (currently not supported)
CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint);
CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error
diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql
index 8dcfdf3..5126a66 100644
--- a/src/test/regress/sql/identity.sql
+++ b/src/test/regress/sql/identity.sql
@@ -16,7 +16,7 @@ SELECT pg_get_serial_sequence('itest1', 'a');
\d itest1_a_seq
-CREATE TABLE itest4 (a int, b text);
+CREATE TABLE itest4 (a int, b text not null);
ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, requires NOT NULL
ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL;
ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- ok
@@ -239,6 +239,44 @@ RESET ROLE;
DROP TABLE itest8;
DROP USER regress_identity_user1;
+-- multiple steps in ALTER TABLE
+CREATE TABLE itest8 (f1 int);
+
+ALTER TABLE itest8
+ ADD COLUMN f2 int NOT NULL,
+ ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+ ADD COLUMN f3 int NOT NULL,
+ ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY,
+ ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10;
+
+ALTER TABLE itest8
+ ADD COLUMN f4 int;
+
+ALTER TABLE itest8
+ ALTER COLUMN f4 SET NOT NULL,
+ ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY,
+ ALTER COLUMN f4 SET DATA TYPE bigint;
+
+ALTER TABLE itest8
+ ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY;
+
+ALTER TABLE itest8
+ ALTER COLUMN f5 DROP IDENTITY,
+ ALTER COLUMN f5 DROP NOT NULL,
+ ALTER COLUMN f5 SET DATA TYPE bigint;
+
+INSERT INTO itest8 VALUES(0), (1);
+
+TABLE itest8;
+\d+ itest8
+\d itest8_f2_seq
+\d itest8_f3_seq
+\d itest8_f4_seq
+\d itest8_f5_seq
+DROP TABLE itest8;
+
-- typed tables (currently not supported)