mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
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@c48fa1e57a
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user