diff --git a/include/my_bit.h b/include/my_bit.h index 8ee8b41c8d4..e86c3ec883f 100644 --- a/include/my_bit.h +++ b/include/my_bit.h @@ -175,6 +175,11 @@ static inline uchar last_byte_mask(uint bits) return (uchar) ((2U << used) - 1); } +static inline uint my_bits_in_bytes(uint n) +{ + return ((n + 7) / 8); +} + #ifdef _MSC_VER #include #endif diff --git a/mysql-test/suite/galera/r/galera_fk_multicolumn.result b/mysql-test/suite/galera/r/galera_fk_multicolumn.result index b626d963af8..c10c0a5c702 100644 --- a/mysql-test/suite/galera/r/galera_fk_multicolumn.result +++ b/mysql-test/suite/galera/r/galera_fk_multicolumn.result @@ -2,7 +2,7 @@ connection node_2; connection node_1; CREATE TABLE t0 ( f1 INT PRIMARY KEY, -f2 INT UNIQUE +f2 INT UNIQUE NOT NULL ); CREATE TABLE t1 ( f1 INT PRIMARY KEY, diff --git a/mysql-test/suite/galera/t/galera_fk_multicolumn.test b/mysql-test/suite/galera/t/galera_fk_multicolumn.test index ad42f65924d..eeed46bca6c 100644 --- a/mysql-test/suite/galera/t/galera_fk_multicolumn.test +++ b/mysql-test/suite/galera/t/galera_fk_multicolumn.test @@ -7,7 +7,7 @@ CREATE TABLE t0 ( f1 INT PRIMARY KEY, - f2 INT UNIQUE + f2 INT UNIQUE NOT NULL ); CREATE TABLE t1 ( diff --git a/mysql-test/suite/innodb/r/foreign_null,COPY.rdiff b/mysql-test/suite/innodb/r/foreign_null,COPY.rdiff new file mode 100644 index 00000000000..e39d5c0e9db --- /dev/null +++ b/mysql-test/suite/innodb/r/foreign_null,COPY.rdiff @@ -0,0 +1,18 @@ +--- foreign_null.result ++++ foreign_null,COPY.result +@@ -139,6 +139,7 @@ + ALTER TABLE `t#2` DROP INDEX f1; + SET FOREIGN_KEY_CHECKS=1; + ALTER TABLE `t#1` MODIFY COLUMN f2 INT; ++ERROR HY000: Error on rename of './test/#sql-alter' to './test/t@00231' (errno: 150 "Foreign key constraint is incorrectly formed") + DROP TABLE `t#2`, `t#1`; + # Drop referenced index and modify column + CREATE TABLE `t#1`(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +@@ -147,6 +148,7 @@ + ALTER TABLE `t#1` DROP INDEX f2; + SET FOREIGN_KEY_CHECKS=1; + ALTER TABLE `t#2` MODIFY COLUMN f1 INT NOT NULL; ++ERROR HY000: Error on rename of './test/#sql-alter' to './test/t@00232' (errno: 150 "Foreign key constraint is incorrectly formed") + DROP TABLE `t#2`, `t#1`; + # Self referential modifying column + CREATE TABLE t1(f1 INT, f2 INT, index(f2), foreign key(f1) references t1(f2) ON UPDATE CASCADE)engine=innodb; diff --git a/mysql-test/suite/innodb/r/foreign_null.result b/mysql-test/suite/innodb/r/foreign_null.result new file mode 100644 index 00000000000..a5d1afc7e0c --- /dev/null +++ b/mysql-test/suite/innodb/r/foreign_null.result @@ -0,0 +1,156 @@ +call mtr.add_suppression("InnoDB: In ALTER TABLE .* has or is referenced in foreign key constraints which are not compatible with the new table definition."); +# modify child column NOT NULL on UPDATE CASCADE..parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE CASCADE)ENGINE=InnoDB; +ALTER TABLE t2 MODIFY COLUMN f1 INT NOT NULL; +ERROR HY000: Column 'f1' cannot be NOT NULL: needed in a foreign key constraint 't2_ibfk_1' SET NULL +DROP TABLE t2, t1; +# modify child column NOT NULL ON DELETE CASCADE..parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, FOREIGN KEY(f1) REFERENCES t1(f2) ON DELETE CASCADE)ENGINE=InnoDB; +ALTER TABLE t2 MODIFY COLUMN f1 INT NOT NULL; +DROP TABLE t2, t1; +# modify child column NOT NULL ON UPDATE SET NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY(f1) REFERENCES t1(f1) ON UPDATE SET NULL)ENGINE=InnoDB; +ALTER TABLE t2 MODIFY COLUMN f1 INT NOT NULL; +ERROR HY000: Column 'f1' cannot be NOT NULL: needed in a foreign key constraint 't2_ibfk_1' SET NULL +DROP TABLE t2, t1; +# modify child column NOT NULL ON DELETE SET NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON DELETE SET NULL)ENGINE=InnoDB; +ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL; +ERROR HY000: Column 'f2' cannot be NOT NULL: needed in a foreign key constraint 't2_ibfk_1' SET NULL +DROP TABLE t2, t1; +# modify child column NOT NULL ON UPDATE RESTRICT..parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON UPDATE RESTRICT)ENGINE=InnoDB; +ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL; +DROP TABLE t2, t1; +# modify child column NOT NULL ON DELETE RESTRICT..parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON DELETE RESTRICT)ENGINE=InnoDB; +ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL; +DROP TABLE t2, t1; +# modify child column NOT NULL ON UPDATE NO ACTION..PARENT COLUMN NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON UPDATE NO ACTION)ENGINE=InnoDB; +ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL; +DROP TABLE t2, t1; +# modify child column NOT NULL ON DELETE NO ACTION..PARENT COLUMN NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON DELETE NO ACTION)ENGINE=InnoDB; +ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL; +DROP TABLE t2, t1; +# modify parent column NULL ON UPDATE CASCADE child column NOT NULL +CREATE TABLE `t#1`(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE `t#2`(f1 INT NOT NULL, +FOREIGN KEY(f1) REFERENCES `t#1`(f2) +ON UPDATE CASCADE)ENGINE=InnoDB; +ALTER TABLE `t#1` MODIFY COLUMN f2 INT; +ERROR HY000: Cannot change column 'f2': used in a foreign key constraint 't#2_ibfk_1' of table 'test.t#2' +DROP TABLE `t#2`, `t#1`; +# modify parent column NULL ON DELETE CASCADE child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON DELETE CASCADE)ENGINE=InnoDB; +ALTER TABLE t1 MODIFY COLUMN f2 INT; +DROP TABLE t2, t1; +# modify parent column NULL ON UPDATE SET NULL child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE SET NULL)ENGINE=InnoDB; +ERROR HY000: Can't create table `test`.`t2` (errno: 150 "Foreign key constraint is incorrectly formed") +DROP TABLE t1; +# modify parent column NULL ON DELETE SET NULL child NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON DELETE SET NULL)ENGINE=InnoDB; +ERROR HY000: Can't create table `test`.`t2` (errno: 150 "Foreign key constraint is incorrectly formed") +DROP TABLE t1; +# modify parent column NULL ON UPDATE RESTRICT child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE RESTRICT)ENGINE=InnoDB; +ALTER TABLE t1 MODIFY COLUMN f2 INT; +DROP TABLE t2, t1; +# modify parent column NULL ON DELETE RESTRICT child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE RESTRICT)ENGINE=InnoDB; +ALTER TABLE t1 MODIFY COLUMN f2 INT; +DROP TABLE t2, t1; +# modify parent column NULL ON UPDATE NO ACTION child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE NO ACTION)ENGINE=InnoDB; +ALTER TABLE t1 MODIFY COLUMN f2 INT; +DROP TABLE t2, t1; +# modify parent column NULL ON DELETE NO ACTION child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON DELETE NO ACTION)ENGINE=InnoDB; +ALTER TABLE t1 MODIFY COLUMN f2 INT; +DROP TABLE t2, t1; +# foreign key constraint for multiple columns +# modify parent column NULL ON UPDATE CASCADE child column NOT NULL +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL, +INDEX(f1, f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, f2 INT NOT NULL, +INDEX(f1, f2), +FOREIGN KEY(f1, f2) REFERENCES t1(f1, f2) ON +UPDATE CASCADE)ENGINE=InnoDB; +ALTER TABLE t1 MODIFY COLUMN f1 INT; +ERROR HY000: Cannot change column 'f1': used in a foreign key constraint 't2_ibfk_1' of table 'test.t2' +DROP TABLE t2, t1; +# modify child column NOT NULL ON UPDATE CASCADE parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, INDEX(f1, f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, INDEX(f1, f2), +FOREIGN KEY(f1, f2) REFERENCES t1(f1, f2) ON +UPDATE CASCADE)ENGINE=InnoDB; +ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL; +ERROR HY000: Column 'f2' cannot be NOT NULL: needed in a foreign key constraint 't2_ibfk_1' SET NULL +DROP TABLE t2, t1; +# allow foreign key constraints when parent table created later +SET FOREIGN_KEY_CHECKS=0; +CREATE TABLE t2(f1 INT, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE CASCADE)ENGINE=InnoDB; +SET FOREIGN_KEY_CHECKS=1; +ALTER TABLE t2 MODIFY COLUMN f1 INT NOT NULL; +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +INSERT INTO t1 VALUES(1, 1); +INSERT INTO t2 VALUES(1); +UPDATE t1 SET f2= NULL; +ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`t2`, CONSTRAINT `t2_ibfk_1` FOREIGN KEY (`f1`) REFERENCES `t1` (`f2`) ON UPDATE CASCADE) +SELECT * FROM t2; +f1 +1 +SET FOREIGN_KEY_CHECKS=0; +UPDATE t1 SET f2= NULL; +SELECT * FROM t2; +f1 +1 +DROP TABLE t2, t1; +# Modify column + Drop column & Drop foreign key constraint +CREATE TABLE t1(f1 INT, f2 INT, KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, f3 INT, +FOREIGN KEY fdx(f2) REFERENCES t1(f1), +FOREIGN KEY fdx2(f3) REFERENCES t1(f2))ENGINE=InnoDB; +ALTER TABLE t2 MODIFY f2 INT NOT NULL, DROP FOREIGN KEY fdx; +ALTER TABLE t2 ADD FOREIGN KEY fdx (f2) REFERENCES t1(f1); +ALTER TABLE t2 DROP COLUMN f2, DROP FOREIGN KEY fdx; +DROP TABLE t2, t1; +# Drop foreign index & modify column +CREATE TABLE `t#1`(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE `t#2`(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES `t#1`(f2) ON UPDATE CASCADE)ENGINE=InnoDB; +SET FOREIGN_KEY_CHECKS=0; +ALTER TABLE `t#2` DROP INDEX f1; +SET FOREIGN_KEY_CHECKS=1; +ALTER TABLE `t#1` MODIFY COLUMN f2 INT; +DROP TABLE `t#2`, `t#1`; +# Drop referenced index and modify column +CREATE TABLE `t#1`(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE `t#2`(f1 INT, FOREIGN KEY(f1) REFERENCES `t#1`(f2) ON UPDATE CASCADE)ENGINE=InnoDB; +SET FOREIGN_KEY_CHECKS=0; +ALTER TABLE `t#1` DROP INDEX f2; +SET FOREIGN_KEY_CHECKS=1; +ALTER TABLE `t#2` MODIFY COLUMN f1 INT NOT NULL; +DROP TABLE `t#2`, `t#1`; +# Self referential modifying column +CREATE TABLE t1(f1 INT, f2 INT, index(f2), foreign key(f1) references t1(f2) ON UPDATE CASCADE)engine=innodb; +ALTER TABLE t1 MODIFY COLUMN f2 INT NOT NULL; +ALTER TABLE t1 MODIFY COLUMN f1 INT NOT NULL; +ALTER TABLE t1 MODIFY COLUMN f1 INT; +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/r/innodb-index.result b/mysql-test/suite/innodb/r/innodb-index.result index ad169757571..a590d4f0953 100644 --- a/mysql-test/suite/innodb/r/innodb-index.result +++ b/mysql-test/suite/innodb/r/innodb-index.result @@ -455,11 +455,11 @@ ERROR HY000: Cannot drop index 'b': needed in a foreign key constraint alter table t2 drop index b, drop index c, drop index d; ERROR HY000: Cannot drop index 'b': needed in a foreign key constraint alter table t2 MODIFY b INT NOT NULL, ALGORITHM=COPY; -ERROR HY000: Cannot change column 'b': used in a foreign key constraint 't2_ibfk_1' +ERROR HY000: Column 'b' cannot be NOT NULL: needed in a foreign key constraint 't2_ibfk_1' SET NULL set @old_sql_mode = @@sql_mode; set @@sql_mode = 'STRICT_TRANS_TABLES'; alter table t2 MODIFY b INT NOT NULL, ALGORITHM=INPLACE; -ERROR HY000: Column 'b' cannot be NOT NULL: needed in a foreign key constraint 'test/t2_ibfk_1' SET NULL +ERROR HY000: Column 'b' cannot be NOT NULL: needed in a foreign key constraint 't2_ibfk_1' SET NULL set @@sql_mode = @old_sql_mode; SET FOREIGN_KEY_CHECKS=0; alter table t2 DROP COLUMN b, ALGORITHM=COPY; @@ -480,10 +480,10 @@ info: Records: 0 Duplicates: 0 Warnings: 0 set @@sql_mode = 'STRICT_TRANS_TABLES'; alter table t2 add primary key (alpha), change a alpha int, change b beta int not null, change c charlie int not null; -ERROR HY000: Column 'b' cannot be NOT NULL: needed in a foreign key constraint 'test/t2_ibfk_1' SET NULL +ERROR HY000: Column 'b' cannot be NOT NULL: needed in a foreign key constraint 't2_ibfk_1' SET NULL alter table t2 add primary key (alpha), change a alpha int, change c charlie int not null, change d delta int not null; -ERROR HY000: Column 'd' cannot be NOT NULL: needed in a foreign key constraint 'test/t2_ibfk_3' SET NULL +ERROR HY000: Column 'd' cannot be NOT NULL: needed in a foreign key constraint 't2_ibfk_3' SET NULL alter table t2 add primary key (alpha), change a alpha int, change b beta int, modify c int not null; affected rows: 0 diff --git a/mysql-test/suite/innodb/r/innodb.result b/mysql-test/suite/innodb/r/innodb.result index 168327d781e..e1d99c3b731 100644 --- a/mysql-test/suite/innodb/r/innodb.result +++ b/mysql-test/suite/innodb/r/innodb.result @@ -3057,7 +3057,7 @@ ALTER TABLE t2 ADD FOREIGN KEY (a) REFERENCES t1 (a) ON DELETE SET NULL; set @old_sql_mode = @@sql_mode; set @@sql_mode = 'STRICT_TRANS_TABLES'; ALTER TABLE t2 MODIFY a INT NOT NULL; -ERROR HY000: Column 'a' cannot be NOT NULL: needed in a foreign key constraint 'test/t2_ibfk_1' SET NULL +ERROR HY000: Column 'a' cannot be NOT NULL: needed in a foreign key constraint 't2_ibfk_1' SET NULL set @@sql_mode = @old_sql_mode; DELETE FROM t1; DROP TABLE t2,t1; diff --git a/mysql-test/suite/innodb/t/foreign_null.combinations b/mysql-test/suite/innodb/t/foreign_null.combinations new file mode 100644 index 00000000000..e84e17b06ac --- /dev/null +++ b/mysql-test/suite/innodb/t/foreign_null.combinations @@ -0,0 +1,2 @@ +[COPY] +[INPLACE] diff --git a/mysql-test/suite/innodb/t/foreign_null.test b/mysql-test/suite/innodb/t/foreign_null.test new file mode 100644 index 00000000000..6869154fc9f --- /dev/null +++ b/mysql-test/suite/innodb/t/foreign_null.test @@ -0,0 +1,225 @@ +--source include/have_innodb.inc +call mtr.add_suppression("InnoDB: In ALTER TABLE .* has or is referenced in foreign key constraints which are not compatible with the new table definition."); + +let $MYSQLD_DATADIR= `select @@datadir`; +let $algorithm=`select regexp_replace('$MTR_COMBINATIONS', 'innodb,\|,innodb', '')`; + +--echo # modify child column NOT NULL on UPDATE CASCADE..parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE CASCADE)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +--error ER_FK_COLUMN_NOT_NULL +eval ALTER TABLE t2 MODIFY COLUMN f1 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify child column NOT NULL ON DELETE CASCADE..parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, FOREIGN KEY(f1) REFERENCES t1(f2) ON DELETE CASCADE)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t2 MODIFY COLUMN f1 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify child column NOT NULL ON UPDATE SET NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY(f1) REFERENCES t1(f1) ON UPDATE SET NULL)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +--error ER_FK_COLUMN_NOT_NULL +eval ALTER TABLE t2 MODIFY COLUMN f1 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify child column NOT NULL ON DELETE SET NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON DELETE SET NULL)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +--error ER_FK_COLUMN_NOT_NULL +eval ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify child column NOT NULL ON UPDATE RESTRICT..parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON UPDATE RESTRICT)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify child column NOT NULL ON DELETE RESTRICT..parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON DELETE RESTRICT)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify child column NOT NULL ON UPDATE NO ACTION..PARENT COLUMN NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON UPDATE NO ACTION)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify child column NOT NULL ON DELETE NO ACTION..PARENT COLUMN NULL +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, FOREIGN KEY (f2) REFERENCES t1(f2) ON DELETE NO ACTION)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify parent column NULL ON UPDATE CASCADE child column NOT NULL +CREATE TABLE `t#1`(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE `t#2`(f1 INT NOT NULL, + FOREIGN KEY(f1) REFERENCES `t#1`(f2) + ON UPDATE CASCADE)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD +eval ALTER TABLE `t#1` MODIFY COLUMN f2 INT,ALGORITHM=$algorithm; +DROP TABLE `t#2`, `t#1`; + +--echo # modify parent column NULL ON DELETE CASCADE child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON DELETE CASCADE)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t1 MODIFY COLUMN f2 INT,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify parent column NULL ON UPDATE SET NULL child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +--error ER_CANT_CREATE_TABLE +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE SET NULL)ENGINE=InnoDB; +DROP TABLE t1; + +--echo # modify parent column NULL ON DELETE SET NULL child NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +--error ER_CANT_CREATE_TABLE +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON DELETE SET NULL)ENGINE=InnoDB; +DROP TABLE t1; + +--echo # modify parent column NULL ON UPDATE RESTRICT child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE RESTRICT)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t1 MODIFY COLUMN f2 INT,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify parent column NULL ON DELETE RESTRICT child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE RESTRICT)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t1 MODIFY COLUMN f2 INT,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify parent column NULL ON UPDATE NO ACTION child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE NO ACTION)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t1 MODIFY COLUMN f2 INT,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify parent column NULL ON DELETE NO ACTION child column NOT NULL +CREATE TABLE t1(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES t1(f2) ON DELETE NO ACTION)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t1 MODIFY COLUMN f2 INT,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # foreign key constraint for multiple columns +--echo # modify parent column NULL ON UPDATE CASCADE child column NOT NULL +CREATE TABLE t1(f1 INT NOT NULL, f2 INT NOT NULL, + INDEX(f1, f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT NOT NULL, f2 INT NOT NULL, + INDEX(f1, f2), + FOREIGN KEY(f1, f2) REFERENCES t1(f1, f2) ON + UPDATE CASCADE)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +--error ER_FK_COLUMN_CANNOT_CHANGE_CHILD +eval ALTER TABLE t1 MODIFY COLUMN f1 INT,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # modify child column NOT NULL ON UPDATE CASCADE parent column NULL +CREATE TABLE t1(f1 INT, f2 INT, INDEX(f1, f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, INDEX(f1, f2), + FOREIGN KEY(f1, f2) REFERENCES t1(f1, f2) ON + UPDATE CASCADE)ENGINE=InnoDB; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +--error ER_FK_COLUMN_NOT_NULL +eval ALTER TABLE t2 MODIFY COLUMN f2 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # allow foreign key constraints when parent table created later +SET FOREIGN_KEY_CHECKS=0; +CREATE TABLE t2(f1 INT, FOREIGN KEY(f1) REFERENCES t1(f2) ON UPDATE CASCADE)ENGINE=InnoDB; +SET FOREIGN_KEY_CHECKS=1; +ALTER TABLE t2 MODIFY COLUMN f1 INT NOT NULL; +CREATE TABLE t1(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +INSERT INTO t1 VALUES(1, 1); +INSERT INTO t2 VALUES(1); +--error ER_ROW_IS_REFERENCED_2 +UPDATE t1 SET f2= NULL; +SELECT * FROM t2; +SET FOREIGN_KEY_CHECKS=0; +UPDATE t1 SET f2= NULL; +SELECT * FROM t2; +DROP TABLE t2, t1; + +--echo # Modify column + Drop column & Drop foreign key constraint +CREATE TABLE t1(f1 INT, f2 INT, KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE t2(f1 INT, f2 INT, f3 INT, + FOREIGN KEY fdx(f2) REFERENCES t1(f1), + FOREIGN KEY fdx2(f3) REFERENCES t1(f2))ENGINE=InnoDB; + +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t2 MODIFY f2 INT NOT NULL, DROP FOREIGN KEY fdx,ALGORITHM=$algorithm; + +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t2 ADD FOREIGN KEY fdx (f2) REFERENCES t1(f1),ALGORITHM=$algorithm; + +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t2 DROP COLUMN f2, DROP FOREIGN KEY fdx,ALGORITHM=$algorithm; +DROP TABLE t2, t1; + +--echo # Drop foreign index & modify column +CREATE TABLE `t#1`(f1 INT, f2 INT NOT NULL, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE `t#2`(f1 INT NOT NULL, FOREIGN KEY(f1) REFERENCES `t#1`(f2) ON UPDATE CASCADE)ENGINE=InnoDB; + +SET FOREIGN_KEY_CHECKS=0; +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE `t#2` DROP INDEX f1,ALGORITHM=$algorithm; +SET FOREIGN_KEY_CHECKS=1; + +let $error_code=0; +if ($algorithm == "COPY") +{ + let $error_code= ER_ERROR_ON_RENAME; +} + +--replace_regex /#sql-alter-[0-9a-f_\-]*/#sql-alter/ +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE '' $MYSQLD_DATADIR ./; +--error $error_code +eval ALTER TABLE `t#1` MODIFY COLUMN f2 INT,ALGORITHM=$algorithm; +DROP TABLE `t#2`, `t#1`; + +--echo # Drop referenced index and modify column +CREATE TABLE `t#1`(f1 INT, f2 INT, PRIMARY KEY(f1), KEY(f2))ENGINE=InnoDB; +CREATE TABLE `t#2`(f1 INT, FOREIGN KEY(f1) REFERENCES `t#1`(f2) ON UPDATE CASCADE)ENGINE=InnoDB; +SET FOREIGN_KEY_CHECKS=0; + +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE `t#1` DROP INDEX f2,ALGORITHM=$algorithm; +SET FOREIGN_KEY_CHECKS=1; + +--replace_regex /#sql-alter-[0-9a-f_\-]*/#sql-alter/ +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE '' $MYSQLD_DATADIR ./; +--error $error_code +eval ALTER TABLE `t#2` MODIFY COLUMN f1 INT NOT NULL,ALGORITHM=$algorithm; +DROP TABLE `t#2`, `t#1`; + +--echo # Self referential modifying column +CREATE TABLE t1(f1 INT, f2 INT, index(f2), foreign key(f1) references t1(f2) ON UPDATE CASCADE)engine=innodb; + +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t1 MODIFY COLUMN f2 INT NOT NULL,ALGORITHM=$algorithm; + +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t1 MODIFY COLUMN f1 INT NOT NULL,ALGORITHM=$algorithm; + +replace_result ,ALGORITHM=COPY '' ,ALGORITHM=INPLACE ''; +eval ALTER TABLE t1 MODIFY COLUMN f1 INT,ALGORITHM=$algorithm; +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/innodb-index.test b/mysql-test/suite/innodb/t/innodb-index.test index 9350672bee9..7495fc4d17e 100644 --- a/mysql-test/suite/innodb/t/innodb-index.test +++ b/mysql-test/suite/innodb/t/innodb-index.test @@ -168,7 +168,7 @@ alter table t4 drop index d; alter table t2 drop index b; --error ER_DROP_INDEX_FK alter table t2 drop index b, drop index c, drop index d; ---error ER_FK_COLUMN_CANNOT_CHANGE +--error ER_FK_COLUMN_NOT_NULL alter table t2 MODIFY b INT NOT NULL, ALGORITHM=COPY; # NULL -> NOT NULL only allowed INPLACE if strict sql_mode is on. set @old_sql_mode = @@sql_mode; diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 0a0ea7acb2e..bd43a70324a 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -9589,18 +9589,20 @@ static Create_field *get_field_by_old_name(Alter_info *alter_info, enum fk_column_change_type { FK_COLUMN_NO_CHANGE, FK_COLUMN_DATA_CHANGE, - FK_COLUMN_RENAMED, FK_COLUMN_DROPPED + FK_COLUMN_RENAMED, FK_COLUMN_DROPPED, FK_COLUMN_NOT_NULL }; /** Check that ALTER TABLE's changes on columns of a foreign key are allowed. @param[in] thd Thread context. + @param[in] table table to be altered @param[in] alter_info Alter_info describing changes to be done by ALTER TABLE. - @param[in] fk_columns List of columns of the foreign key to check. + @param[in] fk Foreign key information. @param[out] bad_column_name Name of field on which ALTER TABLE tries to do prohibited operation. + @param[in] referenced Check the referenced fields @note This function takes into account value of @@foreign_key_checks setting. @@ -9611,17 +9613,27 @@ enum fk_column_change_type change in foreign key column. @retval FK_COLUMN_RENAMED Foreign key column is renamed. @retval FK_COLUMN_DROPPED Foreign key column is dropped. + @retval FK_COLUMN_NOT_NULL Foreign key column cannot be null + if ON...SET NULL or ON UPDATE + CASCADE conflicts with NOT NULL */ static enum fk_column_change_type -fk_check_column_changes(THD *thd, Alter_info *alter_info, - List &fk_columns, - const char **bad_column_name) +fk_check_column_changes(THD *thd, const TABLE *table, + Alter_info *alter_info, + FOREIGN_KEY_INFO *fk, + const char **bad_column_name, + bool referenced=false) { + List &fk_columns= referenced + ? fk->referenced_fields + : fk->foreign_fields; List_iterator_fast column_it(fk_columns); LEX_CSTRING *column; + int n_col= 0; *bad_column_name= NULL; + enum fk_column_change_type result= FK_COLUMN_NO_CHANGE; while ((column= column_it++)) { @@ -9640,8 +9652,8 @@ fk_check_column_changes(THD *thd, Alter_info *alter_info, SE that foreign keys should be updated to use new name of column like it happens in case of in-place algorithm. */ - *bad_column_name= column->str; - return FK_COLUMN_RENAMED; + result= FK_COLUMN_RENAMED; + goto func_exit; } /* @@ -9654,17 +9666,55 @@ fk_check_column_changes(THD *thd, Alter_info *alter_info, new_field->flags&= ~AUTO_INCREMENT_FLAG; const bool equal_result= old_field->is_equal(*new_field); new_field->flags= flags; + const bool old_field_not_null= old_field->flags & NOT_NULL_FLAG; + const bool new_field_not_null= new_field->flags & NOT_NULL_FLAG; - if ((equal_result == IS_EQUAL_NO) || - ((new_field->flags & NOT_NULL_FLAG) && - !(old_field->flags & NOT_NULL_FLAG))) + if ((equal_result == IS_EQUAL_NO)) { /* Column in a FK has changed significantly and it may break referential intergrity. */ - *bad_column_name= column->str; - return FK_COLUMN_DATA_CHANGE; + result= FK_COLUMN_DATA_CHANGE; + goto func_exit; + } + + if (old_field_not_null != new_field_not_null) + { + if (referenced && !new_field_not_null) + { + /* + Don't allow referenced column to change from + NOT NULL to NULL when foreign key relation is + ON UPDATE CASCADE and the referencing column + is declared as NOT NULL + */ + if (fk->update_method == FK_OPTION_CASCADE && + !fk->is_nullable(false, n_col)) + { + result= FK_COLUMN_DATA_CHANGE; + goto func_exit; + } + } + else if (!referenced && new_field_not_null) + { + /* + Don't allow the foreign column to change + from NULL to NOT NULL when foreign key type is + 1) UPDATE SET NULL + 2) DELETE SET NULL + 3) UPDATE CASCADE and referenced column is declared as NULL + */ + if (fk->update_method == FK_OPTION_SET_NULL || + fk->delete_method == FK_OPTION_SET_NULL || + (fk->update_method == FK_OPTION_CASCADE && + fk->referenced_key_name && + fk->is_nullable(true, n_col))) + { + result= FK_COLUMN_NOT_NULL; + goto func_exit; + } + } } } else @@ -9678,12 +9728,15 @@ fk_check_column_changes(THD *thd, Alter_info *alter_info, field being dropped since it is easy to break referential integrity in this case. */ - *bad_column_name= column->str; - return FK_COLUMN_DROPPED; + result= FK_COLUMN_DROPPED; + goto func_exit; } + n_col++; } - return FK_COLUMN_NO_CHANGE; +func_exit: + *bad_column_name= column->str; + return result; } @@ -9775,9 +9828,8 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, enum fk_column_change_type changes; const char *bad_column_name; - changes= fk_check_column_changes(thd, alter_info, - f_key->referenced_fields, - &bad_column_name); + changes= fk_check_column_changes(thd, table, alter_info, f_key, + &bad_column_name, true); switch(changes) { @@ -9811,6 +9863,9 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, f_key->foreign_id->str, buff.c_ptr()); DBUG_RETURN(true); } + /* FK_COLUMN_NOT_NULL error happens only when changing + the foreign key column from NULL to NOT NULL */ + case FK_COLUMN_NOT_NULL: default: DBUG_ASSERT(0); } @@ -9849,8 +9904,7 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, enum fk_column_change_type changes; const char *bad_column_name; - changes= fk_check_column_changes(thd, alter_info, - f_key->foreign_fields, + changes= fk_check_column_changes(thd, table, alter_info, f_key, &bad_column_name); switch(changes) @@ -9872,6 +9926,10 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, my_error(ER_FK_COLUMN_CANNOT_DROP, MYF(0), bad_column_name, f_key->foreign_id->str); DBUG_RETURN(true); + case FK_COLUMN_NOT_NULL: + my_error(ER_FK_COLUMN_NOT_NULL, MYF(0), bad_column_name, + f_key->foreign_id->str); + DBUG_RETURN(true); default: DBUG_ASSERT(0); } diff --git a/sql/table.h b/sql/table.h index 6910ff0aff1..45fd8c01d9e 100644 --- a/sql/table.h +++ b/sql/table.h @@ -36,6 +36,7 @@ #include "sql_i_s.h" #include "sql_type.h" /* vers_kind_t */ #include "privilege.h" /* privilege_t */ +#include "my_bit.h" /* Buffer for unix timestamp in microseconds: @@ -1882,6 +1883,70 @@ typedef struct st_foreign_key_info LEX_CSTRING *referenced_key_name; List foreign_fields; List referenced_fields; +private: + unsigned char *fields_nullable= nullptr; + + /** + Get the number of fields exist in foreign key relationship + */ + unsigned get_n_fields() const noexcept + { + unsigned n_fields= foreign_fields.elements; + if (n_fields == 0) + n_fields= referenced_fields.elements; + return n_fields; + } + + /** + Assign nullable field for referenced and foreign fields + based on number of fields. This nullable fields + should be allocated by engine for passing the + foreign key information + @param thd thread to allocate the memory + @param num_fields number of fields + */ + void assign_nullable(THD *thd, unsigned num_fields) noexcept + { + fields_nullable= + (unsigned char *)thd_calloc(thd, + my_bits_in_bytes(2 * num_fields)); + } + +public: + /** + Set nullable bit for the field in the given field + @param referenced set null bit for referenced column + @param field field number + @param n_fields number of fields + */ + void set_nullable(THD *thd, bool referenced, + unsigned field, unsigned n_fields) noexcept + { + if (!fields_nullable) + assign_nullable(thd, n_fields); + DBUG_ASSERT(fields_nullable); + DBUG_ASSERT(field < n_fields); + size_t bit= size_t{field} + referenced * n_fields; + fields_nullable[bit / 8]|= (unsigned char)(1 << (bit % 8)); + } + + /** + Check whether the given field_no in foreign key field or + referenced key field + @param referenced check referenced field nullable value + @param field field number + @return true if the field is nullable or false if it is not + */ + bool is_nullable(bool referenced, unsigned field) const noexcept + { + if (!fields_nullable) + return false; + unsigned n_field= get_n_fields(); + DBUG_ASSERT(field < n_field); + size_t bit= size_t{field} + referenced * n_field; + return fields_nullable[bit / 8] & (1 << (bit % 8)); + } + } FOREIGN_KEY_INFO; LEX_CSTRING *fk_option_name(enum_fk_option opt); diff --git a/storage/innobase/dict/dict0crea.cc b/storage/innobase/dict/dict0crea.cc index e904c2e3e86..f0e47938178 100644 --- a/storage/innobase/dict/dict0crea.cc +++ b/storage/innobase/dict/dict0crea.cc @@ -1838,8 +1838,8 @@ dict_foreigns_has_s_base_col( foreign = *it; ulint type = foreign->type; - type &= ~(DICT_FOREIGN_ON_DELETE_NO_ACTION - | DICT_FOREIGN_ON_UPDATE_NO_ACTION); + type &= ~(foreign->DELETE_NO_ACTION + | foreign->UPDATE_NO_ACTION); if (type == 0) { continue; @@ -1897,8 +1897,12 @@ dict_create_add_foreigns_to_dictionary( foreign = *it; ut_ad(foreign->id != NULL); - error = dict_create_add_foreign_to_dictionary( - table->name.m_name, foreign, trx); + if (!foreign->check_fk_constraint_valid()) { + error = DB_CANNOT_ADD_CONSTRAINT; + } else { + error = dict_create_add_foreign_to_dictionary( + table->name.m_name, foreign, trx); + } if (error != DB_SUCCESS) { break; diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc index 1df848d259c..b5b0331b6d9 100644 --- a/storage/innobase/dict/dict0dict.cc +++ b/storage/innobase/dict/dict0dict.cc @@ -3037,8 +3037,8 @@ dict_foreign_add_to_cache( for_in_cache->n_fields, for_in_cache->referenced_index, check_charsets, for_in_cache->type - & (DICT_FOREIGN_ON_DELETE_SET_NULL - | DICT_FOREIGN_ON_UPDATE_SET_NULL)); + & (foreign->DELETE_SET_NULL + | foreign->UPDATE_SET_NULL)); if (index == NULL && !(ignore_err & DICT_ERR_IGNORE_FK_NOKEY)) { @@ -4003,27 +4003,27 @@ dict_print_info_on_foreign_key_in_create_format(const trx_t *trx, str.append(")"); - if (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE) { + if (foreign->type & foreign->DELETE_CASCADE) { str.append(" ON DELETE CASCADE"); } - if (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) { + if (foreign->type & foreign->DELETE_SET_NULL) { str.append(" ON DELETE SET NULL"); } - if (foreign->type & DICT_FOREIGN_ON_DELETE_NO_ACTION) { + if (foreign->type & foreign->DELETE_NO_ACTION) { str.append(" ON DELETE NO ACTION"); } - if (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE) { + if (foreign->type & foreign->UPDATE_CASCADE) { str.append(" ON UPDATE CASCADE"); } - if (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL) { + if (foreign->type & foreign->UPDATE_SET_NULL) { str.append(" ON UPDATE SET NULL"); } - if (foreign->type & DICT_FOREIGN_ON_UPDATE_NO_ACTION) { + if (foreign->type & foreign->UPDATE_NO_ACTION) { str.append(" ON UPDATE NO ACTION"); } @@ -4086,27 +4086,27 @@ dict_print_info_on_foreign_keys( str.append(")"); - if (foreign->type == DICT_FOREIGN_ON_DELETE_CASCADE) { + if (foreign->type == foreign->DELETE_CASCADE) { str.append(" ON DELETE CASCADE"); } - if (foreign->type == DICT_FOREIGN_ON_DELETE_SET_NULL) { + if (foreign->type == foreign->DELETE_SET_NULL) { str.append(" ON DELETE SET NULL"); } - if (foreign->type & DICT_FOREIGN_ON_DELETE_NO_ACTION) { + if (foreign->type & foreign->DELETE_NO_ACTION) { str.append(" ON DELETE NO ACTION"); } - if (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE) { + if (foreign->type & foreign->UPDATE_CASCADE) { str.append(" ON UPDATE CASCADE"); } - if (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL) { + if (foreign->type & foreign->UPDATE_SET_NULL) { str.append(" ON UPDATE SET NULL"); } - if (foreign->type & DICT_FOREIGN_ON_UPDATE_NO_ACTION) { + if (foreign->type & foreign->UPDATE_NO_ACTION) { str.append(" ON UPDATE NO ACTION"); } } diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 2c24bbf4485..1abf394c7bd 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -12524,13 +12524,13 @@ create_table_info_t::create_foreign_keys() case FK_OPTION_RESTRICT: break; case FK_OPTION_CASCADE: - foreign->type |= DICT_FOREIGN_ON_DELETE_CASCADE; + foreign->type |= foreign->DELETE_CASCADE; break; case FK_OPTION_SET_NULL: - foreign->type |= DICT_FOREIGN_ON_DELETE_SET_NULL; + foreign->type |= foreign->DELETE_SET_NULL; break; case FK_OPTION_NO_ACTION: - foreign->type |= DICT_FOREIGN_ON_DELETE_NO_ACTION; + foreign->type |= foreign->DELETE_NO_ACTION; break; case FK_OPTION_SET_DEFAULT: // TODO: MDEV-10393 Foreign keys SET DEFAULT action @@ -12545,13 +12545,13 @@ create_table_info_t::create_foreign_keys() case FK_OPTION_RESTRICT: break; case FK_OPTION_CASCADE: - foreign->type |= DICT_FOREIGN_ON_UPDATE_CASCADE; + foreign->type |= foreign->UPDATE_CASCADE; break; case FK_OPTION_SET_NULL: - foreign->type |= DICT_FOREIGN_ON_UPDATE_SET_NULL; + foreign->type |= foreign->UPDATE_SET_NULL; break; case FK_OPTION_NO_ACTION: - foreign->type |= DICT_FOREIGN_ON_UPDATE_NO_ACTION; + foreign->type |= foreign->UPDATE_NO_ACTION; break; case FK_OPTION_SET_DEFAULT: // TODO: MDEV-10393 Foreign keys SET DEFAULT action @@ -15266,28 +15266,43 @@ get_foreign_key_info( name = thd_make_lex_string(thd, name, ptr, strlen(ptr), 1); f_key_info.foreign_fields.push_back(name); + + if (dict_index_t* fidx = foreign->foreign_index) { + if (fidx->fields[i].col->is_nullable()) { + f_key_info.set_nullable(thd, false, i, + foreign->n_fields); + } + } ptr = foreign->referenced_col_names[i]; name = thd_make_lex_string(thd, name, ptr, strlen(ptr), 1); f_key_info.referenced_fields.push_back(name); + + if (dict_index_t* ref_idx = foreign->referenced_index) { + if (ref_idx->fields[i].col->is_nullable()) { + f_key_info.set_nullable(thd, true, i, + foreign->n_fields); + } + } + } while (++i < foreign->n_fields); - if (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE) { + if (foreign->type & foreign->DELETE_CASCADE) { f_key_info.delete_method = FK_OPTION_CASCADE; - } else if (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) { + } else if (foreign->type & foreign->DELETE_SET_NULL) { f_key_info.delete_method = FK_OPTION_SET_NULL; - } else if (foreign->type & DICT_FOREIGN_ON_DELETE_NO_ACTION) { + } else if (foreign->type & foreign->DELETE_NO_ACTION) { f_key_info.delete_method = FK_OPTION_NO_ACTION; } else { f_key_info.delete_method = FK_OPTION_RESTRICT; } - if (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE) { + if (foreign->type & foreign->UPDATE_CASCADE) { f_key_info.update_method = FK_OPTION_CASCADE; - } else if (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL) { + } else if (foreign->type & foreign->UPDATE_SET_NULL) { f_key_info.update_method = FK_OPTION_SET_NULL; - } else if (foreign->type & DICT_FOREIGN_ON_UPDATE_NO_ACTION) { + } else if (foreign->type & foreign->UPDATE_NO_ACTION) { f_key_info.update_method = FK_OPTION_NO_ACTION; } else { f_key_info.update_method = FK_OPTION_RESTRICT; diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index d3a7e3d3749..613ba560ab2 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -2759,8 +2759,8 @@ innobase_check_fk_option( return(true); } - if (foreign->type & (DICT_FOREIGN_ON_UPDATE_SET_NULL - | DICT_FOREIGN_ON_DELETE_SET_NULL)) { + if (foreign->type & (foreign->UPDATE_SET_NULL + | foreign->DELETE_SET_NULL)) { for (ulint j = 0; j < foreign->n_fields; j++) { if ((dict_index_get_nth_col( @@ -2795,13 +2795,13 @@ innobase_set_foreign_key_option( case FK_OPTION_NO_ACTION: case FK_OPTION_RESTRICT: case FK_OPTION_SET_DEFAULT: - foreign->type = DICT_FOREIGN_ON_DELETE_NO_ACTION; + foreign->type = foreign->DELETE_NO_ACTION; break; case FK_OPTION_CASCADE: - foreign->type = DICT_FOREIGN_ON_DELETE_CASCADE; + foreign->type = foreign->DELETE_CASCADE; break; case FK_OPTION_SET_NULL: - foreign->type = DICT_FOREIGN_ON_DELETE_SET_NULL; + foreign->type = foreign->DELETE_SET_NULL; break; case FK_OPTION_UNDEF: break; @@ -2811,13 +2811,13 @@ innobase_set_foreign_key_option( case FK_OPTION_NO_ACTION: case FK_OPTION_RESTRICT: case FK_OPTION_SET_DEFAULT: - foreign->type |= DICT_FOREIGN_ON_UPDATE_NO_ACTION; + foreign->type |= foreign->UPDATE_NO_ACTION; break; case FK_OPTION_CASCADE: - foreign->type |= DICT_FOREIGN_ON_UPDATE_CASCADE; + foreign->type |= foreign->UPDATE_CASCADE; break; case FK_OPTION_SET_NULL: - foreign->type |= DICT_FOREIGN_ON_UPDATE_SET_NULL; + foreign->type |= foreign->UPDATE_SET_NULL; break; case FK_OPTION_UNDEF: break; @@ -2968,8 +2968,8 @@ innobase_check_fk_stored( { ulint type = foreign->type; - type &= ~(DICT_FOREIGN_ON_DELETE_NO_ACTION - | DICT_FOREIGN_ON_UPDATE_NO_ACTION); + type &= ~(foreign->DELETE_NO_ACTION + | foreign->UPDATE_NO_ACTION); if (type == 0 || s_cols == NULL) { return(false); @@ -4220,103 +4220,157 @@ innobase_dropping_foreign( return(false); } -/** Determines if an InnoDB FOREIGN KEY constraint depends on a -column that is being dropped or modified to NOT NULL. +/** Determines if an InnoDB FOREIGN KEY constraint depends on +the nullability changes of a column. +Enforce the following rules: + +i) Don't allow the referencing column from NULL TO NOT NULL when + 1) Foreign key constraint type is ON UPDATE SET NULL + 2) Foreign key constraint type is ON DELETE SET NULL + 3) Foreign key constraint type is UPDATE CASCADE and referenced + column declared as NULL + +ii) Don't allow the referenced column from NOT NULL to NULL when +foreign key constraint type is UPDATE CASCADE and referencing column +declared as NOT NULL + @param user_table InnoDB table as it is before the ALTER operation -@param col_name Name of the column being altered @param drop_fk constraints being dropped @param n_drop_fk number of constraints that are being dropped -@param drop true=drop column, false=set NOT NULL +@param col_name modified column name +@param new_field_flags Modified field flags @retval true Not allowed (will call my_error()) @retval false Allowed */ -MY_ATTRIBUTE((pure, nonnull(1,4), warn_unused_result)) static -bool -innobase_check_foreigns_low( - const dict_table_t* user_table, - dict_foreign_t** drop_fk, - ulint n_drop_fk, - const char* col_name, - bool drop) +bool check_foreigns_nullability(const dict_table_t *user_table, + dict_foreign_t **drop_fk, ulint n_drop_fk, + const char *col_name, uint32_t new_field_flags) { - dict_foreign_t* foreign; - ut_ad(mutex_own(&dict_sys.mutex)); + ut_ad(mutex_own(&dict_sys.mutex)); - /* Check if any FOREIGN KEY constraints are defined on this - column. */ + /* Changing from NULL to NOT NULL. So check referenced set */ + if ((new_field_flags & NOT_NULL_FLAG)) + { + for (dict_foreign_t *foreign : user_table->foreign_set) + { + if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) + continue; - for (dict_foreign_set::const_iterator it = user_table->foreign_set.begin(); - it != user_table->foreign_set.end(); - ++it) { + if (foreign->on_update_cascade_null(col_name)) + goto non_null_error; - foreign = *it; + if (foreign->type & (foreign->DELETE_SET_NULL | + foreign->UPDATE_SET_NULL)) + { + if (foreign->foreign_index + && foreign->col_fk_exists(col_name) != UINT_MAX) + { +non_null_error: + const char* fid = strchr(foreign->id, '/'); + fid= fid ? fid + 1 : foreign->id; + my_error(ER_FK_COLUMN_NOT_NULL, MYF(0), col_name, fid); + return true; + } + } + } + } + else + { + for (dict_foreign_t *foreign : user_table->referenced_set) + { + if (foreign->on_update_cascade_not_null(col_name)) + { + char display_name[FN_REFLEN]; + const int dblen= int(table_name_t(const_cast(foreign-> + foreign_table_name)).dblen()); + char tbl_name[MAX_TABLE_NAME_LEN]; + uint errors; + ulint tbl_name_len= strlen(foreign->foreign_table_name) - dblen + 1; + strncpy(tbl_name, foreign->foreign_table_name + dblen + 1, + tbl_name_len); + tbl_name[tbl_name_len - 1]= '\0'; + innobase_convert_to_system_charset(tbl_name, + strchr(foreign->foreign_table_name, + '/') + 1, + MAX_TABLE_NAME_LEN, &errors); + if (errors) + { + strncpy(tbl_name, foreign->foreign_table_name + dblen + 1, + tbl_name_len); + tbl_name[tbl_name_len - 1]= '\0'; + } - if (!drop && !(foreign->type - & (DICT_FOREIGN_ON_DELETE_SET_NULL - | DICT_FOREIGN_ON_UPDATE_SET_NULL))) { - continue; - } + my_snprintf(display_name, FN_REFLEN - 1, "%.*s.%s", + dblen, foreign->foreign_table_name, tbl_name); - if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) { - continue; - } + display_name[FN_REFLEN - 1]= '\0'; + const char* fid = strchr(foreign->id, '/'); + fid= fid ? fid + 1 : foreign->id; + my_error(ER_FK_COLUMN_CANNOT_CHANGE_CHILD, MYF(0), col_name, + fid, display_name); + return true; + } + } + } - for (unsigned f = 0; f < foreign->n_fields; f++) { - if (!strcmp(foreign->foreign_col_names[f], - col_name)) { - my_error(drop - ? ER_FK_COLUMN_CANNOT_DROP - : ER_FK_COLUMN_NOT_NULL, MYF(0), - col_name, foreign->id); - return(true); - } - } - } + return false; +} - if (!drop) { - /* SET NULL clauses on foreign key constraints of - child tables affect the child tables, not the parent table. - The column can be NOT NULL in the parent table. */ - return(false); - } +/** Determines if an InnoDB FOREIGN KEY constraint depends on +the column when it is being dropped. +@param user_table InnoDB table as it is before the ALTER operation +@param drop_fk constraints being dropped +@param n_drop_fk number of constraints that are being dropped +@param col_name column name to be dropped +@retval true Not allowed (will call my_error()) +@retval false Allowed +*/ +static +bool check_foreign_drop_col(const dict_table_t *user_table, + dict_foreign_t **drop_fk, ulint n_drop_fk, + const char *col_name) +{ + ut_ad(mutex_own(&dict_sys.mutex)); - /* Check if any FOREIGN KEY constraints in other tables are - referring to the column that is being dropped. */ - for (dict_foreign_set::const_iterator it - = user_table->referenced_set.begin(); - it != user_table->referenced_set.end(); - ++it) { + /* Check if any FOREIGN KEY constraints are defined on this column. */ + for (dict_foreign_t *foreign : user_table->foreign_set) + { + if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) + continue; - foreign = *it; + for (unsigned f = 0; f < foreign->n_fields; f++) + if (!strcmp(foreign->foreign_col_names[f], col_name)) + { + my_error(ER_FK_COLUMN_CANNOT_DROP, MYF(0), + col_name, foreign->id); + return true; + } + } - if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) { - continue; - } + /* Check if any FOREIGN KEY constraints in other tables are + referring to the column that is being dropped. */ + for (dict_foreign_t *foreign : user_table->referenced_set) + { + if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) + continue; - for (unsigned f = 0; f < foreign->n_fields; f++) { - char display_name[FN_REFLEN]; - - if (strcmp(foreign->referenced_col_names[f], - col_name)) { - continue; - } - - char* buf_end = innobase_convert_name( - display_name, (sizeof display_name) - 1, - foreign->foreign_table_name, - strlen(foreign->foreign_table_name), - NULL); - *buf_end = '\0'; - my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD, - MYF(0), col_name, foreign->id, - display_name); - - return(true); - } - } - - return(false); + for (unsigned f = 0; f < foreign->n_fields; f++) + { + char display_name[FN_REFLEN]; + if (strcmp(foreign->referenced_col_names[f], col_name)) + continue; + char* buf_end = innobase_convert_name( + display_name, (sizeof display_name) - 1, + foreign->foreign_table_name, + strlen(foreign->foreign_table_name), NULL); + *buf_end = '\0'; + my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD, + MYF(0), col_name, foreign->id, display_name); + return true; + } + } + return false; } /** Determines if an InnoDB FOREIGN KEY constraint depends on a @@ -4351,16 +4405,25 @@ innobase_check_foreigns( return field.field == *fp; }); - if (it == end || (it->flags & NOT_NULL_FLAG)) { - if (innobase_check_foreigns_low( - user_table, drop_fk, n_drop_fk, - (*fp)->field_name.str, it == end)) { - return(true); + if (it == end) { + if (check_foreign_drop_col( + user_table, drop_fk, n_drop_fk, + (*fp)->field_name.str)) { + return true; + } + } else if ((it->flags & NOT_NULL_FLAG) + != ((*fp)->flags & NOT_NULL_FLAG)) { + + if (check_foreigns_nullability(user_table, drop_fk, + n_drop_fk, + (*fp)->field_name.str, + it->flags)) { + return true; } } } - return(false); + return false; } /** Convert a default value for ADD COLUMN. @@ -9836,8 +9899,8 @@ innobase_update_foreign_try( fk->foreign_col_names, fk->n_fields, fk->referenced_index, TRUE, fk->type - & (DICT_FOREIGN_ON_DELETE_SET_NULL - | DICT_FOREIGN_ON_UPDATE_SET_NULL), + & (fk->DELETE_SET_NULL + | fk->UPDATE_SET_NULL), NULL, NULL, NULL); if (!fk->foreign_index) { my_error(ER_FK_INCORRECT_OPTION, diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index 6bf16bcbdc3..5cebfe6710d 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -1451,45 +1451,149 @@ typedef std::set, /** Data structure for a foreign key constraint; an example: FOREIGN KEY (A, B) REFERENCES TABLE2 (C, D). Most fields will be initialized to 0, NULL or FALSE in dict_mem_foreign_create(). */ -struct dict_foreign_t{ - mem_heap_t* heap; /*!< this object is allocated from - this memory heap */ - char* id; /*!< id of the constraint as a - null-terminated string */ - unsigned n_fields:10; /*!< number of indexes' first fields - for which the foreign key - constraint is defined: we allow the - indexes to contain more fields than - mentioned in the constraint, as long - as the first fields are as mentioned */ - unsigned type:6; /*!< 0 or DICT_FOREIGN_ON_DELETE_CASCADE - or DICT_FOREIGN_ON_DELETE_SET_NULL */ - char* foreign_table_name;/*!< foreign table name */ - char* foreign_table_name_lookup; - /*!< foreign table name used for dict lookup */ - dict_table_t* foreign_table; /*!< table where the foreign key is */ - const char** foreign_col_names;/*!< names of the columns in the - foreign key */ - char* referenced_table_name;/*!< referenced table name */ - char* referenced_table_name_lookup; - /*!< referenced table name for dict lookup*/ - dict_table_t* referenced_table;/*!< table where the referenced key - is */ - const char** referenced_col_names;/*!< names of the referenced - columns in the referenced table */ - dict_index_t* foreign_index; /*!< foreign index; we require that - both tables contain explicitly defined - indexes for the constraint: InnoDB - does not generate new indexes - implicitly */ - dict_index_t* referenced_index;/*!< referenced index */ +struct dict_foreign_t +{ + /* Object is allocated from this memory heap */ + mem_heap_t *heap; + /* id of the constraint as a null terminated string */ + char *id; + /* number of indexes first fields for which the foreign key + constraint is defined: We allow the indexes to contain more + fields than mentioned in the constraint, as long as the first + fields are as mentioned */ + unsigned n_fields:10; + /* 0 or DELETE_CASCADE OR DELETE_SET_NULL */ + unsigned type:6; + /* foreign table name */ + char *foreign_table_name; + /* Foreign table name used for dict lookup */ + char *foreign_table_name_lookup; + /* table where the foreign key is */ + dict_table_t *foreign_table; + /* names of the columns in the foreign key */ + const char **foreign_col_names; + /* referenced table name */ + char *referenced_table_name; + /* referenced table name for dict lookup */ + char *referenced_table_name_lookup; + /* Table where the referenced key is */ + dict_table_t *referenced_table; + /* Names of the referenced columns in the referenced table */ + const char **referenced_col_names; + /* foreign index; we require that both tables contain explicitly + defined indexes for the constraint: InnoDB does not generate + new indexes implicitly */ + dict_index_t *foreign_index; + /* referenced index */ + dict_index_t *referenced_index; + /* set of virtual columns affected by foreign key constraint */ + dict_vcol_set *v_cols; + /** Check whether the fulltext index gets affected by + foreign key constraint */ + bool affects_fulltext() const; + /** The flags for ON_UPDATE and ON_DELETE can be ORed; + the default is that a foreign key constraint is enforced, + therefore RESTRICT just means no flag */ + static constexpr unsigned DELETE_CASCADE= 1U; + static constexpr unsigned DELETE_SET_NULL= 2U; + static constexpr unsigned UPDATE_CASCADE= 4U; + static constexpr unsigned UPDATE_SET_NULL= 8U; + static constexpr unsigned DELETE_NO_ACTION= 16U; + static constexpr unsigned UPDATE_NO_ACTION= 32U; +private: + /** Check whether the name exists in given column names + @retval offset or UINT_MAX if name not found */ + unsigned col_exists(const char *name, const char **names) const noexcept + { + for (unsigned i= 0; i < n_fields; i++) + { + if (!strcmp(names[i], name)) + return i; + } + return UINT_MAX; + } - dict_vcol_set* v_cols; /*!< set of virtual columns affected - by foreign key constraint. */ +public: + /** Check whether the name exists in the foreign key column names + @retval offset in case of success + @retval UINT_MAX in case of failure */ + unsigned col_fk_exists(const char *name) const noexcept + { + return col_exists(name, foreign_col_names); + } - /** Check whether the fulltext index gets affected by - foreign key constraint */ - bool affects_fulltext() const; + /** Check whether the name exists in the referenced + key column names + @retval offset in case of success + @retval UINT_MAX in case of failure */ + unsigned col_ref_exists(const char *name) const noexcept + { + return col_exists(name, referenced_col_names); + } + + /** Check whether the foreign key constraint depends on + the nullability of the referenced column to be modified + @param name column to be modified + @return true in case of no conflict or false */ + bool on_update_cascade_not_null(const char *name) const noexcept + { + if (!foreign_index || type != UPDATE_CASCADE) + return false; + unsigned offset= col_ref_exists(name); + if (offset == UINT_MAX) + return false; + + ut_ad(offset < n_fields); + return foreign_index->fields[offset].col->prtype & DATA_NOT_NULL; + } + + /** Check whether the foreign key constraint depends on + the nullability of the foreign column to be modified + @param name column to be modified + @return true in case of no conflict or false */ + bool on_update_cascade_null(const char *name) const noexcept + { + if (!referenced_index || type != UPDATE_CASCADE) + return false; + unsigned offset= col_fk_exists(name); + if (offset == UINT_MAX) + return false; + + ut_ad(offset < n_fields); + return !(referenced_index->fields[offset].col->prtype & DATA_NOT_NULL); + } + + /** This is called during CREATE TABLE statement + to check the foreign key nullability constraint + @return true if foreign key constraint is valid + or else false */ + bool check_fk_constraint_valid() + { + if (!type || type & (DELETE_CASCADE | DELETE_NO_ACTION | + UPDATE_NO_ACTION)) + return true; + + if (!referenced_index) + return true; + + for (unsigned i= 0; i < n_fields; i++) + { + dict_col_t *col = foreign_index->fields[i].col; + if (col->prtype & DATA_NOT_NULL) + { + /* Foreign type is ON DELETE SET NULL + or ON UPDATE SET NULL */ + if (type & (DELETE_SET_NULL | UPDATE_SET_NULL)) + return false; + + dict_col_t *ref_col= referenced_index->fields[i].col; + /* Referenced index respective fields shouldn't be NULL */ + if (!(ref_col->prtype & DATA_NOT_NULL)) + return false; + } + } + return true; + } }; std::ostream& @@ -1667,17 +1771,6 @@ struct dict_foreign_set_free { const dict_foreign_set& m_foreign_set; }; -/** The flags for ON_UPDATE and ON_DELETE can be ORed; the default is that -a foreign key constraint is enforced, therefore RESTRICT just means no flag */ -/* @{ */ -#define DICT_FOREIGN_ON_DELETE_CASCADE 1U /*!< ON DELETE CASCADE */ -#define DICT_FOREIGN_ON_DELETE_SET_NULL 2U /*!< ON UPDATE SET NULL */ -#define DICT_FOREIGN_ON_UPDATE_CASCADE 4U /*!< ON DELETE CASCADE */ -#define DICT_FOREIGN_ON_UPDATE_SET_NULL 8U /*!< ON UPDATE SET NULL */ -#define DICT_FOREIGN_ON_DELETE_NO_ACTION 16U /*!< ON DELETE NO ACTION */ -#define DICT_FOREIGN_ON_UPDATE_NO_ACTION 32U /*!< ON UPDATE NO ACTION */ -/* @} */ - /** Display an identifier. @param[in,out] s output stream @param[in] id_name SQL identifier (other than table name) diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 350a002f83f..4f14ccbd7ab 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -903,10 +903,10 @@ row_ins_foreign_fill_virtual( return DB_OUT_OF_MEMORY; } ut_ad(!node->is_delete - || (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL)); - ut_ad(foreign->type & (DICT_FOREIGN_ON_DELETE_SET_NULL - | DICT_FOREIGN_ON_UPDATE_SET_NULL - | DICT_FOREIGN_ON_UPDATE_CASCADE)); + || (foreign->type & foreign->DELETE_SET_NULL)); + ut_ad(foreign->type & (foreign->DELETE_SET_NULL + | foreign->UPDATE_SET_NULL + | foreign->UPDATE_CASCADE)); for (uint16_t i = 0; i < n_v_fld; i++) { @@ -1021,8 +1021,8 @@ row_ins_foreign_check_on_constraint( node = static_cast(thr->run_node); if (node->is_delete && 0 == (foreign->type - & (DICT_FOREIGN_ON_DELETE_CASCADE - | DICT_FOREIGN_ON_DELETE_SET_NULL))) { + & (foreign->DELETE_CASCADE + | foreign->DELETE_SET_NULL))) { row_ins_foreign_report_err("Trying to delete", thr, foreign, @@ -1032,8 +1032,8 @@ row_ins_foreign_check_on_constraint( } if (!node->is_delete && 0 == (foreign->type - & (DICT_FOREIGN_ON_UPDATE_CASCADE - | DICT_FOREIGN_ON_UPDATE_SET_NULL))) { + & (foreign->UPDATE_CASCADE + | foreign->UPDATE_SET_NULL))) { /* This is an UPDATE */ @@ -1056,7 +1056,7 @@ row_ins_foreign_check_on_constraint( cascade->foreign = foreign; if (node->is_delete - && (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE)) { + && (foreign->type & foreign->DELETE_CASCADE)) { cascade->is_delete = PLAIN_DELETE; } else { cascade->is_delete = NO_DELETE; @@ -1199,8 +1199,8 @@ row_ins_foreign_check_on_constraint( } if (node->is_delete - ? (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) - : (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL)) { + ? (foreign->type & foreign->DELETE_SET_NULL) + : (foreign->type & foreign->UPDATE_SET_NULL)) { /* Build the appropriate update vector which sets foreign->n_fields first fields in rec to SQL NULL */ @@ -1245,12 +1245,12 @@ row_ins_foreign_check_on_constraint( } } else if (table->fts && cascade->is_delete == PLAIN_DELETE && foreign->affects_fulltext()) { - /* DICT_FOREIGN_ON_DELETE_CASCADE case */ + /* dict_foreign_t::DELETE_CASCADE case */ fts_trx_add_op(trx, table, doc_id, FTS_DELETE, NULL); } if (!node->is_delete - && (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE)) { + && (foreign->type & foreign->UPDATE_CASCADE)) { /* Build the appropriate update vector which sets changing foreign->n_fields first fields in rec to new values */