diff --git a/mysql-test/r/rpl_slave_skip.result b/mysql-test/r/rpl_slave_skip.result new file mode 100644 index 00000000000..a59ac3eb884 --- /dev/null +++ b/mysql-test/r/rpl_slave_skip.result @@ -0,0 +1,144 @@ +stop slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +reset master; +reset slave; +drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9; +start slave; +**** On Master **** +CREATE TABLE t1 (a INT, b SET('master','slave')) ENGINE=INNODB; +CREATE TABLE t2 (a INT, b SET('master','slave')) ENGINE=MYISAM; +==== Skipping normal transactions ==== +**** On Slave **** +STOP SLAVE; +**** On Master **** +BEGIN; +INSERT INTO t1 VALUES (1, 'master'); +INSERT INTO t1 VALUES (2, 'master'); +INSERT INTO t1 VALUES (3, 'master'); +COMMIT; +BEGIN; +INSERT INTO t1 VALUES (4, 'master,slave'); +INSERT INTO t1 VALUES (5, 'master,slave'); +INSERT INTO t1 VALUES (6, 'master,slave'); +COMMIT; +SELECT * FROM t1 ORDER BY a; +a b +1 master +2 master +3 master +4 master,slave +5 master,slave +6 master,slave +**** On Slave **** +SET GLOBAL SQL_SLAVE_SKIP_COUNTER=2; +START SLAVE; +SELECT * FROM t1 ORDER BY a; +a b +4 master,slave +5 master,slave +6 master,slave +**** On Master **** +DELETE FROM t1; +==== Skipping two normal transactions ==== +**** On Slave **** +STOP SLAVE; +**** On Master **** +BEGIN; +INSERT INTO t1 VALUES (1, 'master'); +INSERT INTO t1 VALUES (2, 'master'); +INSERT INTO t1 VALUES (3, 'master'); +COMMIT; +BEGIN; +INSERT INTO t1 VALUES (4, 'master'); +INSERT INTO t1 VALUES (5, 'master'); +INSERT INTO t1 VALUES (6, 'master'); +COMMIT; +BEGIN; +INSERT INTO t1 VALUES (7, 'master,slave'); +INSERT INTO t1 VALUES (8, 'master,slave'); +INSERT INTO t1 VALUES (9, 'master,slave'); +COMMIT; +SELECT * FROM t1 ORDER BY a; +a b +1 master +2 master +3 master +4 master +5 master +6 master +7 master,slave +8 master,slave +9 master,slave +**** On Slave **** +SET GLOBAL SQL_SLAVE_SKIP_COUNTER=8; +START SLAVE; +SELECT * FROM t1 ORDER BY a; +a b +7 master,slave +8 master,slave +9 master,slave +**** On Master **** +DELETE FROM t1; +==== Skipping without autocommit ==== +**** On Slave **** +STOP SLAVE; +**** On Master **** +SET AUTOCOMMIT=0; +INSERT INTO t1 VALUES (1, 'master'); +INSERT INTO t1 VALUES (2, 'master'); +INSERT INTO t1 VALUES (3, 'master'); +COMMIT; +INSERT INTO t1 VALUES (4, 'master,slave'); +INSERT INTO t1 VALUES (5, 'master,slave'); +INSERT INTO t1 VALUES (6, 'master,slave'); +COMMIT; +SELECT * FROM t1 ORDER BY a; +a b +1 master +2 master +3 master +4 master,slave +5 master,slave +6 master,slave +**** On Slave **** +SET GLOBAL SQL_SLAVE_SKIP_COUNTER=2; +START SLAVE; +SELECT * FROM t1 ORDER BY a; +a b +4 master,slave +5 master,slave +6 master,slave +==== Rollback of transaction with non-transactional change ==== +**** On Master **** +DELETE FROM t1; +SET AUTOCOMMIT=1; +**** On Slave **** +STOP SLAVE; +**** On Master **** +BEGIN; +INSERT INTO t1 VALUES (1, ''); +INSERT INTO t2 VALUES (2, 'master'); +INSERT INTO t1 VALUES (3, ''); +ROLLBACK; +BEGIN; +INSERT INTO t1 VALUES (4, ''); +INSERT INTO t2 VALUES (5, 'master,slave'); +INSERT INTO t1 VALUES (6, ''); +ROLLBACK; +SELECT * FROM t1 ORDER BY a; +a b +SELECT * FROM t2 ORDER BY a; +a b +2 master +5 master,slave +**** On Slave **** +SET GLOBAL SQL_SLAVE_SKIP_COUNTER=2; +START SLAVE; +SELECT * FROM t1 ORDER BY a; +a b +SELECT * FROM t2 ORDER BY a; +a b +5 master,slave +==== Cleanup ==== +**** On Master **** +DROP TABLE t1, t2; diff --git a/mysql-test/suite/binlog/r/binlog_killed.result b/mysql-test/suite/binlog/r/binlog_killed.result index ba4f38fb4c1..ddd80283eca 100644 --- a/mysql-test/suite/binlog/r/binlog_killed.result +++ b/mysql-test/suite/binlog/r/binlog_killed.result @@ -9,4 +9,110 @@ insert into t2 values (null, null), (null, get_lock("a", 10)); select @result /* must be zero either way */; @result 0 +delete from t1; +delete from t2; +insert into t1 values (1,1),(2,2); +begin; +update t1 set b=11 where a=2; +update t1 set b=b+10; +kill query ID; +rollback; +ERROR 70100: Query execution was interrupted +select * from t1 /* must be the same as before (1,1),(2,2) */; +a b +1 1 +2 2 +begin; +delete from t1 where a=2; +delete from t1 where a=2; +kill query ID; +rollback; +ERROR 70100: Query execution was interrupted +select * from t1 /* must be the same as before (1,1),(2,2) */; +a b +1 1 +2 2 +drop table if exists t4; +create table t4 (a int, b int) engine=innodb; +insert into t4 values (3, 3); +begin; +insert into t1 values (3, 3); +begin; +insert into t1 select * from t4 for update; +kill query ID; +rollback; +ERROR 70100: Query execution was interrupted +rollback; +select * from t1 /* must be the same as before (1,1),(2,2) */; +a b +1 1 +2 2 +drop table t4; +create function bug27563(n int) +RETURNS int(11) +DETERMINISTIC +begin +if n > 1 then +select get_lock("a", 10) into @a; +end if; +return n; +end| +delete from t2; +insert into t2 values (1,1), (2,2); +reset master; +select get_lock("a", 20); +get_lock("a", 20) +1 +update t2 set b=b + bug27563(b) order by a; +kill query ID; +ERROR 70100: Query execution was interrupted +select * from t2 /* must be (1,2), (2,2) */; +a b +1 2 +2 2 +show master status /* must have the update event more to FD */; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 211 +select +(@a:=load_file("MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) +is not null; +(@a:=load_file("MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) +is not null +1 +select 0 /* must return 0 to mean the killed query is in */; +0 +0 +select RELEASE_LOCK("a"); +RELEASE_LOCK("a") +1 +delete from t2; +insert into t2 values (1,1), (2,2); +reset master; +select get_lock("a", 20); +get_lock("a", 20) +1 +delete from t2 where a=1 or a=bug27563(2) order by a; +kill query ID; +ERROR 70100: Query execution was interrupted +select * from t2 /* must be (1,2), (2,2) */; +a b +1 1 +2 2 +show master status /* must have the update event more to FD */; +File Position Binlog_Do_DB Binlog_Ignore_DB +master-bin.000001 98 +select +(@a:=load_file("MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) +is not null; +(@a:=load_file("MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) +is not null +1 +select 0 /* must return 0 to mean the killed query is in */; +0 +0 +select RELEASE_LOCK("a"); +RELEASE_LOCK("a") +1 +drop function bug27563; drop table t1,t2,t3; +end of the tests diff --git a/mysql-test/suite/binlog/t/binlog_killed.test b/mysql-test/suite/binlog/t/binlog_killed.test index 6c0b4b46a4e..89028d1c6b7 100644 --- a/mysql-test/suite/binlog/t/binlog_killed.test +++ b/mysql-test/suite/binlog/t/binlog_killed.test @@ -55,194 +55,239 @@ enable_result_log; select @result /* must be zero either way */; -# the functions are either *insensitive* to killing or killing can cause -# strange problmes with the error propagation out of SF's stack -# Bug#27563, Bug#27565, BUG#24971 + +--remove_file $MYSQLTEST_VARDIR/tmp/kill_query_calling_sp.binlog + # -# TODO: use if's block as regression test for the bugs or remove +# bug#27571 asynchronous setting mysql_`query`::error and Query_log_e::error_code # -if (0) -{ + +# checking that killing inside of select loops is safe as before +# killing after the loop can be only simulated - another test + +delete from t1; +delete from t2; +insert into t1 values (1,1),(2,2); +let $ID= `select connection_id()`; + +# +# simple update +# +connection con1; +begin; update t1 set b=11 where a=2; + +connection con2; +send update t1 set b=b+10; + +connection con1; +--replace_result $ID ID +eval kill query $ID; +rollback; + +connection con2; +--error ER_QUERY_INTERRUPTED +reap; +select * from t1 /* must be the same as before (1,1),(2,2) */; + +# +# multi update +# commented out as Bug #31807 multi-update,delete killing does not report with ER_QUERY_INTERRUPTED +# in the way +# +# connection con1; +# begin; update t1 set b=b+10; + +# connection con2; +# send update t1 as t_1,t1 as t_2 set t_1.b=11 where t_2.a=2; + +# connection con1; +# --replace_result $ID ID +# eval kill query $ID; +# rollback; + +# disable_abort_on_error; + +# connection con2; +# --error HY000,ER_QUERY_INTERRUPTED +# reap; +# select * from t1 /* must be the same as before (1,1),(2,2) */; + +# enable_abort_on_error; +# +# simple delete +# +connection con1; +begin; delete from t1 where a=2; + +connection con2; +send delete from t1 where a=2; + +connection con1; +--replace_result $ID ID +eval kill query $ID; +rollback; + +connection con2; +--error ER_QUERY_INTERRUPTED +reap; +select * from t1 /* must be the same as before (1,1),(2,2) */; + +# +# multi delete +# the same as for multi-update +# +# connection con1; +# begin; delete from t1 where a=2; + +# connection con2; +# send delete t1 from t1 where t1.a=2; + +# connection con1; +# --replace_result $ID ID +# eval kill query $ID; +# rollback; + +# connection con2; +# --error 0,ER_QUERY_INTERRUPTED +# reap; +# select * from t1 /* must be the same as before (1,1),(2,2) */; +# +# insert select +# +connection con1; +--disable_warnings +drop table if exists t4; +--enable_warnings +create table t4 (a int, b int) engine=innodb; +insert into t4 values (3, 3); +begin; insert into t1 values (3, 3); + +connection con2; +begin; +send insert into t1 select * from t4 for update; + +connection con1; +--replace_result $ID ID +eval kill query $ID; +rollback; + +connection con2; +--error ER_QUERY_INTERRUPTED +reap; +rollback; +select * from t1 /* must be the same as before (1,1),(2,2) */; + +drop table t4; # cleanup for the sub-case + +### +## non-ta table case: killing must be recorded in binlog +### delimiter |; -create function bug27563() +create function bug27563(n int) RETURNS int(11) DETERMINISTIC begin - select get_lock("a", 10) into @a; - return 1; + if n > 1 then + select get_lock("a", 10) into @a; + end if; + return n; end| delimiter ;| -# the function is sensitive to killing requiring innodb though with wrong client error -# TO FIX in BUG#27565; TODO: remove --error 1105 afterwards -delimiter |; -create function bug27565() -RETURNS int(11) -DETERMINISTIC -begin - select a from t1 where a=1 into @a for update; - return 1; -end| -delimiter ;| +# +# update +# +delete from t2; +insert into t2 values (1,1), (2,2); reset master; - - -### ta table case: killing causes rollback - -# A. autocommit ON connection con1; select get_lock("a", 20); connection con2; let $ID= `select connection_id()`; -send insert into t1 values (bug27563(),1); +send update t2 set b=b + bug27563(b) order by a; connection con1; +--replace_result $ID ID eval kill query $ID; connection con2; -# todo (re-record test): after bugs 27563,27565 got fixed affected rows will report zero ---enable_info -# todo: remove 0 return after fixing Bug#27563 ---error 0,ER_QUERY_INTERRUPTED -reap; ### pb: wrong error ---disable_info -###--replace_column 2 # 5 # -### show binlog events from 98 /* nothing in binlog unless Bug#27563 */; -show master status /* must be only FD event unless Bug#27563 */; -select count(*) from t1 /* must be zero unless Bug#27563 */; - -# M. multi-statement-ta -connection con2; -let $ID= `select connection_id()`; -begin; -send insert into t1 values (bug27563(),1); - -connection con1; -eval kill query $ID; -connection con2; -# todo (re-record test): after bugs 27563,27565 got fixed affected rows will report zero ---enable_info -# todo: remove 0 return after fixing Bug#27563 ---error 0,ER_QUERY_INTERRUPTED +--error ER_QUERY_INTERRUPTED reap; ---disable_info -select count(*) from t1 /* must be zero unless Bug#27563 */; -commit; +select * from t2 /* must be (1,2), (2,2) */; +show master status /* must have the update event more to FD */; +# a proof the query is binlogged with an error -### non-ta table case: killing must be recorded in binlog - -reset master; - -connection con2; -let $ID= `select connection_id()`; -send insert into t2 values (bug27563(),1); - -connection con1; -eval kill query $ID; - -connection con2; -# todo: remove 0 return after fixing Bug#27563 ---error 0,ER_QUERY_INTERRUPTED -reap; -select count(*) from t2 /* must be one */; -#show binlog events from 98 /* must have the insert on non-ta table */; -show master status /* must have the insert event more to FD */; -# the value of the error flag of KILLED_QUERY is tested further +--exec $MYSQL_BINLOG --start-position=98 $MYSQLTEST_VARDIR/log/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval select +(@a:=load_file("$MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) +is not null; +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +let $error_code= `select @a like "%#%error_code=0%" /* must return 0*/`; +eval select $error_code /* must return 0 to mean the killed query is in */; +# cleanup for the sub-case connection con1; select RELEASE_LOCK("a"); +--remove_file $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog -### test with effective killing of SF() - -delete from t1; -delete from t2; -insert into t1 values (1,1); -insert into t2 values (1,1); - -# -# Bug#27565 -# test where KILL is propagated as error to the top level -# still another bug with the error message to the user -# todo: fix reexecute the result file after fixing -# -begin; update t1 set b=0 where a=1; - -connection con2; -let $ID= `select connection_id()`; -send update t2 set b=bug27565()-1 where a=1; - -connection con1; -eval kill query $ID; -commit; - -connection con2; -# todo: fix Bug #27565 killed query of SF() is not reported correctly and -# remove 1105 (wrong) -#--error ER_QUERY_INTERRUPTED ---error 1105,ER_QUERY_INTERRUPTED -reap; ### pb: wrong error -select * from t1 /* must be: (1,0) */; -select * from t2 /* must be as before: (1,1) */; - -## bug#22725 with effective and propagating killing # -# top-level ta-table -connection con1; -delete from t3; -reset master; -begin; update t1 set b=0 where a=1; +# delete +# -connection con2; -let $ID= `select connection_id()`; -# the query won't perform completely since the function gets interrupted -send insert into t3 values (0,0),(1,bug27565()); - -connection con1; -eval kill query $ID; -rollback; - -connection con2; -# todo: fix Bug #27565 killed query of SF() is not reported correctly and -# remove 1105 (wrong) -#--error ER_QUERY_INTERRUPTED ---error 1105,ER_QUERY_INTERRUPTED -reap; ### pb: wrong error -select count(*) from t3 /* must be zero */; -show master status /* nothing in binlog */; - -# top-level non-ta-table -connection con1; delete from t2; +insert into t2 values (1,1), (2,2); reset master; -begin; update t1 set b=0 where a=1; +connection con1; +select get_lock("a", 20); connection con2; let $ID= `select connection_id()`; -# the query won't perform completely since the function gets intrurrupted -send insert into t2 values (0,0),(1,bug27565()) /* non-ta t2 */; +send delete from t2 where a=1 or a=bug27563(2) order by a; connection con1; +--replace_result $ID ID eval kill query $ID; -rollback; connection con2; -# todo: fix Bug #27565 killed query of SF() is not reported correctly and -# remove 1105 (wrong) -#--error ER_QUERY_INTERRUPTED ---error 1105,ER_QUERY_INTERRUPTED -reap; ### pb: wrong error +--error ER_QUERY_INTERRUPTED +reap; +select * from t2 /* must be (1,2), (2,2) */; +show master status /* must have the update event more to FD */; -select count(*) from t2 /* count must be one */; -show master status /* insert into non-ta must be in binlog */; +# a proof the query is binlogged with an error + +--exec $MYSQL_BINLOG --start-position=98 $MYSQLTEST_VARDIR/log/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval select +(@a:=load_file("$MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) +is not null; +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +let $error_code= `select @a like "%#%error_code=0%" /* must return 0*/`; +eval select $error_code /* must return 0 to mean the killed query is in */; + +# cleanup for the sub-case +connection con1; +select RELEASE_LOCK("a"); +--remove_file $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog + +# +# load data - see simulation tests +# + + +# bug#27571 cleanup drop function bug27563; -drop function bug27565; -} -system rm $MYSQLTEST_VARDIR/tmp/kill_query_calling_sp.binlog ; + +# +# common cleanup +# drop table t1,t2,t3; +--echo end of the tests diff --git a/mysql-test/suite/rpl/t/rpl_sp_effects.test b/mysql-test/suite/rpl/t/rpl_sp_effects.test index 027bfd69f36..c1092e3260f 100644 --- a/mysql-test/suite/rpl/t/rpl_sp_effects.test +++ b/mysql-test/suite/rpl/t/rpl_sp_effects.test @@ -201,6 +201,10 @@ sync_slave_with_master; connection slave; SELECT 'slave', a FROM t1 ORDER BY a; +# +# cleanup +# + connection master; drop table t1; drop function f1; @@ -208,4 +212,50 @@ drop function f2; drop procedure p1; sync_slave_with_master; +# +# bug#26199 Replication Failure on Slave when using stored procs +# with bit-type parameters + +connection master; + +create table t2 (b BIT(7)); +delimiter //; +create procedure sp_bug26199(bitvalue BIT(7)) +begin + insert into t2 set b = bitvalue; +end // + +create function sf_bug26199(b BIT(7)) returns int +begin + insert into t2 values(b); + return 0; +end// + +DELIMITER ;// + + + +call sp_bug26199(b'1110'); +call sp_bug26199('\0'); +select sf_bug26199(b'1111111'); +select sf_bug26199(b'101111111'); +select sf_bug26199('\''); +select hex(b) from t2; + +sync_slave_with_master; +#connection slave; +select hex(b) from t2; + +# +# cleanup bug#26199 +# +connection master; +drop table t2; +drop procedure sp_bug26199; +drop function sf_bug26199; + +sync_slave_with_master; + SET GLOBAL log_bin_trust_function_creators = 0; + +--echo end of the tests diff --git a/mysql-test/t/binlog_killed_bug27571-master.opt b/mysql-test/t/binlog_killed_bug27571-master.opt new file mode 100644 index 00000000000..d269cf246d5 --- /dev/null +++ b/mysql-test/t/binlog_killed_bug27571-master.opt @@ -0,0 +1 @@ +--loose-debug=d,stop_after_row_loop_done diff --git a/mysql-test/t/binlog_killed_bug27571.test b/mysql-test/t/binlog_killed_bug27571.test new file mode 100644 index 00000000000..6fa3c6d256f --- /dev/null +++ b/mysql-test/t/binlog_killed_bug27571.test @@ -0,0 +1,68 @@ +--source include/have_innodb.inc +--source include/not_embedded.inc +--source include/have_log_bin.inc + +# +# bug#27571 asynchronous setting mysql_`query`::error and Query_log_e::error_code +# +# Checking that if killing happens inbetween of the end of rows loop and +# recording into binlog that will not lead to recording any error incl +# the killed error. +# + +connect (looser, localhost, root,,); +connect (killer, localhost, root,,); + +create table t1 (a int auto_increment, b int, PRIMARY KEY (a)) ENGINE=InnoDB; + +delete from t1; +insert into t1 values (1,1),(2,2); +reset master; + +connection looser; +let $ID= `select connection_id()`; +send update t1 set b=11 where a=2; + +connection killer; +sleep 1; # let 1 second for the update to get to the sleeping point +--replace_result $ID ID +eval kill query $ID; + +connection looser; +--error 0 # zero even though the query must be got killed while it was sleepin for 5 secs +reap; + +# +# this is another possible artifact. The killed error was not caught +# as that is logical as killing was not effective: +# data are ok and well as binlog event is without killed error (further). +# The reason of the following `show error' is to prove that +# killing simulation was effective +# +show errors; + +connection killer; + +# nothing is rolled back + +select * from t1 where a=2 /* must be 11 */; + +# a proof the query is binlogged with an error + +--exec $MYSQL_BINLOG --start-position=98 $MYSQLTEST_VARDIR/log/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval select +(@a:=load_file("$MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) +is not null; +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +let $error_code= `select @a like "%#%error_code=0%"`; + +eval select $error_code /* must return 1*/; + +# +# cleanup +# + +drop table t1; + +--echo end of the tests diff --git a/mysql-test/t/binlog_killed_simulate-master.opt b/mysql-test/t/binlog_killed_simulate-master.opt new file mode 100644 index 00000000000..90c70ecee29 --- /dev/null +++ b/mysql-test/t/binlog_killed_simulate-master.opt @@ -0,0 +1 @@ +--loose-debug=d,simulate_kill_bug27571 diff --git a/mysql-test/t/binlog_killed_simulate.test b/mysql-test/t/binlog_killed_simulate.test new file mode 100644 index 00000000000..d6234d1bfd7 --- /dev/null +++ b/mysql-test/t/binlog_killed_simulate.test @@ -0,0 +1,65 @@ +# +# bug#27571 asynchronous setting mysql_$query()'s local error and +# Query_log_event::error_code +# + +--disable_warnings +drop table if exists t1,t2; +--enable_warnings + +# +# Checking that killing upon successful row-loop does not affect binlogging +# + +create table t1 (a int) engine=MyISAM; +insert into t1 set a=1; +reset master; + +update t1 set a=2 /* will be "killed" after work has been done */; + +# a proof the query is binlogged with no error + +--exec $MYSQL_BINLOG --start-position=98 $MYSQLTEST_VARDIR/log/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval select +(@a:=load_file("$MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) +is not null; +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +let $error_code= `select @a like "%#%error_code=0%" /* must return 1 */`; +eval select $error_code /* must return 1 as query completed before got killed*/; + +# cleanup for the sub-case +system rm $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog; + + +# +# Checking that killing inside of row-loop for LOAD DATA into +# non-transactional table affects binlogging +# + +create table t2 (a int, b int) ENGINE=MyISAM; +reset master; +--error ER_QUERY_INTERRUPTED +load data infile '../std_data_ln/rpl_loaddata.dat' into table t2 /* will be "killed" in the middle */; + + +# a proof the query is binlogged with an error + +source include/show_binlog_events.inc; + +--exec $MYSQL_BINLOG --start-position=98 $MYSQLTEST_VARDIR/log/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog +--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR +eval select +(@a:=load_file("$MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog")) +is not null; +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +let $error_code= `select @a like "%#%error_code=0%" /* must return 0*/`; +eval select $error_code /* must return 0 to mean the killed query is in */; + +# cleanup for the sub-case +system rm $MYSQLTEST_VARDIR/tmp/binlog_killed_bug27571.binlog; + + +drop table t1,t2; + +--echo end of the tests diff --git a/mysql-test/t/rpl_slave_skip-slave.opt b/mysql-test/t/rpl_slave_skip-slave.opt new file mode 100644 index 00000000000..627becdbfb5 --- /dev/null +++ b/mysql-test/t/rpl_slave_skip-slave.opt @@ -0,0 +1 @@ +--innodb diff --git a/mysql-test/t/rpl_slave_skip.test b/mysql-test/t/rpl_slave_skip.test new file mode 100644 index 00000000000..04aafc51129 --- /dev/null +++ b/mysql-test/t/rpl_slave_skip.test @@ -0,0 +1,203 @@ +source include/have_innodb.inc; +source include/master-slave.inc; + +# This test is for checking that the use of SQL_SLAVE_SKIP_COUNTER +# behaves as expected, i.e., that it is guaranteed to skip an entire +# group and not start executing in the middle of a transaction. + +# We are checking the correct behaviour when using both a +# transactional and non-transactional table. The non-transactional +# table comes into play when rolling back a transaction containing a +# write to this table. In that case, the transaction should still be +# written to the binary log, and the slave will apply it and then roll +# it back to get the non-transactional change into the table. + +--echo **** On Master **** +CREATE TABLE t1 (a INT, b SET('master','slave')) ENGINE=INNODB; +CREATE TABLE t2 (a INT, b SET('master','slave')) ENGINE=MYISAM; + +--echo ==== Skipping normal transactions ==== + +--echo **** On Slave **** +sync_slave_with_master; +STOP SLAVE; +source include/wait_for_slave_to_stop.inc; + +--echo **** On Master **** +connection master; + +BEGIN; +INSERT INTO t1 VALUES (1, 'master'); +INSERT INTO t1 VALUES (2, 'master'); +INSERT INTO t1 VALUES (3, 'master'); +COMMIT; + +BEGIN; +INSERT INTO t1 VALUES (4, 'master,slave'); +INSERT INTO t1 VALUES (5, 'master,slave'); +INSERT INTO t1 VALUES (6, 'master,slave'); +COMMIT; + +save_master_pos; + +SELECT * FROM t1 ORDER BY a; + +# This will skip a begin event and the first INSERT of the +# transaction, and it should keep skipping until it has reached the +# transaction terminator. + +--echo **** On Slave **** +connection slave; +SET GLOBAL SQL_SLAVE_SKIP_COUNTER=2; +START SLAVE; +source include/wait_for_slave_to_start.inc; +sync_with_master; +SELECT * FROM t1 ORDER BY a; + +--echo **** On Master **** +connection master; +DELETE FROM t1; +sync_slave_with_master; + +--echo ==== Skipping two normal transactions ==== + +--echo **** On Slave **** +connection slave; +STOP SLAVE; +source include/wait_for_slave_to_stop.inc; + +--echo **** On Master **** +connection master; + +BEGIN; +INSERT INTO t1 VALUES (1, 'master'); +INSERT INTO t1 VALUES (2, 'master'); +INSERT INTO t1 VALUES (3, 'master'); +COMMIT; + +BEGIN; +INSERT INTO t1 VALUES (4, 'master'); +INSERT INTO t1 VALUES (5, 'master'); +INSERT INTO t1 VALUES (6, 'master'); +COMMIT; + +BEGIN; +INSERT INTO t1 VALUES (7, 'master,slave'); +INSERT INTO t1 VALUES (8, 'master,slave'); +INSERT INTO t1 VALUES (9, 'master,slave'); +COMMIT; + +save_master_pos; + +SELECT * FROM t1 ORDER BY a; + +# This will skip a begin event and the first INSERT of the +# transaction, and it should keep skipping until it has reached the +# transaction terminator. + +--echo **** On Slave **** +connection slave; +SET GLOBAL SQL_SLAVE_SKIP_COUNTER=8; +START SLAVE; +source include/wait_for_slave_to_start.inc; +sync_with_master; +SELECT * FROM t1 ORDER BY a; + +--echo **** On Master **** +connection master; +DELETE FROM t1; +sync_slave_with_master; + +--echo ==== Skipping without autocommit ==== + +# Testing without using autocommit instead. It should still write a +# BEGIN event, so the behaviour should be the same + +--echo **** On Slave **** +connection slave; +STOP SLAVE; +source include/wait_for_slave_to_stop.inc; + +--echo **** On Master **** +connection master; +SET AUTOCOMMIT=0; + +INSERT INTO t1 VALUES (1, 'master'); +INSERT INTO t1 VALUES (2, 'master'); +INSERT INTO t1 VALUES (3, 'master'); +COMMIT; + +INSERT INTO t1 VALUES (4, 'master,slave'); +INSERT INTO t1 VALUES (5, 'master,slave'); +INSERT INTO t1 VALUES (6, 'master,slave'); +COMMIT; + +save_master_pos; + +SELECT * FROM t1 ORDER BY a; + +# This will skip a begin event and the first INSERT of the +# transaction, and it should keep skipping until it has reached the +# transaction terminator. + +--echo **** On Slave **** +connection slave; +SET GLOBAL SQL_SLAVE_SKIP_COUNTER=2; +START SLAVE; +source include/wait_for_slave_to_start.inc; +sync_with_master; +SELECT * FROM t1 ORDER BY a; + +# Testing with a non-transactional table in the transaction. This will +# log a ROLLBACK as a transaction terminator, which is a normal Query +# log event. + +--echo ==== Rollback of transaction with non-transactional change ==== + +--echo **** On Master **** +connection master; +DELETE FROM t1; +SET AUTOCOMMIT=1; + +--echo **** On Slave **** +sync_slave_with_master; +STOP SLAVE; +source include/wait_for_slave_to_stop.inc; + +--echo **** On Master **** +connection master; +disable_warnings; +BEGIN; +INSERT INTO t1 VALUES (1, ''); +INSERT INTO t2 VALUES (2, 'master'); +INSERT INTO t1 VALUES (3, ''); +ROLLBACK; + +BEGIN; +INSERT INTO t1 VALUES (4, ''); +INSERT INTO t2 VALUES (5, 'master,slave'); +INSERT INTO t1 VALUES (6, ''); +ROLLBACK; +enable_warnings; + +save_master_pos; + +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +--echo **** On Slave **** +connection slave; +SET GLOBAL SQL_SLAVE_SKIP_COUNTER=2; +START SLAVE; +source include/wait_for_slave_to_start.inc; +sync_with_master; + +SELECT * FROM t1 ORDER BY a; +SELECT * FROM t2 ORDER BY a; + +--echo ==== Cleanup ==== + +--echo **** On Master **** +connection master; +DROP TABLE t1, t2; +sync_slave_with_master; diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index e27127a5804..47649006ccf 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -4410,7 +4410,7 @@ Item_func_regex::regcomp(bool send_error) res= &conv; } - if ((error= my_regcomp(&preg, res->c_ptr(), + if ((error= my_regcomp(&preg, res->c_ptr_safe(), regex_lib_flags, regex_lib_charset))) { if (send_error) diff --git a/sql/log_event.cc b/sql/log_event.cc index 53c877f28c0..e300de353b0 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -5472,12 +5472,13 @@ Begin_load_query_log_event::do_shall_skip(Relay_log_info *rli) #ifndef MYSQL_CLIENT Execute_load_query_log_event:: Execute_load_query_log_event(THD *thd_arg, const char* query_arg, - ulong query_length_arg, uint fn_pos_start_arg, - uint fn_pos_end_arg, - enum_load_dup_handling dup_handling_arg, - bool using_trans, bool suppress_use): + ulong query_length_arg, uint fn_pos_start_arg, + uint fn_pos_end_arg, + enum_load_dup_handling dup_handling_arg, + bool using_trans, bool suppress_use, + THD::killed_state killed_err_arg): Query_log_event(thd_arg, query_arg, query_length_arg, using_trans, - suppress_use), + suppress_use, killed_err_arg), file_id(thd_arg->file_id), fn_pos_start(fn_pos_start_arg), fn_pos_end(fn_pos_end_arg), dup_handling(dup_handling_arg) { diff --git a/sql/log_event.h b/sql/log_event.h index ab2424d8466..4bd496af2a4 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -2744,10 +2744,12 @@ public: #ifndef MYSQL_CLIENT Execute_load_query_log_event(THD* thd, const char* query_arg, - ulong query_length, uint fn_pos_start_arg, - uint fn_pos_end_arg, - enum_load_dup_handling dup_handling_arg, - bool using_trans, bool suppress_use); + ulong query_length, uint fn_pos_start_arg, + uint fn_pos_end_arg, + enum_load_dup_handling dup_handling_arg, + bool using_trans, bool suppress_use, + THD::killed_state + killed_err_arg= THD::KILLED_NO_VALUE); #ifdef HAVE_REPLICATION void pack_info(Protocol* protocol); #endif /* HAVE_REPLICATION */ diff --git a/sql/slave.cc b/sql/slave.cc index 3b80a58676b..a3ac0c2f34f 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1807,6 +1807,58 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) /* */ +<<<<<<< gca sql/slave.cc 1.241.1.61 + DBUG_PRINT("info",("type_code=%d, server_id=%d",type_code,ev->server_id)); + + if ((ev->server_id == (uint32) ::server_id && + !replicate_same_server_id && + type_code != FORMAT_DESCRIPTION_EVENT) || + (rli->slave_skip_counter && + type_code != ROTATE_EVENT && type_code != STOP_EVENT && + type_code != START_EVENT_V3 && type_code!= FORMAT_DESCRIPTION_EVENT)) + { + DBUG_PRINT("info", ("event skipped")); + if (thd->options & OPTION_BEGIN) + rli->inc_event_relay_log_pos(); + else + { + rli->inc_group_relay_log_pos((type_code == ROTATE_EVENT || + type_code == STOP_EVENT || + type_code == FORMAT_DESCRIPTION_EVENT) ? + LL(0) : ev->log_pos, + 1/* skip lock*/); + flush_relay_log_info(rli); + } + + /* + Protect against common user error of setting the counter to 1 + instead of 2 while recovering from an insert which used auto_increment, + rand or user var. + */ + if (rli->slave_skip_counter && + !((type_code == INTVAR_EVENT || + type_code == RAND_EVENT || + type_code == USER_VAR_EVENT) && + rli->slave_skip_counter == 1) && + /* + The events from ourselves which have something to do with the relay + log itself must be skipped, true, but they mustn't decrement + rli->slave_skip_counter, because the user is supposed to not see + these events (they are not in the master's binlog) and if we + decremented, START SLAVE would for example decrement when it sees + the Rotate, so the event which the user probably wanted to skip + would not be skipped. + */ + !(ev->server_id == (uint32) ::server_id && + (type_code == ROTATE_EVENT || type_code == STOP_EVENT || + type_code == START_EVENT_V3 || type_code == FORMAT_DESCRIPTION_EVENT))) + --rli->slave_skip_counter; + pthread_mutex_unlock(&rli->data_lock); + delete ev; + return 0; // avoid infinite update loops + } + pthread_mutex_unlock(&rli->data_lock); +<<<<<<< local sql/slave.cc 1.321 DBUG_PRINT("exec_event",("%s(type_code: %d; server_id: %d)", ev->get_type_str(), type_code, ev->server_id)); DBUG_PRINT("info", ("thd->options: %s%s; rli->last_event_start_time: %lu", @@ -1839,6 +1891,108 @@ static int exec_relay_log_event(THD* thd, Relay_log_info* rli) log (remember that now the relay log starts with its Format_desc, has a Rotate etc). */ +<<<<<<< remote sql/slave.cc 1.241.1.62 + DBUG_PRINT("info",("type_code: %d; server_id: %d; slave_skip_counter: %d", + type_code, ev->server_id, rli->slave_skip_counter)); + + /* + If the slave skip counter is positive, we still need to set the + OPTION_BEGIN flag correctly and not skip the log events that + start or end a transaction. If we do this, the slave will not + notice that it is inside a transaction, and happily start + executing from inside the transaction. + + Note that the code block below is strictly 5.0. + */ +#if MYSQL_VERSION_ID < 50100 + if (unlikely(rli->slave_skip_counter > 0)) + { + switch (type_code) + { + case QUERY_EVENT: + { + Query_log_event* const qev= (Query_log_event*) ev; + DBUG_PRINT("info", ("QUERY_EVENT { query: '%s', q_len: %u }", + qev->query, qev->q_len)); + if (memcmp("BEGIN", qev->query, qev->q_len+1) == 0) + thd->options|= OPTION_BEGIN; + else if (memcmp("COMMIT", qev->query, qev->q_len+1) == 0 || + memcmp("ROLLBACK", qev->query, qev->q_len+1) == 0) + thd->options&= ~OPTION_BEGIN; + } + break; + + case XID_EVENT: + DBUG_PRINT("info", ("XID_EVENT")); + thd->options&= ~OPTION_BEGIN; + break; + } + } +#endif + + if ((ev->server_id == (uint32) ::server_id && + !replicate_same_server_id && + type_code != FORMAT_DESCRIPTION_EVENT) || + (rli->slave_skip_counter && + type_code != ROTATE_EVENT && type_code != STOP_EVENT && + type_code != START_EVENT_V3 && type_code!= FORMAT_DESCRIPTION_EVENT)) + { + DBUG_PRINT("info", ("event skipped")); + if (thd->options & OPTION_BEGIN) + rli->inc_event_relay_log_pos(); + else + { + rli->inc_group_relay_log_pos((type_code == ROTATE_EVENT || + type_code == STOP_EVENT || + type_code == FORMAT_DESCRIPTION_EVENT) ? + LL(0) : ev->log_pos, + 1/* skip lock*/); + flush_relay_log_info(rli); + } + + DBUG_PRINT("info", ("thd->options: %s", + (thd->options & OPTION_BEGIN) ? "OPTION_BEGIN" : "")) + + /* + Protect against common user error of setting the counter to 1 + instead of 2 while recovering from an insert which used auto_increment, + rand or user var. + */ + if (rli->slave_skip_counter && + !((type_code == INTVAR_EVENT || + type_code == RAND_EVENT || + type_code == USER_VAR_EVENT) && + rli->slave_skip_counter == 1) && +#if MYSQL_VERSION_ID < 50100 + /* + Decrease the slave skip counter only if we are not inside + a transaction or the slave skip counter is more than + 1. The slave skip counter will be decreased from 1 to 0 + when reaching the final ROLLBACK, COMMIT, or XID_EVENT. + */ + (!(thd->options & OPTION_BEGIN) || rli->slave_skip_counter > 1) && +#endif + /* + The events from ourselves which have something to do with the relay + log itself must be skipped, true, but they mustn't decrement + rli->slave_skip_counter, because the user is supposed to not see + these events (they are not in the master's binlog) and if we + decremented, START SLAVE would for example decrement when it sees + the Rotate, so the event which the user probably wanted to skip + would not be skipped. + */ + !(ev->server_id == (uint32) ::server_id && + (type_code == ROTATE_EVENT || + type_code == STOP_EVENT || + type_code == START_EVENT_V3 || + type_code == FORMAT_DESCRIPTION_EVENT))) + --rli->slave_skip_counter; + pthread_mutex_unlock(&rli->data_lock); + delete ev; + return 0; // avoid infinite update loops + } + pthread_mutex_unlock(&rli->data_lock); +>>>>>>> thd->server_id = ev->server_id; // use the original server id for logging thd->set_time(); // time the query diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 093544cdd0a..5f92dd3b3a5 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -102,8 +102,9 @@ sp_get_item_value(THD *thd, Item *item, String *str) case REAL_RESULT: case INT_RESULT: case DECIMAL_RESULT: - return item->val_str(str); - + if (item->field_type() != MYSQL_TYPE_BIT) + return item->val_str(str); + else {/* Bit type is handled as binary string */} case STRING_RESULT: { String *result= item->val_str(str); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 0746a261de1..33d19852371 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -38,6 +38,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, ha_rows deleted= 0; uint usable_index= MAX_KEY; SELECT_LEX *select_lex= &thd->lex->select_lex; + THD::killed_state killed_status= THD::NOT_KILLED; DBUG_ENTER("mysql_delete"); if (open_and_lock_tables(thd, table_list)) @@ -300,8 +301,8 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, else table->file->unlock_row(); // Row failed selection, release lock on it } - if (thd->killed && !error) - error= 1; // Aborted + killed_status= thd->killed; + error= (killed_status == THD::NOT_KILLED)? error : 1; if (will_batch && (loc_error= table->file->end_bulk_delete())) { if (error != 1) @@ -351,6 +352,11 @@ cleanup: { if (error < 0) thd->clear_error(); +<<<<<<< gca sql/sql_delete.cc 1.144.1.57 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_table, FALSE); + if (mysql_bin_log.write(&qinfo) && transactional_table) +<<<<<<< local sql/sql_delete.cc 1.230 /* [binlog]: If 'handler::delete_all_rows()' was called and the @@ -364,6 +370,11 @@ cleanup: if (log_result && transactional_table) { +<<<<<<< remote sql/sql_delete.cc 1.144.1.58 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_table, FALSE, killed_status); + if (mysql_bin_log.write(&qinfo) && transactional_table) +>>>>>>> error=1; } } @@ -764,7 +775,8 @@ void multi_delete::send_error(uint errcode,const char *err) } thd->transaction.all.modified_non_trans_table= true; } - DBUG_ASSERT(!normal_tables || !deleted || thd->transaction.stmt.modified_non_trans_table); + DBUG_ASSERT(!normal_tables || !deleted || + thd->transaction.stmt.modified_non_trans_table); DBUG_VOID_RETURN; } @@ -862,6 +874,7 @@ int multi_delete::do_deletes() bool multi_delete::send_eof() { + THD::killed_state killed_status= THD::NOT_KILLED; thd->proc_info="deleting from reference tables"; /* Does deletes for the last n - 1 tables, returns 0 if ok */ @@ -869,7 +882,7 @@ bool multi_delete::send_eof() /* compute a total error to know if something failed */ local_error= local_error || error; - + killed_status= (local_error == 0)? THD::NOT_KILLED : thd->killed; /* reset used flags */ thd->proc_info="end"; @@ -881,18 +894,29 @@ bool multi_delete::send_eof() { query_cache_invalidate3(thd, delete_tables, 1); } - DBUG_ASSERT(!normal_tables || !deleted || thd->transaction.stmt.modified_non_trans_table); + DBUG_ASSERT(!normal_tables || !deleted || + thd->transaction.stmt.modified_non_trans_table); if ((local_error == 0) || thd->transaction.stmt.modified_non_trans_table) { if (mysql_bin_log.is_open()) { if (local_error == 0) thd->clear_error(); +<<<<<<< gca sql/sql_delete.cc 1.144.1.57 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_tables, FALSE); + if (mysql_bin_log.write(&qinfo) && !normal_tables) +<<<<<<< local sql/sql_delete.cc 1.230 if (thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query, thd->query_length, transactional_tables, FALSE) && !normal_tables) { +<<<<<<< remote sql/sql_delete.cc 1.144.1.58 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_tables, FALSE, killed_status); + if (mysql_bin_log.write(&qinfo) && !normal_tables) +>>>>>>> local_error=1; // Log write failed: roll back the SQL statement } } diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 7e438ac3a7c..a631a82be1e 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -3084,10 +3084,19 @@ void select_insert::send_error(uint errcode,const char *err) bool select_insert::send_eof() { +<<<<<<< gca sql/sql_insert.cc 1.146.1.105 + int error, error2; + bool changed, transactional_table= table->file->has_transactions(); +<<<<<<< local sql/sql_insert.cc 1.300 int error; bool const trans_table= table->file->has_transactions(); ulonglong id; bool changed; +<<<<<<< remote sql/sql_insert.cc 1.146.1.106 + int error, error2; + bool changed, transactional_table= table->file->has_transactions(); + THD::killed_state killed_status= thd->killed; +>>>>>>> DBUG_ENTER("select_insert::send_eof"); DBUG_PRINT("enter", ("trans_table=%d, table_type='%s'", trans_table, table->file->table_type())); @@ -3120,6 +3129,14 @@ bool select_insert::send_eof() { if (!error) thd->clear_error(); +<<<<<<< gca sql/sql_insert.cc 1.146.1.105 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_table, FALSE); + mysql_bin_log.write(&qinfo); + } + if ((error2=ha_autocommit_or_rollback(thd,error)) && ! error) + error=error2; +<<<<<<< local sql/sql_insert.cc 1.300 thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query, thd->query_length, trans_table, FALSE); @@ -3138,6 +3155,14 @@ bool select_insert::send_eof() } table->file->ha_release_auto_increment(); +<<<<<<< remote sql/sql_insert.cc 1.146.1.106 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_table, FALSE, killed_status); + mysql_bin_log.write(&qinfo); + } + if ((error2=ha_autocommit_or_rollback(thd,error)) && ! error) + error=error2; +>>>>>>> if (error) { table->file->print_error(error,MYF(0)); diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 23547349a37..3a4df7ccc9b 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -85,7 +85,8 @@ static int read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, #ifndef EMBEDDED_LIBRARY static bool write_execute_load_query_log_event(THD *thd, bool duplicates, bool ignore, - bool transactional_table); + bool transactional_table, + THD::killed_state killed_status); #endif /* EMBEDDED_LIBRARY */ /* @@ -134,6 +135,7 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, char *tdb= thd->db ? thd->db : db; // Result is never null ulong skip_lines= ex->skip_lines; bool transactional_table; + THD::killed_state killed_status= THD::NOT_KILLED; DBUG_ENTER("mysql_load"); #ifdef EMBEDDED_LIBRARY @@ -403,7 +405,16 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, free_blobs(table); /* if pack_blob was used */ table->copy_blobs=0; thd->count_cuted_fields= CHECK_FIELD_IGNORE; - + /* + simulated killing in the middle of per-row loop + must be effective for binlogging + */ + DBUG_EXECUTE_IF("simulate_kill_bug27571", + { + error=1; + thd->killed= THD::KILL_QUERY; + };); + killed_status= (error == 0)? THD::NOT_KILLED : thd->killed; /* We must invalidate the table in query cache before binlog writing and ha_autocommit_... @@ -419,6 +430,12 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, if (mysql_bin_log.is_open()) { { +<<<<<<< gca sql/sql_load.cc 1.78.1.39 + if (thd->transaction.stmt.modified_non_trans_table) + write_execute_load_query_log_event(thd, handle_duplicates, + ignore, transactional_table); + else +<<<<<<< local sql/sql_load.cc 1.131 /* Make sure last block (the one which caused the error) gets logged. This is needed because otherwise after write of (to @@ -444,6 +461,13 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, read_info.end_io_cache(); /* If the file was not empty, wrote_create_file is true */ if (lf_info.wrote_create_file) +<<<<<<< remote sql/sql_load.cc 1.78.1.40 + if (thd->transaction.stmt.modified_non_trans_table) + write_execute_load_query_log_event(thd, handle_duplicates, + ignore, transactional_table, + killed_status); + else +>>>>>>> { if (thd->transaction.stmt.modified_non_trans_table) write_execute_load_query_log_event(thd, handle_duplicates, @@ -473,6 +497,16 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, if (mysql_bin_log.is_open()) { /* +<<<<<<< gca sql/sql_load.cc 1.78.1.39 + As already explained above, we need to call end_io_cache() or the last + block will be logged only after Execute_load_query_log_event (which is + wrong), when read_info is destroyed. + */ + read_info.end_io_cache(); + if (lf_info.wrote_create_file) + write_execute_load_query_log_event(thd, handle_duplicates, + ignore, transactional_table); +<<<<<<< local sql/sql_load.cc 1.131 We need to do the job that is normally done inside binlog_query() here, which is to ensure that the pending event is written before tables are unlocked and before any other @@ -496,6 +530,17 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list, ignore, transactional_table); } } +<<<<<<< remote sql/sql_load.cc 1.78.1.40 + As already explained above, we need to call end_io_cache() or the last + block will be logged only after Execute_load_query_log_event (which is + wrong), when read_info is destroyed. + */ + read_info.end_io_cache(); + if (lf_info.wrote_create_file) + write_execute_load_query_log_event(thd, handle_duplicates, + ignore, transactional_table, + killed_status); +>>>>>>> } #endif /*!EMBEDDED_LIBRARY*/ if (transactional_table) @@ -523,7 +568,8 @@ err: /* Not a very useful function; just to avoid duplication of code */ static bool write_execute_load_query_log_event(THD *thd, bool duplicates, bool ignore, - bool transactional_table) + bool transactional_table, + THD::killed_state killed_err_arg) { Execute_load_query_log_event e(thd, thd->query, thd->query_length, @@ -531,8 +577,14 @@ static bool write_execute_load_query_log_event(THD *thd, (char*)thd->lex->fname_end - (char*)thd->query, (duplicates == DUP_REPLACE) ? LOAD_DUP_REPLACE : (ignore ? LOAD_DUP_IGNORE : LOAD_DUP_ERROR), +<<<<<<< gca sql/sql_load.cc 1.78.1.39 + transactional_table, FALSE); +<<<<<<< local sql/sql_load.cc 1.131 transactional_table, FALSE); e.flags|= LOG_EVENT_UPDATE_TABLE_MAP_VERSION_F; +<<<<<<< remote sql/sql_load.cc 1.78.1.40 + transactional_table, FALSE, killed_err_arg); +>>>>>>> return mysql_bin_log.write(&e); } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 0b97930efb9..f07437f044f 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -200,9 +200,18 @@ int mysql_update(THD *thd, SQL_SELECT *select; READ_RECORD info; SELECT_LEX *select_lex= &thd->lex->select_lex; +<<<<<<< gca sql/sql_update.cc 1.154.2.70 + bool need_reopen; + List all_fields; +<<<<<<< local sql/sql_update.cc 1.258 bool need_reopen; ulonglong id; List all_fields; +<<<<<<< remote sql/sql_update.cc 1.154.2.71 + bool need_reopen; + List all_fields; + THD::killed_state killed_status= THD::NOT_KILLED; +>>>>>>> DBUG_ENTER("mysql_update"); for ( ; ; ) @@ -713,11 +722,66 @@ int mysql_update(THD *thd, table->file->unlock_row(); thd->row_count++; } +<<<<<<< gca sql/sql_update.cc 1.154.2.70 +<<<<<<< local sql/sql_update.cc 1.258 dup_key_found= 0; +<<<<<<< remote sql/sql_update.cc 1.154.2.71 + /* + Caching the killed status to pass as the arg to query event constuctor; + The cached value can not change whereas the killed status can + (externally) since this point and change of the latter won't affect + binlogging. + It's assumed that if an error was set in combination with an effective + killed status then the error is due to killing. + */ + killed_status= thd->killed; // get the status of the volatile + // simulated killing after the loop must be ineffective for binlogging + DBUG_EXECUTE_IF("simulate_kill_bug27571", + { + thd->killed= THD::KILL_QUERY; + };); + error= (killed_status == THD::NOT_KILLED)? error : 1; + +>>>>>>> if (!transactional_table && updated > 0) thd->transaction.stmt.modified_non_trans_table= TRUE; +<<<<<<< gca sql/sql_update.cc 1.154.2.70 + + /* + todo bug#27571: to avoid asynchronization of `error' and + `error_code' of binlog event constructor + + The concept, which is a bit different for insert(!), is to + replace `error' assignment with the following lines + + killed_status= thd->killed; // get the status of the volatile + + Notice: thd->killed is type of "state" whereas the lhs has + "status" the suffix which translates according to WordNet: a state + at a particular time - at the time of the end of per-row loop in + our case. Binlogging ops are conducted with the status. + + error= (killed_status == THD::NOT_KILLED)? error : 1; + + which applies to most mysql_$query functions. + Event's constructor will accept `killed_status' as an argument: + + Query_log_event qinfo(..., killed_status); + + thd->killed might be changed after killed_status had got cached and this + won't affect binlogging event but other effects remain. + + Open issue: In a case the error happened not because of KILLED - + and then KILLED was caught later still within the loop - we shall + do something to avoid binlogging of incorrect ER_SERVER_SHUTDOWN + error_code. + */ + + if (thd->killed && !error) + error= 1; // Aborted +<<<<<<< local sql/sql_update.cc 1.258 /* todo bug#27571: to avoid asynchronization of `error' and @@ -774,6 +838,8 @@ int mysql_update(THD *thd, if (will_batch) table->file->end_bulk_update(); table->file->try_semi_consistent_read(0); +<<<<<<< remote sql/sql_update.cc 1.154.2.71 +>>>>>>> end_read_record(&info); delete select; thd->proc_info= "end"; @@ -803,6 +869,12 @@ int mysql_update(THD *thd, { if (error < 0) thd->clear_error(); +<<<<<<< gca sql/sql_update.cc 1.154.2.70 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_table, FALSE); + if (mysql_bin_log.write(&qinfo) && transactional_table) + error=1; // Rollback update +<<<<<<< local sql/sql_update.cc 1.258 if (thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query, thd->query_length, transactional_table, FALSE) && @@ -810,6 +882,12 @@ int mysql_update(THD *thd, { error=1; // Rollback update } +<<<<<<< remote sql/sql_update.cc 1.154.2.71 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_table, FALSE, killed_status); + if (mysql_bin_log.write(&qinfo) && transactional_table) + error=1; // Rollback update +>>>>>>> } if (thd->transaction.stmt.modified_non_trans_table) thd->transaction.all.modified_non_trans_table= TRUE; @@ -1751,9 +1829,24 @@ void multi_update::send_error(uint errcode,const char *err) */ if (mysql_bin_log.is_open()) { +<<<<<<< gca sql/sql_update.cc 1.154.2.70 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_tables, FALSE); + mysql_bin_log.write(&qinfo); +<<<<<<< local sql/sql_update.cc 1.258 thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query, thd->query_length, transactional_tables, FALSE); +<<<<<<< remote sql/sql_update.cc 1.154.2.71 + /* + THD::killed status might not have been set ON at time of an error + got caught and if happens later the killed error is written + into repl event. + */ + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_tables, FALSE); + mysql_bin_log.write(&qinfo); +>>>>>>> } thd->transaction.all.modified_non_trans_table= TRUE; } @@ -1946,12 +2039,25 @@ err2: bool multi_update::send_eof() { char buff[STRING_BUFFER_USUAL_SIZE]; +<<<<<<< gca sql/sql_update.cc 1.154.2.70 +<<<<<<< local sql/sql_update.cc 1.258 ulonglong id; DBUG_ENTER("multi_update::send_eof"); +<<<<<<< remote sql/sql_update.cc 1.154.2.71 + THD::killed_state killed_status= THD::NOT_KILLED; +>>>>>>> thd->proc_info="updating reference tables"; - /* Does updates for the last n - 1 tables, returns 0 if ok */ + /* + Does updates for the last n - 1 tables, returns 0 if ok; + error takes into account killed status gained in do_updates() + */ int local_error = (table_count) ? do_updates(0) : 0; + /* + if local_error is not set ON until after do_updates() then + later carried out killing should not affect binlogging. + */ + killed_status= (local_error == 0)? THD::NOT_KILLED : thd->killed; thd->proc_info= "end"; /* We must invalidate the query cache before binlog writing and @@ -1978,6 +2084,12 @@ bool multi_update::send_eof() { if (local_error == 0) thd->clear_error(); +<<<<<<< gca sql/sql_update.cc 1.154.2.70 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_tables, FALSE); + if (mysql_bin_log.write(&qinfo) && trans_safe) + local_error= 1; // Rollback update +<<<<<<< local sql/sql_update.cc 1.258 if (thd->binlog_query(THD::ROW_QUERY_TYPE, thd->query, thd->query_length, transactional_tables, FALSE) && @@ -1985,6 +2097,12 @@ bool multi_update::send_eof() { local_error= 1; // Rollback update } +<<<<<<< remote sql/sql_update.cc 1.154.2.71 + Query_log_event qinfo(thd, thd->query, thd->query_length, + transactional_tables, FALSE, killed_status); + if (mysql_bin_log.write(&qinfo) && trans_safe) + local_error= 1; // Rollback update +>>>>>>> } if (thd->transaction.stmt.modified_non_trans_table) thd->transaction.all.modified_non_trans_table= TRUE;