From 6942facc652f37f2c22002307084e476653c8be1 Mon Sep 17 00:00:00 2001 From: OmarGamal10 Date: Sun, 15 Feb 2026 02:22:52 +0200 Subject: [PATCH 1/3] MDEV-7270: Fix replicas not honoring slave_skip_errors for 1677 Setting slave_skip_errors to 1677 (ER_SLAVE_CONVERSION_FAILED) results in slaves ignoring the configruation and aborting replication on a column mismatch. The intended behaviour should logically be ignoring the event and logging a warning. --- .../rpl/r/rpl_row_slave_skip_errors.result | 42 ++++++++++++ .../rpl/t/rpl_row_slave_skip_errors-slave.opt | 1 + .../rpl/t/rpl_row_slave_skip_errors.test | 67 +++++++++++++++++++ sql/log_event_server.cc | 40 +++++------ sql/rpl_utility.h | 2 +- sql/rpl_utility_server.cc | 33 +++++---- sql/slave.h | 14 ++++ 7 files changed, 163 insertions(+), 36 deletions(-) create mode 100644 mysql-test/suite/rpl/r/rpl_row_slave_skip_errors.result create mode 100644 mysql-test/suite/rpl/t/rpl_row_slave_skip_errors-slave.opt create mode 100644 mysql-test/suite/rpl/t/rpl_row_slave_skip_errors.test diff --git a/mysql-test/suite/rpl/r/rpl_row_slave_skip_errors.result b/mysql-test/suite/rpl/r/rpl_row_slave_skip_errors.result new file mode 100644 index 0000000000000..0ebc0aa246cc0 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_row_slave_skip_errors.result @@ -0,0 +1,42 @@ +include/master-slave.inc +[connection master] +CREATE TABLE t (name VARCHAR(25) DEFAULT NULL); +include/sync_slave_sql_with_master.inc +call mtr.add_suppression("Slave SQL.*Error executing row event: .Table .test.t. doesn.t exist., Error_code: 1146"); +call mtr.add_suppression("Slave SQL.*Column 0 of table .test.t. cannot be converted from type.* Error_code: 1677"); +call mtr.add_suppression("The slave coordinator and worker threads are stopped, possibly leaving data in inconsistent state"); +call mtr.add_suppression("Got error 1 during COMMIT"); +ALTER TABLE t MODIFY name VARCHAR(255); +connection master; +INSERT INTO t VALUE ('Omar'); +# Sync should be successful. Slave should not stop with an error +# ER_SLAVE_CONVERSION_FAILED. It should be up and running in spite +# of errors as we have set slave_skip_error=1677. +include/sync_slave_sql_with_master.inc +include/check_slave_no_error.inc +# Verify master has one row and slave has none, row events from master should be skipped. +connection master; +SELECT * FROM t; +name +Omar +connection slave; +SELECT * FROM t; +name +connection master; +SELECT COUNT(*) AS master_count FROM t; +master_count +1 +connection slave; +SELECT COUNT(*) AS slave_count FROM t; +slave_count +0 +==== Clean up ==== +include/stop_slave.inc +RESET MASTER; +RESET SLAVE; +include/start_slave.inc +connection master; +include/sync_slave_sql_with_master.inc +connection master; +DROP TABLE t; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_row_slave_skip_errors-slave.opt b/mysql-test/suite/rpl/t/rpl_row_slave_skip_errors-slave.opt new file mode 100644 index 0000000000000..9f70627692a0a --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_slave_skip_errors-slave.opt @@ -0,0 +1 @@ +--slave-skip-errors=1677 \ No newline at end of file diff --git a/mysql-test/suite/rpl/t/rpl_row_slave_skip_errors.test b/mysql-test/suite/rpl/t/rpl_row_slave_skip_errors.test new file mode 100644 index 0000000000000..eac077dd14c3c --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_row_slave_skip_errors.test @@ -0,0 +1,67 @@ +# ==== Purpose ==== +# +# Check that slave-skip-errors skips following errors like +# ER_SLAVE_CONVERSION_FAILED and ER_NO_SUCH_TABLE. +# +# This test is adapted from the upstream mysql test for Bug#17653275, to verify the fix for Mariadb MDEV-7270. +# +# ==== Implementation ==== +# On slave, set slave_skip_errors=1677, so that slave skips ER_SLAVE_CONVERSION_FAILED +# reported during application of row based events. +# On master, create a table t with a varchar filed of length 25. On slave +# increase the varchar field width to 255, so that updates that are received +# from master will fail on slave with error ER_SLAVE_CONVERSION_FAILED. +# +# Verify that slave doesn't break inspite of these errors. + + +--source include/have_debug.inc +--source include/have_binlog_format_row.inc +--source include/master-slave.inc + +# On master create table t which contains a field named 'name' with length +# varchar(25). +CREATE TABLE t (name VARCHAR(25) DEFAULT NULL); +--source include/sync_slave_sql_with_master.inc + +# On slave alter the name field length to varchar(255). +call mtr.add_suppression("Slave SQL.*Error executing row event: .Table .test.t. doesn.t exist., Error_code: 1146"); +call mtr.add_suppression("Slave SQL.*Column 0 of table .test.t. cannot be converted from type.* Error_code: 1677"); +call mtr.add_suppression("The slave coordinator and worker threads are stopped, possibly leaving data in inconsistent state"); +call mtr.add_suppression("Got error 1 during COMMIT"); +ALTER TABLE t MODIFY name VARCHAR(255); + +connection master; +INSERT INTO t VALUE ('Omar'); +--echo # Sync should be successful. Slave should not stop with an error +--echo # ER_SLAVE_CONVERSION_FAILED. It should be up and running in spite +--echo # of errors as we have set slave_skip_error=1677. +--source include/sync_slave_sql_with_master.inc +--source include/check_slave_no_error.inc + +--echo # Verify master has one row and slave has none, row events from master should be skipped. +connection master; +SELECT * FROM t; + +connection slave; +SELECT * FROM t; + +connection master; +SELECT COUNT(*) AS master_count FROM t; +connection slave; +SELECT COUNT(*) AS slave_count FROM t; + + +--echo ==== Clean up ==== +--source include/stop_slave.inc +RESET MASTER; +RESET SLAVE; +--source include/start_slave.inc + +connection master; +--source include/sync_slave_sql_with_master.inc + +connection master; +DROP TABLE t; +--source include/rpl_end.inc + diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 3700cb55ac0a4..3de3a9c9a5903 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -296,20 +296,6 @@ inline int idempotent_error_code(int err_code) return (ret); } -/** - Ignore error code specified on command line. -*/ - -inline int ignored_error_code(int err_code) -{ - if (use_slave_mask && bitmap_is_set(&slave_error_mask, err_code)) - { - statistic_increment(slave_skipped_errors, LOCK_status); - return 1; - } - return err_code == ER_SLAVE_IGNORED_TABLE; -} - /* This function converts an engine's error to a server error. @@ -5687,20 +5673,29 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi) RPL_TABLE_LIST *ptr= static_cast(table_list_ptr); DBUG_ASSERT(ptr->m_tabledef_valid); TABLE *conv_table; - if (!ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table)) + int compatible= + ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table); + if (compatible == 1) { - DBUG_PRINT("debug", ("Table: %s.%s is not compatible with master", - ptr->table->s->db.str, - ptr->table->s->table_name.str)); - /* - We should not honour --slave-skip-errors at this point as we are - having severe errors which should not be skiped. - */ + DBUG_PRINT("debug", + ("Table: %s.%s is not compatible with master", + ptr->table->s->db.str, ptr->table->s->table_name.str)); thd->is_slave_error= 1; /* remove trigger's tables */ error= ERR_BAD_TABLE_DEF; goto err; } + if (compatible == 2) + { + DBUG_PRINT("debug", + ("Table: %s.%s is not compatible with master but error " + "is ignored, event is skipped", + ptr->table->s->db.str, ptr->table->s->table_name.str)); + + error= 0; + goto err; + } + DBUG_PRINT("debug", ("Table: %s.%s is compatible with master" " - conv_table: %p", ptr->table->s->db.str, @@ -5840,7 +5835,6 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi) before in some other ROWS event. */ rgi->set_row_stmt_start_timestamp(); - THD_STAGE_INFO(thd, stage_executing); do { diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h index c28e8aa10ebef..ca3ac61a0487c 100644 --- a/sql/rpl_utility.h +++ b/sql/rpl_utility.h @@ -190,7 +190,7 @@ class table_def @retval 0 if the table definition is compatible with @c table */ #ifndef MYSQL_CLIENT - bool compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, + int compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, TABLE **conv_table_var) const; /** diff --git a/sql/rpl_utility_server.cc b/sql/rpl_utility_server.cc index ccad7bd070905..98e031a1a48ea 100644 --- a/sql/rpl_utility_server.cc +++ b/sql/rpl_utility_server.cc @@ -18,6 +18,7 @@ #include #include "rpl_utility.h" #include "log_event.h" +#include "slave.h" #if defined(MYSQL_CLIENT) #error MYSQL_CLIENT must not be defined here @@ -893,7 +894,6 @@ const Type_handler *table_def::field_type_handler(uint col) const return Type_handler::get_handler_by_real_type(typecode); } - /** Is the definition compatible with a table? @@ -918,13 +918,14 @@ const Type_handler *table_def::field_type_handler(uint col) const @param tmp_table_var[out] Virtual temporary table for performing conversions, if necessary. - @retval true Master table is compatible with slave table. - @retval false Master table is not compatible with slave table. + @retval 0 Master table is compatible with slave table. + @retval 1 Master table is not compatible with slave table and error is not + ignored, abort replication. + @retval 2 Master table is not compatible with slave table but error is + ignored, skip event. */ -bool -table_def::compatible_with(THD *thd, rpl_group_info *rgi, - TABLE *table, TABLE **conv_table_var) - const +int table_def::compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, + TABLE **conv_table_var) const { /* We only check the initial columns for the tables. @@ -945,11 +946,11 @@ table_def::compatible_with(THD *thd, rpl_group_info *rgi, field->table->s->db.str, field->table->s->table_name.str, field->field_name.str); - return false; + return 1; } if (!h) - return false; // An unknown data type found in the binary log + return 1; // An unknown data type found in the binary log Conv_source source(h, field_metadata(col), field->charset()); enum_conv_type convtype= can_convert_field_to(field, source, rli, Conv_param(m_flags)); @@ -970,7 +971,7 @@ table_def::compatible_with(THD *thd, rpl_group_info *rgi, */ tmp_table= create_conversion_table(thd, rgi, table); if (tmp_table == NULL) - return false; + return 1; /* Clear all fields up to, but not including, this column. */ @@ -983,6 +984,14 @@ table_def::compatible_with(THD *thd, rpl_group_info *rgi, } else { + if (ignored_error_code(ER_SLAVE_CONVERSION_FAILED)) + { + rli->report(WARNING_LEVEL, ER_SLAVE_CONVERSION_FAILED, + rgi->gtid_info(), ER_THD(thd, ER_SLAVE_CONVERSION_FAILED), + col, table->s->db.str, table->s->table_name.str, + field->field_name.str); + return 2; + } DBUG_PRINT("debug", ("Checking column %d -" " field '%s' can not be converted", col, field->field_name.str)); @@ -1003,7 +1012,7 @@ table_def::compatible_with(THD *thd, rpl_group_info *rgi, ER_THD(thd, ER_SLAVE_CONVERSION_FAILED), col, db_name, tbl_name, source_type.c_ptr_safe(), target_type.c_ptr_safe()); - return false; + return 1; } } @@ -1028,7 +1037,7 @@ table_def::compatible_with(THD *thd, rpl_group_info *rgi, #endif *conv_table_var= tmp_table; - return true; + return 0; } diff --git a/sql/slave.h b/sql/slave.h index 02de9135c2a10..8489a4d355461 100644 --- a/sql/slave.h +++ b/sql/slave.h @@ -151,6 +151,20 @@ extern ulonglong slave_skipped_errors; extern const char *relay_log_index; extern const char *relay_log_basename; +/** + Ignore error code specified on command line. +*/ + +inline int ignored_error_code(int err_code) +{ + if (use_slave_mask && bitmap_is_set(&slave_error_mask, err_code)) + { + statistic_increment(slave_skipped_errors, LOCK_status); + return 1; + } + return err_code == ER_SLAVE_IGNORED_TABLE; +} + /* 4 possible values for Master_info::slave_running and Relay_log_info::slave_running. From 6050f1d57651d177dce8663dddd212e93a0b998b Mon Sep 17 00:00:00 2001 From: OmarGamal10 Date: Sun, 15 Feb 2026 03:54:02 +0200 Subject: [PATCH 2/3] Add an enum for Row master-slave compatibility for cleaner handling --- sql/log_event_old.cc | 8 ++++-- sql/log_event_server.cc | 6 ++--- sql/rpl_utility.h | 13 +++++++--- sql/rpl_utility_server.cc | 54 ++++++++++++++++++++++----------------- 4 files changed, 50 insertions(+), 31 deletions(-) diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc index 5078c4d954e3f..51a7bd981dcd9 100644 --- a/sql/log_event_old.cc +++ b/sql/log_event_old.cc @@ -146,7 +146,9 @@ Old_rows_log_event::do_apply_event(Old_rows_log_event *ev, rpl_group_info *rgi) RPL_TABLE_LIST *ptr=static_cast(table_list_ptr); DBUG_ASSERT(ptr->m_tabledef_valid); TABLE *conv_table; - if (!ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table)) + if (ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, + &conv_table) == + table_def::TABLE_INCOMPATIBLE) { ev_thd->is_slave_error= 1; rgi->slave_close_thread_tables(ev_thd); @@ -1445,7 +1447,9 @@ int Old_rows_log_event::do_apply_event(rpl_group_info *rgi) */ RPL_TABLE_LIST *ptr=static_cast(table_list_ptr); TABLE *conv_table; - if (ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table)) + if (ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, + &conv_table) == + table_def::TABLE_INCOMPATIBLE) { thd->is_slave_error= 1; rgi->slave_close_thread_tables(thd); diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 3de3a9c9a5903..e39f067248c7a 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -5675,7 +5675,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi) TABLE *conv_table; int compatible= ptr->m_tabledef.compatible_with(thd, rgi, ptr->table, &conv_table); - if (compatible == 1) + if (compatible == table_def::TABLE_INCOMPATIBLE) { DBUG_PRINT("debug", ("Table: %s.%s is not compatible with master", @@ -5685,10 +5685,10 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi) error= ERR_BAD_TABLE_DEF; goto err; } - if (compatible == 2) + if (compatible == table_def::TABLE_INCOMPATIBLE_IGNORED) { DBUG_PRINT("debug", - ("Table: %s.%s is not compatible with master but error " + ("Table: %s.%s is not compatible with master. Error 1677 " "is ignored, event is skipped", ptr->table->s->db.str, ptr->table->s->table_name.str)); diff --git a/sql/rpl_utility.h b/sql/rpl_utility.h index ca3ac61a0487c..c21b06e3cdf3c 100644 --- a/sql/rpl_utility.h +++ b/sql/rpl_utility.h @@ -58,6 +58,13 @@ class table_def ~table_def(); + enum enum_compatibility_master_slave + { + TABLE_COMPATIBILE= 0, + TABLE_INCOMPATIBLE= 1, + TABLE_INCOMPATIBLE_IGNORED= 2 + }; + /** Return the number of fields there is type data for. @@ -65,7 +72,6 @@ class table_def */ ulong size() const { return m_size; } - /** Returns internal binlog type code for one field, without translation to real types. @@ -190,8 +196,9 @@ class table_def @retval 0 if the table definition is compatible with @c table */ #ifndef MYSQL_CLIENT - int compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, - TABLE **conv_table_var) const; + enum_compatibility_master_slave + compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, + TABLE **conv_table_var) const; /** Create a virtual in-memory temporary table structure. diff --git a/sql/rpl_utility_server.cc b/sql/rpl_utility_server.cc index 98e031a1a48ea..6a89ef7228c88 100644 --- a/sql/rpl_utility_server.cc +++ b/sql/rpl_utility_server.cc @@ -918,14 +918,15 @@ const Type_handler *table_def::field_type_handler(uint col) const @param tmp_table_var[out] Virtual temporary table for performing conversions, if necessary. - @retval 0 Master table is compatible with slave table. - @retval 1 Master table is not compatible with slave table and error is not - ignored, abort replication. - @retval 2 Master table is not compatible with slave table but error is - ignored, skip event. + @retval TABLE_COMPATIBLE Master table is compatible with slave table. + @retval TABLE_INCOMPATIBLE Master table is not compatible with slave table + and error is not ignored, abort replication. + @retval TABLE_INCOMPATIBLE_IGNORED Master table is not compatible with slave + table but error is ignored, skip event. */ -int table_def::compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, - TABLE **conv_table_var) const +table_def::enum_compatibility_master_slave +table_def::compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, + TABLE **conv_table_var) const { /* We only check the initial columns for the tables. @@ -946,11 +947,12 @@ int table_def::compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, field->table->s->db.str, field->table->s->table_name.str, field->field_name.str); - return 1; + return TABLE_INCOMPATIBLE; } if (!h) - return 1; // An unknown data type found in the binary log + return TABLE_INCOMPATIBLE; // An unknown data type found in the binary + // log Conv_source source(h, field_metadata(col), field->charset()); enum_conv_type convtype= can_convert_field_to(field, source, rli, Conv_param(m_flags)); @@ -971,7 +973,7 @@ int table_def::compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, */ tmp_table= create_conversion_table(thd, rgi, table); if (tmp_table == NULL) - return 1; + return TABLE_INCOMPATIBLE; /* Clear all fields up to, but not including, this column. */ @@ -984,17 +986,6 @@ int table_def::compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, } else { - if (ignored_error_code(ER_SLAVE_CONVERSION_FAILED)) - { - rli->report(WARNING_LEVEL, ER_SLAVE_CONVERSION_FAILED, - rgi->gtid_info(), ER_THD(thd, ER_SLAVE_CONVERSION_FAILED), - col, table->s->db.str, table->s->table_name.str, - field->field_name.str); - return 2; - } - DBUG_PRINT("debug", ("Checking column %d -" - " field '%s' can not be converted", - col, field->field_name.str)); DBUG_ASSERT(col < size() && col < table->s->fields); DBUG_ASSERT(table->s->db.str && table->s->table_name.str); DBUG_ASSERT(table->in_use); @@ -1008,11 +999,28 @@ int table_def::compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, field->sql_rpl_type(&target_type); DBUG_ASSERT(source_type.length() > 0); DBUG_ASSERT(target_type.length() > 0); + + if (ignored_error_code(ER_SLAVE_CONVERSION_FAILED)) + { + DBUG_PRINT("debug", ("Checking column %d -" + " field '%s' can not be converted. Error 1677 is " + "ignored, event is skipped", + col, field->field_name.str)); + rli->report(WARNING_LEVEL, ER_SLAVE_CONVERSION_FAILED, + rgi->gtid_info(), ER_THD(thd, ER_SLAVE_CONVERSION_FAILED), + col, table->s->db.str, table->s->table_name.str, + field->field_name.str); + return TABLE_INCOMPATIBLE_IGNORED; + } + + DBUG_PRINT("debug", ("Checking column %d -" + " field '%s' can not be converted", + col, field->field_name.str)); rli->report(ERROR_LEVEL, ER_SLAVE_CONVERSION_FAILED, rgi->gtid_info(), ER_THD(thd, ER_SLAVE_CONVERSION_FAILED), col, db_name, tbl_name, source_type.c_ptr_safe(), target_type.c_ptr_safe()); - return 1; + return TABLE_INCOMPATIBLE; } } @@ -1037,7 +1045,7 @@ int table_def::compatible_with(THD *thd, rpl_group_info *rgi, TABLE *table, #endif *conv_table_var= tmp_table; - return 0; + return TABLE_COMPATIBILE; } From 39e0f13853fa746fd15858955eea9e9eafc056fb Mon Sep 17 00:00:00 2001 From: OmarGamal10 Date: Wed, 18 Feb 2026 20:52:46 +0200 Subject: [PATCH 3/3] Trigger clang-msan rebuild