diff --git a/mysql-test/suite/innodb/r/innodb-fk-virtual.result b/mysql-test/suite/innodb/r/innodb-fk-virtual.result index b39e770c729..2537e7f534e 100644 --- a/mysql-test/suite/innodb/r/innodb-fk-virtual.result +++ b/mysql-test/suite/innodb/r/innodb-fk-virtual.result @@ -84,3 +84,53 @@ select * from b; cola v_cola p_cola c_cola 10 2 2 12 drop table b, a; +# +# MDEV-41666 Corruption on statement rollback when +# ON UPDATE SET NULL affects the base column +# +CREATE TABLE t18 ( +c5 INT NOT NULL, +PRIMARY KEY (c5) +) ENGINE=InnoDB; +CREATE TABLE t21 ( +c1 INT NOT NULL, +c2 INT DEFAULT NULL, +c2v INT AS (c2 DIV 1), c1v INT AS (FLOOR(c1)), +KEY i15 (c2v, c1v), +CONSTRAINT t21_ibfk_1 FOREIGN KEY (c2) REFERENCES t18 (c5) ON UPDATE SET NULL +) ENGINE=InnoDB; +INSERT INTO t18 (c5) VALUES (1), (2); +INSERT INTO t21 (c1, c2) VALUES (0, 1); +UPDATE t18 SET c5 = 3; +ERROR 23000: Duplicate entry '3' for key 'PRIMARY' +SELECT c5 FROM t18 ORDER BY c5; +c5 +1 +2 +SELECT c1, c2 FROM t21; +c1 c2 +0 1 +DROP TABLE t21, t18; +CREATE TABLE parent ( +id INT PRIMARY KEY +) ENGINE=InnoDB; +CREATE TABLE child ( +a INT DEFAULT NULL, +parent_id INT DEFAULT NULL, +av INT AS (IFNULL(a, 0)), pv INT AS (IFNULL(parent_id, 0)), +KEY idx_functional (av, pv), +CONSTRAINT fk_parent FOREIGN KEY (parent_id) REFERENCES parent (id) +ON DELETE SET NULL +) ENGINE=InnoDB; +INSERT INTO parent VALUES (1); +INSERT INTO child(a, parent_id) VALUES (NULL, 1); +START TRANSACTION; +DELETE FROM parent WHERE id = 1; +ROLLBACK; +SELECT id FROM parent; +id +1 +SELECT a, parent_id FROM child; +a parent_id +NULL 1 +DROP TABLE child, parent; diff --git a/mysql-test/suite/innodb/t/innodb-fk-virtual.test b/mysql-test/suite/innodb/t/innodb-fk-virtual.test index 094fda35abb..8be0ed7f768 100644 --- a/mysql-test/suite/innodb/t/innodb-fk-virtual.test +++ b/mysql-test/suite/innodb/t/innodb-fk-virtual.test @@ -59,3 +59,59 @@ delete from a; select * from a; select * from b; drop table b, a; + +--echo # +--echo # MDEV-41666 Corruption on statement rollback when +--echo # ON UPDATE SET NULL affects the base column +--echo # +CREATE TABLE t18 ( + c5 INT NOT NULL, + PRIMARY KEY (c5) +) ENGINE=InnoDB; + +CREATE TABLE t21 ( + c1 INT NOT NULL, + c2 INT DEFAULT NULL, + c2v INT AS (c2 DIV 1), c1v INT AS (FLOOR(c1)), + KEY i15 (c2v, c1v), + CONSTRAINT t21_ibfk_1 FOREIGN KEY (c2) REFERENCES t18 (c5) ON UPDATE SET NULL +) ENGINE=InnoDB; + +INSERT INTO t18 (c5) VALUES (1), (2); +INSERT INTO t21 (c1, c2) VALUES (0, 1); + +# This first changes 1 to 3 and cascades c2 = 1 to c2 = NULL. +# Updating the second parent row (2 to 3) then hits the duplicate +# primary key. The whole statement must roll back, including the +# functional-index update in t21. +--error ER_DUP_ENTRY +UPDATE t18 SET c5 = 3; +SELECT c5 FROM t18 ORDER BY c5; +SELECT c1, c2 FROM t21; +DROP TABLE t21, t18; + +# Bug#39872048: DELETE with ON DELETE SET NULL must preserve all functional +# index key parts when its effects are rolled back. +CREATE TABLE parent ( + id INT PRIMARY KEY +) ENGINE=InnoDB; + +CREATE TABLE child ( + a INT DEFAULT NULL, + parent_id INT DEFAULT NULL, + av INT AS (IFNULL(a, 0)), pv INT AS (IFNULL(parent_id, 0)), + KEY idx_functional (av, pv), + CONSTRAINT fk_parent FOREIGN KEY (parent_id) REFERENCES parent (id) + ON DELETE SET NULL +) ENGINE=InnoDB; + +INSERT INTO parent VALUES (1); +INSERT INTO child(a, parent_id) VALUES (NULL, 1); + +START TRANSACTION; +DELETE FROM parent WHERE id = 1; +ROLLBACK; + +SELECT id FROM parent; +SELECT a, parent_id FROM child; +DROP TABLE child, parent; diff --git a/storage/innobase/dict/dict0mem.cc b/storage/innobase/dict/dict0mem.cc index cf4c1e278db..766f42fefb8 100644 --- a/storage/innobase/dict/dict0mem.cc +++ b/storage/innobase/dict/dict0mem.cc @@ -964,11 +964,15 @@ dict_mem_fill_vcol_set_for_base_col( table, v_col->base_col[j]->ind)) == 0) { - if (*v_cols == NULL) { - *v_cols = UT_NEW_NOKEY(dict_vcol_set()); + /* A change to a base column changes the + complete secondary-index record. Include + every virtual key part of each affected index, + so that a cascaded update has the old values + required for undo. */ + for (const auto& v_idx : v_col->v_indexes) { + dict_mem_fill_vcol_has_index( + v_idx.index, v_cols); } - - (*v_cols)->insert(v_col); } } }