1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-39306 Index corruption on UPDATE when using versioned InnoDB table (on UPDATE)

Duplicated by:

MDEV-39711 UPDATE on System-Versioned Tables uses the after-value
           in Row-format Binlog's before-image
MDEV-24646 ASAN heap-use-after-free in Field_blob::pack /
           THD::binlog_update_row upon DML on table with virtual column

vers_update_fields() recomputed virtual fields with VCOL_UPDATE_FOR_READ.
For BLOB virtual columns this mode swaps Field_blob::value and read_value
after the computation (swap_values). After the swap, Field_blob::value
holds the buffer that record[1] (the old row image) points to. The next
VCOL_UPDATE_FOR_WRITE computation, done by fill_record() right after
vers_update_fields(), stores the new value into that buffer and so
overwrites the old row image. As a result, InnoDB builds the old
secondary-index key from the new value (MDEV-39306), and the row-format
binlog writes the new value into the before-image (MDEV-39711), or a
freed buffer (MDEV-24646).

REPLACE had the same problem with record[2]. TABLE::delete_row() saves
the new row into record[2] and turns the conflicting row into history
with vers_update_end(). Its VCOL_UPDATE_FOR_WRITE computation stored
the virtual blobs of the old row into Field_blob::value, the buffer
record[2] points to, so the new row was written with them. Versioned
DELETE had the same problem with record[1]: it points to the virtual
blobs computed with VCOL_UPDATE_FOR_DELETE in Field_blob::value, and
vers_update_end() overwrote them.

The fix removes the virtual fields computation from vers_update_fields().
Its callers now recompute virtual fields with VCOL_UPDATE_FOR_WRITE after
the system fields are set: fill_record() already did, while
update_generated_fields(), multi_update::do_updates() and the RBR
appliers do it explicitly. multi_update::do_updates() still computes
virtual fields twice per row: before BEFORE triggers and after
vers_update_fields(), since the system fields cannot be set before
compare_record(). For REPLACE and DELETE, TABLE::delete_row() keeps
Field_blob::value with remember_blob_values() while it updates the
history row.

In the RBR appliers no VCOL_UPDATE_FOR_WRITE followed
vers_update_fields(), so record[1] was not overwritten there. Their
explicit computation replaces the one removed from vers_update_fields().
It uses VCOL_UPDATE_FOR_WRITE, so an error in a virtual field
expression now stops the slave instead of being turned into a warning.

In the Update_rows applier, vers_insert_history_row() also overwrote
the error of ha_update_row(), so a failed update on the slave was
ignored and its history row was written. The history row is now
written only when the update succeeded.

Based on the MDEV-24646/MDEV-39711 fix by ParadoxV5 (0c2f3de1a2),
which switched vers_update_fields() to VCOL_UPDATE_FOR_WRITE.
This commit is contained in:
Aleksey Midenkov
2026-07-01 16:55:49 +03:00
parent 100c630b0a
commit 599416569d
17 changed files with 962 additions and 17 deletions

View File

@@ -88,3 +88,58 @@ x s e if(row_start = @ins_time, "OLD", "NEW") check_row(row_start, row_end)
6 2000-01-01 2018-01-01 NEW CURRENT ROW
drop table t,log_tbl;
drop procedure log;
#
# MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
# table (on UPDATE)
#
create table t1 (x int, s date, e date, period for app (s, e),
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
v text as (concat('v-', x)) virtual,
w decimal(20,6) as (x + unix_timestamp(rs)) virtual,
ws decimal(20,6) as (x + unix_timestamp(rs)) stored,
key (v(10)), key (w))
with system versioning;
insert t1 (x, s, e) values (1, '2000-01-01', '2020-01-01'),
(2, '2000-01-01', '2020-01-01');
delete from t1 for portion of app from '2005-01-01' to '2010-01-01'
where x = 1;
update t1 for portion of app from '2005-01-01' to '2010-01-01' set x= 3
where x = 2;
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select x, s, e, v, ws = x + unix_timestamp(rs) as ok
from t1 for system_time all force index (v)
order by x, s, rs;
x s e v ok
1 2000-01-01 2020-01-01 v-1 1
1 2000-01-01 2005-01-01 v-1 1
1 2010-01-01 2020-01-01 v-1 1
2 2000-01-01 2020-01-01 v-2 1
2 2000-01-01 2005-01-01 v-2 1
2 2010-01-01 2020-01-01 v-2 1
3 2005-01-01 2010-01-01 v-3 1
drop table t1;
# Error from virtual fields computed after the system fields
set @save_sql_mode= @@sql_mode;
set sql_mode= 'strict_all_tables,error_for_division_by_zero';
create table t1 (x int, s date, e date, period for app (s, e),
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
b int as (1 div (year(rs) - 2000 + year(e) - 2005)) stored)
with system versioning engine=innodb;
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (x, s, e) values (1, '2000-01-01', '2020-01-01');
set timestamp= unix_timestamp('2000-06-01 00:00:00');
update t1 for portion of app from '2005-01-01' to '2010-01-01' set x= 2;
ERROR 22012: Division by 0
set timestamp= default;
select x, s, e, b from t1;
x s e b
1 2000-01-01 2020-01-01 0
drop table t1;
set sql_mode= @save_sql_mode;
# End of 10.11 tests

View File

@@ -64,4 +64,62 @@ select *, if(row_start = @ins_time, "OLD", "NEW"), check_row(row_start, row_end)
drop table t,log_tbl;
drop procedure log;
if ($default_engine == MEMORY)
{
--disable_query_log
set default_storage_engine= myisam;
--enable_query_log
}
--echo #
--echo # MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
--echo # table (on UPDATE)
--echo #
create table t1 (x int, s date, e date, period for app (s, e),
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
v text as (concat('v-', x)) virtual,
w decimal(20,6) as (x + unix_timestamp(rs)) virtual,
ws decimal(20,6) as (x + unix_timestamp(rs)) stored,
key (v(10)), key (w))
with system versioning;
insert t1 (x, s, e) values (1, '2000-01-01', '2020-01-01'),
(2, '2000-01-01', '2020-01-01');
delete from t1 for portion of app from '2005-01-01' to '2010-01-01'
where x = 1;
update t1 for portion of app from '2005-01-01' to '2010-01-01' set x= 3
where x = 2;
check table t1 extended;
select x, s, e, v, ws = x + unix_timestamp(rs) as ok
from t1 for system_time all force index (v)
order by x, s, rs;
drop table t1;
--echo # Error from virtual fields computed after the system fields
# Only the left part [2000, 2005), inserted by period_make_insert(),
# divides by zero, and only with the new row_start.
set @save_sql_mode= @@sql_mode;
set sql_mode= 'strict_all_tables,error_for_division_by_zero';
create table t1 (x int, s date, e date, period for app (s, e),
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
b int as (1 div (year(rs) - 2000 + year(e) - 2005)) stored)
with system versioning engine=innodb;
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (x, s, e) values (1, '2000-01-01', '2020-01-01');
set timestamp= unix_timestamp('2000-06-01 00:00:00');
--error ER_DIVISION_BY_ZERO
update t1 for portion of app from '2005-01-01' to '2010-01-01' set x= 2;
set timestamp= default;
select x, s, e, b from t1;
drop table t1;
set sql_mode= @save_sql_mode;
--disable_query_log
eval set default_storage_engine= $default_engine;
--enable_query_log
--echo # End of 10.11 tests
source suite/versioning/common_finish.inc;

View File

@@ -168,3 +168,124 @@ f1 f3 check_row(row_start, row_end)
2 2 HISTORICAL ROW
# Cleanup
drop tables t1, t2;
#
# MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
# table (on UPDATE)
#
# Virtual blob over row_end, computed before and after row_end is set
create table t1 (id int primary key, a text,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
v text as (concat(a, '-', unix_timestamp(re))) virtual,
key (v(40)))
with system versioning;
insert t1 (id, a) values (1, 'one'), (2, 'two');
delete from t1 where id = 1;
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select id, a from t1 for system_time all force index (v) order by id;
id a
1 one
2 two
drop table t1;
# AFTER DELETE trigger reads OLD of a virtual blob over row_end
create table t1 (id int primary key, a text,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
v text as (concat('v-', a, '-', unix_timestamp(re))) virtual)
with system versioning;
create table log (id int, ok int);
create trigger tr after delete on t1 for each row
insert log values (old.id,
old.v = concat('v-', old.a, '-', unix_timestamp(old.re)));
insert t1 (id, a) values (1, 'one'), (2, 'two');
delete from t1;
select * from log order by id;
id ok
1 1
2 1
drop table t1, log;
# Multi-table DELETE
create table t1 (id int primary key, a text,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
v text as (concat('v-', a, '-', unix_timestamp(re))) virtual)
with system versioning;
create table log (id int, ok int);
create trigger tr after delete on t1 for each row
insert log values (old.id,
old.v = concat('v-', old.a, '-', unix_timestamp(old.re)));
create table t2 (x int);
insert t1 (id, a) values (1, 'one'), (2, 'two');
insert t2 values (1);
delete t1 from t1, t2;
select * from log order by id;
id ok
1 1
2 1
drop table t1, t2, log;
# History row already exists: the row is deleted
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
create table log (id int, old_v text);
create trigger tr after delete on t1 for each row
insert log values (old.id, old.v);
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (id, a) values (1, 'one');
delete from t1;
insert t1 (id, a) values (1, 'two');
delete from t1;
set timestamp= default;
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select * from log order by old_v;
id old_v
1 v-one
1 v-two
select id, a, v from t1 for system_time all force index (v) order by v;
id a v
1 one v-one
drop table t1, log;
# Negative lifetime: the row is deleted
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
create table log (id int, old_v text);
create trigger tr after delete on t1 for each row
insert log values (old.id, old.v);
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (id, a) values (1, 'one');
set timestamp= unix_timestamp('2000-06-01 00:00:00');
delete from t1;
set timestamp= default;
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select * from log;
id old_v
1 v-one
select id, a, v from t1 for system_time all;
id a v
drop table t1, log;
# ha_update_row() of the history row fails
create table p (id int primary key, a text, v text as (concat('v-', a)) virtual)
with system versioning engine=innodb;
create table c (id int, foreign key (id) references p (id)) engine=innodb;
insert p (id, a) values (1, 'one');
insert c values (1);
delete from p;
ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`c`, CONSTRAINT `c_ibfk_1` FOREIGN KEY (`id`) REFERENCES `p` (`id`))
delete p from p, c;
ERROR 23000: Cannot delete or update a parent row: a foreign key constraint fails (`test`.`c`, CONSTRAINT `c_ibfk_1` FOREIGN KEY (`id`) REFERENCES `p` (`id`))
check table p extended;
Table Op Msg_type Msg_text
test.p check status OK
select id, a, v from p for system_time all;
id a v
1 one v-one
drop table c, p;
# End of 10.11 tests

View File

@@ -3767,5 +3767,24 @@ x row_end
set timestamp= default;
set time_zone= default;
drop table t1;
#
# MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
# table (on UPDATE)
#
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning
partition by system_time (partition p0 history, partition pn current);
insert t1 (id, a) values (1, 'one'), (2, 'two');
delete from t1 where id = 1;
replace t1 (id, a) values (2, 'final');
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select id, a, v from t1 for system_time all force index (v) order by v;
id a v
2 final v-final
1 one v-one
2 two v-two
drop table t1;
# End of 10.11 tests
set global innodb_stats_persistent= @save_persistent;

View File

@@ -165,4 +165,70 @@ insert into t1 (c1) values (1);
replace delayed into t1 (c1) values (1);
drop table t1;
set sql_mode=@old_mode;
#
# MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
# table (on UPDATE)
#
# REPLACE turns the conflicting row into history
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
insert t1 (id, a) values (1, 'old'), (2, 'start');
replace t1 (id, a) values (2, 'final');
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select id, a, v from t1 for system_time all force index (v) order by v;
id a v
2 final v-final
1 old v-old
2 start v-start
drop table t1;
# History row already exists: the conflicting row is deleted
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (id, a) values (1, 'old'), (2, 'start');
replace t1 (id, a) values (2, 'middle');
replace t1 (id, a) values (2, 'final');
set timestamp= default;
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select id, a, v from t1 for system_time all force index (v) order by v;
id a v
2 final v-final
1 old v-old
2 start v-start
drop table t1;
# Negative lifetime: the conflicting row is deleted, no history row
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (id, a) values (1, 'old'), (2, 'start');
set timestamp= unix_timestamp('2000-06-01 00:00:00');
replace t1 (id, a) values (2, 'final');
set timestamp= default;
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select id, a, v from t1 for system_time all force index (v) order by v;
id a v
2 final v-final
1 old v-old
drop table t1;
# Two conflicting rows
create table t1 (id int primary key, u int unique, a text,
v text as (concat('v-', a)) virtual, key (v(10)))
with system versioning;
insert t1 (id, u, a) values (1, 1, 'one'), (2, 2, 'two');
replace t1 (id, u, a) values (1, 2, 'final');
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select id, u, a, v from t1 for system_time all force index (v) order by v;
id u a v
1 2 final v-final
1 1 one v-one
2 2 two v-two
drop table t1;
# End of 10.11 tests

View File

@@ -74,6 +74,101 @@ i
connection master;
drop table t;
#
# MDEV-24646 ASAN heap-use-after-free in Field_blob::pack /
# THD::binlog_update_row upon DML on table with virtual column
#
connection master;
create table t (a text, v blob as (a) virtual)
with system versioning as select 'foo' as a;
update t set a='bar';
connection slave;
select * from t;
v a
bar bar
connection master;
drop table t;
#
# MDEV-39711 UPDATE on System-Versioned Tables uses the after-value
# in Row-format Binlog's before-image
# for non-trivial Text/Blob Virtual Columns
#
connection master;
create table t (a text, v text as (right(a, 1)) virtual)
with system versioning as select 'before' as a;
update t set a='after';
connection slave;
select * from t;
v a
r after
connection master;
drop table t;
#
# MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
# table (on UPDATE)
# Unversioned master, versioned slave, minimal row image
#
connection master;
set statement sql_log_bin=0 for
create table t (id int primary key, a text, v text as (right(a, 1)) virtual,
key (v(10))) engine=innodb;
connection slave;
set statement sql_log_bin=0 for
create table t (id int primary key, a text, v text as (right(a, 1)) virtual,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
w decimal(20,6) as (id + unix_timestamp(rs)) virtual,
ws decimal(20,6) as (id + unix_timestamp(rs)) stored,
key (v(10)), key (w), key (ws))
engine=innodb with system versioning;
connection master;
set @old_row_image= @@binlog_row_image;
set binlog_row_image= minimal;
insert t (id, a) values (1, 'before'), (2, 'start');
update t set a= 'after';
set binlog_row_image= @old_row_image;
connection slave;
check table t extended;
Table Op Msg_type Msg_text
test.t check status OK
select id, a, v, ws = id + unix_timestamp(rs) as ok
from t for system_time all force index (v) order by v, id;
id a v ok
1 before e 1
1 after r 1
2 after r 1
2 start t 1
connection master;
drop table t;
#
# MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
# table (on UPDATE)
# Unversioned master, versioned slave, failed update
#
connection master;
set statement sql_log_bin=0 for
create table t (id int primary key, a int) engine=innodb;
connection slave;
call mtr.add_suppression("Slave SQL.*Duplicate entry");
set statement sql_log_bin=0 for
create table t (id int primary key, a int, unique (a))
engine=innodb with system versioning;
connection master;
insert t values (1, 1), (2, 2);
update t set a= 2 where id = 1;
include/wait_for_slave_sql_error_and_skip.inc [errno=1062]
connection slave;
select id, a, row_end > now(6) as current
from t for system_time all order by id, current;
id a current
1 1 1
2 2 1
connection master;
drop table t;
connection slave;
connection master;
# End of 10.11 tests
#
# MDEV-25347 DML events for auto-partitioned tables are written into binary log twice
#
flush binary logs;

View File

@@ -490,4 +490,94 @@ create table t (a int, b int, c int with system versioning, unique(b));
insert into t (b) values (1);
insert into t (b) values (1),(1) on duplicate key update a = 3;
drop table t;
#
# MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
# table (on UPDATE)
#
create table t1 (c1 date key, c2 text as (c1) virtual, index (c2(10)))
with system versioning;
insert t1 values ('2000-01-01', default), ('2000-01-03', default);
update t1 set c1= c1 + interval 1 day;
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select c1, c2 from t1 for system_time all force index (c2) order by c2;
c1 c2
2000-01-01 2000-01-01
2000-01-02 2000-01-02
2000-01-03 2000-01-03
2000-01-04 2000-01-04
drop table t1;
# Multi-table UPDATE
create table t1 (c1 date key, c2 text as (c1) virtual, index (c2(10)))
with system versioning;
create table t2 (x int) engine=myisam;
insert t1 values ('2000-01-01', default), ('2000-01-03', default);
insert t2 values (1);
update t1, t2 set t1.c1= t1.c1 + interval 1 day;
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select c1, c2 from t1 for system_time all force index (c2) order by c2;
c1 c2
2000-01-01 2000-01-01
2000-01-02 2000-01-02
2000-01-03 2000-01-03
2000-01-04 2000-01-04
drop table t1, t2;
# INSERT ... ON DUPLICATE KEY UPDATE
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
insert t1 (id, a) values (1, 'old'), (2, 'start');
insert t1 (id, a) values (2, 'x') on duplicate key update a= 'final';
check table t1 extended;
Table Op Msg_type Msg_text
test.t1 check status OK
select id, a, v from t1 for system_time all force index (v) order by v;
id a v
2 final v-final
1 old v-old
2 start v-start
drop table t1;
# Virtual fields of the new row are computed once
set @save_sql_mode= @@sql_mode;
set sql_mode= '';
create table t1 (a varchar(10), b int as (a) virtual)
with system versioning;
insert t1 (a) values ('x');
update t1 set a= 'y';
Warnings:
Warning 1366 Incorrect integer value: 'x' for column `test`.`t1`.`b` at row 1
Warning 1366 Incorrect integer value: 'y' for column `test`.`t1`.`b` at row 1
Warning 1366 Incorrect integer value: 'x' for column `test`.`t1`.`b` at row 1
Warning 1366 Incorrect integer value: 'x' for column `test`.`t1`.`b` at row 2
drop table t1;
set sql_mode= @save_sql_mode;
# Error from virtual fields computed after the system fields
set @save_sql_mode= @@sql_mode;
set sql_mode= 'strict_all_tables,error_for_division_by_zero';
create table t1 (a int,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
b int as (1 div (year(rs) - 2000)) stored)
with system versioning engine=innodb;
create table t2 (x int) engine=myisam;
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (a) values (1);
insert t2 values (1);
set timestamp= unix_timestamp('2000-06-01 00:00:00');
flush status;
update t1, t2 set t1.a= 2;
ERROR 22012: Division by 0
# Non-zero: t1 is updated through the temporary table
show status like 'Handler_tmp_write';
Variable_name Value
Handler_tmp_write 1
set timestamp= default;
select a, b from t1;
a b
1 1
drop table t1, t2;
set sql_mode= @save_sql_mode;
# End of 10.11 tests

View File

@@ -121,4 +121,114 @@ select f1, f3, check_row(row_start, row_end) from t1 for system_time all order b
--echo # Cleanup
drop tables t1, t2;
if ($default_engine == MEMORY)
{
--disable_query_log
set default_storage_engine= myisam;
--enable_query_log
}
--echo #
--echo # MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
--echo # table (on UPDATE)
--echo #
--echo # Virtual blob over row_end, computed before and after row_end is set
create table t1 (id int primary key, a text,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
v text as (concat(a, '-', unix_timestamp(re))) virtual,
key (v(40)))
with system versioning;
insert t1 (id, a) values (1, 'one'), (2, 'two');
delete from t1 where id = 1;
check table t1 extended;
select id, a from t1 for system_time all force index (v) order by id;
drop table t1;
--echo # AFTER DELETE trigger reads OLD of a virtual blob over row_end
create table t1 (id int primary key, a text,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
v text as (concat('v-', a, '-', unix_timestamp(re))) virtual)
with system versioning;
create table log (id int, ok int);
create trigger tr after delete on t1 for each row
insert log values (old.id,
old.v = concat('v-', old.a, '-', unix_timestamp(old.re)));
insert t1 (id, a) values (1, 'one'), (2, 'two');
delete from t1;
select * from log order by id;
drop table t1, log;
--echo # Multi-table DELETE
create table t1 (id int primary key, a text,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
v text as (concat('v-', a, '-', unix_timestamp(re))) virtual)
with system versioning;
create table log (id int, ok int);
create trigger tr after delete on t1 for each row
insert log values (old.id,
old.v = concat('v-', old.a, '-', unix_timestamp(old.re)));
create table t2 (x int);
insert t1 (id, a) values (1, 'one'), (2, 'two');
insert t2 values (1);
delete t1 from t1, t2;
select * from log order by id;
drop table t1, t2, log;
--echo # History row already exists: the row is deleted
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
create table log (id int, old_v text);
create trigger tr after delete on t1 for each row
insert log values (old.id, old.v);
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (id, a) values (1, 'one');
delete from t1;
insert t1 (id, a) values (1, 'two');
delete from t1;
set timestamp= default;
check table t1 extended;
select * from log order by old_v;
select id, a, v from t1 for system_time all force index (v) order by v;
drop table t1, log;
--echo # Negative lifetime: the row is deleted
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
create table log (id int, old_v text);
create trigger tr after delete on t1 for each row
insert log values (old.id, old.v);
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (id, a) values (1, 'one');
set timestamp= unix_timestamp('2000-06-01 00:00:00');
delete from t1;
set timestamp= default;
check table t1 extended;
select * from log;
select id, a, v from t1 for system_time all;
drop table t1, log;
--echo # ha_update_row() of the history row fails
create table p (id int primary key, a text, v text as (concat('v-', a)) virtual)
with system versioning engine=innodb;
create table c (id int, foreign key (id) references p (id)) engine=innodb;
insert p (id, a) values (1, 'one');
insert c values (1);
--error ER_ROW_IS_REFERENCED_2
delete from p;
--error ER_ROW_IS_REFERENCED_2
delete p from p, c;
check table p extended;
select id, a, v from p for system_time all;
drop table c, p;
--disable_query_log
eval set default_storage_engine= $default_engine;
--enable_query_log
--echo # End of 10.11 tests
--source suite/versioning/common_finish.inc

View File

@@ -2957,6 +2957,29 @@ set timestamp= default;
set time_zone= default;
drop table t1;
if ($default_engine == MEMORY)
{
--disable_query_log
set default_storage_engine= myisam;
--enable_query_log
}
--echo #
--echo # MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
--echo # table (on UPDATE)
--echo #
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning
partition by system_time (partition p0 history, partition pn current);
insert t1 (id, a) values (1, 'one'), (2, 'two');
delete from t1 where id = 1;
replace t1 (id, a) values (2, 'final');
check table t1 extended;
select id, a, v from t1 for system_time all force index (v) order by v;
drop table t1;
--disable_query_log
eval set default_storage_engine= $default_engine;
--enable_query_log
--echo # End of 10.11 tests
set global innodb_stats_persistent= @save_persistent;

View File

@@ -195,6 +195,62 @@ drop table t1;
set sql_mode=@old_mode;
if ($default_engine == MEMORY)
{
--disable_query_log
set default_storage_engine= myisam;
--enable_query_log
}
--echo #
--echo # MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
--echo # table (on UPDATE)
--echo #
--echo # REPLACE turns the conflicting row into history
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
insert t1 (id, a) values (1, 'old'), (2, 'start');
replace t1 (id, a) values (2, 'final');
check table t1 extended;
select id, a, v from t1 for system_time all force index (v) order by v;
drop table t1;
--echo # History row already exists: the conflicting row is deleted
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (id, a) values (1, 'old'), (2, 'start');
replace t1 (id, a) values (2, 'middle');
replace t1 (id, a) values (2, 'final');
set timestamp= default;
check table t1 extended;
select id, a, v from t1 for system_time all force index (v) order by v;
drop table t1;
--echo # Negative lifetime: the conflicting row is deleted, no history row
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (id, a) values (1, 'old'), (2, 'start');
set timestamp= unix_timestamp('2000-06-01 00:00:00');
replace t1 (id, a) values (2, 'final');
set timestamp= default;
check table t1 extended;
select id, a, v from t1 for system_time all force index (v) order by v;
drop table t1;
--echo # Two conflicting rows
create table t1 (id int primary key, u int unique, a text,
v text as (concat('v-', a)) virtual, key (v(10)))
with system versioning;
insert t1 (id, u, a) values (1, 1, 'one'), (2, 2, 'two');
replace t1 (id, u, a) values (1, 2, 'final');
check table t1 extended;
select id, u, a, v from t1 for system_time all force index (v) order by v;
drop table t1;
--disable_query_log
eval set default_storage_engine= $default_engine;
--enable_query_log
--echo # End of 10.11 tests
--source suite/versioning/common_finish.inc

View File

@@ -71,4 +71,94 @@ select * from t;
--connection master
drop table t;
--echo #
--echo # MDEV-24646 ASAN heap-use-after-free in Field_blob::pack /
--echo # THD::binlog_update_row upon DML on table with virtual column
--echo #
--connection master
create table t (a text, v blob as (a) virtual)
with system versioning as select 'foo' as a;
update t set a='bar';
--sync_slave_with_master
select * from t;
--connection master
drop table t;
--echo #
--echo # MDEV-39711 UPDATE on System-Versioned Tables uses the after-value
--echo # in Row-format Binlog's before-image
--echo # for non-trivial Text/Blob Virtual Columns
--echo #
--connection master
create table t (a text, v text as (right(a, 1)) virtual)
with system versioning as select 'before' as a;
update t set a='after';
--sync_slave_with_master
select * from t;
--connection master
drop table t;
--echo #
--echo # MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
--echo # table (on UPDATE)
--echo # Unversioned master, versioned slave, minimal row image
--echo #
--connection master
set statement sql_log_bin=0 for
create table t (id int primary key, a text, v text as (right(a, 1)) virtual,
key (v(10))) engine=innodb;
--connection slave
set statement sql_log_bin=0 for
create table t (id int primary key, a text, v text as (right(a, 1)) virtual,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
w decimal(20,6) as (id + unix_timestamp(rs)) virtual,
ws decimal(20,6) as (id + unix_timestamp(rs)) stored,
key (v(10)), key (w), key (ws))
engine=innodb with system versioning;
--connection master
set @old_row_image= @@binlog_row_image;
set binlog_row_image= minimal;
insert t (id, a) values (1, 'before'), (2, 'start');
update t set a= 'after';
set binlog_row_image= @old_row_image;
--sync_slave_with_master
check table t extended;
select id, a, v, ws = id + unix_timestamp(rs) as ok
from t for system_time all force index (v) order by v, id;
--connection master
drop table t;
--echo #
--echo # MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
--echo # table (on UPDATE)
--echo # Unversioned master, versioned slave, failed update
--echo #
--connection master
set statement sql_log_bin=0 for
create table t (id int primary key, a int) engine=innodb;
--connection slave
call mtr.add_suppression("Slave SQL.*Duplicate entry");
set statement sql_log_bin=0 for
create table t (id int primary key, a int, unique (a))
engine=innodb with system versioning;
--connection master
insert t values (1, 1), (2, 2);
# Duplicate on the slave only
update t set a= 2 where id = 1;
let $slave_sql_errno= 1062;
--source include/wait_for_slave_sql_error_and_skip.inc
--connection slave
select id, a, row_end > now(6) as current
from t for system_time all order by id, current;
--connection master
drop table t;
--sync_slave_with_master
--connection master
--echo # End of 10.11 tests
--source rpl_common.inc

View File

@@ -438,6 +438,83 @@ insert into t (b) values (1);
insert into t (b) values (1),(1) on duplicate key update a = 3;
drop table t;
if ($default_engine == MEMORY)
{
--disable_query_log
set default_storage_engine= myisam;
--enable_query_log
}
--echo #
--echo # MDEV-39306 Index corruption on UPDATE when using versioned InnoDB
--echo # table (on UPDATE)
--echo #
create table t1 (c1 date key, c2 text as (c1) virtual, index (c2(10)))
with system versioning;
insert t1 values ('2000-01-01', default), ('2000-01-03', default);
update t1 set c1= c1 + interval 1 day;
check table t1 extended;
select c1, c2 from t1 for system_time all force index (c2) order by c2;
drop table t1;
--echo # Multi-table UPDATE
create table t1 (c1 date key, c2 text as (c1) virtual, index (c2(10)))
with system versioning;
create table t2 (x int) engine=myisam;
insert t1 values ('2000-01-01', default), ('2000-01-03', default);
insert t2 values (1);
update t1, t2 set t1.c1= t1.c1 + interval 1 day;
check table t1 extended;
select c1, c2 from t1 for system_time all force index (c2) order by c2;
drop table t1, t2;
--echo # INSERT ... ON DUPLICATE KEY UPDATE
create table t1 (id int primary key, a text, v text as (concat('v-', a)) virtual,
key (v(10))) with system versioning;
insert t1 (id, a) values (1, 'old'), (2, 'start');
insert t1 (id, a) values (2, 'x') on duplicate key update a= 'final';
check table t1 extended;
select id, a, v from t1 for system_time all force index (v) order by v;
drop table t1;
--echo # Virtual fields of the new row are computed once
set @save_sql_mode= @@sql_mode;
set sql_mode= '';
create table t1 (a varchar(10), b int as (a) virtual)
with system versioning;
insert t1 (a) values ('x');
update t1 set a= 'y';
drop table t1;
set sql_mode= @save_sql_mode;
--echo # Error from virtual fields computed after the system fields
set @save_sql_mode= @@sql_mode;
set sql_mode= 'strict_all_tables,error_for_division_by_zero';
create table t1 (a int,
rs timestamp(6) as row start invisible,
re timestamp(6) as row end invisible,
period for system_time (rs, re),
b int as (1 div (year(rs) - 2000)) stored)
with system versioning engine=innodb;
# One-row MyISAM t2 is a const table and InnoDB t1 is not, so t1 is updated
# through a temporary table in multi_update::do_updates()
create table t2 (x int) engine=myisam;
set timestamp= unix_timestamp('2001-01-01 00:00:00');
insert t1 (a) values (1);
insert t2 values (1);
set timestamp= unix_timestamp('2000-06-01 00:00:00');
flush status;
--error ER_DIVISION_BY_ZERO
update t1, t2 set t1.a= 2;
--echo # Non-zero: t1 is updated through the temporary table
show status like 'Handler_tmp_write';
set timestamp= default;
select a, b from t1;
drop table t1, t2;
set sql_mode= @save_sql_mode;
--disable_query_log
eval set default_storage_engine= $default_engine;
--enable_query_log
--echo # End of 10.11 tests
source suite/versioning/common_finish.inc;

View File

@@ -7947,10 +7947,16 @@ Write_rows_log_event::write_row(rpl_group_info *rgi,
if (table->versioned(VERS_TIMESTAMP))
{
ulong sec_part;
// Check whether a row came from unversioned table and fix vers fields.
if (opt_secure_timestamp > SECTIME_REPL ||
(table->vers_start_field()->get_timestamp(&sec_part) == 0 && sec_part == 0))
table->vers_update_fields();
/*
If the row came from an unversioned table or the system fields from the
master are not trusted, set the system fields here. Then compute
vcols, which may depend on them.
*/
if ((opt_secure_timestamp > SECTIME_REPL ||
(table->vers_start_field()->get_timestamp(&sec_part) == 0 &&
sec_part == 0)) &&
table->vers_update_fields_vcols())
DBUG_RETURN(HA_ERR_GENERIC);
}
/*
@@ -8869,8 +8875,12 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
if (m_table->versioned())
{
if ((m_vers_from_plain || opt_secure_timestamp > SECTIME_REPL) &&
m_table->versioned(VERS_TIMESTAMP))
m_table->vers_update_fields();
m_table->versioned(VERS_TIMESTAMP) &&
m_table->vers_update_fields_vcols())
{
error= HA_ERR_GENERIC;
goto err;
}
Field *end= m_table->vers_end_field();
const uchar *old_ptr= end->ptr_in_record(m_table->record[1]);
@@ -8884,7 +8894,7 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
tl->trg_event_map= trg_event_map_save;
if (unlikely(error == HA_ERR_RECORD_IS_THE_SAME))
error= 0;
if (m_vers_from_plain && m_table->versioned(VERS_TIMESTAMP))
if (!error && m_vers_from_plain && m_table->versioned(VERS_TIMESTAMP))
{
store_record(m_table, record[2]);
error= vers_insert_history_row(m_table);

View File

@@ -309,6 +309,7 @@ int TABLE::delete_row(bool treat_versioned)
{
int err= 0;
uchar *del_buf= record[replace ? 1 : 0];
String *blob_storage= NULL;
bool delete_row= !treat_versioned
|| in_use->lex->vers_conditions.delete_history
@@ -326,6 +327,17 @@ int TABLE::delete_row(bool treat_versioned)
{
store_record(this, record[1]);
}
/*
vers_update_end() computes virtual blobs into Field_blob::value, which
holds the virtual blobs of the new row in record[2] (REPLACE) or of the
old row in record[1] (computed with VCOL_UPDATE_FOR_DELETE)
*/
if (s->virtual_not_stored_blob_fields)
{
blob_storage= (String *) alloca(sizeof(String) *
s->virtual_not_stored_blob_fields);
remember_blob_values(blob_storage);
}
vers_update_end();
Field *row_start= vers_start_field();
@@ -335,6 +347,8 @@ int TABLE::delete_row(bool treat_versioned)
if (likely(!delete_row))
err= file->ha_update_row(record[1], record[0]);
if (replace && blob_storage)
restore_blob_values(blob_storage);
if (unlikely(err))
{
/*
@@ -350,7 +364,7 @@ int TABLE::delete_row(bool treat_versioned)
|| err == HA_ERR_FOUND_DUPP_UNIQUE
|| err == HA_ERR_FOREIGN_DUPLICATE_KEY;
if (!delete_row)
return err;
goto end;
}
if (delete_row)
@@ -363,6 +377,15 @@ int TABLE::delete_row(bool treat_versioned)
if (delete_row)
err= file->ha_delete_row(del_buf);
end:
/*
DELETE: record[0] is the history row, which AFTER DELETE triggers read as
OLD, so its generated blobs stay in Field_blob::value. Free the buffers
moved aside by remember_blob_values(), kept for ha_update_row() and
ha_delete_row() (the ones of record[1] in single-table DELETE).
*/
if (!replace && blob_storage)
free_blob_values(blob_storage);
return err;
}

View File

@@ -3047,8 +3047,13 @@ int multi_update::do_updates()
goto err2;
}
}
if (has_vers_fields && table->versioned())
table->vers_update_fields();
/*
Virtual fields are computed again because they may depend on the
system fields, which are set only after compare_record().
*/
if (has_vers_fields && table->versioned() &&
table->vers_update_fields_vcols())
goto err2;
if (unlikely((local_error=
table->file->ha_update_row(table->record[1],

View File

@@ -8468,6 +8468,21 @@ void TABLE::restore_blob_values(String *blob_storage)
}
/*
Free blob values stored by remember_blob_values() without restoring them
*/
void TABLE::free_blob_values(String *blob_storage)
{
for (Field **vfield_ptr= vfield; *vfield_ptr; vfield_ptr++)
{
if ((*vfield_ptr)->type() == MYSQL_TYPE_BLOB &&
!(*vfield_ptr)->vcol_info->stored_in_db)
(blob_storage++)->free();
}
}
/**
@brief
Allocate space for keys
@@ -9441,10 +9456,13 @@ int TABLE::update_generated_fields()
next_number_field= NULL;
}
if (likely(!res) && vfield)
res= update_virtual_fields(file, VCOL_UPDATE_FOR_WRITE);
if (likely(!res) && versioned())
vers_update_fields();
if (likely(!res))
{
if (versioned())
res= vers_update_fields_vcols();
else if (vfield)
res= update_virtual_fields(file, VCOL_UPDATE_FOR_WRITE);
}
if (likely(!res))
res= verify_constraints(false) == VIEW_CHECK_ERROR;
return res;
@@ -9570,7 +9588,18 @@ bool TABLE::check_period_overlaps(const KEY &key,
return true;
}
/* returns true if vers_end_field was updated */
/**
Set row_start and row_end of the row in record[0] unless they have
explicit values. For VERS_TRX_ID only row_end is set, regardless of its
explicit value.
Virtual fields are not computed: the caller must call
update_virtual_fields(VCOL_UPDATE_FOR_WRITE) afterwards, or use
vers_update_fields_vcols().
@retval true vers_end_field was updated
@retval false vers_end_field has an explicit value and was not updated
*/
bool TABLE::vers_update_fields()
{
bool res= false;
@@ -9586,12 +9615,27 @@ bool TABLE::vers_update_fields()
res= true;
}
if (vfield)
update_virtual_fields(file, VCOL_UPDATE_FOR_READ);
return res;
}
/**
Set the system fields and compute virtual fields, which may depend on them.
@see vers_update_fields()
@retval 0 Success
@retval other Error from update_virtual_fields(VCOL_UPDATE_FOR_WRITE)
*/
int TABLE::vers_update_fields_vcols()
{
vers_update_fields();
if (vfield)
return update_virtual_fields(file, VCOL_UPDATE_FOR_WRITE);
return 0;
}
void TABLE::vers_update_end()
{
if (vers_end_field()->store_timestamp(in_use->query_start(),

View File

@@ -1807,6 +1807,7 @@ public:
void move_fields(Field **ptr, const uchar *to, const uchar *from);
void remember_blob_values(String *blob_storage);
void restore_blob_values(String *blob_storage);
void free_blob_values(String *blob_storage);
uint actual_n_key_parts(KEY *keyinfo);
ulong actual_key_flags(KEY *keyinfo);
@@ -1947,6 +1948,8 @@ public:
inline int delete_row(){ return delete_row<false>(versioned(VERS_TIMESTAMP)); }
template <bool replace>
int delete_row(bool versioned);
/* vers_update_fields() and update_virtual_fields(VCOL_UPDATE_FOR_WRITE) */
int vers_update_fields_vcols();
/* Used in majority of DML (called from fill_record()) */
bool vers_update_fields();
/* Used in DELETE, DUP REPLACE and insert history row */