MDEV-34392 Inplace algorithm violates the foreign key constraint
Don't allow the referencing key 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 Don't allow the referenced key column from NOT NULL to NULL when foreign key constraint type is UPDATE CASCADE and referencing key columns doesn't allow NULL values get_foreign_key_info(): InnoDB sends the information about nullability of the foreign key fields and referenced key fields. fk_check_column_changes(): Enforce the above rules for COPY algorithm innobase_check_foreign_drop_col(): Checks whether the dropped column exists in existing foreign key relation innobase_check_foreign_low() : Enforce the above rules for INPLACE algorithm dict_foreign_t::check_fk_constraint_valid(): This is used by CREATE TABLE statement to check nullability for foreign key relation.
This commit is contained in:
parent
45298b730b
commit
cc810e64d4
@ -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 <intrin.h>
|
||||
#endif
|
||||
|
@ -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,
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
CREATE TABLE t0 (
|
||||
f1 INT PRIMARY KEY,
|
||||
f2 INT UNIQUE
|
||||
f2 INT UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE t1 (
|
||||
|
18
mysql-test/suite/innodb/r/foreign_null,COPY.rdiff
Normal file
18
mysql-test/suite/innodb/r/foreign_null,COPY.rdiff
Normal file
@ -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;
|
156
mysql-test/suite/innodb/r/foreign_null.result
Normal file
156
mysql-test/suite/innodb/r/foreign_null.result
Normal file
@ -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;
|
@ -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
|
||||
|
@ -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;
|
||||
|
2
mysql-test/suite/innodb/t/foreign_null.combinations
Normal file
2
mysql-test/suite/innodb/t/foreign_null.combinations
Normal file
@ -0,0 +1,2 @@
|
||||
[COPY]
|
||||
[INPLACE]
|
225
mysql-test/suite/innodb/t/foreign_null.test
Normal file
225
mysql-test/suite/innodb/t/foreign_null.test
Normal file
@ -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;
|
@ -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;
|
||||
|
@ -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<LEX_CSTRING> &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<LEX_CSTRING> &fk_columns= referenced
|
||||
? fk->referenced_fields
|
||||
: fk->foreign_fields;
|
||||
List_iterator_fast<LEX_CSTRING> 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);
|
||||
}
|
||||
|
65
sql/table.h
65
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<LEX_CSTRING> foreign_fields;
|
||||
List<LEX_CSTRING> 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);
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<char*>(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,
|
||||
|
@ -1451,45 +1451,149 @@ typedef std::set<dict_v_col_t*, std::less<dict_v_col_t*>,
|
||||
/** 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)
|
||||
|
@ -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<upd_node_t*>(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 */
|
||||
|
Loading…
x
Reference in New Issue
Block a user