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:
Thirunarayanan Balathandayuthapani 2024-09-30 13:37:26 +05:30
parent 45298b730b
commit cc810e64d4
18 changed files with 919 additions and 215 deletions

View File

@ -175,6 +175,11 @@ static inline uchar last_byte_mask(uint bits)
return (uchar) ((2U << used) - 1); return (uchar) ((2U << used) - 1);
} }
static inline uint my_bits_in_bytes(uint n)
{
return ((n + 7) / 8);
}
#ifdef _MSC_VER #ifdef _MSC_VER
#include <intrin.h> #include <intrin.h>
#endif #endif

View File

@ -2,7 +2,7 @@ connection node_2;
connection node_1; connection node_1;
CREATE TABLE t0 ( CREATE TABLE t0 (
f1 INT PRIMARY KEY, f1 INT PRIMARY KEY,
f2 INT UNIQUE f2 INT UNIQUE NOT NULL
); );
CREATE TABLE t1 ( CREATE TABLE t1 (
f1 INT PRIMARY KEY, f1 INT PRIMARY KEY,

View File

@ -7,7 +7,7 @@
CREATE TABLE t0 ( CREATE TABLE t0 (
f1 INT PRIMARY KEY, f1 INT PRIMARY KEY,
f2 INT UNIQUE f2 INT UNIQUE NOT NULL
); );
CREATE TABLE t1 ( CREATE TABLE t1 (

View 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;

View 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;

View File

@ -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; alter table t2 drop index b, drop index c, drop index d;
ERROR HY000: Cannot drop index 'b': needed in a foreign key constraint ERROR HY000: Cannot drop index 'b': needed in a foreign key constraint
alter table t2 MODIFY b INT NOT NULL, ALGORITHM=COPY; 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 @old_sql_mode = @@sql_mode;
set @@sql_mode = 'STRICT_TRANS_TABLES'; set @@sql_mode = 'STRICT_TRANS_TABLES';
alter table t2 MODIFY b INT NOT NULL, ALGORITHM=INPLACE; 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 @@sql_mode = @old_sql_mode;
SET FOREIGN_KEY_CHECKS=0; SET FOREIGN_KEY_CHECKS=0;
alter table t2 DROP COLUMN b, ALGORITHM=COPY; 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'; set @@sql_mode = 'STRICT_TRANS_TABLES';
alter table t2 add primary key (alpha), change a alpha int, alter table t2 add primary key (alpha), change a alpha int,
change b beta int not null, change c charlie int not null; 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, alter table t2 add primary key (alpha), change a alpha int,
change c charlie int not null, change d delta int not null; 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, alter table t2 add primary key (alpha), change a alpha int,
change b beta int, modify c int not null; change b beta int, modify c int not null;
affected rows: 0 affected rows: 0

View File

@ -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 @old_sql_mode = @@sql_mode;
set @@sql_mode = 'STRICT_TRANS_TABLES'; set @@sql_mode = 'STRICT_TRANS_TABLES';
ALTER TABLE t2 MODIFY a INT NOT NULL; 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; set @@sql_mode = @old_sql_mode;
DELETE FROM t1; DELETE FROM t1;
DROP TABLE t2,t1; DROP TABLE t2,t1;

View File

@ -0,0 +1,2 @@
[COPY]
[INPLACE]

View 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;

View File

@ -168,7 +168,7 @@ alter table t4 drop index d;
alter table t2 drop index b; alter table t2 drop index b;
--error ER_DROP_INDEX_FK --error ER_DROP_INDEX_FK
alter table t2 drop index b, drop index c, drop index d; 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; alter table t2 MODIFY b INT NOT NULL, ALGORITHM=COPY;
# NULL -> NOT NULL only allowed INPLACE if strict sql_mode is on. # NULL -> NOT NULL only allowed INPLACE if strict sql_mode is on.
set @old_sql_mode = @@sql_mode; set @old_sql_mode = @@sql_mode;

View File

@ -9589,18 +9589,20 @@ static Create_field *get_field_by_old_name(Alter_info *alter_info,
enum fk_column_change_type enum fk_column_change_type
{ {
FK_COLUMN_NO_CHANGE, FK_COLUMN_DATA_CHANGE, 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. Check that ALTER TABLE's changes on columns of a foreign key are allowed.
@param[in] thd Thread context. @param[in] thd Thread context.
@param[in] table table to be altered
@param[in] alter_info Alter_info describing changes to be done @param[in] alter_info Alter_info describing changes to be done
by ALTER TABLE. 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 @param[out] bad_column_name Name of field on which ALTER TABLE tries to
do prohibited operation. do prohibited operation.
@param[in] referenced Check the referenced fields
@note This function takes into account value of @@foreign_key_checks @note This function takes into account value of @@foreign_key_checks
setting. setting.
@ -9611,17 +9613,27 @@ enum fk_column_change_type
change in foreign key column. change in foreign key column.
@retval FK_COLUMN_RENAMED Foreign key column is renamed. @retval FK_COLUMN_RENAMED Foreign key column is renamed.
@retval FK_COLUMN_DROPPED Foreign key column is dropped. @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 static enum fk_column_change_type
fk_check_column_changes(THD *thd, Alter_info *alter_info, fk_check_column_changes(THD *thd, const TABLE *table,
List<LEX_CSTRING> &fk_columns, Alter_info *alter_info,
const char **bad_column_name) 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); List_iterator_fast<LEX_CSTRING> column_it(fk_columns);
LEX_CSTRING *column; LEX_CSTRING *column;
int n_col= 0;
*bad_column_name= NULL; *bad_column_name= NULL;
enum fk_column_change_type result= FK_COLUMN_NO_CHANGE;
while ((column= column_it++)) 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 SE that foreign keys should be updated to use new name of column
like it happens in case of in-place algorithm. like it happens in case of in-place algorithm.
*/ */
*bad_column_name= column->str; result= FK_COLUMN_RENAMED;
return 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; new_field->flags&= ~AUTO_INCREMENT_FLAG;
const bool equal_result= old_field->is_equal(*new_field); const bool equal_result= old_field->is_equal(*new_field);
new_field->flags= flags; 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) || if ((equal_result == IS_EQUAL_NO))
((new_field->flags & NOT_NULL_FLAG) &&
!(old_field->flags & NOT_NULL_FLAG)))
{ {
/* /*
Column in a FK has changed significantly and it Column in a FK has changed significantly and it
may break referential intergrity. may break referential intergrity.
*/ */
*bad_column_name= column->str; result= FK_COLUMN_DATA_CHANGE;
return 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 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 field being dropped since it is easy to break referential
integrity in this case. integrity in this case.
*/ */
*bad_column_name= column->str; result= FK_COLUMN_DROPPED;
return FK_COLUMN_DROPPED; goto func_exit;
} }
n_col++;
} }
return FK_COLUMN_NO_CHANGE; 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; enum fk_column_change_type changes;
const char *bad_column_name; const char *bad_column_name;
changes= fk_check_column_changes(thd, alter_info, changes= fk_check_column_changes(thd, table, alter_info, f_key,
f_key->referenced_fields, &bad_column_name, true);
&bad_column_name);
switch(changes) 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()); f_key->foreign_id->str, buff.c_ptr());
DBUG_RETURN(true); 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: default:
DBUG_ASSERT(0); DBUG_ASSERT(0);
} }
@ -9849,8 +9904,7 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table,
enum fk_column_change_type changes; enum fk_column_change_type changes;
const char *bad_column_name; const char *bad_column_name;
changes= fk_check_column_changes(thd, alter_info, changes= fk_check_column_changes(thd, table, alter_info, f_key,
f_key->foreign_fields,
&bad_column_name); &bad_column_name);
switch(changes) 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, my_error(ER_FK_COLUMN_CANNOT_DROP, MYF(0), bad_column_name,
f_key->foreign_id->str); f_key->foreign_id->str);
DBUG_RETURN(true); 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: default:
DBUG_ASSERT(0); DBUG_ASSERT(0);
} }

View File

@ -36,6 +36,7 @@
#include "sql_i_s.h" #include "sql_i_s.h"
#include "sql_type.h" /* vers_kind_t */ #include "sql_type.h" /* vers_kind_t */
#include "privilege.h" /* privilege_t */ #include "privilege.h" /* privilege_t */
#include "my_bit.h"
/* /*
Buffer for unix timestamp in microseconds: Buffer for unix timestamp in microseconds:
@ -1882,6 +1883,70 @@ typedef struct st_foreign_key_info
LEX_CSTRING *referenced_key_name; LEX_CSTRING *referenced_key_name;
List<LEX_CSTRING> foreign_fields; List<LEX_CSTRING> foreign_fields;
List<LEX_CSTRING> referenced_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; } FOREIGN_KEY_INFO;
LEX_CSTRING *fk_option_name(enum_fk_option opt); LEX_CSTRING *fk_option_name(enum_fk_option opt);

View File

@ -1838,8 +1838,8 @@ dict_foreigns_has_s_base_col(
foreign = *it; foreign = *it;
ulint type = foreign->type; ulint type = foreign->type;
type &= ~(DICT_FOREIGN_ON_DELETE_NO_ACTION type &= ~(foreign->DELETE_NO_ACTION
| DICT_FOREIGN_ON_UPDATE_NO_ACTION); | foreign->UPDATE_NO_ACTION);
if (type == 0) { if (type == 0) {
continue; continue;
@ -1897,8 +1897,12 @@ dict_create_add_foreigns_to_dictionary(
foreign = *it; foreign = *it;
ut_ad(foreign->id != NULL); ut_ad(foreign->id != NULL);
error = dict_create_add_foreign_to_dictionary( if (!foreign->check_fk_constraint_valid()) {
table->name.m_name, foreign, trx); error = DB_CANNOT_ADD_CONSTRAINT;
} else {
error = dict_create_add_foreign_to_dictionary(
table->name.m_name, foreign, trx);
}
if (error != DB_SUCCESS) { if (error != DB_SUCCESS) {
break; break;

View File

@ -3037,8 +3037,8 @@ dict_foreign_add_to_cache(
for_in_cache->n_fields, for_in_cache->n_fields,
for_in_cache->referenced_index, check_charsets, for_in_cache->referenced_index, check_charsets,
for_in_cache->type for_in_cache->type
& (DICT_FOREIGN_ON_DELETE_SET_NULL & (foreign->DELETE_SET_NULL
| DICT_FOREIGN_ON_UPDATE_SET_NULL)); | foreign->UPDATE_SET_NULL));
if (index == NULL if (index == NULL
&& !(ignore_err & DICT_ERR_IGNORE_FK_NOKEY)) { && !(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(")"); str.append(")");
if (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE) { if (foreign->type & foreign->DELETE_CASCADE) {
str.append(" ON 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"); 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"); 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"); 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"); 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"); str.append(" ON UPDATE NO ACTION");
} }
@ -4086,27 +4086,27 @@ dict_print_info_on_foreign_keys(
str.append(")"); str.append(")");
if (foreign->type == DICT_FOREIGN_ON_DELETE_CASCADE) { if (foreign->type == foreign->DELETE_CASCADE) {
str.append(" ON 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"); 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"); 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"); 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"); 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"); str.append(" ON UPDATE NO ACTION");
} }
} }

View File

@ -12524,13 +12524,13 @@ create_table_info_t::create_foreign_keys()
case FK_OPTION_RESTRICT: case FK_OPTION_RESTRICT:
break; break;
case FK_OPTION_CASCADE: case FK_OPTION_CASCADE:
foreign->type |= DICT_FOREIGN_ON_DELETE_CASCADE; foreign->type |= foreign->DELETE_CASCADE;
break; break;
case FK_OPTION_SET_NULL: case FK_OPTION_SET_NULL:
foreign->type |= DICT_FOREIGN_ON_DELETE_SET_NULL; foreign->type |= foreign->DELETE_SET_NULL;
break; break;
case FK_OPTION_NO_ACTION: case FK_OPTION_NO_ACTION:
foreign->type |= DICT_FOREIGN_ON_DELETE_NO_ACTION; foreign->type |= foreign->DELETE_NO_ACTION;
break; break;
case FK_OPTION_SET_DEFAULT: case FK_OPTION_SET_DEFAULT:
// TODO: MDEV-10393 Foreign keys SET DEFAULT action // TODO: MDEV-10393 Foreign keys SET DEFAULT action
@ -12545,13 +12545,13 @@ create_table_info_t::create_foreign_keys()
case FK_OPTION_RESTRICT: case FK_OPTION_RESTRICT:
break; break;
case FK_OPTION_CASCADE: case FK_OPTION_CASCADE:
foreign->type |= DICT_FOREIGN_ON_UPDATE_CASCADE; foreign->type |= foreign->UPDATE_CASCADE;
break; break;
case FK_OPTION_SET_NULL: case FK_OPTION_SET_NULL:
foreign->type |= DICT_FOREIGN_ON_UPDATE_SET_NULL; foreign->type |= foreign->UPDATE_SET_NULL;
break; break;
case FK_OPTION_NO_ACTION: case FK_OPTION_NO_ACTION:
foreign->type |= DICT_FOREIGN_ON_UPDATE_NO_ACTION; foreign->type |= foreign->UPDATE_NO_ACTION;
break; break;
case FK_OPTION_SET_DEFAULT: case FK_OPTION_SET_DEFAULT:
// TODO: MDEV-10393 Foreign keys SET DEFAULT action // TODO: MDEV-10393 Foreign keys SET DEFAULT action
@ -15266,28 +15266,43 @@ get_foreign_key_info(
name = thd_make_lex_string(thd, name, ptr, name = thd_make_lex_string(thd, name, ptr,
strlen(ptr), 1); strlen(ptr), 1);
f_key_info.foreign_fields.push_back(name); 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]; ptr = foreign->referenced_col_names[i];
name = thd_make_lex_string(thd, name, ptr, name = thd_make_lex_string(thd, name, ptr,
strlen(ptr), 1); strlen(ptr), 1);
f_key_info.referenced_fields.push_back(name); 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); } 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; 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; 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; f_key_info.delete_method = FK_OPTION_NO_ACTION;
} else { } else {
f_key_info.delete_method = FK_OPTION_RESTRICT; 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; 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; 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; f_key_info.update_method = FK_OPTION_NO_ACTION;
} else { } else {
f_key_info.update_method = FK_OPTION_RESTRICT; f_key_info.update_method = FK_OPTION_RESTRICT;

View File

@ -2759,8 +2759,8 @@ innobase_check_fk_option(
return(true); return(true);
} }
if (foreign->type & (DICT_FOREIGN_ON_UPDATE_SET_NULL if (foreign->type & (foreign->UPDATE_SET_NULL
| DICT_FOREIGN_ON_DELETE_SET_NULL)) { | foreign->DELETE_SET_NULL)) {
for (ulint j = 0; j < foreign->n_fields; j++) { for (ulint j = 0; j < foreign->n_fields; j++) {
if ((dict_index_get_nth_col( if ((dict_index_get_nth_col(
@ -2795,13 +2795,13 @@ innobase_set_foreign_key_option(
case FK_OPTION_NO_ACTION: case FK_OPTION_NO_ACTION:
case FK_OPTION_RESTRICT: case FK_OPTION_RESTRICT:
case FK_OPTION_SET_DEFAULT: case FK_OPTION_SET_DEFAULT:
foreign->type = DICT_FOREIGN_ON_DELETE_NO_ACTION; foreign->type = foreign->DELETE_NO_ACTION;
break; break;
case FK_OPTION_CASCADE: case FK_OPTION_CASCADE:
foreign->type = DICT_FOREIGN_ON_DELETE_CASCADE; foreign->type = foreign->DELETE_CASCADE;
break; break;
case FK_OPTION_SET_NULL: case FK_OPTION_SET_NULL:
foreign->type = DICT_FOREIGN_ON_DELETE_SET_NULL; foreign->type = foreign->DELETE_SET_NULL;
break; break;
case FK_OPTION_UNDEF: case FK_OPTION_UNDEF:
break; break;
@ -2811,13 +2811,13 @@ innobase_set_foreign_key_option(
case FK_OPTION_NO_ACTION: case FK_OPTION_NO_ACTION:
case FK_OPTION_RESTRICT: case FK_OPTION_RESTRICT:
case FK_OPTION_SET_DEFAULT: case FK_OPTION_SET_DEFAULT:
foreign->type |= DICT_FOREIGN_ON_UPDATE_NO_ACTION; foreign->type |= foreign->UPDATE_NO_ACTION;
break; break;
case FK_OPTION_CASCADE: case FK_OPTION_CASCADE:
foreign->type |= DICT_FOREIGN_ON_UPDATE_CASCADE; foreign->type |= foreign->UPDATE_CASCADE;
break; break;
case FK_OPTION_SET_NULL: case FK_OPTION_SET_NULL:
foreign->type |= DICT_FOREIGN_ON_UPDATE_SET_NULL; foreign->type |= foreign->UPDATE_SET_NULL;
break; break;
case FK_OPTION_UNDEF: case FK_OPTION_UNDEF:
break; break;
@ -2968,8 +2968,8 @@ innobase_check_fk_stored(
{ {
ulint type = foreign->type; ulint type = foreign->type;
type &= ~(DICT_FOREIGN_ON_DELETE_NO_ACTION type &= ~(foreign->DELETE_NO_ACTION
| DICT_FOREIGN_ON_UPDATE_NO_ACTION); | foreign->UPDATE_NO_ACTION);
if (type == 0 || s_cols == NULL) { if (type == 0 || s_cols == NULL) {
return(false); return(false);
@ -4220,103 +4220,157 @@ innobase_dropping_foreign(
return(false); return(false);
} }
/** Determines if an InnoDB FOREIGN KEY constraint depends on a /** Determines if an InnoDB FOREIGN KEY constraint depends on
column that is being dropped or modified to NOT NULL. 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 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 drop_fk constraints being dropped
@param n_drop_fk number of constraints that are 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 true Not allowed (will call my_error())
@retval false Allowed @retval false Allowed
*/ */
MY_ATTRIBUTE((pure, nonnull(1,4), warn_unused_result))
static static
bool bool check_foreigns_nullability(const dict_table_t *user_table,
innobase_check_foreigns_low( dict_foreign_t **drop_fk, ulint n_drop_fk,
const dict_table_t* user_table, const char *col_name, uint32_t new_field_flags)
dict_foreign_t** drop_fk,
ulint n_drop_fk,
const char* col_name,
bool drop)
{ {
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 /* Changing from NULL to NOT NULL. So check referenced set */
column. */ 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(); if (foreign->on_update_cascade_null(col_name))
it != user_table->foreign_set.end(); goto non_null_error;
++it) {
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 my_snprintf(display_name, FN_REFLEN - 1, "%.*s.%s",
& (DICT_FOREIGN_ON_DELETE_SET_NULL dblen, foreign->foreign_table_name, tbl_name);
| DICT_FOREIGN_ON_UPDATE_SET_NULL))) {
continue;
}
if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk)) { display_name[FN_REFLEN - 1]= '\0';
continue; 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++) { return false;
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);
}
}
}
if (!drop) { /** Determines if an InnoDB FOREIGN KEY constraint depends on
/* SET NULL clauses on foreign key constraints of the column when it is being dropped.
child tables affect the child tables, not the parent table. @param user_table InnoDB table as it is before the ALTER operation
The column can be NOT NULL in the parent table. */ @param drop_fk constraints being dropped
return(false); @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 /* Check if any FOREIGN KEY constraints are defined on this column. */
referring to the column that is being dropped. */ for (dict_foreign_t *foreign : user_table->foreign_set)
for (dict_foreign_set::const_iterator it {
= user_table->referenced_set.begin(); if (innobase_dropping_foreign(foreign, drop_fk, n_drop_fk))
it != user_table->referenced_set.end(); continue;
++it) {
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)) { /* Check if any FOREIGN KEY constraints in other tables are
continue; 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++) { for (unsigned f = 0; f < foreign->n_fields; f++)
char display_name[FN_REFLEN]; {
char display_name[FN_REFLEN];
if (strcmp(foreign->referenced_col_names[f], if (strcmp(foreign->referenced_col_names[f], col_name))
col_name)) { continue;
continue; char* buf_end = innobase_convert_name(
} display_name, (sizeof display_name) - 1,
foreign->foreign_table_name,
char* buf_end = innobase_convert_name( strlen(foreign->foreign_table_name), NULL);
display_name, (sizeof display_name) - 1, *buf_end = '\0';
foreign->foreign_table_name, my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD,
strlen(foreign->foreign_table_name), MYF(0), col_name, foreign->id, display_name);
NULL); return true;
*buf_end = '\0'; }
my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD, }
MYF(0), col_name, foreign->id, return false;
display_name);
return(true);
}
}
return(false);
} }
/** Determines if an InnoDB FOREIGN KEY constraint depends on a /** Determines if an InnoDB FOREIGN KEY constraint depends on a
@ -4351,16 +4405,25 @@ innobase_check_foreigns(
return field.field == *fp; return field.field == *fp;
}); });
if (it == end || (it->flags & NOT_NULL_FLAG)) { if (it == end) {
if (innobase_check_foreigns_low( if (check_foreign_drop_col(
user_table, drop_fk, n_drop_fk, user_table, drop_fk, n_drop_fk,
(*fp)->field_name.str, it == end)) { (*fp)->field_name.str)) {
return(true); 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. /** Convert a default value for ADD COLUMN.
@ -9836,8 +9899,8 @@ innobase_update_foreign_try(
fk->foreign_col_names, fk->foreign_col_names,
fk->n_fields, fk->referenced_index, TRUE, fk->n_fields, fk->referenced_index, TRUE,
fk->type fk->type
& (DICT_FOREIGN_ON_DELETE_SET_NULL & (fk->DELETE_SET_NULL
| DICT_FOREIGN_ON_UPDATE_SET_NULL), | fk->UPDATE_SET_NULL),
NULL, NULL, NULL); NULL, NULL, NULL);
if (!fk->foreign_index) { if (!fk->foreign_index) {
my_error(ER_FK_INCORRECT_OPTION, my_error(ER_FK_INCORRECT_OPTION,

View File

@ -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: /** Data structure for a foreign key constraint; an example:
FOREIGN KEY (A, B) REFERENCES TABLE2 (C, D). Most fields will be FOREIGN KEY (A, B) REFERENCES TABLE2 (C, D). Most fields will be
initialized to 0, NULL or FALSE in dict_mem_foreign_create(). */ initialized to 0, NULL or FALSE in dict_mem_foreign_create(). */
struct dict_foreign_t{ struct dict_foreign_t
mem_heap_t* heap; /*!< this object is allocated from {
this memory heap */ /* Object is allocated from this memory heap */
char* id; /*!< id of the constraint as a mem_heap_t *heap;
null-terminated string */ /* id of the constraint as a null terminated string */
unsigned n_fields:10; /*!< number of indexes' first fields char *id;
for which the foreign key /* number of indexes first fields for which the foreign key
constraint is defined: we allow the constraint is defined: We allow the indexes to contain more
indexes to contain more fields than fields than mentioned in the constraint, as long as the first
mentioned in the constraint, as long fields are as mentioned */
as the first fields are as mentioned */ unsigned n_fields:10;
unsigned type:6; /*!< 0 or DICT_FOREIGN_ON_DELETE_CASCADE /* 0 or DELETE_CASCADE OR DELETE_SET_NULL */
or DICT_FOREIGN_ON_DELETE_SET_NULL */ unsigned type:6;
char* foreign_table_name;/*!< foreign table name */ /* foreign table name */
char* foreign_table_name_lookup; char *foreign_table_name;
/*!< foreign table name used for dict lookup */ /* Foreign table name used for dict lookup */
dict_table_t* foreign_table; /*!< table where the foreign key is */ char *foreign_table_name_lookup;
const char** foreign_col_names;/*!< names of the columns in the /* table where the foreign key is */
foreign key */ dict_table_t *foreign_table;
char* referenced_table_name;/*!< referenced table name */ /* names of the columns in the foreign key */
char* referenced_table_name_lookup; const char **foreign_col_names;
/*!< referenced table name for dict lookup*/ /* referenced table name */
dict_table_t* referenced_table;/*!< table where the referenced key char *referenced_table_name;
is */ /* referenced table name for dict lookup */
const char** referenced_col_names;/*!< names of the referenced char *referenced_table_name_lookup;
columns in the referenced table */ /* Table where the referenced key is */
dict_index_t* foreign_index; /*!< foreign index; we require that dict_table_t *referenced_table;
both tables contain explicitly defined /* Names of the referenced columns in the referenced table */
indexes for the constraint: InnoDB const char **referenced_col_names;
does not generate new indexes /* foreign index; we require that both tables contain explicitly
implicitly */ defined indexes for the constraint: InnoDB does not generate
dict_index_t* referenced_index;/*!< referenced index */ 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 public:
by foreign key constraint. */ /** 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 /** Check whether the name exists in the referenced
foreign key constraint */ key column names
bool affects_fulltext() const; @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& std::ostream&
@ -1667,17 +1771,6 @@ struct dict_foreign_set_free {
const dict_foreign_set& m_foreign_set; 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. /** Display an identifier.
@param[in,out] s output stream @param[in,out] s output stream
@param[in] id_name SQL identifier (other than table name) @param[in] id_name SQL identifier (other than table name)

View File

@ -903,10 +903,10 @@ row_ins_foreign_fill_virtual(
return DB_OUT_OF_MEMORY; return DB_OUT_OF_MEMORY;
} }
ut_ad(!node->is_delete ut_ad(!node->is_delete
|| (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL)); || (foreign->type & foreign->DELETE_SET_NULL));
ut_ad(foreign->type & (DICT_FOREIGN_ON_DELETE_SET_NULL ut_ad(foreign->type & (foreign->DELETE_SET_NULL
| DICT_FOREIGN_ON_UPDATE_SET_NULL | foreign->UPDATE_SET_NULL
| DICT_FOREIGN_ON_UPDATE_CASCADE)); | foreign->UPDATE_CASCADE));
for (uint16_t i = 0; i < n_v_fld; i++) { 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); node = static_cast<upd_node_t*>(thr->run_node);
if (node->is_delete && 0 == (foreign->type if (node->is_delete && 0 == (foreign->type
& (DICT_FOREIGN_ON_DELETE_CASCADE & (foreign->DELETE_CASCADE
| DICT_FOREIGN_ON_DELETE_SET_NULL))) { | foreign->DELETE_SET_NULL))) {
row_ins_foreign_report_err("Trying to delete", row_ins_foreign_report_err("Trying to delete",
thr, foreign, thr, foreign,
@ -1032,8 +1032,8 @@ row_ins_foreign_check_on_constraint(
} }
if (!node->is_delete && 0 == (foreign->type if (!node->is_delete && 0 == (foreign->type
& (DICT_FOREIGN_ON_UPDATE_CASCADE & (foreign->UPDATE_CASCADE
| DICT_FOREIGN_ON_UPDATE_SET_NULL))) { | foreign->UPDATE_SET_NULL))) {
/* This is an UPDATE */ /* This is an UPDATE */
@ -1056,7 +1056,7 @@ row_ins_foreign_check_on_constraint(
cascade->foreign = foreign; cascade->foreign = foreign;
if (node->is_delete if (node->is_delete
&& (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE)) { && (foreign->type & foreign->DELETE_CASCADE)) {
cascade->is_delete = PLAIN_DELETE; cascade->is_delete = PLAIN_DELETE;
} else { } else {
cascade->is_delete = NO_DELETE; cascade->is_delete = NO_DELETE;
@ -1199,8 +1199,8 @@ row_ins_foreign_check_on_constraint(
} }
if (node->is_delete if (node->is_delete
? (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL) ? (foreign->type & foreign->DELETE_SET_NULL)
: (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL)) { : (foreign->type & foreign->UPDATE_SET_NULL)) {
/* Build the appropriate update vector which sets /* Build the appropriate update vector which sets
foreign->n_fields first fields in rec to SQL NULL */ 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 } else if (table->fts && cascade->is_delete == PLAIN_DELETE
&& foreign->affects_fulltext()) { && 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); fts_trx_add_op(trx, table, doc_id, FTS_DELETE, NULL);
} }
if (!node->is_delete if (!node->is_delete
&& (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE)) { && (foreign->type & foreign->UPDATE_CASCADE)) {
/* Build the appropriate update vector which sets changing /* Build the appropriate update vector which sets changing
foreign->n_fields first fields in rec to new values */ foreign->n_fields first fields in rec to new values */