diff options
-rw-r--r-- | mysql-test/std_data/rpl/mysql-5.7.11-stm-temporal-round-binlog.000001 | bin | 0 -> 695 bytes | |||
-rw-r--r-- | mysql-test/std_data/rpl/mysql-8.0.13-stm-temporal-round-binlog.000001 | bin | 0 -> 1095 bytes | |||
-rw-r--r-- | mysql-test/suite/rpl/r/rpl_mysql57_stm_temporal_round.result | 21 | ||||
-rw-r--r-- | mysql-test/suite/rpl/r/rpl_mysql80_stm_temporal_round.result | 22 | ||||
-rw-r--r-- | mysql-test/suite/rpl/t/rpl_mysql57_stm_temporal_round.test | 50 | ||||
-rw-r--r-- | mysql-test/suite/rpl/t/rpl_mysql80_stm_temporal_round.test | 54 | ||||
-rw-r--r-- | sql/log_event.cc | 88 | ||||
-rw-r--r-- | sql/log_event.h | 53 | ||||
-rw-r--r-- | sql/slave.cc | 18 | ||||
-rw-r--r-- | sql/sql_class.h | 6 |
10 files changed, 256 insertions, 56 deletions
diff --git a/mysql-test/std_data/rpl/mysql-5.7.11-stm-temporal-round-binlog.000001 b/mysql-test/std_data/rpl/mysql-5.7.11-stm-temporal-round-binlog.000001 Binary files differnew file mode 100644 index 00000000000..a8de8b8c760 --- /dev/null +++ b/mysql-test/std_data/rpl/mysql-5.7.11-stm-temporal-round-binlog.000001 diff --git a/mysql-test/std_data/rpl/mysql-8.0.13-stm-temporal-round-binlog.000001 b/mysql-test/std_data/rpl/mysql-8.0.13-stm-temporal-round-binlog.000001 Binary files differnew file mode 100644 index 00000000000..79e242d0783 --- /dev/null +++ b/mysql-test/std_data/rpl/mysql-8.0.13-stm-temporal-round-binlog.000001 diff --git a/mysql-test/suite/rpl/r/rpl_mysql57_stm_temporal_round.result b/mysql-test/suite/rpl/r/rpl_mysql57_stm_temporal_round.result new file mode 100644 index 00000000000..cd2cbd5aa54 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_mysql57_stm_temporal_round.result @@ -0,0 +1,21 @@ +# +# MDEV-8894 Inserting fractional seconds into MySQL 5.6 master breaks consistency on MariaDB 10 slave +# +include/master-slave.inc +[connection master] +connection slave; +include/stop_slave.inc +connection master; +include/rpl_stop_server.inc [server_number=1] +include/rpl_start_server.inc [server_number=1] +connection slave; +CHANGE MASTER TO master_host='127.0.0.1', master_port=SERVER_MYPORT_1, master_user='root', master_log_file='master-bin.000001', master_log_pos=4; +include/start_slave.inc +connection master; +connection slave; +SELECT * FROM t1 ORDER BY id; +id a +1 2001-01-01 00:00:01.000 +include/stop_slave.inc +DROP TABLE t1; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/r/rpl_mysql80_stm_temporal_round.result b/mysql-test/suite/rpl/r/rpl_mysql80_stm_temporal_round.result new file mode 100644 index 00000000000..c1408deb467 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_mysql80_stm_temporal_round.result @@ -0,0 +1,22 @@ +# +# MDEV-8894 Inserting fractional seconds into MySQL 5.6 master breaks consistency on MariaDB 10 slave +# +include/master-slave.inc +[connection master] +connection slave; +include/stop_slave.inc +connection master; +include/rpl_stop_server.inc [server_number=1] +include/rpl_start_server.inc [server_number=1] +connection slave; +CHANGE MASTER TO master_host='127.0.0.1', master_port=SERVER_MYPORT_1, master_user='root', master_log_file='master-bin.000001', master_log_pos=4; +include/start_slave.inc +connection master; +connection slave; +SELECT * FROM t1 ORDER BY id; +id a +1 2001-01-01 00:00:01.000 +2 2001-01-01 00:00:00.999 +include/stop_slave.inc +DROP TABLE t1; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_mysql57_stm_temporal_round.test b/mysql-test/suite/rpl/t/rpl_mysql57_stm_temporal_round.test new file mode 100644 index 00000000000..e29d15fbf4f --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_mysql57_stm_temporal_round.test @@ -0,0 +1,50 @@ +--echo # +--echo # MDEV-8894 Inserting fractional seconds into MySQL 5.6 master breaks consistency on MariaDB 10 slave +--echo # + +--source include/have_innodb.inc +--source include/master-slave.inc + +--connection slave +--source include/stop_slave.inc + +--connection master +--let $datadir= `SELECT @@datadir` + +--let $rpl_server_number= 1 +--source include/rpl_stop_server.inc + +--remove_file $datadir/master-bin.000001 + +# +# Simulate MySQL 5.7.x master +# +# mysql-5.7.11-stm-temporal-round-binlog.000001 was recorded with +# "mysqld --log-bin --binlog-format=statement", with the following SQL script: +# +#CREATE TABLE t1 (id SERIAL, a DATETIME(3)); +#INSERT INTO t1 (a) VALUES ('2001-01-01 00:00:00.999999'); +# + +--copy_file $MYSQL_TEST_DIR/std_data/rpl/mysql-5.7.11-stm-temporal-round-binlog.000001 $datadir/master-bin.000001 + +--let $rpl_server_number= 1 +--source include/rpl_start_server.inc + +--source include/wait_until_connected_again.inc + +--connection slave +--replace_result $SERVER_MYPORT_1 SERVER_MYPORT_1 +eval CHANGE MASTER TO master_host='127.0.0.1', master_port=$SERVER_MYPORT_1, master_user='root', master_log_file='master-bin.000001', master_log_pos=4; + +--source include/start_slave.inc + +--connection master +--sync_slave_with_master +SELECT * FROM t1 ORDER BY id; + +--source include/stop_slave.inc +DROP TABLE t1; + +--let $rpl_only_running_threads= 1 +--source include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_mysql80_stm_temporal_round.test b/mysql-test/suite/rpl/t/rpl_mysql80_stm_temporal_round.test new file mode 100644 index 00000000000..a4d0734d225 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_mysql80_stm_temporal_round.test @@ -0,0 +1,54 @@ +--echo # +--echo # MDEV-8894 Inserting fractional seconds into MySQL 5.6 master breaks consistency on MariaDB 10 slave +--echo # + +--source include/have_innodb.inc +--source include/master-slave.inc + +--connection slave +--source include/stop_slave.inc + +--connection master +--let $datadir= `SELECT @@datadir` + +--let $rpl_server_number= 1 +--source include/rpl_stop_server.inc + +--remove_file $datadir/master-bin.000001 + +# +# Simulate MySQL 8.0.x master +# +# mysql-8.0.13-stm-temporal-round-binlog.000001 was recorded with +# "mysqld --log-bin --binlog-format=statement", with the following SQL script: +# +#SET NAMES utf8mb4 COLLATE utf8mb4_general_ci; +#SET sql_mode=''; +#CREATE TABLE t1 (id SERIAL, a DATETIME(3)); +#INSERT INTO t1 (a) VALUES ('2001-01-01 00:00:00.999999'); +#SET sql_mode=TIME_TRUNCATE_FRACTIONAL; +#INSERT INTO t1 (a) VALUES ('2001-01-01 00:00:00.999999'); +# + +--copy_file $MYSQL_TEST_DIR/std_data/rpl/mysql-8.0.13-stm-temporal-round-binlog.000001 $datadir/master-bin.000001 + +--let $rpl_server_number= 1 +--source include/rpl_start_server.inc + +--source include/wait_until_connected_again.inc + +--connection slave +--replace_result $SERVER_MYPORT_1 SERVER_MYPORT_1 +eval CHANGE MASTER TO master_host='127.0.0.1', master_port=$SERVER_MYPORT_1, master_user='root', master_log_file='master-bin.000001', master_log_pos=4; + +--source include/start_slave.inc + +--connection master +--sync_slave_with_master +SELECT * FROM t1 ORDER BY id; + +--source include/stop_slave.inc +DROP TABLE t1; + +--let $rpl_only_running_threads= 1 +--source include/rpl_end.inc diff --git a/sql/log_event.cc b/sql/log_event.cc index 651fb4ce5b1..fc56656815d 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -101,16 +101,11 @@ TYPELIB binlog_checksum_typelib= TODO: correct the constant when it has been determined (which main tree to push and when) */ -const uchar checksum_version_split_mysql[3]= {5, 6, 1}; -const ulong checksum_version_product_mysql= - (checksum_version_split_mysql[0] * 256 + - checksum_version_split_mysql[1]) * 256 + - checksum_version_split_mysql[2]; -const uchar checksum_version_split_mariadb[3]= {5, 3, 0}; -const ulong checksum_version_product_mariadb= - (checksum_version_split_mariadb[0] * 256 + - checksum_version_split_mariadb[1]) * 256 + - checksum_version_split_mariadb[2]; +const Version checksum_version_split_mysql(5, 6, 1); +const Version checksum_version_split_mariadb(5, 3, 0); + +// First MySQL version with fraction seconds +const Version fsp_version_split_mysql(5, 6, 0); #if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION) static int rows_event_stmt_cleanup(rpl_group_info *rgi, THD* thd); @@ -4541,6 +4536,7 @@ code_name(int code) } #endif + /** Macro to check that there is enough space to read from memory. @@ -4764,6 +4760,30 @@ Query_log_event::Query_log_event(const char* buf, uint event_len, } } +#if !defined(MYSQL_CLIENT) + if (description_event->server_version_split.kind == + Format_description_log_event::master_version_split::KIND_MYSQL) + { + // Handle MariaDB/MySQL incompatible sql_mode bits + sql_mode_t mysql_sql_mode= sql_mode; + sql_mode&= MODE_MASK_MYSQL_COMPATIBLE; // Unset MySQL specific bits + + /* + sql_mode flags related to fraction second rounding/truncation + have opposite meaning in MySQL vs MariaDB. + MySQL: + - rounds fractional seconds by default + - truncates if TIME_TRUNCATE_FRACTIONAL is set + MariaDB: + - truncates fractional seconds by default + - rounds if TIME_ROUND_FRACTIONAL is set + */ + if (description_event->server_version_split >= fsp_version_split_mysql && + !(mysql_sql_mode & MODE_MYSQL80_TIME_TRUNCATE_FRACTIONAL)) + sql_mode|= MODE_TIME_ROUND_FRACTIONAL; + } +#endif + /** Layout for the data buffer is as follows +--------+-----------+------+------+---------+----+-------+ @@ -6549,26 +6569,24 @@ bool Format_description_log_event::start_decryption(Start_encryption_log_event* return crypto_data.init(sele->crypto_scheme, sele->key_version); } -static inline void -do_server_version_split(char* version, - Format_description_log_event::master_version_split *split_versions) + +Version::Version(const char *version, const char **endptr) { - char *p= version, *r; + const char *p= version; ulong number; for (uint i= 0; i<=2; i++) { + char *r; number= strtoul(p, &r, 10); /* It is an invalid version if any version number greater than 255 or first number is not followed by '.'. */ if (number < 256 && (*r == '.' || i != 0)) - split_versions->ver[i]= (uchar) number; + m_ver[i]= (uchar) number; else { - split_versions->ver[0]= 0; - split_versions->ver[1]= 0; - split_versions->ver[2]= 0; + *this= Version(); break; } @@ -6576,12 +6594,19 @@ do_server_version_split(char* version, if (*r == '.') p++; // skip the dot } + endptr[0]= p; +} + + +Format_description_log_event:: + master_version_split::master_version_split(const char *version) +{ + const char *p; + static_cast<Version*>(this)[0]= Version(version, &p); if (strstr(p, "MariaDB") != 0 || strstr(p, "-maria-") != 0) - split_versions->kind= - Format_description_log_event::master_version_split::KIND_MARIADB; + kind= KIND_MARIADB; else - split_versions->kind= - Format_description_log_event::master_version_split::KIND_MYSQL; + kind= KIND_MYSQL; } @@ -6595,20 +6620,14 @@ do_server_version_split(char* version, */ void Format_description_log_event::calc_server_version_split() { - do_server_version_split(server_version, &server_version_split); + server_version_split= master_version_split(server_version); DBUG_PRINT("info",("Format_description_log_event::server_version_split:" " '%s' %d %d %d", server_version, - server_version_split.ver[0], - server_version_split.ver[1], server_version_split.ver[2])); + server_version_split[0], + server_version_split[1], server_version_split[2])); } -static inline ulong -version_product(const Format_description_log_event::master_version_split* version_split) -{ - return ((version_split->ver[0] * 256 + version_split->ver[1]) * 256 - + version_split->ver[2]); -} /** @return TRUE is the event's version is earlier than one that introduced @@ -6618,9 +6637,9 @@ bool Format_description_log_event::is_version_before_checksum(const master_version_split *version_split) { - return version_product(version_split) < + return *version_split < (version_split->kind == master_version_split::KIND_MARIADB ? - checksum_version_product_mariadb : checksum_version_product_mysql); + checksum_version_split_mariadb : checksum_version_split_mysql); } /** @@ -6636,7 +6655,6 @@ enum enum_binlog_checksum_alg get_checksum_alg(const char* buf, ulong len) { enum enum_binlog_checksum_alg ret; char version[ST_SERVER_VER_LEN]; - Format_description_log_event::master_version_split version_split; DBUG_ENTER("get_checksum_alg"); DBUG_ASSERT(buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT); @@ -6646,7 +6664,7 @@ enum enum_binlog_checksum_alg get_checksum_alg(const char* buf, ulong len) ST_SERVER_VER_LEN); version[ST_SERVER_VER_LEN - 1]= 0; - do_server_version_split(version, &version_split); + Format_description_log_event::master_version_split version_split(version); ret= Format_description_log_event::is_version_before_checksum(&version_split) ? BINLOG_CHECKSUM_ALG_UNDEF : (enum_binlog_checksum_alg)buf[len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN]; diff --git a/sql/log_event.h b/sql/log_event.h index 84025554ee9..38a40c90799 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -2728,6 +2728,38 @@ protected: }; +class Version +{ +protected: + uchar m_ver[3]; + int cmp(const Version &other) const + { + return memcmp(m_ver, other.m_ver, 3); + } +public: + Version() + { + m_ver[0]= m_ver[1]= m_ver[2]= '\0'; + } + Version(uchar v0, uchar v1, uchar v2) + { + m_ver[0]= v0; + m_ver[1]= v1; + m_ver[2]= v2; + } + Version(const char *version, const char **endptr); + const uchar& operator [] (size_t i) const + { + DBUG_ASSERT(i < 3); + return m_ver[i]; + } + bool operator<(const Version &other) const { return cmp(other) < 0; } + bool operator>(const Version &other) const { return cmp(other) > 0; } + bool operator<=(const Version &other) const { return cmp(other) <= 0; } + bool operator>=(const Version &other) const { return cmp(other) >= 0; } +}; + + /** @class Format_description_log_event @@ -2754,10 +2786,17 @@ public: by the checksum alg decription byte */ uint8 *post_header_len; - struct master_version_split { + class master_version_split: public Version { + public: enum {KIND_MYSQL, KIND_MARIADB}; int kind; - uchar ver[3]; + master_version_split() :kind(KIND_MARIADB) { } + master_version_split(const char *version); + bool version_is_valid() const + { + /* It is invalid only when all version numbers are 0 */ + return !(m_ver[0] == 0 && m_ver[1] == 0 && m_ver[2] == 0); + } }; master_version_split server_version_split; const uint8 *event_type_permutation; @@ -2781,17 +2820,9 @@ public: (post_header_len != NULL)); } - bool version_is_valid() const - { - /* It is invalid only when all version numbers are 0 */ - return !(server_version_split.ver[0] == 0 && - server_version_split.ver[1] == 0 && - server_version_split.ver[2] == 0); - } - bool is_valid() const { - return header_is_valid() && version_is_valid(); + return header_is_valid() && server_version_split.version_is_valid(); } int get_data_size() diff --git a/sql/slave.cc b/sql/slave.cc index 16fa890d86c..2a8c977451d 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -7910,8 +7910,8 @@ bool rpl_master_has_bug(const Relay_log_info *rli, uint bug_id, bool report, { struct st_version_range_for_one_bug { uint bug_id; - const uchar introduced_in[3]; // first version with bug - const uchar fixed_in[3]; // first version with fix + Version introduced_in; // first version with bug + Version fixed_in; // first version with fix }; static struct st_version_range_for_one_bug versions_for_all_bugs[]= { @@ -7921,19 +7921,17 @@ bool rpl_master_has_bug(const Relay_log_info *rli, uint bug_id, bool report, {33029, { 5, 1, 0 }, { 5, 1, 12 } }, {37426, { 5, 1, 0 }, { 5, 1, 26 } }, }; - const uchar *master_ver= - rli->relay_log.description_event_for_exec->server_version_split.ver; - - DBUG_ASSERT(sizeof(rli->relay_log.description_event_for_exec->server_version_split.ver) == 3); + const Version &master_ver= + rli->relay_log.description_event_for_exec->server_version_split; for (uint i= 0; i < sizeof(versions_for_all_bugs)/sizeof(*versions_for_all_bugs);i++) { - const uchar *introduced_in= versions_for_all_bugs[i].introduced_in, - *fixed_in= versions_for_all_bugs[i].fixed_in; + const Version &introduced_in= versions_for_all_bugs[i].introduced_in; + const Version &fixed_in= versions_for_all_bugs[i].fixed_in; if ((versions_for_all_bugs[i].bug_id == bug_id) && - (memcmp(introduced_in, master_ver, 3) <= 0) && - (memcmp(fixed_in, master_ver, 3) > 0) && + introduced_in <= master_ver && + fixed_in > master_ver && (pred == NULL || (*pred)(param))) { if (!report) diff --git a/sql/sql_class.h b/sql/sql_class.h index 1997add96dc..1078eacd035 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -156,9 +156,15 @@ enum enum_binlog_row_image { #define MODE_HIGH_NOT_PRECEDENCE (1ULL << 29) #define MODE_NO_ENGINE_SUBSTITUTION (1ULL << 30) #define MODE_PAD_CHAR_TO_FULL_LENGTH (1ULL << 31) +/* SQL mode bits defined above are common for MariaDB and MySQL */ +#define MODE_MASK_MYSQL_COMPATIBLE 0xFFFFFFFFULL +/* The following modes are specific to MariaDB */ #define MODE_EMPTY_STRING_IS_NULL (1ULL << 32) #define MODE_SIMULTANEOUS_ASSIGNMENT (1ULL << 33) #define MODE_TIME_ROUND_FRACTIONAL (1ULL << 34) +/* The following modes are specific to MySQL */ +#define MODE_MYSQL80_TIME_TRUNCATE_FRACTIONAL (1ULL << 32) + /* Bits for different old style modes */ #define OLD_MODE_NO_DUP_KEY_WARNINGS_WITH_IGNORE (1 << 0) |