Commit f4a3fdfb authored by Tom Lane's avatar Tom Lane

Avoid order-of-execution problems with ALTER TABLE ADD PRIMARY KEY.

Up to now, DefineIndex() was responsible for adding attnotnull constraints
to the columns of a primary key, in any case where it hadn't been
convenient for transformIndexConstraint() to mark those columns as
is_not_null.  It (or rather its minion index_check_primary_key) did this
by executing an ALTER TABLE SET NOT NULL command for the target table.

The trouble with this solution is that if we're creating the index due
to ALTER TABLE ADD PRIMARY KEY, and the outer ALTER TABLE has additional
sub-commands, the inner ALTER TABLE's operations executed at the wrong
time with respect to the outer ALTER TABLE's operations.  In particular,
the inner ALTER would perform a validation scan at a point where the
table's storage might be inconsistent with its catalog entries.  (This is
on the hairy edge of being a security problem, but AFAICS it isn't one
because the inner scan would only be interested in the tuples' null
bitmaps.)  This can result in unexpected failures, such as the one seen
in bug #15580 from Allison Kaptur.

To fix, let's remove the attempt to do SET NOT NULL from DefineIndex(),
reducing index_check_primary_key's role to verifying that the columns are
already not null.  (It shouldn't ever see such a case, but it seems wise
to keep the check for safety.)  Instead, make transformIndexConstraint()
generate ALTER TABLE SET NOT NULL subcommands to be executed ahead of
the ADD PRIMARY KEY operation in every case where it can't force the
column to be created already-not-null.  This requires only minor surgery
in parse_utilcmd.c, and it makes for a much more satisfying spec for
transformIndexConstraint(): it's no longer having to take it on faith
that someone else will handle addition of NOT NULL constraints.

To make that work, we have to move the execution of AT_SetNotNull into
an ALTER pass that executes ahead of AT_PASS_ADD_INDEX.  I moved it to
AT_PASS_COL_ATTRS, and put that after AT_PASS_ADD_COL to avoid failure
when the column is being added in the same command.  This incidentally
fixes a bug in the only previous usage of AT_PASS_COL_ATTRS, for
AT_SetIdentity: it didn't work either for a newly-added column.

Playing around with this exposed a separate bug in ALTER TABLE ONLY ...
ADD PRIMARY KEY for partitioned tables.  The intent of the ONLY modifier
in that context is to prevent doing anything that would require holding
lock for a long time --- but the implied SET NOT NULL would recurse to
the child partitions, and do an expensive validation scan for any child
where the column(s) were not already NOT NULL.  To fix that, invent a
new ALTER subcommand AT_CheckNotNull that just insists that a child
column be already NOT NULL, and apply that, not AT_SetNotNull, when
recursing to children in this scenario.  This results in a slightly laxer
definition of ALTER TABLE ONLY ... SET NOT NULL for partitioned tables,
too: that command will now work as long as all children are already NOT
NULL, whereas before it just threw up its hands if there were any
partitions.

In passing, clean up the API of generateClonedIndexStmt(): remove a
useless argument, ensure that the output argument is not left undefined,
update the header comment.

A small side effect of this change is that no-such-column errors in ALTER
TABLE ADD PRIMARY KEY now produce a different message that includes the
table name, because they are now detected by the SET NOT NULL step which
has historically worded its error that way.  That seems fine to me, so
I didn't make any effort to avoid the wording change.

The basic bug #15580 is of very long standing, and these other bugs
aren't new in v12 either.  However, this is a pretty significant change
in the way ALTER TABLE ADD PRIMARY KEY works.  On balance it seems best
not to back-patch, at least not till we get some more confidence that
this patch has no new bugs.

Patch by me, but thanks to Jie Zhang for a preliminary version.

Discussion: https://postgr.es/m/15580-d1a6de5a3d65da51@postgresql.org
Discussion: https://postgr.es/m/1396E95157071C4EBBA51892C5368521017F2E6E63@G08CNEXMBPEKD02.g08.fujitsu.local
parent c06e3550
...@@ -185,13 +185,14 @@ relationHasPrimaryKey(Relation rel) ...@@ -185,13 +185,14 @@ relationHasPrimaryKey(Relation rel)
* *
* We check for a pre-existing primary key, and that all columns of the index * We check for a pre-existing primary key, and that all columns of the index
* are simple column references (not expressions), and that all those * are simple column references (not expressions), and that all those
* columns are marked NOT NULL. If they aren't (which can only happen during * columns are marked NOT NULL. If not, fail.
* ALTER TABLE ADD CONSTRAINT, since the parser forces such columns to be
* created NOT NULL during CREATE TABLE), do an ALTER SET NOT NULL to mark
* them so --- or fail if they are not in fact nonnull.
* *
* As of PG v10, the SET NOT NULL is applied to child tables as well, so * We used to automatically change unmarked columns to NOT NULL here by doing
* that the behavior is like a manual SET NOT NULL. * our own local ALTER TABLE command. But that doesn't work well if we're
* executing one subcommand of an ALTER TABLE: the operations may not get
* performed in the right order overall. Now we expect that the parser
* inserted any required ALTER TABLE SET NOT NULL operations before trying
* to create a primary-key index.
* *
* Caller had better have at least ShareLock on the table, else the not-null * Caller had better have at least ShareLock on the table, else the not-null
* checking isn't trustworthy. * checking isn't trustworthy.
...@@ -202,12 +203,11 @@ index_check_primary_key(Relation heapRel, ...@@ -202,12 +203,11 @@ index_check_primary_key(Relation heapRel,
bool is_alter_table, bool is_alter_table,
IndexStmt *stmt) IndexStmt *stmt)
{ {
List *cmds;
int i; int i;
/* /*
* If ALTER TABLE and CREATE TABLE .. PARTITION OF, check that there isn't * If ALTER TABLE or CREATE TABLE .. PARTITION OF, check that there isn't
* already a PRIMARY KEY. In CREATE TABLE for an ordinary relations, we * already a PRIMARY KEY. In CREATE TABLE for an ordinary relation, we
* have faith that the parser rejected multiple pkey clauses; and CREATE * have faith that the parser rejected multiple pkey clauses; and CREATE
* INDEX doesn't have a way to say PRIMARY KEY, so it's no problem either. * INDEX doesn't have a way to say PRIMARY KEY, so it's no problem either.
*/ */
...@@ -222,9 +222,9 @@ index_check_primary_key(Relation heapRel, ...@@ -222,9 +222,9 @@ index_check_primary_key(Relation heapRel,
/* /*
* Check that all of the attributes in a primary key are marked as not * Check that all of the attributes in a primary key are marked as not
* null, otherwise attempt to ALTER TABLE .. SET NOT NULL * null. (We don't really expect to see that; it'd mean the parser messed
* up. But it seems wise to check anyway.)
*/ */
cmds = NIL;
for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
{ {
AttrNumber attnum = indexInfo->ii_IndexAttrNumbers[i]; AttrNumber attnum = indexInfo->ii_IndexAttrNumbers[i];
...@@ -249,30 +249,13 @@ index_check_primary_key(Relation heapRel, ...@@ -249,30 +249,13 @@ index_check_primary_key(Relation heapRel,
attform = (Form_pg_attribute) GETSTRUCT(atttuple); attform = (Form_pg_attribute) GETSTRUCT(atttuple);
if (!attform->attnotnull) if (!attform->attnotnull)
{ ereport(ERROR,
/* Add a subcommand to make this one NOT NULL */ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
AlterTableCmd *cmd = makeNode(AlterTableCmd); errmsg("primary key column \"%s\" is not marked NOT NULL",
NameStr(attform->attname))));
cmd->subtype = AT_SetNotNull;
cmd->name = pstrdup(NameStr(attform->attname));
cmds = lappend(cmds, cmd);
}
ReleaseSysCache(atttuple); ReleaseSysCache(atttuple);
} }
/*
* XXX: possible future improvement: when being called from ALTER TABLE,
* it would be more efficient to merge this with the outer ALTER TABLE, so
* as to avoid two scans. But that seems to complicate DefineIndex's API
* unduly.
*/
if (cmds)
{
EventTriggerAlterTableStart((Node *) stmt);
AlterTableInternal(RelationGetRelid(heapRel), cmds, true);
EventTriggerAlterTableEnd();
}
} }
/* /*
......
...@@ -142,9 +142,9 @@ static List *on_commits = NIL; ...@@ -142,9 +142,9 @@ static List *on_commits = NIL;
#define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */ #define AT_PASS_ALTER_TYPE 1 /* ALTER COLUMN TYPE */
#define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */ #define AT_PASS_OLD_INDEX 2 /* re-add existing indexes */
#define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ #define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */
#define AT_PASS_COL_ATTRS 4 /* set other column attributes */
/* 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 5 /* ADD COLUMN */ #define AT_PASS_ADD_COL 4 /* ADD COLUMN */
#define AT_PASS_COL_ATTRS 5 /* set other column attributes */
#define AT_PASS_ADD_INDEX 6 /* ADD indexes */ #define AT_PASS_ADD_INDEX 6 /* ADD indexes */
#define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */ #define AT_PASS_ADD_CONSTR 7 /* ADD constraints, defaults */
#define AT_PASS_MISC 8 /* other stuff */ #define AT_PASS_MISC 8 /* other stuff */
...@@ -370,9 +370,13 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); ...@@ -370,9 +370,13 @@ static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid);
static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid); static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid);
static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing); 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(Relation rel, bool recurse, bool recursing); static void ATPrepSetNotNull(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, bool recursing,
LOCKMODE lockmode);
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,
const char *colName, LOCKMODE lockmode);
static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr); static bool NotNullImpliedByRelConstraints(Relation rel, Form_pg_attribute attr);
static bool ConstraintImpliedByRelConstraint(Relation scanrel, static bool ConstraintImpliedByRelConstraint(Relation scanrel,
List *partConstraint, List *existedConstraints); List *partConstraint, List *existedConstraints);
...@@ -1068,7 +1072,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ...@@ -1068,7 +1072,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
RelationGetDescr(parent), RelationGetDescr(parent),
gettext_noop("could not convert row type")); gettext_noop("could not convert row type"));
idxstmt = idxstmt =
generateClonedIndexStmt(NULL, RelationGetRelid(rel), idxRel, generateClonedIndexStmt(NULL, idxRel,
attmap, RelationGetDescr(rel)->natts, attmap, RelationGetDescr(rel)->natts,
&constraintOid); &constraintOid);
DefineIndex(RelationGetRelid(rel), DefineIndex(RelationGetRelid(rel),
...@@ -3765,6 +3769,15 @@ AlterTableGetLockLevel(List *cmds) ...@@ -3765,6 +3769,15 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AccessExclusiveLock; cmd_lockmode = AccessExclusiveLock;
break; break;
case AT_CheckNotNull:
/*
* This only examines the table's schema; but lock must be
* strong enough to prevent concurrent DROP NOT NULL.
*/
cmd_lockmode = AccessShareLock;
break;
default: /* oops */ default: /* oops */
elog(ERROR, "unrecognized alter table type: %d", elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype); (int) cmd->subtype);
...@@ -3889,15 +3902,19 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, ...@@ -3889,15 +3902,19 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
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);
/* No command-specific prep needed */
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);
ATPrepSetNotNull(rel, recurse, recursing); /* Need command-specific recursion decision */
ATPrepSetNotNull(wqueue, rel, cmd, recurse, recursing, lockmode);
pass = AT_PASS_COL_ATTRS;
break;
case AT_CheckNotNull: /* check column is already marked NOT NULL */
ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
/* No command-specific prep needed */ /* No command-specific prep needed */
pass = AT_PASS_ADD_CONSTR; pass = AT_PASS_COL_ATTRS;
break; break;
case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
...@@ -4214,6 +4231,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, ...@@ -4214,6 +4231,9 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */ case AT_SetNotNull: /* ALTER COLUMN SET NOT NULL */
address = ATExecSetNotNull(tab, rel, cmd->name, lockmode); address = ATExecSetNotNull(tab, rel, cmd->name, lockmode);
break; break;
case AT_CheckNotNull: /* check column is already marked NOT NULL */
ATExecCheckNotNull(tab, rel, cmd->name, lockmode);
break;
case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */ case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode); address = ATExecSetStatistics(rel, cmd->name, cmd->num, cmd->def, lockmode);
break; break;
...@@ -5966,9 +5986,6 @@ add_column_collation_dependency(Oid relid, int32 attnum, Oid collid) ...@@ -5966,9 +5986,6 @@ add_column_collation_dependency(Oid relid, int32 attnum, Oid collid)
/* /*
* ALTER TABLE ALTER COLUMN DROP NOT NULL * ALTER TABLE ALTER COLUMN DROP NOT NULL
*
* Return the address of the modified column. If the column was already
* nullable, InvalidObjectAddress is returned.
*/ */
static void static void
...@@ -5990,6 +6007,11 @@ ATPrepDropNotNull(Relation rel, bool recurse, bool recursing) ...@@ -5990,6 +6007,11 @@ ATPrepDropNotNull(Relation rel, bool recurse, bool recursing)
errhint("Do not specify the ONLY keyword."))); errhint("Do not specify the ONLY keyword.")));
} }
} }
/*
* Return the address of the modified column. If the column was already
* nullable, InvalidObjectAddress is returned.
*/
static ObjectAddress static ObjectAddress
ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
{ {
...@@ -6116,23 +6138,33 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode) ...@@ -6116,23 +6138,33 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
*/ */
static void static void
ATPrepSetNotNull(Relation rel, bool recurse, bool recursing) ATPrepSetNotNull(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, bool recursing,
LOCKMODE lockmode)
{ {
/* /*
* If the parent is a partitioned table, like check constraints, NOT NULL * If we're already recursing, there's nothing to do; the topmost
* constraints must be added to the child tables. Complain if requested * invocation of ATSimpleRecursion already visited all children.
* otherwise and partitions exist.
*/ */
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) if (recursing)
return;
/*
* If we have ALTER TABLE ONLY ... SET NOT NULL on a partitioned table,
* apply ALTER TABLE ... CHECK NOT NULL to every child. Otherwise, use
* normal recursion logic.
*/
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
!recurse)
{ {
PartitionDesc partdesc = RelationGetPartitionDesc(rel); AlterTableCmd *newcmd = makeNode(AlterTableCmd);
if (partdesc && partdesc->nparts > 0 && !recurse && !recursing) newcmd->subtype = AT_CheckNotNull;
ereport(ERROR, newcmd->name = pstrdup(cmd->name);
(errcode(ERRCODE_INVALID_TABLE_DEFINITION), ATSimpleRecursion(wqueue, rel, newcmd, true, lockmode);
errmsg("cannot add constraint to only the partitioned table when partitions exist"),
errhint("Do not specify the ONLY keyword.")));
} }
else
ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
} }
/* /*
...@@ -6207,6 +6239,46 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel, ...@@ -6207,6 +6239,46 @@ ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
return address; return address;
} }
/*
* ALTER TABLE ALTER COLUMN CHECK NOT NULL
*
* This doesn't exist in the grammar, but we generate AT_CheckNotNull
* commands against the partitions of a partitioned table if the user
* writes ALTER TABLE ONLY ... SET NOT NULL on the partitioned table,
* or tries to create a primary key on it (which internally creates
* AT_SetNotNull on the partitioned table). Such a command doesn't
* allow us to actually modify any partition, but we want to let it
* go through if the partitions are already properly marked.
*
* In future, this might need to adjust the child table's state, likely
* by incrementing an inheritance count for the attnotnull constraint.
* For now we need only check for the presence of the flag.
*/
static void
ATExecCheckNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode)
{
HeapTuple tuple;
tuple = SearchSysCacheAttName(RelationGetRelid(rel), colName);
if (!HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
colName, RelationGetRelationName(rel))));
if (!((Form_pg_attribute) GETSTRUCT(tuple))->attnotnull)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraint must be added to child tables too"),
errdetail("Column \"%s\" of relation \"%s\" is not already NOT NULL.",
colName, RelationGetRelationName(rel)),
errhint("Do not specify the ONLY keyword.")));
ReleaseSysCache(tuple);
}
/* /*
* NotNullImpliedByRelConstraints * NotNullImpliedByRelConstraints
* Does rel's existing constraints imply NOT NULL for the given attribute? * Does rel's existing constraints imply NOT NULL for the given attribute?
...@@ -11269,6 +11341,16 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, ...@@ -11269,6 +11341,16 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
NIL, NIL,
con->conname); con->conname);
} }
else if (cmd->subtype == AT_SetNotNull)
{
/*
* The parser will create AT_SetNotNull subcommands for
* columns of PRIMARY KEY indexes/constraints, but we need
* not do anything with them here, because the columns'
* NOT NULL marks will already have been propagated into
* the new table definition.
*/
}
else else
elog(ERROR, "unexpected statement subtype: %d", elog(ERROR, "unexpected statement subtype: %d",
(int) cmd->subtype); (int) cmd->subtype);
...@@ -15649,7 +15731,7 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel) ...@@ -15649,7 +15731,7 @@ AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
IndexStmt *stmt; IndexStmt *stmt;
Oid constraintOid; Oid constraintOid;
stmt = generateClonedIndexStmt(NULL, RelationGetRelid(attachrel), stmt = generateClonedIndexStmt(NULL,
idxRel, attmap, idxRel, attmap,
RelationGetDescr(rel)->natts, RelationGetDescr(rel)->natts,
&constraintOid); &constraintOid);
......
This diff is collapsed.
...@@ -1764,6 +1764,7 @@ typedef enum AlterTableType ...@@ -1764,6 +1764,7 @@ typedef enum AlterTableType
AT_ColumnDefault, /* alter column default */ AT_ColumnDefault, /* alter column default */
AT_DropNotNull, /* alter column drop not null */ AT_DropNotNull, /* alter column drop not null */
AT_SetNotNull, /* alter column set not null */ AT_SetNotNull, /* alter column set not null */
AT_CheckNotNull, /* check column is already marked not null */
AT_SetStatistics, /* alter column set statistics */ AT_SetStatistics, /* alter column set statistics */
AT_SetOptions, /* alter column set ( options ) */ AT_SetOptions, /* alter column set ( options ) */
AT_ResetOptions, /* alter column reset ( options ) */ AT_ResetOptions, /* alter column reset ( options ) */
......
...@@ -27,7 +27,7 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, ...@@ -27,7 +27,7 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt); extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent, extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
PartitionBoundSpec *spec); PartitionBoundSpec *spec);
extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel, Oid heapOid, extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel,
Relation source_idx, Relation source_idx,
const AttrNumber *attmap, int attmap_length, const AttrNumber *attmap, int attmap_length,
Oid *constraintOid); Oid *constraintOid);
......
...@@ -23,8 +23,7 @@ NOTICE: DDL test: type simple, tag CREATE TABLE ...@@ -23,8 +23,7 @@ NOTICE: DDL test: type simple, tag CREATE TABLE
CREATE TABLE part1 PARTITION OF part FOR VALUES FROM (1) to (100); CREATE TABLE part1 PARTITION OF part FOR VALUES FROM (1) to (100);
NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE TABLE
ALTER TABLE part ADD PRIMARY KEY (a); ALTER TABLE part ADD PRIMARY KEY (a);
NOTICE: DDL test: type alter table, tag CREATE INDEX NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: SET NOT NULL NOTICE: subcommand: SET NOT NULL
NOTICE: subcommand: SET NOT NULL NOTICE: subcommand: SET NOT NULL
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: ADD INDEX NOTICE: subcommand: ADD INDEX
...@@ -85,6 +85,8 @@ CREATE TABLE employees OF employee_type ( ...@@ -85,6 +85,8 @@ CREATE TABLE employees OF employee_type (
salary WITH OPTIONS DEFAULT 1000 salary WITH OPTIONS DEFAULT 1000
); );
NOTICE: DDL test: type simple, tag CREATE TABLE NOTICE: DDL test: type simple, tag CREATE TABLE
NOTICE: DDL test: type alter table, tag ALTER TABLE
NOTICE: subcommand: SET NOT NULL
NOTICE: DDL test: type simple, tag CREATE INDEX NOTICE: DDL test: type simple, tag CREATE INDEX
-- Inheritance -- Inheritance
CREATE TABLE person ( CREATE TABLE person (
......
...@@ -117,6 +117,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS) ...@@ -117,6 +117,9 @@ get_altertable_subcmdtypes(PG_FUNCTION_ARGS)
case AT_SetNotNull: case AT_SetNotNull:
strtype = "SET NOT NULL"; strtype = "SET NOT NULL";
break; break;
case AT_CheckNotNull:
strtype = "CHECK NOT NULL";
break;
case AT_SetStatistics: case AT_SetStatistics:
strtype = "SET STATS"; strtype = "SET STATS";
break; break;
......
...@@ -978,7 +978,7 @@ drop table atacc1; ...@@ -978,7 +978,7 @@ drop table atacc1;
create table atacc1 ( test int ); create table atacc1 ( test int );
-- add a primary key constraint (fails) -- add a primary key constraint (fails)
alter table atacc1 add constraint atacc_test1 primary key (test1); alter table atacc1 add constraint atacc_test1 primary key (test1);
ERROR: column "test1" named in key does not exist ERROR: column "test1" of relation "atacc1" does not exist
drop table atacc1; drop table atacc1;
-- adding a new column as primary key to a non-empty table. -- adding a new column as primary key to a non-empty table.
-- should fail unless the column has a non-null default value. -- should fail unless the column has a non-null default value.
...@@ -990,6 +990,13 @@ ERROR: column "test2" contains null values ...@@ -990,6 +990,13 @@ ERROR: column "test2" contains null values
-- now add a primary key column with a default (succeeds). -- now add a primary key column with a default (succeeds).
alter table atacc1 add column test2 int default 0 primary key; alter table atacc1 add column test2 int default 0 primary key;
drop table atacc1; drop table atacc1;
-- this combination used to have order-of-execution problems (bug #15580)
create table atacc1 (a int);
insert into atacc1 values(1);
alter table atacc1
add column b float8 not null default random(),
add primary key(a);
drop table atacc1;
-- something a little more complicated -- something a little more complicated
create table atacc1 ( test int, test2 int); create table atacc1 ( test int, test2 int);
-- add a primary key constraint -- add a primary key constraint
...@@ -1404,9 +1411,9 @@ ERROR: column "a" does not exist ...@@ -1404,9 +1411,9 @@ ERROR: column "a" does not exist
alter table atacc1 rename "........pg.dropped.1........" to x; alter table atacc1 rename "........pg.dropped.1........" to x;
ERROR: column "........pg.dropped.1........" does not exist ERROR: column "........pg.dropped.1........" does not exist
alter table atacc1 add primary key(a); alter table atacc1 add primary key(a);
ERROR: column "a" named in key does not exist ERROR: column "a" of relation "atacc1" does not exist
alter table atacc1 add primary key("........pg.dropped.1........"); alter table atacc1 add primary key("........pg.dropped.1........");
ERROR: column "........pg.dropped.1........" named in key does not exist ERROR: column "........pg.dropped.1........" of relation "atacc1" does not exist
alter table atacc1 add unique(a); alter table atacc1 add unique(a);
ERROR: column "a" named in key does not exist ERROR: column "a" named in key does not exist
alter table atacc1 add unique("........pg.dropped.1........"); alter table atacc1 add unique("........pg.dropped.1........");
...@@ -3751,7 +3758,8 @@ ERROR: cannot alter inherited column "b" ...@@ -3751,7 +3758,8 @@ ERROR: cannot alter inherited column "b"
-- cannot add/drop NOT NULL or check constraints to *only* the parent, when -- cannot add/drop NOT NULL or check constraints to *only* the parent, when
-- partitions exist -- partitions exist
ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL; ALTER TABLE ONLY list_parted2 ALTER b SET NOT NULL;
ERROR: cannot add constraint to only the partitioned table when partitions exist ERROR: constraint must be added to child tables too
DETAIL: Column "b" of relation "part_2" is not already NOT NULL.
HINT: Do not specify the ONLY keyword. HINT: Do not specify the ONLY keyword.
ALTER TABLE ONLY list_parted2 ADD CONSTRAINT check_b CHECK (b <> 'zz'); ALTER TABLE ONLY list_parted2 ADD CONSTRAINT check_b CHECK (b <> 'zz');
ERROR: constraint must be added to child tables too ERROR: constraint must be added to child tables too
......
...@@ -290,6 +290,18 @@ SELECT seqtypid::regtype FROM pg_sequence WHERE seqrelid = 'itest3_a_seq'::regcl ...@@ -290,6 +290,18 @@ SELECT seqtypid::regtype FROM pg_sequence WHERE seqrelid = 'itest3_a_seq'::regcl
ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error
ERROR: identity column type must be smallint, integer, or bigint ERROR: identity column type must be smallint, integer, or bigint
-- kinda silly to change property in the same command, but it should work
ALTER TABLE itest3
ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY,
ALTER COLUMN c SET GENERATED ALWAYS;
\d itest3
Table "public.itest3"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+----------------------------------
a | integer | | not null | generated by default as identity
b | text | | |
c | integer | | not null | generated always as identity
-- ALTER COLUMN ... SET -- ALTER COLUMN ... SET
CREATE TABLE itest6 (a int GENERATED ALWAYS AS IDENTITY, b text); CREATE TABLE itest6 (a int GENERATED ALWAYS AS IDENTITY, b text);
INSERT INTO itest6 DEFAULT VALUES; INSERT INTO itest6 DEFAULT VALUES;
......
...@@ -1098,6 +1098,21 @@ select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid ...@@ -1098,6 +1098,21 @@ select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid
idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t
(2 rows) (2 rows)
drop table idxpart;
-- Related to the above scenario: ADD PRIMARY KEY on the parent mustn't
-- automatically propagate NOT NULL to child columns.
create table idxpart (a int) partition by range (a);
create table idxpart0 (like idxpart);
alter table idxpart0 add unique (a);
alter table idxpart attach partition idxpart0 default;
alter table only idxpart add primary key (a); -- fail, no NOT NULL constraint
ERROR: constraint must be added to child tables too
DETAIL: Column "a" of relation "idxpart0" is not already NOT NULL.
HINT: Do not specify the ONLY keyword.
alter table idxpart0 alter column a set not null;
alter table only idxpart add primary key (a); -- now it works
alter table idxpart0 alter column a drop not null; -- fail, pkey needs it
ERROR: column "a" is marked NOT NULL in parent table
drop table idxpart; drop table idxpart;
-- if a partition has a unique index without a constraint, does not attach -- if a partition has a unique index without a constraint, does not attach
-- automatically; creates a new index instead. -- automatically; creates a new index instead.
......
...@@ -749,6 +749,14 @@ alter table atacc1 add column test2 int primary key; ...@@ -749,6 +749,14 @@ alter table atacc1 add column test2 int primary key;
alter table atacc1 add column test2 int default 0 primary key; alter table atacc1 add column test2 int default 0 primary key;
drop table atacc1; drop table atacc1;
-- this combination used to have order-of-execution problems (bug #15580)
create table atacc1 (a int);
insert into atacc1 values(1);
alter table atacc1
add column b float8 not null default random(),
add primary key(a);
drop table atacc1;
-- something a little more complicated -- something a little more complicated
create table atacc1 ( test int, test2 int); create table atacc1 ( test int, test2 int);
-- add a primary key constraint -- add a primary key constraint
......
...@@ -174,6 +174,12 @@ SELECT seqtypid::regtype FROM pg_sequence WHERE seqrelid = 'itest3_a_seq'::regcl ...@@ -174,6 +174,12 @@ SELECT seqtypid::regtype FROM pg_sequence WHERE seqrelid = 'itest3_a_seq'::regcl
ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error
-- kinda silly to change property in the same command, but it should work
ALTER TABLE itest3
ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY,
ALTER COLUMN c SET GENERATED ALWAYS;
\d itest3
-- ALTER COLUMN ... SET -- ALTER COLUMN ... SET
......
...@@ -578,6 +578,18 @@ select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid ...@@ -578,6 +578,18 @@ select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid
order by indexrelid::regclass::text collate "C"; order by indexrelid::regclass::text collate "C";
drop table idxpart; drop table idxpart;
-- Related to the above scenario: ADD PRIMARY KEY on the parent mustn't
-- automatically propagate NOT NULL to child columns.
create table idxpart (a int) partition by range (a);
create table idxpart0 (like idxpart);
alter table idxpart0 add unique (a);
alter table idxpart attach partition idxpart0 default;
alter table only idxpart add primary key (a); -- fail, no NOT NULL constraint
alter table idxpart0 alter column a set not null;
alter table only idxpart add primary key (a); -- now it works
alter table idxpart0 alter column a drop not null; -- fail, pkey needs it
drop table idxpart;
-- if a partition has a unique index without a constraint, does not attach -- if a partition has a unique index without a constraint, does not attach
-- automatically; creates a new index instead. -- automatically; creates a new index instead.
create table idxpart (a int, b int) partition by range (a); create table idxpart (a int, b int) partition by range (a);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment