revision-id: e8a96aeb62ab6880cb703fc7935083f12438d782 (mariadb-10.2.26-17-ge8a96aeb62a) parent(s): 609ea2f37b8169a7c282fe2d607c2412467ccbbb author: Sujatha committer: Sujatha timestamp: 2019-08-13 14:02:46 +0530 message: MENT-325: DROP TABLE IF EXISTS killed on master but was replicated Problem: ======= DROP TABLE IF EXISTS was killed. The table still exists on the master but the DDL was still logged. Fix: === When DROP TABLE command execution is interrupted because of kill command the replicated DROP TABLE command should include only the list of tables which were dropped successfully. For DROP TABLE IF EXISTS .. The command should contain the list of all tables which were actually dropped and unknown tables till the point at which kill command is executed. For DROP TABLE ... The command should contain the list of tables which were dropped. --- .../suite/rpl/r/rpl_failed_drop_tbl_binlog.result | 42 +++++++++++ .../suite/rpl/t/rpl_failed_drop_tbl_binlog.test | 81 ++++++++++++++++++++++ sql/sql_table.cc | 48 ++++++++++--- 3 files changed, 161 insertions(+), 10 deletions(-) diff --git a/mysql-test/suite/rpl/r/rpl_failed_drop_tbl_binlog.result b/mysql-test/suite/rpl/r/rpl_failed_drop_tbl_binlog.result new file mode 100644 index 00000000000..de166047bed --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_failed_drop_tbl_binlog.result @@ -0,0 +1,42 @@ +include/master-slave.inc +[connection master] +connect master2,localhost,root,,; +connection master; +CREATE TABLE t1 (A INT); +connection slave; +connection master; +LOCK TABLE t1 WRITE; +SET debug_sync='rm_table_no_locks_before_delete_table WAIT_FOR go'; +DROP TABLE IF EXISTS t1; +connection master1; +SET debug_sync='open_tables_after_open_and_process_table SIGNAL go WAIT_FOR go2'; +PREPARE x FROM "INSERT t1 VALUES (10)"; +connection master2; +KILL QUERY 9; +SET debug_sync='now SIGNAL go2'; +connection master; +ERROR 70100: Query execution was interrupted +include/assert.inc [Drop with single table should not be written to the binary log if the query execution fails] +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (A INT) +disconnect master2; +"Table t1 should be listed" +SHOW TABLES; +Tables_in_test +t1 +connection master; +UNLOCK TABLES; +SET debug_sync='reset'; +# DROP TABLE command with multiple tables fails the command should be written into the binary log. +DROP TABLE t1,t2; +ERROR 42S02: Unknown table 'test.t2' +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (A INT) +master-bin.000001 # Gtid # # GTID #-#-# +master-bin.000001 # Query # # use `test`; DROP TABLE `t1`,`t2` /* generated by server */ +connection slave; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_failed_drop_tbl_binlog.test b/mysql-test/suite/rpl/t/rpl_failed_drop_tbl_binlog.test new file mode 100644 index 00000000000..e8eaeb493c7 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_failed_drop_tbl_binlog.test @@ -0,0 +1,81 @@ +# ==== Purpose ==== +# +# Check that when the execution of a DROP TABLE command with single table +# fails it should not be written to the binary log. Also test that when the +# execution of DROP TABLE command with multiple tables fails the command +# should be written into the binary log. +# +# ==== Implementation ==== +# +# Steps: +# 0 - Create table named t1. +# 1 - Simulate a scenario where DROP TABLE command waits for an exclusive +# MDL lock to drop the table. +# 2 - Kill the query from another session. +# 3 - The DROP TABLE query execution will be interrupted. Ensure that the +# failed DROP TABLE command is not written to the binary log. +# 4 - Execute DROP TABLE t1, t2 statement. It will fail as 't2' table is +# not present. It should error out with unknown table error. +# 5 - The command should be written to binary log as it has already dropped +# table t1. +# +# ==== References ==== +# +# MENT-325: DROP TABLE IF EXISTS killed on master but was replicated. +# + +--source include/have_debug_sync.inc +--source include/have_binlog_format_statement.inc +--source include/master-slave.inc + +connect (master2,localhost,root,,); + +--connection master +CREATE TABLE t1 (A INT); +# Save master position +--let $saved_master_pos=query_get_value('SHOW MASTER STATUS', Position, 1) +--sync_slave_with_master + +--connection master +--let id=`select connection_id()` +LOCK TABLE t1 WRITE; +SET debug_sync='rm_table_no_locks_before_delete_table WAIT_FOR go'; +--send DROP TABLE IF EXISTS t1 + +--connection master1 +--let $wait_condition= SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE STATE like "debug sync point: rm_table_no_locks_before_delete_table%" +--source include/wait_condition.inc +SET debug_sync='open_tables_after_open_and_process_table SIGNAL go WAIT_FOR go2'; +--send PREPARE x FROM "INSERT t1 VALUES (10)" + +--connection master2 +--let $wait_condition= SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE STATE like "debug sync point: open_tables_after_open_and_process_table%" +--source include/wait_condition.inc +eval KILL QUERY $id; +SET debug_sync='now SIGNAL go2'; + +--connection master +--error ER_QUERY_INTERRUPTED +reap; + +--let $current_master_pos=query_get_value('SHOW MASTER STATUS', Position, 1) +--let $assert_text= Drop with single table should not be written to the binary log if the query execution fails +--let $assert_cond= $current_master_pos = $saved_master_pos +--source include/assert.inc + +--source include/show_binlog_events.inc +disconnect master2; + +--echo "Table t1 should be listed" +SHOW TABLES; + +--connection master +UNLOCK TABLES; +SET debug_sync='reset'; +--echo # DROP TABLE command with multiple tables fails the command should be written into the binary log. +--error ER_BAD_TABLE_ERROR +DROP TABLE t1,t2; +--source include/show_binlog_events.inc +--sync_slave_with_master + +--source include/rpl_end.inc diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 41f62de2a70..6f2fbb66fe6 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -2187,6 +2187,7 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, bool non_tmp_error= 0; bool trans_tmp_table_deleted= 0, non_trans_tmp_table_deleted= 0; bool non_tmp_table_deleted= 0; + bool write_non_tmp_table_drop_in_binlog= false; bool is_drop_tmp_if_exists_added= 0; bool was_view= 0; String built_query; @@ -2265,7 +2266,7 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, } } - for (table= tables; table; table= table->next_local) + for (table= tables; table && !thd->killed; table= table->next_local) { bool is_trans= 0; bool table_creation_was_logged= 0; @@ -2372,11 +2373,21 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, if (!dont_log_query) { /* - Note that unless if_exists is TRUE or a temporary table was deleted, + Note that unless if_exists is TRUE or a temporary table was deleted, there is no means to know if the statement should be written to the binary log. See further information on this variable in what follows. */ non_tmp_table_deleted= (if_exists ? TRUE : non_tmp_table_deleted); + } + } + DEBUG_SYNC(thd, "rm_table_no_locks_before_delete_table"); + error= 0; + if (drop_temporary || + (ha_table_exists(thd, db, alias, &table_type) == 0 && table_type == 0) || + (!drop_view && (was_view= (table_type == view_pseudo_hton)))) + { + if (non_tmp_table_deleted) + { /* Don't write the database name if it is the current one (or if thd->db is NULL). @@ -2390,14 +2401,8 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, append_identifier(thd, &built_query, table->table_name, table->table_name_length); built_query.append(","); + write_non_tmp_table_drop_in_binlog= true; } - } - DEBUG_SYNC(thd, "rm_table_no_locks_before_delete_table"); - error= 0; - if (drop_temporary || - (ha_table_exists(thd, db, alias, &table_type) == 0 && table_type == 0) || - (!drop_view && (was_view= (table_type == view_pseudo_hton)))) - { /* One of the following cases happened: . "DROP TEMPORARY" but a temporary table was not found. @@ -2499,6 +2504,28 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, error= 1; else if (frm_delete_error && if_exists) thd->clear_error(); + + /* + Upon successful deletion of the table include the table name as + part of DROP TABLE command which will be written to the binary log. + */ + if(non_tmp_table_deleted) + { + /* + Don't write the database name if it is the current one (or if + thd->db is NULL). + */ + if (thd->db == NULL || strcmp(db,thd->db) != 0) + { + append_identifier(thd, &built_query, db, db_length); + built_query.append("."); + } + + append_identifier(thd, &built_query, table->table_name, + table->table_name_length); + built_query.append(","); + write_non_tmp_table_drop_in_binlog= true; + } } non_tmp_error|= MY_TEST(error); } @@ -2586,7 +2613,8 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, is_drop_tmp_if_exists_added, 0); } - if (non_tmp_table_deleted) + if (non_tmp_table_deleted && + (write_non_tmp_table_drop_in_binlog == true)) { /* Chop of the last comma */ built_query.chop();