Skip to content

Commit 6353a80

Browse files
committed
MDEV-15990 REPLACE on a precise-versioned table returns ER_DUP_ENTRY
We had a protection against it, by allowing versioned delete if: trx->id != table->vers_start_id() For replace this check fails: replace calls ha_delete_row(record[2]), but table->vers_start_id() returns the value from record[0], which is irrelevant. The same problem hits Field::is_max, which may have checked the wrong record. Fix: * Refactor Field::is_max to optionally accept a pointer as an argument. * Refactor vers_start_id and vers_end_id to always accept a pointer to the record. there is a difference with is_max is that is_max accepts the pointer to the field data, rather than to the record. Method val_int() would be too effortful to refactor to accept the argument, so instead the value in record is fetched directly, like it is done in Field_longlong.
1 parent 2e2b2a0 commit 6353a80

File tree

12 files changed

+80
-40
lines changed

12 files changed

+80
-40
lines changed

mysql-test/suite/versioning/r/replace.result

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,11 @@ connection default;
6363
replace into t1 values (1),(2);
6464
connection con1;
6565
replace into t1 values (1),(2);
66-
ERROR 23000: Duplicate entry '1' for key 'PRIMARY'
6766
drop table t1;
6867
#
68+
# MDEV-14794 Limitations which the row end as a part of PK imposes due to
69+
# CURRENT_TIMESTAMP behavior and otherwise
70+
#
6971
# MDEV-15330 Server crash or assertion `table->insert_values' failure in write_record upon LOAD DATA
7072
#
7173
create table t1 (a int, b int, c int, vc int as (c), unique(a), unique(b)) with system versioning;
@@ -88,3 +90,18 @@ Note 1831 Duplicate index `data_2`. This is deprecated and will be disallowed in
8890
alter table t1 add system versioning;
8991
replace into t1 values ('o'), ('o');
9092
drop table t1;
93+
#
94+
# MDEV-15990 REPLACE on a precise-versioned table returns duplicate key
95+
# error (ER_DUP_ENTRY)
96+
#
97+
create or replace table t1 (
98+
pk int primary key, i int,
99+
row_start bigint unsigned as row start,
100+
row_end bigint unsigned as row end,
101+
period for system_time(row_start, row_end)
102+
) engine=InnoDB with system versioning;
103+
replace into t1 (pk,i) values (1,10),(1,100),(1,1000);
104+
select pk, i, row_end from t1 for system_time all;
105+
pk i row_end
106+
1 1000 18446744073709551615
107+
drop table t1;

mysql-test/suite/versioning/t/replace.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ replace into t1 values (1),(2);
7878
--connection con1
7979
--error ER_DUP_ENTRY
8080
replace into t1 values (1),(2);
81+
drop table t1;
8182

8283
drop table t1;
8384

@@ -116,4 +117,20 @@ alter table t1 add system versioning;
116117
replace into t1 values ('o'), ('o');
117118
drop table t1;
118119

120+
--echo #
121+
--echo # MDEV-15990 REPLACE on a precise-versioned table returns duplicate key
122+
--echo # error (ER_DUP_ENTRY)
123+
--echo #
124+
125+
create or replace table t1 (
126+
pk int primary key, i int,
127+
row_start bigint unsigned as row start,
128+
row_end bigint unsigned as row end,
129+
period for system_time(row_start, row_end)
130+
) engine=InnoDB with system versioning;
131+
replace into t1 (pk,i) values (1,10),(1,100),(1,1000);
132+
select pk, i, row_end from t1 for system_time all;
133+
drop table t1;
134+
135+
119136
--source suite/versioning/common_finish.inc

sql/field.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4723,17 +4723,17 @@ void Field_longlong::set_max()
47234723
int8store(ptr, unsigned_flag ? ULONGLONG_MAX : LONGLONG_MAX);
47244724
}
47254725

4726-
bool Field_longlong::is_max()
4726+
bool Field_longlong::is_max(const uchar *ptr_arg) const
47274727
{
47284728
DBUG_ASSERT(marked_for_read());
47294729
if (unsigned_flag)
47304730
{
47314731
ulonglong j;
4732-
j= uint8korr(ptr);
4732+
j= uint8korr(ptr_arg);
47334733
return j == ULONGLONG_MAX;
47344734
}
47354735
longlong j;
4736-
j= sint8korr(ptr);
4736+
j= sint8korr(ptr_arg);
47374737
return j == LONGLONG_MAX;
47384738
}
47394739

@@ -5792,13 +5792,13 @@ void Field_timestampf::set_max()
57925792
DBUG_VOID_RETURN;
57935793
}
57945794

5795-
bool Field_timestampf::is_max()
5795+
bool Field_timestampf::is_max(const uchar *ptr_arg) const
57965796
{
57975797
DBUG_ENTER("Field_timestampf::is_max");
57985798
DBUG_ASSERT(marked_for_read());
57995799

5800-
DBUG_RETURN(mi_sint4korr(ptr) == TIMESTAMP_MAX_VALUE &&
5801-
mi_sint3korr(ptr + 4) == TIME_MAX_SECOND_PART);
5800+
DBUG_RETURN(mi_sint4korr(ptr_arg) == TIMESTAMP_MAX_VALUE &&
5801+
mi_sint3korr(ptr_arg + 4) == TIME_MAX_SECOND_PART);
58025802
}
58035803

58045804
my_time_t Field_timestampf::get_timestamp(const uchar *pos,

sql/field.h

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -803,8 +803,9 @@ class Field: public Value_source
803803
*/
804804
virtual void set_max()
805805
{ DBUG_ASSERT(0); }
806-
virtual bool is_max()
806+
virtual bool is_max(const uchar *ptr_arg) const
807807
{ DBUG_ASSERT(0); return false; }
808+
bool is_max() const { return is_max(ptr); }
808809

809810
uchar *ptr; // Position to field in record
810811

@@ -2857,7 +2858,7 @@ class Field_longlong :public Field_int
28572858
return unpack_int64(to, from, from_end);
28582859
}
28592860
void set_max() override;
2860-
bool is_max() override;
2861+
bool is_max(const uchar *ptr_arg) const override;
28612862
ulonglong get_max_int_value() const override
28622863
{
28632864
return unsigned_flag ? 0xFFFFFFFFFFFFFFFFULL : 0x7FFFFFFFFFFFFFFFULL;
@@ -3437,7 +3438,7 @@ class Field_timestampf :public Field_timestamp_with_dec {
34373438
return memcmp(a_ptr, b_ptr, pack_length());
34383439
}
34393440
void set_max() override;
3440-
bool is_max() override;
3441+
bool is_max(const uchar *ptr_arg) const override;
34413442
my_time_t get_timestamp(const uchar *pos, ulong *sec_part) const override;
34423443
bool val_native(Native *to) override;
34433444
uint size_of() const override { return sizeof *this; }
@@ -5973,17 +5974,21 @@ bool check_expression(Virtual_column_info *vcol, const LEX_CSTRING *name,
59735974
#define f_visibility(x) (static_cast<field_visibility_t> ((x) & INVISIBLE_MAX_BITS))
59745975

59755976
inline
5976-
ulonglong TABLE::vers_end_id() const
5977+
ulonglong TABLE::vers_end_id(const uchar *record_arg) const
59775978
{
59785979
DBUG_ASSERT(versioned(VERS_TRX_ID));
5979-
return static_cast<ulonglong>(vers_end_field()->val_int());
5980+
DBUG_ASSERT(dynamic_cast<Field_longlong*>(vers_end_field()));
5981+
const uchar *ptr= vers_end_field()->ptr_in_record(record_arg);
5982+
return static_cast<ulonglong>(sint8korr(ptr));
59805983
}
59815984

59825985
inline
5983-
ulonglong TABLE::vers_start_id() const
5986+
ulonglong TABLE::vers_start_id(const uchar *record_arg) const
59845987
{
59855988
DBUG_ASSERT(versioned(VERS_TRX_ID));
5986-
return static_cast<ulonglong>(vers_start_field()->val_int());
5989+
DBUG_ASSERT(dynamic_cast<Field_longlong*>(vers_start_field()));
5990+
const uchar *ptr= vers_start_field()->ptr_in_record(record_arg);
5991+
return static_cast<ulonglong>(sint8korr(ptr));
59875992
}
59885993

59895994
double pos_in_interval_for_string(CHARSET_INFO *cset,

sql/handler.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7551,8 +7551,12 @@ int handler::ha_check_overlaps(const uchar *old_data, const uchar* new_data)
75517551
DBUG_ASSERT(this == table->file);
75527552
if (!table_share->period.unique_keys)
75537553
return 0;
7554-
if (table->versioned() && !table->vers_end_field()->is_max())
7555-
return 0;
7554+
if (table->versioned())
7555+
{
7556+
Field *end= table->vers_end_field();
7557+
if (!end->is_max(end->ptr_in_record(new_data)))
7558+
return 0;
7559+
}
75567560

75577561
const bool after_write= ha_table_flags() & HA_CHECK_UNIQUE_AFTER_WRITE;
75587562
const bool is_update= !after_write && old_data;

sql/log_event_server.cc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8720,8 +8720,6 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
87208720
return error;
87218721
}
87228722

8723-
const bool history_change= m_table->versioned() ?
8724-
!m_table->vers_end_field()->is_max() : false;
87258723
TABLE_LIST *tl= m_table->pos_in_table_list;
87268724
uint8 trg_event_map_save= tl->trg_event_map;
87278725

@@ -8772,8 +8770,12 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
87728770
{
87738771
if (m_vers_from_plain && m_table->versioned(VERS_TIMESTAMP))
87748772
m_table->vers_update_fields();
8775-
if (!history_change && !m_table->vers_end_field()->is_max())
8773+
Field *end= m_table->vers_end_field();
8774+
const uchar *old_ptr= end->ptr_in_record(m_table->record[1]);
8775+
8776+
if (end->is_max(old_ptr) && !end->is_max())
87768777
{
8778+
// This is a versioned delete, and we'll have to invoke ON DELETE actions
87778779
tl->trg_event_map|= trg2bit(TRG_EVENT_DELETE);
87788780
}
87798781
}

sql/sql_delete.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,8 @@ int update_portion_of_time(THD *thd, TABLE *table,
288288
inline
289289
int TABLE::delete_row()
290290
{
291-
if (!versioned(VERS_TIMESTAMP) || !vers_end_field()->is_max())
291+
if (!versioned(VERS_TIMESTAMP) ||
292+
!vers_end_field()->is_max())
292293
return file->ha_delete_row(record[0]);
293294

294295
store_record(this, record[1]);

sql/sql_insert.cc

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2114,25 +2114,12 @@ int Write_record::replace_row(ha_rows *inserted, ha_rows *deleted)
21142114
if (can_optimize && key_nr == last_unique_key)
21152115
{
21162116
DBUG_PRINT("info", ("Updating row using ha_update_row()"));
2117-
if (table->versioned(VERS_TRX_ID))
2118-
table->vers_start_field()->store(0, false);
2119-
21202117
error= table->file->ha_update_row(table->record[1], table->record[0]);
21212118

21222119
if (likely(!error))
21232120
++*deleted;
21242121
else if (error != HA_ERR_RECORD_IS_THE_SAME)
21252122
return on_ha_error(error);
2126-
2127-
if (versioned)
2128-
{
2129-
store_record(table, record[2]);
2130-
error= vers_insert_history_row(table);
2131-
restore_record(table, record[2]);
2132-
if (unlikely(error))
2133-
return on_ha_error(error);
2134-
}
2135-
21362123
break;
21372124
}
21382125
else

sql/sql_insert.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ class Write_record
124124
bool has_delete_triggers= use_triggers &&
125125
table->triggers->has_delete_triggers();
126126
bool referenced_by_fk= table->file->referenced_by_foreign_key();
127-
can_optimize= !referenced_by_fk && !has_delete_triggers;
127+
can_optimize= !referenced_by_fk && !has_delete_triggers
128+
&& !versioned && !table->versioned();
128129
}
129130
}
130131
Write_record(THD *thd, TABLE *table, COPY_INFO *info,

sql/table.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7668,6 +7668,11 @@ static void do_mark_index_columns(TABLE *table, uint index,
76687668
table->s->primary_key != MAX_KEY && table->s->primary_key != index)
76697669
do_mark_index_columns(table, table->s->primary_key, bitmap, read);
76707670

7671+
if (table->versioned(VERS_TRX_ID))
7672+
{
7673+
table->vers_start_field()->register_field_in_read_map();
7674+
table->vers_end_field()->register_field_in_read_map();
7675+
}
76717676
}
76727677
/*
76737678
mark columns used by key, but don't reset other fields

0 commit comments

Comments
 (0)