Skip to content

Commit 72429ca

Browse files
committed
MDEV-30046 wrong row targeted with "insert ... on duplicate" and "replace"
When HA_DUPLICATE_POS is not supported, the row to replace was navigated by ha_index_read_idx_map, which uses only hash to navigate. Suchwise, given a hash collision it may choose an incorrect row. handler::position would be correct and very convenient to use here. dup_ref is already set by handler independently of the engine capabilities, when an extra lookup is made (for long unique or something else, for example WITHOUT OVERLAPS) such error will be indicated by file->lookup_errkey != -1.
1 parent 7f161a5 commit 72429ca

File tree

5 files changed

+63
-8
lines changed

5 files changed

+63
-8
lines changed

mysql-test/main/long_unique_bugs.result

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,25 @@ SELECT * FROM t1;
674674
a b c
675675
3 2 2
676676
DROP TABLE t1;
677+
# MDEV-30046 wrong row targeted with "insert ... on duplicate" and
678+
# "replace", leading to data corruption
679+
create table t (s blob, n int, unique (s)) engine=innodb;
680+
insert into t values ('Hrecvx_0004ln-00',1), ('Hrecvx_0004mm-00',1);
681+
replace into t values ('Hrecvx_0004mm-00',2);
682+
select * from t;
683+
s n
684+
Hrecvx_0004ln-00 1
685+
Hrecvx_0004mm-00 2
686+
drop table t;
687+
create table t (s blob, n int, unique (s)) engine=innodb;
688+
insert into t values ('Hrecvx_0004ln-00',1), ('Hrecvx_0004mm-00',1);
689+
insert into t values ('Hrecvx_0004mm-00',2)
690+
on duplicate key update n = values (n);
691+
select * from t;
692+
s n
693+
Hrecvx_0004ln-00 1
694+
Hrecvx_0004mm-00 2
695+
drop table t;
677696
#
678697
# End of 10.5 tests
679698
#

mysql-test/main/long_unique_bugs.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,21 @@ REPLACE INTO t1 VALUES (3,2,2);
655655
SELECT * FROM t1;
656656
DROP TABLE t1;
657657

658+
--echo # MDEV-30046 wrong row targeted with "insert ... on duplicate" and
659+
--echo # "replace", leading to data corruption
660+
--source include/have_innodb.inc
661+
create table t (s blob, n int, unique (s)) engine=innodb;
662+
insert into t values ('Hrecvx_0004ln-00',1), ('Hrecvx_0004mm-00',1);
663+
replace into t values ('Hrecvx_0004mm-00',2);
664+
select * from t;
665+
drop table t;
666+
667+
create table t (s blob, n int, unique (s)) engine=innodb;
668+
insert into t values ('Hrecvx_0004ln-00',1), ('Hrecvx_0004mm-00',1);
669+
insert into t values ('Hrecvx_0004mm-00',2)
670+
on duplicate key update n = values (n);
671+
select * from t;
672+
drop table t;
658673
--echo #
659674
--echo # End of 10.5 tests
660675
--echo #

sql/handler.cc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4569,6 +4569,12 @@ uint handler::get_dup_key(int error)
45694569
DBUG_RETURN(errkey);
45704570
}
45714571

4572+
bool handler::has_dup_ref() const
4573+
{
4574+
DBUG_ASSERT(lookup_errkey != (uint)-1 || errkey != (uint)-1);
4575+
return ha_table_flags() & HA_DUPLICATE_POS || lookup_errkey != (uint)-1;
4576+
}
4577+
45724578

45734579
/**
45744580
Delete all files with extension from bas_ext().
@@ -6996,11 +7002,8 @@ int handler::check_duplicate_long_entry_key(const uchar *new_rec, uint key_no)
69967002
if (error == HA_ERR_FOUND_DUPP_KEY)
69977003
{
69987004
table->file->lookup_errkey= key_no;
6999-
if (ha_table_flags() & HA_DUPLICATE_POS)
7000-
{
7001-
lookup_handler->position(table->record[0]);
7002-
memcpy(table->file->dup_ref, lookup_handler->ref, ref_length);
7003-
}
7005+
lookup_handler->position(table->record[0]);
7006+
memcpy(table->file->dup_ref, lookup_handler->ref, ref_length);
70047007
}
70057008
restore_record(table, file->lookup_buffer);
70067009
table->restore_blob_values(blob_storage);

sql/handler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3464,6 +3464,7 @@ class handler :public Sql_alloc
34643464
virtual void print_error(int error, myf errflag);
34653465
virtual bool get_error_message(int error, String *buf);
34663466
uint get_dup_key(int error);
3467+
bool has_dup_ref() const;
34673468
/**
34683469
Retrieves the names of the table and the key for which there was a
34693470
duplicate entry in the case of HA_ERR_FOREIGN_DUPLICATE_KEY.

sql/sql_insert.cc

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,11 +1884,28 @@ int write_record(THD *thd, TABLE *table, COPY_INFO *info, select_result *sink)
18841884
if (info->handle_duplicates == DUP_REPLACE && table->next_number_field &&
18851885
key_nr == table->s->next_number_index && insert_id_for_cur_row > 0)
18861886
goto err;
1887-
if (table->file->ha_table_flags() & HA_DUPLICATE_POS)
1887+
if (table->file->has_dup_ref())
18881888
{
1889+
/*
1890+
If engine doesn't support HA_DUPLICATE_POS, the handler may init to
1891+
INDEX, but dup_ref could also be set by lookup_handled (and then,
1892+
lookup_errkey is set, f.ex. long unique duplicate).
1893+
1894+
In such case, handler would stay uninitialized, so do it here.
1895+
*/
1896+
bool init_lookup_handler= table->file->lookup_errkey != (uint)-1 &&
1897+
table->file->inited == handler::NONE;
1898+
if (init_lookup_handler && table->file->ha_rnd_init_with_error(false))
1899+
goto err;
1900+
18891901
DBUG_ASSERT(table->file->inited == handler::RND);
1890-
if (table->file->ha_rnd_pos(table->record[1],table->file->dup_ref))
1891-
goto err;
1902+
int rnd_pos_err= table->file->ha_rnd_pos(table->record[1],
1903+
table->file->dup_ref);
1904+
1905+
if (init_lookup_handler)
1906+
table->file->ha_rnd_end();
1907+
if (rnd_pos_err)
1908+
goto err;
18921909
}
18931910
else
18941911
{

0 commit comments

Comments
 (0)