diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 375e035f4e..ffd8db2563 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -313,8 +313,9 @@ static void AlterSeqNamespaces(Relation classRel, Relation rel, LOCKMODE lockmode); static ObjectAddress ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode); -static ObjectAddress ATExecValidateConstraint(Relation rel, char *constrName, - bool recurse, bool recursing, LOCKMODE lockmode); +static ObjectAddress ATExecValidateConstraint(AlteredTableInfo *tab, + Relation rel, char *constrName, bool recurse, + bool recursing, LOCKMODE lockmode); static int transformColumnNameList(Oid relId, List *colList, int16 *attnums, Oid *atttypids); static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid, @@ -3951,13 +3952,13 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, address = ATExecAlterConstraint(rel, cmd, false, false, lockmode); break; case AT_ValidateConstraint: /* VALIDATE CONSTRAINT */ - address = ATExecValidateConstraint(rel, cmd->name, false, false, - lockmode); + address = ATExecValidateConstraint(tab, rel, cmd->name, false, + false, lockmode); break; case AT_ValidateConstraintRecurse: /* VALIDATE CONSTRAINT with * recursion */ - address = ATExecValidateConstraint(rel, cmd->name, true, false, - lockmode); + address = ATExecValidateConstraint(tab, rel, cmd->name, true, + false, lockmode); break; case AT_DropConstraint: /* DROP CONSTRAINT */ ATExecDropConstraint(rel, cmd->name, cmd->behavior, @@ -7660,8 +7661,9 @@ ATExecAlterConstraint(Relation rel, AlterTableCmd *cmd, * was already validated, InvalidObjectAddress is returned. */ static ObjectAddress -ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, - bool recursing, LOCKMODE lockmode) +ATExecValidateConstraint(AlteredTableInfo *tab, Relation rel, + char *constrName, bool recurse, bool recursing, + LOCKMODE lockmode) { Relation conrel; SysScanDesc scan; @@ -7713,22 +7715,45 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, if (con->contype == CONSTRAINT_FOREIGN) { - Relation refrel; + if (tab->rewrite > 0) + { + NewConstraint *newcon; + Constraint *fkconstraint; + + fkconstraint = makeNode(Constraint); + /* for now this is all we need */ + fkconstraint->conname = constrName; + + newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); + newcon->name = constrName; + newcon->contype = CONSTR_FOREIGN; + newcon->refrelid = con->confrelid; + newcon->refindid = con->conindid; + newcon->conid = HeapTupleGetOid(tuple); + newcon->qual = (Node *) fkconstraint; + + tab->constraints = lappend(tab->constraints, newcon); + } + else + { - /* - * Triggers are already in place on both tables, so a concurrent - * write that alters the result here is not possible. Normally we - * can run a query here to do the validation, which would only - * require AccessShareLock. In some cases, it is possible that we - * might need to fire triggers to perform the check, so we take a - * lock at RowShareLock level just in case. - */ - refrel = heap_open(con->confrelid, RowShareLock); + Relation refrel; - validateForeignKeyConstraint(constrName, rel, refrel, - con->conindid, - HeapTupleGetOid(tuple)); - heap_close(refrel, NoLock); + /* + * Triggers are already in place on both tables, so a concurrent + * write that alters the result here is not possible. Normally we + * can run a query here to do the validation, which would only + * require AccessShareLock. In some cases, it is possible that we + * might need to fire triggers to perform the check, so we take a + * lock at RowShareLock level just in case. + */ + refrel = heap_open(con->confrelid, RowShareLock); + + validateForeignKeyConstraint(constrName, rel, refrel, + con->conindid, + HeapTupleGetOid(tuple)); + heap_close(refrel, NoLock); + } /* * Foreign keys do not inherit, so we purposely ignore the @@ -7778,7 +7803,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse, /* find_all_inheritors already got lock */ childrel = heap_open(childoid, NoLock); - ATExecValidateConstraint(childrel, constrName, false, + ATExecValidateConstraint(tab, childrel, constrName, false, true, lockmode); heap_close(childrel, NoLock); } diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index eabd3e160c..ce9ea9ee33 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -941,6 +941,12 @@ ERROR: column "test2" contains null values -- now add a primary key column with a default (succeeds). alter table atacc1 add column test2 int default 0 primary key; drop table atacc1; +-- additionally, we've seen issues with foreign key validation not being +-- properly delayed until after a table rewrite. Check that works ok. +create table atacc1 (a int primary key); +alter table atacc1 add constraint atacc1_fkey foreign key (a) references atacc1 (a) not valid; +alter table atacc1 validate constraint atacc1_fkey, alter A type bigint; +drop table atacc1; -- something a little more complicated create table atacc1 ( test int, test2 int); -- add a primary key constraint diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 8afdf0613e..c52fc9e3fb 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -713,6 +713,13 @@ alter table atacc1 add column test2 int primary key; alter table atacc1 add column test2 int default 0 primary key; drop table atacc1; +-- additionally, we've seen issues with foreign key validation not being +-- properly delayed until after a table rewrite. Check that works ok. +create table atacc1 (a int primary key); +alter table atacc1 add constraint atacc1_fkey foreign key (a) references atacc1 (a) not valid; +alter table atacc1 validate constraint atacc1_fkey, alter A type bigint; +drop table atacc1; + -- something a little more complicated create table atacc1 ( test int, test2 int); -- add a primary key constraint