summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHe Zhenxing <zhenxing.he@sun.com>2009-03-05 18:10:44 +0800
committerHe Zhenxing <zhenxing.he@sun.com>2009-03-05 18:10:44 +0800
commitb4fdb8aec14b533db4a5ea2f99c1a9f3ce07946c (patch)
treec6ec33b757dfff8d69335ab97419c2842f1f61cb
parent62d5d85c5d19b113b772ecadbf32f198831b543b (diff)
downloadmariadb-git-b4fdb8aec14b533db4a5ea2f99c1a9f3ce07946c.tar.gz
BUG#37051 Replication rules not evaluated correctly
Backporting patch to 5.0.
-rw-r--r--mysql-test/include/master-slave-end.inc6
-rw-r--r--mysql-test/include/start_slave.inc21
-rw-r--r--mysql-test/include/stop_slave.inc21
-rw-r--r--mysql-test/include/wait_for_slave_sql_error.inc40
-rw-r--r--mysql-test/include/wait_for_slave_sql_error_and_skip.inc39
-rw-r--r--mysql-test/r/multi_update.result4
-rw-r--r--mysql-test/r/rpl_filter_tables_not_exist.result151
-rw-r--r--mysql-test/t/rpl_filter_tables_not_exist-slave.opt1
-rw-r--r--mysql-test/t/rpl_filter_tables_not_exist.test206
-rw-r--r--sql/log_event.cc47
-rw-r--r--sql/log_event.h22
-rw-r--r--sql/sql_class.cc6
-rw-r--r--sql/sql_class.h7
-rw-r--r--sql/sql_parse.cc48
-rw-r--r--sql/sql_update.cc2
15 files changed, 594 insertions, 27 deletions
diff --git a/mysql-test/include/master-slave-end.inc b/mysql-test/include/master-slave-end.inc
new file mode 100644
index 00000000000..74e4c7b608a
--- /dev/null
+++ b/mysql-test/include/master-slave-end.inc
@@ -0,0 +1,6 @@
+--connection master
+--sync_slave_with_master
+--connection slave
+--disable_query_log
+STOP SLAVE;
+--enable_query_log
diff --git a/mysql-test/include/start_slave.inc b/mysql-test/include/start_slave.inc
new file mode 100644
index 00000000000..78a02736de8
--- /dev/null
+++ b/mysql-test/include/start_slave.inc
@@ -0,0 +1,21 @@
+# ==== Purpose ====
+#
+# Issues START SLAVE on the current connection. Then waits until both
+# the IO and SQL threads have started, or until a timeout is reached.
+#
+# Please use this instead of 'START SLAVE', to reduce the risk of test
+# case bugs.
+#
+# ==== Usage ====
+#
+# source include/wait_for_slave_to_start.inc;
+#
+# Parameters to this macro are $slave_timeout and
+# $master_connection. See wait_for_slave_param.inc for
+# descriptions.
+
+--disable_query_log
+START SLAVE;
+--enable_query_log
+--echo include/start_slave.inc
+source include/wait_for_slave_to_start.inc;
diff --git a/mysql-test/include/stop_slave.inc b/mysql-test/include/stop_slave.inc
new file mode 100644
index 00000000000..7161e6fe739
--- /dev/null
+++ b/mysql-test/include/stop_slave.inc
@@ -0,0 +1,21 @@
+# ==== Purpose ====
+#
+# Issues STOP SLAVE on the current connection. Then waits until both
+# the IO and SQL threads have stopped, or until a timeout is reached.
+#
+# Please use this instead of 'STOP SLAVE', to reduce the risk of test
+# case bugs.
+#
+# ==== Usage ====
+#
+# source include/wait_for_slave_to_start.inc;
+#
+# Parameters to this macro are $slave_timeout and
+# $master_connection. See wait_for_slave_param.inc for
+# descriptions.
+
+--disable_query_log
+STOP SLAVE;
+--enable_query_log
+--echo include/stop_slave.inc
+source include/wait_for_slave_to_stop.inc;
diff --git a/mysql-test/include/wait_for_slave_sql_error.inc b/mysql-test/include/wait_for_slave_sql_error.inc
new file mode 100644
index 00000000000..1a708cee780
--- /dev/null
+++ b/mysql-test/include/wait_for_slave_sql_error.inc
@@ -0,0 +1,40 @@
+# ==== Purpose ====
+#
+# Waits until the SQL thread of the current connection has got an
+# error, or until a timeout is reached. Also waits until the SQL
+# thread has completely stopped.
+#
+# ==== Usage ====
+#
+# source include/wait_for_slave_sql_error.inc;
+#
+# Parameters:
+#
+# $slave_sql_errno
+# The expected SQL error number. This is required.
+# (After BUG#41956 has been fixed, this will be required to be a
+# symbolic name instead of a number.)
+#
+# $slave_timeout
+# See wait_for_slave_param.inc for description.
+#
+# $master_connection
+# See wait_for_slave_param.inc for description.
+
+if (`SELECT '$slave_sql_errno' = ''`) {
+ --echo !!!ERROR IN TEST: you must set \$slave_sql_errno before sourcing wait_fro_slave_sql_error.inc
+ exit;
+}
+
+let $slave_param= Slave_SQL_Running;
+let $slave_param_value= No;
+let $slave_error_message= Failed while waiting for slave to stop the SQL thread (expecting error in the SQL thread);
+source include/wait_for_slave_param.inc;
+
+# NOTE: on mysql-5.0, there is no way to distinguish slave SQL error from IO error
+let $_error= query_get_value(SHOW SLAVE STATUS, Last_Errno, 1);
+if (`SELECT '$_error' != '$slave_sql_errno'`) {
+ --echo Slave stopped with wrong error code: $_error (expected $slave_sql_errno)
+ source include/show_rpl_debug_info.inc;
+ exit;
+}
diff --git a/mysql-test/include/wait_for_slave_sql_error_and_skip.inc b/mysql-test/include/wait_for_slave_sql_error_and_skip.inc
new file mode 100644
index 00000000000..5723720f4b7
--- /dev/null
+++ b/mysql-test/include/wait_for_slave_sql_error_and_skip.inc
@@ -0,0 +1,39 @@
+# ==== Purpose ====
+#
+# Wait for slave SQL error, skip the erroneous statement and restart
+# slave
+#
+# ==== Usage ====
+#
+# let $slave_sql_error= <ERRNO>;
+# source include/wait_for_slave_sql_error_and_skip.inc;
+#
+# Parameters:
+#
+# $slave_sql_errno
+# The error number to wait for. This is required. (See
+# wait_for_slave_sql_error.inc)
+#
+# $show_sql_error
+# If set, will print the error to the query log.
+#
+# $slave_timeout
+# See wait_for_slave_param.inc for description.
+#
+# $master_connection
+# See wait_for_slave_param.inc for description.
+
+echo --source include/wait_for_slave_sql_error_and_skip.inc;
+connection slave;
+source include/wait_for_slave_sql_error.inc;
+if ($show_sql_error)
+{
+ # NOTE: on mysql-5.0, there is no way to distinguish slave SQL error from IO error
+ let $error= query_get_value("SHOW SLAVE STATUS", Last_Error, 1);
+ echo Last_SQL_Error = $error;
+}
+
+# skip the erroneous statement
+set global sql_slave_skip_counter=1;
+source include/start_slave.inc;
+connection master;
diff --git a/mysql-test/r/multi_update.result b/mysql-test/r/multi_update.result
index f6dbac31c2a..c04948c6fa7 100644
--- a/mysql-test/r/multi_update.result
+++ b/mysql-test/r/multi_update.result
@@ -522,7 +522,7 @@ a b
4 4
show master status /* there must be the UPDATE query event */;
File Position Binlog_Do_DB Binlog_Ignore_DB
-master-bin.000001 189
+master-bin.000001 198
delete from t1;
delete from t2;
insert into t1 values (1,2),(3,4),(4,4);
@@ -532,7 +532,7 @@ UPDATE t2,t1 SET t2.a=t2.b where t2.a=t1.a;
ERROR 23000: Duplicate entry '4' for key 1
show master status /* there must be the UPDATE query event */;
File Position Binlog_Do_DB Binlog_Ignore_DB
-master-bin.000001 204
+master-bin.000001 213
drop table t1, t2;
drop table if exists t1, t2, t3;
CREATE TABLE t1 (a int, PRIMARY KEY (a));
diff --git a/mysql-test/r/rpl_filter_tables_not_exist.result b/mysql-test/r/rpl_filter_tables_not_exist.result
new file mode 100644
index 00000000000..7eddaabc636
--- /dev/null
+++ b/mysql-test/r/rpl_filter_tables_not_exist.result
@@ -0,0 +1,151 @@
+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;
+CREATE TABLE t1 (id int, a int);
+CREATE TABLE t2 (id int, b int);
+CREATE TABLE t3 (id int, c int);
+CREATE TABLE t4 (id int, d int);
+CREATE TABLE t5 (id int, e int);
+CREATE TABLE t6 (id int, f int);
+CREATE TABLE t7 (id int, g int);
+CREATE TABLE t8 (id int, h int);
+CREATE TABLE t9 (id int, i int);
+INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t2 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t3 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t4 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t5 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t6 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t7 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t8 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t9 VALUES (1, 1), (2, 2), (3, 3);
+[on slave]
+SHOW TABLES LIKE 't%';
+Tables_in_test (t%)
+t1
+t2
+t3
+[on master]
+UPDATE t7 LEFT JOIN t4 ON (t4.id=t7.id) SET d=0, g=0 where t7.id=1;
+UPDATE t7 LEFT JOIN (t4, t5, t6) ON (t7.id=t4.id and t7.id=t5.id and t7.id=t6.id) SET d=0, e=0, f=0, g=0 where t7.id=1;
+UPDATE t4 LEFT JOIN (t7, t8, t9) ON (t4.id=t7.id and t4.id=t8.id and t4.id=t9.id) SET d=0, g=0, h=0, i=0 where t4.id=1;
+UPDATE t7 LEFT JOIN (t8, t9) ON (t7.id=t8.id and t7.id=t9.id) SET g=0, h=0, i=0 where t7.id=1;
+UPDATE t1 LEFT JOIN t4 ON (t1.id=t4.id) SET d=0 where t1.id=1;
+UPDATE t1 LEFT JOIN t7 ON (t1.id=t7.id) SET g=0 where t1.id=1;
+UPDATE t1 LEFT JOIN (t4, t5, t6) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t6.id) SET d=0, e=0, f=0 where t1.id=1;
+UPDATE t1 LEFT JOIN (t4, t5, t8) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t8.id) SET d=0, e=0, h=0 where t1.id=1;
+UPDATE t1 LEFT JOIN (t7, t8, t5) ON (t1.id=t7.id and t1.id=t8.id and t1.id=t5.id) SET g=0, h=0, e=0 where t1.id=1;
+UPDATE t1 LEFT JOIN (t2, t3, t5) ON (t1.id=t2.id and t1.id=t3.id and t1.id=t5.id) SET e=0 where t1.id=1;
+UPDATE t4 LEFT JOIN t1 ON (t1.id=t4.id) SET a=0, d=0 where t4.id=1;
+UPDATE t4 LEFT JOIN (t1, t7) ON (t4.id=t1.id and t7.id=t4.id) SET a = 0, d=0, g=0 where t4.id=1;
+UPDATE t4 LEFT JOIN (t1, t2, t3) ON (t1.id=t4.id and t2.id=t4.id and t3.id=t4.id) SET a=0, b=0, c=0, d=0 where t4.id=1;
+UPDATE t4 LEFT JOIN (t1, t2, t5) ON (t1.id=t4.id and t2.id=t4.id and t5.id=t4.id) SET a=0, b=0, e=0, d=0 where t4.id=1;
+UPDATE t4 LEFT JOIN (t1, t6, t7) ON (t4.id=t1.id and t4.id=t6.id and t4.id=t7.id) SET a=0, d=0, f=0, g=0 where t4.id=1;
+UPDATE t7 LEFT JOIN (t4, t1, t2) ON (t7.id=t4.id and t7.id=t1.id and t7.id=t2.id) SET a=0, b=0, d=0, g=0 where t7.id=1;
+UPDATE t7 LEFT JOIN (t8, t4, t1) ON (t7.id=t8.id and t7.id=t4.id and t7.id=t1.id) SET a=0, d=0, g=0, h=0 where t7.id=1;
+UPDATE t1 LEFT JOIN t4 ON (t1.id=t4.id) SET a=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t4' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN t4 ON (t1.id=t4.id) SET a=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t1 LEFT JOIN (t4, t7) ON (t1.id=t4.id and t1.id=t7.id) SET a=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t4' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN (t4, t7) ON (t1.id=t4.id and t1.id=t7.id) SET a=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t1 LEFT JOIN (t2, t4, t7) ON (t1.id=t2.id and t1.id=t4.id and t1.id=t7.id) SET a=0, b=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t4' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN (t2, t4, t7) ON (t1.id=t2.id and t1.id=t4.id and t1.id=t7.id) SET a=0, b=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t1 LEFT JOIN (t2, t3, t7) ON (t1.id=t2.id and t1.id=t3.id and t1.id=t7.id) SET a=0, b=0, c=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t7' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN (t2, t3, t7) ON (t1.id=t2.id and t1.id=t3.id and t1.id=t7.id) SET a=0, b=0, c=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t1 LEFT JOIN t7 ON (t1.id=t7.id) SET a=0, g=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t7' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN t7 ON (t1.id=t7.id) SET a=0, g=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t7 LEFT JOIN t1 ON (t1.id=t7.id) SET a=0, g=0 where t7.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t7' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t7 LEFT JOIN t1 ON (t1.id=t7.id) SET a=0, g=0 where t7.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t1 LEFT JOIN (t4, t5, t7) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t7.id) SET a=0, g=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t4' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN (t4, t5, t7) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t7.id) SET a=0, g=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t1 LEFT JOIN (t4, t7, t8) ON (t1.id=t4.id and t1.id=t7.id and t1.id=t8.id) SET a=0, g=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t4' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN (t4, t7, t8) ON (t1.id=t4.id and t1.id=t7.id and t1.id=t8.id) SET a=0, g=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t1 LEFT JOIN (t7, t8, t9) ON (t1.id=t7.id and t1.id=t8.id and t1.id=t9.id) SET a=0, g=0, h=0, i=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t7' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN (t7, t8, t9) ON (t1.id=t7.id and t1.id=t8.id and t1.id=t9.id) SET a=0, g=0, h=0, i=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t7 LEFT JOIN (t1, t2, t3) ON (t7.id=t1.id and t7.id=t2.id and t7.id=t3.id) SET g=0, a=0, b=0, c=0 where t7.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t7' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t7 LEFT JOIN (t1, t2, t3) ON (t7.id=t1.id and t7.id=t2.id and t7.id=t3.id) SET g=0, a=0, b=0, c=0 where t7.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t7 LEFT JOIN (t4, t5, t3) ON (t7.id=t4.id and t7.id=t5.id and t7.id=t3.id) SET g=0, c=0 where t7.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t7' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t7 LEFT JOIN (t4, t5, t3) ON (t7.id=t4.id and t7.id=t5.id and t7.id=t3.id) SET g=0, c=0 where t7.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t7 LEFT JOIN (t8, t9, t3) ON (t7.id=t8.id and t7.id=t9.id and t7.id=t3.id) SET g=0, h=0, i=0, c=0 where t7.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t7' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t7 LEFT JOIN (t8, t9, t3) ON (t7.id=t8.id and t7.id=t9.id and t7.id=t3.id) SET g=0, h=0, i=0, c=0 where t7.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t1 LEFT JOIN t4 ON (t1.id=t4.id) SET a=0, d=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t4' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN t4 ON (t1.id=t4.id) SET a=0, d=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t1 LEFT JOIN (t4, t5, t6) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t6.id) SET a=0, d=0, e=0, f=0 where t1.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t4' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t1 LEFT JOIN (t4, t5, t6) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t6.id) SET a=0, d=0, e=0, f=0 where t1.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t4 LEFT JOIN (t1, t5, t6) ON (t4.id=t1.id and t4.id=t5.id and t4.id=t6.id) SET a=0, e=0, f=0 where t4.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t4' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t4 LEFT JOIN (t1, t5, t6) ON (t4.id=t1.id and t4.id=t5.id and t4.id=t6.id) SET a=0, e=0, f=0 where t4.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+UPDATE t7 LEFT JOIN (t1, t4, t2) ON (t7.id=t1.id and t7.id=t4.id and t7.id=t2.id) SET a=0, b=0, d=0, g=0 where t7.id=1;
+--source include/wait_for_slave_sql_error_and_skip.inc
+Last_SQL_Error = Error 'Table 'test.t7' doesn't exist' on query. Default database: 'test'. Query: 'UPDATE t7 LEFT JOIN (t1, t4, t2) ON (t7.id=t1.id and t7.id=t4.id and t7.id=t2.id) SET a=0, b=0, d=0, g=0 where t7.id=1'
+set global sql_slave_skip_counter=1;
+include/start_slave.inc
+[on slave]
+show tables like 't%';
+Tables_in_test (t%)
+t1
+t2
+t3
+SELECT * FROM t1;
+id a
+1 1
+2 2
+3 3
+SELECT * FROM t2;
+id b
+1 1
+2 2
+3 3
+SELECT * FROM t3;
+id c
+1 1
+2 2
+3 3
+[on master]
+drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
diff --git a/mysql-test/t/rpl_filter_tables_not_exist-slave.opt b/mysql-test/t/rpl_filter_tables_not_exist-slave.opt
new file mode 100644
index 00000000000..42acd3ea33d
--- /dev/null
+++ b/mysql-test/t/rpl_filter_tables_not_exist-slave.opt
@@ -0,0 +1 @@
+--replicate-do-table=test.t1 --replicate-do-table=test.t2 --replicate-do-table=test.t3 --replicate-ignore-table=test.t4 --replicate-ignore-table=test.t5 --replicate-ignore-table=test.t6
diff --git a/mysql-test/t/rpl_filter_tables_not_exist.test b/mysql-test/t/rpl_filter_tables_not_exist.test
new file mode 100644
index 00000000000..d1153e584c8
--- /dev/null
+++ b/mysql-test/t/rpl_filter_tables_not_exist.test
@@ -0,0 +1,206 @@
+# Test evaluation of replication table filter rules
+#
+# ==== Purpose ====
+#
+# Test if replication table filter rules are properly evaluated when
+# some of the tables referenced by the multiple-table update do not
+# exist on slave.
+#
+# ==== Method ====
+#
+# Master creates tables t1, t2, t3, t4, t5, t6, t7, t8, t9 and the
+# slave is started with the following replication table filter rules:
+#
+# --replicate-do-table=t1
+# --replicate-do-table=t2
+# --replicate-do-table=t3
+#
+# and
+#
+# --replicate-ignore-table=t4
+# --replicate-ignore-table=t5
+# --replicate-ignore-table=t6
+#
+# So the slave only replicate changes to tables t1, t2 and t3 and only
+# these tables exist on slave.
+#
+# From now on, tables t1, t2, and t3 are referenced as do tables,
+# tables t4, t5, t6 are referenced as ignore tables, and tables t7,
+# t8, t9 are referenced as other tables.
+#
+# All multi-table update tests reference tables that are not do
+# tables, which do not exist on slave. And the following situations
+# of multi-table update will be tested:
+#
+# 1. Do tables are not referenced at all
+# 2. Do tables are not referenced for update
+# 3. Ignore tables are referenced for update before do tables
+# 4. Only do tables are referenced for update
+# 5. Do tables and other tables are referenced for update
+# 6. Do tables are referenced for update before ignore tables
+#
+# For 1, 2 and 3, the statement should be ignored by slave, for 4, 5
+# and 6 the statement should be accepted by slave and cause an error
+# because of non-exist tables.
+#
+# ==== Related bugs ====
+#
+# BUG#37051 Replication rules not evaluated correctly
+
+
+source include/master-slave.inc;
+
+# These tables are mentioned in do-table rules
+CREATE TABLE t1 (id int, a int);
+CREATE TABLE t2 (id int, b int);
+CREATE TABLE t3 (id int, c int);
+
+# These tables are mentioned in ignore-table rules
+CREATE TABLE t4 (id int, d int);
+CREATE TABLE t5 (id int, e int);
+CREATE TABLE t6 (id int, f int);
+
+# These tables are not mentioned in do-table or ignore-table rules
+CREATE TABLE t7 (id int, g int);
+CREATE TABLE t8 (id int, h int);
+CREATE TABLE t9 (id int, i int);
+
+INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t2 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t3 VALUES (1, 1), (2, 2), (3, 3);
+
+INSERT INTO t4 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t5 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t6 VALUES (1, 1), (2, 2), (3, 3);
+
+INSERT INTO t7 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t8 VALUES (1, 1), (2, 2), (3, 3);
+INSERT INTO t9 VALUES (1, 1), (2, 2), (3, 3);
+
+# Only t1, t2, t3 should be replicated to slave
+sync_slave_with_master;
+echo [on slave];
+SHOW TABLES LIKE 't%';
+
+connection master;
+echo [on master];
+
+#
+# Do tables are not referenced, these statements should be ignored by
+# slave.
+#
+UPDATE t7 LEFT JOIN t4 ON (t4.id=t7.id) SET d=0, g=0 where t7.id=1;
+UPDATE t7 LEFT JOIN (t4, t5, t6) ON (t7.id=t4.id and t7.id=t5.id and t7.id=t6.id) SET d=0, e=0, f=0, g=0 where t7.id=1;
+UPDATE t4 LEFT JOIN (t7, t8, t9) ON (t4.id=t7.id and t4.id=t8.id and t4.id=t9.id) SET d=0, g=0, h=0, i=0 where t4.id=1;
+UPDATE t7 LEFT JOIN (t8, t9) ON (t7.id=t8.id and t7.id=t9.id) SET g=0, h=0, i=0 where t7.id=1;
+
+#
+# Do tables are not referenced for update, these statements should be
+# ignored by slave.
+#
+UPDATE t1 LEFT JOIN t4 ON (t1.id=t4.id) SET d=0 where t1.id=1;
+UPDATE t1 LEFT JOIN t7 ON (t1.id=t7.id) SET g=0 where t1.id=1;
+UPDATE t1 LEFT JOIN (t4, t5, t6) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t6.id) SET d=0, e=0, f=0 where t1.id=1;
+UPDATE t1 LEFT JOIN (t4, t5, t8) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t8.id) SET d=0, e=0, h=0 where t1.id=1;
+UPDATE t1 LEFT JOIN (t7, t8, t5) ON (t1.id=t7.id and t1.id=t8.id and t1.id=t5.id) SET g=0, h=0, e=0 where t1.id=1;
+UPDATE t1 LEFT JOIN (t2, t3, t5) ON (t1.id=t2.id and t1.id=t3.id and t1.id=t5.id) SET e=0 where t1.id=1;
+
+#
+# Ignore tables are referenced for update before do tables, these
+# statements should be ignore by slave.
+#
+UPDATE t4 LEFT JOIN t1 ON (t1.id=t4.id) SET a=0, d=0 where t4.id=1;
+UPDATE t4 LEFT JOIN (t1, t7) ON (t4.id=t1.id and t7.id=t4.id) SET a = 0, d=0, g=0 where t4.id=1;
+UPDATE t4 LEFT JOIN (t1, t2, t3) ON (t1.id=t4.id and t2.id=t4.id and t3.id=t4.id) SET a=0, b=0, c=0, d=0 where t4.id=1;
+UPDATE t4 LEFT JOIN (t1, t2, t5) ON (t1.id=t4.id and t2.id=t4.id and t5.id=t4.id) SET a=0, b=0, e=0, d=0 where t4.id=1;
+UPDATE t4 LEFT JOIN (t1, t6, t7) ON (t4.id=t1.id and t4.id=t6.id and t4.id=t7.id) SET a=0, d=0, f=0, g=0 where t4.id=1;
+UPDATE t7 LEFT JOIN (t4, t1, t2) ON (t7.id=t4.id and t7.id=t1.id and t7.id=t2.id) SET a=0, b=0, d=0, g=0 where t7.id=1;
+UPDATE t7 LEFT JOIN (t8, t4, t1) ON (t7.id=t8.id and t7.id=t4.id and t7.id=t1.id) SET a=0, d=0, g=0, h=0 where t7.id=1;
+
+# Sync slave to make sure all above statements are correctly ignored,
+# if any of the above statement are not ignored, it would cause error
+# and stop slave sql thread.
+sync_slave_with_master;
+connection master;
+
+# Parameters for include/wait_for_slave_sql_error_and_skip.inc:
+# Ask it to show SQL error message.
+let $show_sql_error= 1;
+# The expected error will always be 1146 (ER_NO_SUCH_TABLE).
+let $slave_sql_errno= 1146;
+
+#
+# Only do tables are referenced for update, these statements should
+# cause error on slave
+#
+UPDATE t1 LEFT JOIN t4 ON (t1.id=t4.id) SET a=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t1 LEFT JOIN (t4, t7) ON (t1.id=t4.id and t1.id=t7.id) SET a=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t1 LEFT JOIN (t2, t4, t7) ON (t1.id=t2.id and t1.id=t4.id and t1.id=t7.id) SET a=0, b=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t1 LEFT JOIN (t2, t3, t7) ON (t1.id=t2.id and t1.id=t3.id and t1.id=t7.id) SET a=0, b=0, c=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+#
+# Do tables and other tables are referenced for update, these
+# statements should cause error on slave
+#
+UPDATE t1 LEFT JOIN t7 ON (t1.id=t7.id) SET a=0, g=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t7 LEFT JOIN t1 ON (t1.id=t7.id) SET a=0, g=0 where t7.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t1 LEFT JOIN (t4, t5, t7) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t7.id) SET a=0, g=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t1 LEFT JOIN (t4, t7, t8) ON (t1.id=t4.id and t1.id=t7.id and t1.id=t8.id) SET a=0, g=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t1 LEFT JOIN (t7, t8, t9) ON (t1.id=t7.id and t1.id=t8.id and t1.id=t9.id) SET a=0, g=0, h=0, i=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t7 LEFT JOIN (t1, t2, t3) ON (t7.id=t1.id and t7.id=t2.id and t7.id=t3.id) SET g=0, a=0, b=0, c=0 where t7.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t7 LEFT JOIN (t4, t5, t3) ON (t7.id=t4.id and t7.id=t5.id and t7.id=t3.id) SET g=0, c=0 where t7.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t7 LEFT JOIN (t8, t9, t3) ON (t7.id=t8.id and t7.id=t9.id and t7.id=t3.id) SET g=0, h=0, i=0, c=0 where t7.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+#
+# Do tables are referenced for update before ignore tables
+#
+UPDATE t1 LEFT JOIN t4 ON (t1.id=t4.id) SET a=0, d=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t1 LEFT JOIN (t4, t5, t6) ON (t1.id=t4.id and t1.id=t5.id and t1.id=t6.id) SET a=0, d=0, e=0, f=0 where t1.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t4 LEFT JOIN (t1, t5, t6) ON (t4.id=t1.id and t4.id=t5.id and t4.id=t6.id) SET a=0, e=0, f=0 where t4.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+UPDATE t7 LEFT JOIN (t1, t4, t2) ON (t7.id=t1.id and t7.id=t4.id and t7.id=t2.id) SET a=0, b=0, d=0, g=0 where t7.id=1;
+source include/wait_for_slave_sql_error_and_skip.inc;
+
+sync_slave_with_master;
+echo [on slave];
+
+# We should only have tables t1, t2, t3 on slave
+show tables like 't%';
+
+# The rows in these tables should remain untouched
+SELECT * FROM t1;
+SELECT * FROM t2;
+SELECT * FROM t3;
+
+# Clean up
+connection master;
+echo [on master];
+drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
+source include/master-slave-end.inc;
diff --git a/sql/log_event.cc b/sql/log_event.cc
index b74b38e55b2..ba282fd8e59 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -1117,6 +1117,11 @@ void Query_log_event::pack_info(Protocol *protocol)
static void write_str_with_code_and_len(char **dst, const char *src,
int len, uint code)
{
+ /*
+ only 1 byte to store the length of catalog, so it should not
+ surpass 255
+ */
+ DBUG_ASSERT(len <= 255);
DBUG_ASSERT(src);
*((*dst)++)= code;
*((*dst)++)= (uchar) len;
@@ -1136,16 +1141,8 @@ static void write_str_with_code_and_len(char **dst, const char *src,
bool Query_log_event::write(IO_CACHE* file)
{
- uchar buf[QUERY_HEADER_LEN+
- 1+4+ // code of flags2 and flags2
- 1+8+ // code of sql_mode and sql_mode
- 1+1+FN_REFLEN+ // code of catalog and catalog length and catalog
- 1+4+ // code of autoinc and the 2 autoinc variables
- 1+6+ // code of charset and charset
- 1+1+MAX_TIME_ZONE_NAME_LENGTH+ // code of tz and tz length and tz name
- 1+2+ // code of lc_time_names and lc_time_names_number
- 1+2 // code of charset_database and charset_database_number
- ], *start, *start_of_status;
+ uchar buf[QUERY_HEADER_LEN + MAX_SIZE_LOG_EVENT_STATUS];
+ uchar *start, *start_of_status;
ulong event_length;
if (!query)
@@ -1251,10 +1248,8 @@ bool Query_log_event::write(IO_CACHE* file)
{
/* In the TZ sys table, column Name is of length 64 so this should be ok */
DBUG_ASSERT(time_zone_len <= MAX_TIME_ZONE_NAME_LENGTH);
- *start++= Q_TIME_ZONE_CODE;
- *start++= time_zone_len;
- memcpy(start, time_zone_str, time_zone_len);
- start+= time_zone_len;
+ write_str_with_code_and_len((char **)(&start),
+ time_zone_str, time_zone_len, Q_TIME_ZONE_CODE);
}
if (lc_time_names_number)
{
@@ -1270,7 +1265,17 @@ bool Query_log_event::write(IO_CACHE* file)
int2store(start, charset_database_number);
start+= 2;
}
+ if (table_map_for_update)
+ {
+ *start++= Q_TABLE_MAP_FOR_UPDATE_CODE;
+ int8store(start, table_map_for_update);
+ start+= 8;
+ }
/*
+ NOTE: When adding new status vars, please don't forget to update
+ the MAX_SIZE_LOG_EVENT_STATUS in log_event.h and update function
+ code_name in this file.
+
Here there could be code like
if (command-line-option-which-says-"log_this_variable" && inited)
{
@@ -1348,7 +1353,8 @@ Query_log_event::Query_log_event(THD* thd_arg, const char* query_arg,
auto_increment_increment(thd_arg->variables.auto_increment_increment),
auto_increment_offset(thd_arg->variables.auto_increment_offset),
lc_time_names_number(thd_arg->variables.lc_time_names->number),
- charset_database_number(0)
+ charset_database_number(0),
+ table_map_for_update((ulonglong)thd_arg->table_map_for_update)
{
time_t end_time;
@@ -1471,6 +1477,7 @@ code_name(int code)
case Q_CATALOG_NZ_CODE: return "Q_CATALOG_NZ_CODE";
case Q_LC_TIME_NAMES_CODE: return "Q_LC_TIME_NAMES_CODE";
case Q_CHARSET_DATABASE_CODE: return "Q_CHARSET_DATABASE_CODE";
+ case Q_TABLE_MAP_FOR_UPDATE_CODE: return "Q_TABLE_MAP_FOR_UPDATE_CODE";
}
sprintf(buf, "CODE#%d", code);
return buf;
@@ -1507,7 +1514,8 @@ Query_log_event::Query_log_event(const char* buf, uint event_len,
db(NullS), catalog_len(0), status_vars_len(0),
flags2_inited(0), sql_mode_inited(0), charset_inited(0),
auto_increment_increment(1), auto_increment_offset(1),
- time_zone_len(0), lc_time_names_number(0), charset_database_number(0)
+ time_zone_len(0), lc_time_names_number(0), charset_database_number(0),
+ table_map_for_update(0)
{
ulong data_len;
uint32 tmp;
@@ -1649,6 +1657,11 @@ Query_log_event::Query_log_event(const char* buf, uint event_len,
charset_database_number= uint2korr(pos);
pos+= 2;
break;
+ case Q_TABLE_MAP_FOR_UPDATE_CODE:
+ CHECK_SPACE(pos, end, 8);
+ table_map_for_update= uint8korr(pos);
+ pos+= 8;
+ break;
default:
/* That's why you must write status vars in growing order of code */
DBUG_PRINT("info",("Query_log_event has unknown status vars (first has\
@@ -2036,6 +2049,8 @@ int Query_log_event::exec_event(struct st_relay_log_info* rli,
else
thd->variables.collation_database= thd->db_charset;
+ thd->table_map_for_update= (table_map)table_map_for_update;
+
/* Execute the query (note that we bypass dispatch_command()) */
const char* found_semicolon= NULL;
mysql_parse(thd, thd->query, thd->query_length, &found_semicolon);
diff --git a/sql/log_event.h b/sql/log_event.h
index 5b065a33dd1..6ccbf8e4d5c 100644
--- a/sql/log_event.h
+++ b/sql/log_event.h
@@ -204,12 +204,15 @@ struct sql_ex_info
packet (i.e. a query) sent from client to master;
First, an auxiliary log_event status vars estimation:
*/
-#define MAX_SIZE_LOG_EVENT_STATUS (4 /* flags2 */ + \
- 8 /* sql mode */ + \
- 1 + 1 + 255 /* catalog */ + \
- 4 /* autoinc */ + \
- 6 /* charset */ + \
- MAX_TIME_ZONE_NAME_LENGTH)
+#define MAX_SIZE_LOG_EVENT_STATUS (1 + 4 /* type, flags2 */ + \
+ 1 + 8 /* type, sql_mode */ + \
+ 1 + 1 + 255 /* type, length, catalog */ + \
+ 1 + 4 /* type, auto_increment */ + \
+ 1 + 6 /* type, charset */ + \
+ 1 + 1 + 255 /* type, length, time_zone */ + \
+ 1 + 2 /* type, lc_time_names_number */ + \
+ 1 + 2 /* type, charset_database_number */ + \
+ 1 + 8 /* type, table_map_for_update */)
#define MAX_LOG_EVENT_HEADER ( /* in order of Query_log_event::write */ \
LOG_EVENT_HEADER_LEN + /* write_header */ \
QUERY_HEADER_LEN + /* write_data */ \
@@ -273,6 +276,8 @@ struct sql_ex_info
#define Q_LC_TIME_NAMES_CODE 7
#define Q_CHARSET_DATABASE_CODE 8
+
+#define Q_TABLE_MAP_FOR_UPDATE_CODE 9
/* Intvar event post-header */
#define I_TYPE_OFFSET 0
@@ -800,6 +805,11 @@ public:
const char *time_zone_str;
uint lc_time_names_number; /* 0 means en_US */
uint charset_database_number;
+ /*
+ map for tables that will be updated for a multi-table update query
+ statement, for other query statements, this will be zero.
+ */
+ ulonglong table_map_for_update;
#ifndef MYSQL_CLIENT
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 9ff602bb62e..4ea621f428d 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -172,7 +172,9 @@ THD::THD()
/* statement id */ 0),
Open_tables_state(refresh_version),
lock_id(&main_lock_id),
- user_time(0), in_sub_stmt(0), global_read_lock(0), is_fatal_error(0),
+ user_time(0), in_sub_stmt(0),
+ table_map_for_update(0),
+ global_read_lock(0), is_fatal_error(0),
transaction_rollback_request(0), is_fatal_sub_stmt_error(0),
rand_used(0), time_zone_used(0),
last_insert_id_used(0), last_insert_id_used_bin_log(0), insert_id_used(0),
@@ -651,6 +653,8 @@ void THD::cleanup_after_query()
free_items();
/* Reset where. */
where= THD::DEFAULT_WHERE;
+ /* reset table map for multi-table update */
+ table_map_for_update= 0;
}
diff --git a/sql/sql_class.h b/sql/sql_class.h
index cc7ef7809d4..3e3dfcd08fa 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -1358,6 +1358,13 @@ public:
Note: in the parser, stmt_arena == thd, even for PS/SP.
*/
Query_arena *stmt_arena;
+
+ /*
+ map for tables that will be updated for a multi-table update query
+ statement, for other query statements, this will be zero.
+ */
+ table_map table_map_for_update;
+
/*
next_insert_id is set on SET INSERT_ID= #. This is used as the next
generated auto_increment value in handler.cc
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 1d3632fd468..dafe4baa9e5 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -2584,6 +2584,10 @@ mysql_execute_command(THD *thd)
TABLE_LIST *all_tables;
/* most outer SELECT_LEX_UNIT of query */
SELECT_LEX_UNIT *unit= &lex->unit;
+#ifdef HAVE_REPLICATION
+ /* have table map for update for multi-update statement (BUG#37051) */
+ bool have_table_map_for_update= FALSE;
+#endif
/* Saved variable value */
DBUG_ENTER("mysql_execute_command");
thd->net.no_send_error= 0;
@@ -2663,6 +2667,48 @@ mysql_execute_command(THD *thd)
// force searching in slave.cc:tables_ok()
all_tables->updating= 1;
}
+
+ /*
+ For fix of BUG#37051, the master stores the table map for update
+ in the Query_log_event, and the value is assigned to
+ thd->variables.table_map_for_update before executing the update
+ query.
+
+ If thd->variables.table_map_for_update is set, then we are
+ replicating from a new master, we can use this value to apply
+ filter rules without opening all the tables. However If
+ thd->variables.table_map_for_update is not set, then we are
+ replicating from an old master, so we just skip this and
+ continue with the old method. And of course, the bug would still
+ exist for old masters.
+ */
+ if (lex->sql_command == SQLCOM_UPDATE_MULTI &&
+ thd->table_map_for_update)
+ {
+ have_table_map_for_update= TRUE;
+ table_map table_map_for_update= thd->table_map_for_update;
+ uint nr= 0;
+ TABLE_LIST *table;
+ for (table=all_tables; table; table=table->next_global, nr++)
+ {
+ if (table_map_for_update & ((table_map)1 << nr))
+ table->updating= TRUE;
+ else
+ table->updating= FALSE;
+ }
+
+ if (all_tables_not_ok(thd, all_tables))
+ {
+ /* we warn the slave SQL thread */
+ my_message(ER_SLAVE_IGNORED_TABLE, ER(ER_SLAVE_IGNORED_TABLE), MYF(0));
+ if (thd->one_shot_set)
+ reset_one_shot_variables(thd);
+ DBUG_RETURN(0);
+ }
+
+ for (table=all_tables; table; table=table->next_global)
+ table->updating= TRUE;
+ }
/*
Check if statment should be skipped because of slave filtering
@@ -3608,7 +3654,7 @@ end_with_restore_list:
#ifdef HAVE_REPLICATION
/* Check slave filtering rules */
- if (unlikely(thd->slave_thread))
+ if (unlikely(thd->slave_thread && !have_table_map_for_update))
{
if (all_tables_not_ok(thd, all_tables))
{
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index f15db220a3b..8a3f5bcdc26 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -775,7 +775,7 @@ reopen_tables:
DBUG_RETURN(TRUE);
}
- tables_for_update= get_table_map(fields);
+ thd->table_map_for_update= tables_for_update= get_table_map(fields);
/*
Setup timestamp handling and locking mode