Skip to content

Commit

Permalink
MDEV-31086 MODIFY COLUMN can break FK constraints, and lead to unrest…
Browse files Browse the repository at this point in the history
…orable dumps

- When foreign_key_check is disabled, allowing to modify the
column which is part of foreign key constraint can lead to
refusal of TRUNCATE TABLE, OPTIMIZE TABLE later. So it make
sense to block the column modify operation when foreign key
is involved irrespective of foreign_key_check variable.

Correct way to modify the charset of the column when fk is involved:

SET foreign_key_checks=OFF;
ALTER TABLE child DROP FOREIGN KEY fk, MODIFY m VARCHAR(200) CHARSET utf8mb4;
ALTER TABLE parent MODIFY m VARCHAR(200) CHARSET utf8mb4;
ALTER TABLE child ADD CONSTRAINT FOREIGN KEY (m) REFERENCES PARENT(m);
SET foreign_key_checks=ON;

fk_check_column_changes(): Remove the FOREIGN_KEY_CHECKS while
checking the column change for foreign key constraint. This
is the partial revert of commit 5f1f2fc
and it changes the behaviour of copy alter algorithm

ha_innobase::prepare_inplace_alter_table(): Find the modified
column and check whether it is part of existing and newly
added foreign key constraint.
  • Loading branch information
Thirunarayanan committed Jun 27, 2023
1 parent 423c28f commit 5f09b53
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 14 deletions.
77 changes: 77 additions & 0 deletions mysql-test/suite/innodb/r/fk_col_alter.result
@@ -0,0 +1,77 @@
#
# MDEV-31086 MODIFY COLUMN can break FK constraints, and
# lead to unrestorable dumps
#
CREATE TABLE t1(
id SERIAL,
msg VARCHAR(100) CHARACTER SET utf8mb3,
KEY(msg))ENGINE=InnoDB;
CREATE TABLE t2(
id SERIAL,
msg varchar(100) CHARACTER SET utf8mb4,
CONSTRAINT fk_t1 FOREIGN KEY (msg) REFERENCES t1 (msg))ENGINE=InnoDB;
ERROR HY000: Can't create table `test`.`t2` (errno: 150 "Foreign key constraint is incorrectly formed")
CREATE TABLE t2(
id SERIAL,
msg varchar(100) CHARACTER SET utf8mb3,
msg_1 varchar(100) CHARACTER SET utf8mb3,
INDEX (msg_1),
INDEX (msg),
CONSTRAINT fk_t1 FOREIGN KEY (msg) REFERENCES t1 (msg)
ON DELETE CASCADE)ENGINE=InnoDB;
SET FOREIGN_KEY_CHECKS=1;
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(200) character set utf8mb3, ALGORITHM=COPY;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'fk_t1'
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(200) character set utf8mb3, ALGORITHM=INPLACE;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'test/fk_t1'
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(200) character set utf8mb3, ALGORITHM=COPY;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'fk_t1'
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(400) character set utf8mb3, ALGORITHM=INPLACE;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'test/fk_t1'
SET FOREIGN_KEY_CHECKS=1;
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=COPY;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'fk_t1'
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=INPLACE;
ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Collation change on an indexed column. Try ALGORITHM=COPY
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(100) CHARACTER SET utf8mb4,ALGORITHM=COPY;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'fk_t1'
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(100) CHARACTER SET utf8mb4,ALGORITHM=INPLACE;
ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Collation change on an indexed column. Try ALGORITHM=COPY
SET FOREIGN_KEY_CHECKS=1;
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=COPY;
ERROR HY000: Can't create table `test`.`t2` (errno: 150 "Foreign key constraint is incorrectly formed")
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=INPLACE;
ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Adding foreign keys needs foreign_key_checks=OFF. Try ALGORITHM=COPY
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=COPY;
ERROR HY000: Can't create table `test`.`t2` (errno: 150 "Foreign key constraint is incorrectly formed")
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=INPLACE;
ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Collation change on an indexed column. Try ALGORITHM=COPY
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(200) CHARACTER SET utf8mb3, ALGORITHM=INPLACE;
ERROR HY000: Cannot change column 'msg_1': used in a foreign key constraint 'test/t2_ibfk_0'
SET FOREIGN_KEY_CHECKS=1;
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb3, ALGORITHM=COPY;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'fk_t1' of table 'test.t2'
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb3, ALGORITHM=INPLACE;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'test/fk_t1' of table 'test/t2'
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=COPY;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'fk_t1' of table 'test.t2'
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=INPLACE;
ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Collation change on an indexed column. Try ALGORITHM=COPY
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb3, ALGORITHM=COPY;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'fk_t1' of table 'test.t2'
ALTER TABLE t1 MODIFY msg VARCHAR(400) CHARSET utf8mb3, ALGORITHM=INPLACE;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'test/fk_t1' of table 'test/t2'
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=COPY;
ERROR HY000: Cannot change column 'msg': used in a foreign key constraint 'fk_t1' of table 'test.t2'
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=INPLACE;
ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Collation change on an indexed column. Try ALGORITHM=COPY
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE t2 DROP FOREIGN KEY fk_t1, MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=COPY;
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=COPY;
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY (msg) REFERENCES t1(msg), aLGORITHM=INPLACE;
SET FOREIGN_KEY_CHECKS=1;
DROP TABLE t2, t1;
3 changes: 3 additions & 0 deletions mysql-test/suite/innodb/r/foreign_key.result
Expand Up @@ -246,8 +246,11 @@ CREATE TABLE t1 (a INT, UNIQUE(a), KEY(a)) ENGINE=InnoDB;
ALTER TABLE t1 ADD FOREIGN KEY (a) REFERENCES t1 (a);
SET SESSION FOREIGN_KEY_CHECKS = OFF;
ALTER TABLE t1 CHANGE COLUMN a a TIME NOT NULL;
ERROR HY000: Cannot change column 'a': used in a foreign key constraint 't1_ibfk_1' of table 'test.t1'
ALTER TABLE t1 ADD pk INT NOT NULL AUTO_INCREMENT PRIMARY KEY;
ALTER TABLE t1 CHANGE COLUMN a b TIME;
ERROR 0A000: ALGORITHM=COPY is not supported. Reason: Columns participating in a foreign key are renamed. Try ALGORITHM=INPLACE
ALTER TABLE t1 CHANGE COLUMN a b TIME, DROP FOREIGN KEY t1_ibfk_1;
SET SESSION FOREIGN_KEY_CHECKS = ON;
DROP TABLE t1;
#
Expand Down
2 changes: 2 additions & 0 deletions mysql-test/suite/innodb/r/innodb.result
Expand Up @@ -2544,12 +2544,14 @@ set foreign_key_checks=0;
create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb;
create table t1(a varchar(10) primary key) engine = innodb;
alter table t1 modify column a int;
ERROR HY000: Cannot change column 'a': used in a foreign key constraint 't2_ibfk_1' of table 'test.t2'
set foreign_key_checks=1;
drop table t2,t1;
set foreign_key_checks=0;
create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb DEFAULT CHARSET=latin1;
create table t1(a varchar(10) primary key) engine = innodb DEFAULT CHARSET=latin1;
alter table t1 convert to character set utf8;
ERROR HY000: Cannot change column 'a': used in a foreign key constraint 't2_ibfk_1' of table 'test.t2'
set foreign_key_checks=1;
drop table t2,t1;
call mtr.add_suppression("\\[Warning\\] InnoDB: In ALTER TABLE `test`.`t1` has or is referenced in foreign key constraints which are not compatible with the new table definition.");
Expand Down
106 changes: 106 additions & 0 deletions mysql-test/suite/innodb/t/fk_col_alter.test
@@ -0,0 +1,106 @@
--source include/have_innodb.inc
--echo #
--echo # MDEV-31086 MODIFY COLUMN can break FK constraints, and
--echo # lead to unrestorable dumps
--echo #
CREATE TABLE t1(
id SERIAL,
msg VARCHAR(100) CHARACTER SET utf8mb3,
KEY(msg))ENGINE=InnoDB;

--error ER_CANT_CREATE_TABLE
CREATE TABLE t2(
id SERIAL,
msg varchar(100) CHARACTER SET utf8mb4,
CONSTRAINT fk_t1 FOREIGN KEY (msg) REFERENCES t1 (msg))ENGINE=InnoDB;

CREATE TABLE t2(
id SERIAL,
msg varchar(100) CHARACTER SET utf8mb3,
msg_1 varchar(100) CHARACTER SET utf8mb3,
INDEX (msg_1),
INDEX (msg),
CONSTRAINT fk_t1 FOREIGN KEY (msg) REFERENCES t1 (msg)
ON DELETE CASCADE)ENGINE=InnoDB;

# Changing column used in FK constraint
SET FOREIGN_KEY_CHECKS=1;
--error ER_FK_COLUMN_CANNOT_CHANGE
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(200) character set utf8mb3, ALGORITHM=COPY;
--error ER_FK_COLUMN_CANNOT_CHANGE
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(200) character set utf8mb3, ALGORITHM=INPLACE;

SET FOREIGN_KEY_CHECKS=0;
--error ER_FK_COLUMN_CANNOT_CHANGE
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(200) character set utf8mb3, ALGORITHM=COPY;
--error ER_FK_COLUMN_CANNOT_CHANGE
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(400) character set utf8mb3, ALGORITHM=INPLACE;

# Changing column charset used in FK constraint
SET FOREIGN_KEY_CHECKS=1;
--error ER_FK_COLUMN_CANNOT_CHANGE
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=COPY;

--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=INPLACE;

SET FOREIGN_KEY_CHECKS=0;
--error ER_FK_COLUMN_CANNOT_CHANGE
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(100) CHARACTER SET utf8mb4,ALGORITHM=COPY;

--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
ALTER TABLE t2 MODIFY COLUMN msg VARCHAR(100) CHARACTER SET utf8mb4,ALGORITHM=INPLACE;

# Modify the column in the newly added foreign constraint
SET FOREIGN_KEY_CHECKS=1;
--error ER_CANT_CREATE_TABLE
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=COPY;

--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=INPLACE;


SET FOREIGN_KEY_CHECKS=0;
--error ER_CANT_CREATE_TABLE
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=COPY;

--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(100) CHARACTER SET utf8mb4, ALGORITHM=INPLACE;

--error ER_FK_COLUMN_CANNOT_CHANGE
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY(msg_1) REFERENCES t1(msg),MODIFY COLUMN msg_1 VARCHAR(200) CHARACTER SET utf8mb3, ALGORITHM=INPLACE;

# Change referenced table column
SET FOREIGN_KEY_CHECKS=1;
# Change referenced column length
--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb3, ALGORITHM=COPY;
--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb3, ALGORITHM=INPLACE;
# Change referenced column character set
--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=COPY;
--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=INPLACE;

SET FOREIGN_KEY_CHECKS=0;
# Change referenced column length
--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb3, ALGORITHM=COPY;
--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD
ALTER TABLE t1 MODIFY msg VARCHAR(400) CHARSET utf8mb3, ALGORITHM=INPLACE;

# Change referenced column character set
--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=COPY;
--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=INPLACE;

# Correct way to change character set in foreign key constraint
SET FOREIGN_KEY_CHECKS=0;
ALTER TABLE t2 DROP FOREIGN KEY fk_t1, MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=COPY;
ALTER TABLE t1 MODIFY msg VARCHAR(200) CHARSET utf8mb4, ALGORITHM=COPY;
ALTER TABLE t2 ADD CONSTRAINT FOREIGN KEY (msg) REFERENCES t1(msg), aLGORITHM=INPLACE;
SET FOREIGN_KEY_CHECKS=1;

DROP TABLE t2, t1;
3 changes: 3 additions & 0 deletions mysql-test/suite/innodb/t/foreign_key.test
Expand Up @@ -239,9 +239,12 @@ DROP TABLE t3,t1;
CREATE TABLE t1 (a INT, UNIQUE(a), KEY(a)) ENGINE=InnoDB;
ALTER TABLE t1 ADD FOREIGN KEY (a) REFERENCES t1 (a);
SET SESSION FOREIGN_KEY_CHECKS = OFF;
--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD
ALTER TABLE t1 CHANGE COLUMN a a TIME NOT NULL;
ALTER TABLE t1 ADD pk INT NOT NULL AUTO_INCREMENT PRIMARY KEY;
--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON
ALTER TABLE t1 CHANGE COLUMN a b TIME;
ALTER TABLE t1 CHANGE COLUMN a b TIME, DROP FOREIGN KEY t1_ibfk_1;
SET SESSION FOREIGN_KEY_CHECKS = ON;
DROP TABLE t1;

Expand Down
2 changes: 2 additions & 0 deletions mysql-test/suite/innodb/t/innodb.test
Expand Up @@ -1626,6 +1626,7 @@ drop table t1;
set foreign_key_checks=0;
create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb;
create table t1(a varchar(10) primary key) engine = innodb;
--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD
alter table t1 modify column a int;
set foreign_key_checks=1;
drop table t2,t1;
Expand All @@ -1635,6 +1636,7 @@ drop table t2,t1;
set foreign_key_checks=0;
create table t2 (a varchar(10), foreign key (a) references t1(a)) engine = innodb DEFAULT CHARSET=latin1;
create table t1(a varchar(10) primary key) engine = innodb DEFAULT CHARSET=latin1;
--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD
alter table t1 convert to character set utf8;
set foreign_key_checks=1;
drop table t2,t1;
Expand Down
17 changes: 6 additions & 11 deletions sql/sql_table.cc
Expand Up @@ -9143,17 +9143,12 @@ fk_check_column_changes(THD *thd, Alter_info *alter_info,
((new_field->flags & NOT_NULL_FLAG) &&
!(old_field->flags & NOT_NULL_FLAG)))
{
if (!(thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
{
/*
Column in a FK has changed significantly. Unless
foreign_key_checks are off we prohibit this since this
means values in this column might be changed by ALTER
and thus referential integrity might be broken,
*/
*bad_column_name= column->str;
return FK_COLUMN_DATA_CHANGE;
}
/*
Column in a FK has changed significantly and it
may break referential intergrity.
*/
*bad_column_name= column->str;
return FK_COLUMN_DATA_CHANGE;
}
}
else
Expand Down

0 comments on commit 5f09b53

Please sign in to comment.