From f941fc810cb64c04a38751080e04887f627a182c Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Wed, 21 Oct 2026 20:41:36 +0530 Subject: [PATCH] MDEV-41666 Corruption on statement rollback when ON UPDATE SET NULL affects the base column Problem: ======= - When a foreign key column is the base column of a virtual column that is indexed together with other virtual columns, a cascaded update (ON UPDATE/DELETE SET NULL) computed the old value of only the dependent virtual column. The other virtual key parts of the index stayed NULL in update->old_vrow. If the statement was rolled back, row_upd_store_v_row() used those NULLs, so row_upd_sec_index_entry() searched the secondary index for a record that did not exist and hit the "Record in index was not found on update" assertion. Solution: ========= dict_mem_fill_vcol_set_for_base_col(): Add every virtual key part of each index that contains the dependent virtual column to dict_foreign_t::v_cols, as dict_mem_fill_vcol_from_v_indexes() already does. row_ins_foreign_fill_virtual() then computes the old values of all of them. Based on mysql/mysql-server@c48fa1e57a8e810f89b43d3e94bcbefa1fd69806 --- .../suite/innodb/r/innodb-fk-virtual.result | 50 +++++++++++++++++ .../suite/innodb/t/innodb-fk-virtual.test | 56 +++++++++++++++++++ storage/innobase/dict/dict0mem.cc | 12 ++-- 3 files changed, 114 insertions(+), 4 deletions(-) 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); } } }