diff --git a/mysql-test/suite/versioning/common.inc b/mysql-test/suite/versioning/common.inc index f296b631f94..3f508f9baf2 100644 --- a/mysql-test/suite/versioning/common.inc +++ b/mysql-test/suite/versioning/common.inc @@ -43,6 +43,18 @@ begin return NULL; end~~ +create function if not exists current_row(sys_trx_end varbinary(255)) +returns int +deterministic +begin + if default_engine() = 'innodb' then + return sys_trx_end = 18446744073709551615; + elseif default_engine() = 'myisam' then + return sys_trx_end = timestamp'2038-01-19 03:14:07.999999'; + end if; + return NULL; +end~~ + create function if not exists sys_commit_ts(sys_field varchar(255)) returns varchar(255) deterministic diff --git a/mysql-test/suite/versioning/common_finish.inc b/mysql-test/suite/versioning/common_finish.inc index 3113c01af2e..ede6bd4ba0c 100644 --- a/mysql-test/suite/versioning/common_finish.inc +++ b/mysql-test/suite/versioning/common_finish.inc @@ -4,6 +4,7 @@ drop procedure innodb_verify_vtq; drop function default_engine; drop function sys_commit_ts; drop function sys_datatype; +drop function current_row; drop procedure concat_exec2; drop procedure concat_exec3; --enable_query_log diff --git a/mysql-test/suite/versioning/key_type.combinations b/mysql-test/suite/versioning/key_type.combinations new file mode 100644 index 00000000000..1929aee9a84 --- /dev/null +++ b/mysql-test/suite/versioning/key_type.combinations @@ -0,0 +1,2 @@ +[unique] +[pk] diff --git a/mysql-test/suite/versioning/key_type.inc b/mysql-test/suite/versioning/key_type.inc new file mode 100644 index 00000000000..648430771cf --- /dev/null +++ b/mysql-test/suite/versioning/key_type.inc @@ -0,0 +1,23 @@ +--disable_query_log +if ($MTR_COMBINATION_UNIQUE) +{ + set @KEY_TYPE= 'unique'; +} +if ($MTR_COMBINATION_PK) +{ + set @KEY_TYPE= 'primary key'; +} + +delimiter ~~; +create procedure create_table(name varchar(255), cols varchar(255)) +begin + if (cols is null or cols = '') then + set cols= ''; + else + set cols= concat(', ', cols); + end if; + set @str= concat('create or replace table ', name, '(id int ', @KEY_TYPE, cols, ') with system versioning'); + prepare stmt from @str; execute stmt; drop prepare stmt; +end~~ +delimiter ;~~ +--enable_query_log diff --git a/mysql-test/suite/versioning/r/replace.result b/mysql-test/suite/versioning/r/replace.result new file mode 100644 index 00000000000..e8bbbf36681 --- /dev/null +++ b/mysql-test/suite/versioning/r/replace.result @@ -0,0 +1,10 @@ +call create_table('t', 'x int'); +insert t values (1, 2); +replace t values (1, 3); +select *, current_row(sys_trx_end) as current from t for system_time all +order by x; +id x current +1 2 0 +1 3 1 +drop database test; +create database test; diff --git a/mysql-test/suite/versioning/r/update.result b/mysql-test/suite/versioning/r/update.result index 7505bec0d80..797b309b887 100644 --- a/mysql-test/suite/versioning/r/update.result +++ b/mysql-test/suite/versioning/r/update.result @@ -66,14 +66,15 @@ set @str= concat(' with system versioning engine ', engine); prepare stmt from @str; execute stmt; drop prepare stmt; -insert into t1 (x, y) values (1, 1), (2, 1), (3, 1); +insert into t1 (x, y) values (1, 1), (2, 1), (3, 1), (4, 1), (5, 1); start transaction; update t1 set y= y + 1 where x = 3; +update t1 set y= y + 1 where x = 2; update t1 set y= y + 1 where x = 3; +update t1 set y= y + 1 where x > 3; +update t1 set y= y + 1 where x > 4; commit; -select x, y from t1 for system_time -between timestamp '0000-0-0 0:0:0' - and timestamp '2038-01-19 04:14:07'; +select x, y, sys_trx_end = 18446744073709551615 as current from t1 for system_time all; drop table t1; end~~ create procedure test_04( @@ -285,19 +286,19 @@ call verify_vtq; No A B C D 1 1 1 1 1 2 1 1 1 1 -call test_03('timestamp(6)', 'myisam', 'sys_end'); -x y -1 1 -2 1 -3 3 -3 1 -3 2 +# Multiple UPDATE of same rows in single transaction create historical +# rows only once (applicable to InnoDB only). call test_03('bigint unsigned', 'innodb', 'vtq_commit_ts(sys_end)'); -x y -1 1 -2 1 -3 3 -3 1 +x y current +1 1 1 +2 2 1 +3 3 1 +4 2 1 +5 3 1 +3 1 0 +2 1 0 +4 1 0 +5 1 0 call verify_vtq; No A B C D 1 1 1 1 1 diff --git a/mysql-test/suite/versioning/t/replace.test b/mysql-test/suite/versioning/t/replace.test new file mode 100644 index 00000000000..3d8a8191145 --- /dev/null +++ b/mysql-test/suite/versioning/t/replace.test @@ -0,0 +1,13 @@ +--source suite/versioning/common.inc +--source suite/versioning/key_type.inc +--source suite/versioning/engines.inc + +call create_table('t', 'x int'); + +insert t values (1, 2); +replace t values (1, 3); +select *, current_row(sys_trx_end) as current from t for system_time all +order by x; + +drop database test; +create database test; diff --git a/mysql-test/suite/versioning/t/update.test b/mysql-test/suite/versioning/t/update.test index f99d1c6f07b..7f72384b6e5 100644 --- a/mysql-test/suite/versioning/t/update.test +++ b/mysql-test/suite/versioning/t/update.test @@ -74,16 +74,17 @@ begin engine ', engine); prepare stmt from @str; execute stmt; drop prepare stmt; - insert into t1 (x, y) values (1, 1), (2, 1), (3, 1); + insert into t1 (x, y) values (1, 1), (2, 1), (3, 1), (4, 1), (5, 1); start transaction; update t1 set y= y + 1 where x = 3; + update t1 set y= y + 1 where x = 2; update t1 set y= y + 1 where x = 3; + update t1 set y= y + 1 where x > 3; + update t1 set y= y + 1 where x > 4; commit; - select x, y from t1 for system_time - between timestamp '0000-0-0 0:0:0' - and timestamp '2038-01-19 04:14:07'; + select x, y, sys_trx_end = 18446744073709551615 as current from t1 for system_time all; drop table t1; end~~ @@ -234,7 +235,8 @@ call test_02('timestamp(6)', 'myisam', 'sys_end'); call test_02('bigint unsigned', 'innodb', 'vtq_commit_ts(sys_end)'); call verify_vtq; -call test_03('timestamp(6)', 'myisam', 'sys_end'); +--echo # Multiple UPDATE of same rows in single transaction create historical +--echo # rows only once (applicable to InnoDB only). call test_03('bigint unsigned', 'innodb', 'vtq_commit_ts(sys_end)'); call verify_vtq; diff --git a/sql/field.h b/sql/field.h index fe9c162ef8d..bda7f85e882 100644 --- a/sql/field.h +++ b/sql/field.h @@ -4642,4 +4642,19 @@ bool check_expression(Virtual_column_info *vcol, LEX_CSTRING *name, #define f_bit_as_char(x) ((x) & FIELDFLAG_TREAT_BIT_AS_CHAR) #define f_is_hex_escape(x) ((x) & FIELDFLAG_HEX_ESCAPE) +inline +ulonglong TABLE::vers_end_id() const +{ + DBUG_ASSERT(versioned_by_engine()); + return static_cast<ulonglong>(vers_end_field()->val_int()); +} + +inline +ulonglong TABLE::vers_start_id() const +{ + DBUG_ASSERT(versioned_by_engine()); + return static_cast<ulonglong>(vers_start_field()->val_int()); +} + + #endif /* FIELD_INCLUDED */ diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 40ba695499f..44106eb7f54 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1050,7 +1050,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, } } - if (table->versioned_by_sql()) + if (table->versioned()) table->vers_update_fields(); if ((res= table_list->view_check_option(thd, @@ -1937,15 +1937,30 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) */ if (last_uniq_key(table,key_nr) && !table->file->referenced_by_foreign_key() && - (!table->triggers || !table->triggers->has_delete_triggers()) && - !table->versioned_by_sql()) + (!table->triggers || !table->triggers->has_delete_triggers())) { + if (table->versioned_by_engine()) + { + bitmap_set_bit(table->write_set, table->vers_start_field()->field_index); + table->vers_start_field()->set_notnull(); + table->vers_start_field()->store(0, false); + } if ((error=table->file->ha_update_row(table->record[1], table->record[0])) && error != HA_ERR_RECORD_IS_THE_SAME) goto err; if (error != HA_ERR_RECORD_IS_THE_SAME) + { info->deleted++; + if (table->versioned_by_sql()) + { + store_record(table, record[2]); + error= vers_insert_history_row(table); + restore_record(table, record[2]); + if (error) + goto err; + } + } else error= 0; thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row); @@ -3832,7 +3847,7 @@ int select_insert::send_data(List<Item> &values) DBUG_RETURN(0); thd->count_cuted_fields= CHECK_FIELD_WARN; // Calculate cuted fields - if (table->versioned_by_sql()) + if (table->versioned()) table->vers_update_fields(); store_values(values); if (table->default_field && table->update_default_fields(0, info.ignore)) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 6dbce3bd883..4a9a5a18a00 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -781,7 +781,7 @@ int mysql_update(THD *thd, TRG_EVENT_UPDATE)) break; /* purecov: inspected */ - if (has_vers_fields && table->versioned_by_sql()) + if (has_vers_fields && table->versioned()) table->vers_update_fields(); found++; @@ -2195,7 +2195,7 @@ int multi_update::send_data(List<Item> ¬_used_values) if (table->default_field && table->update_default_fields(1, ignore)) DBUG_RETURN(1); - if (has_vers_fields && table->versioned_by_sql()) + if (has_vers_fields && table->versioned()) table->vers_update_fields(); if ((error= cur_table->view_check_option(thd, ignore)) != @@ -2550,7 +2550,7 @@ int multi_update::do_updates() goto err2; } } - if (has_vers_fields && table->versioned_by_sql()) + if (has_vers_fields && table->versioned()) table->vers_update_fields(); if ((local_error=table->file->ha_update_row(table->record[1], diff --git a/sql/table.cc b/sql/table.cc index 65035acce9f..3e479f7edcb 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -7691,11 +7691,14 @@ void TABLE::vers_update_fields() { DBUG_ENTER("vers_update_fields"); - bitmap_set_bit(write_set, vers_start_field()->field_index); - bitmap_set_bit(write_set, vers_end_field()->field_index); + if (versioned_by_sql()) + { + bitmap_set_bit(write_set, vers_start_field()->field_index); + if (vers_start_field()->set_time()) + DBUG_ASSERT(0); + } - if (vers_start_field()->set_time()) - DBUG_ASSERT(0); + bitmap_set_bit(write_set, vers_end_field()->field_index); vers_end_field()->set_max(); DBUG_VOID_RETURN; diff --git a/sql/table.h b/sql/table.h index 4a8dacc79bd..869469f18a0 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1546,6 +1546,9 @@ public: return field[s->row_end_field]; } + ulonglong vers_start_id() const; + ulonglong vers_end_id() const; + int delete_row(); /** Number of additional fields used in versioned tables */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 9a91fd338c8..b1f706a03ec 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -9185,8 +9185,9 @@ ha_innobase::update_row( error = row_update_for_mysql(m_prebuilt); if (error == DB_SUCCESS && vers_ins_row - && trx->id != static_cast<trx_id_t>( - table->vers_start_field()->val_int())) { + /* Multiple UPDATE of same rows in single transaction create + historical rows only once. */ + && trx->id != table->vers_start_id()) { error = row_insert_for_mysql((byte*) old_row, m_prebuilt, ROW_INS_HISTORICAL);