List:Commits« Previous MessageNext Message »
From:Sven Sandberg Date:July 31 2008 9:12am
Subject:Re: bzr commit into mysql-5.1 branch (hezx:2632) Bug#37051
View as plain text  
Hi,

Patch is good to push. Great work!

/Sven


He Zhenxing wrote:
> #At file:///media/sda3/work/mysql/bzrwork/b37051/5.1-rpl-new/
> 
>  2632 He Zhenxing	2008-07-31
>       BUG#37051 Replication rules not evaluated correctly
>       
>       The problem of this bug is that we need to get the list of tables
>       to be updated for a multi-table update statement, which requires to
>       open all the tables referenced by the statement and resolve all
>       the fields involved in update in order to figure out the list of
>       tables for update. However if there are replicate filter rules,
>       some tables might not exist on slave and result in a failure
>       before we could examine the filter rules.
>       
>       I think the whole problem can not be solved on slave alone,
>       the master must record and send the information of tables
>       involved for update to slave, so that the slave do not need to
>       open all the tables referenced by the multi-table update statement to
>       figure out which tables are involved for update.
>       
>       So a status variable is added to Query_log event to store the
>       value of table map for update on master. And on slave, it will
>       try to get the value of this variable and use it to examine
>       filter rules without opening any tables on slave, if this values
>       is not available, the old approach is used and thus the bug will
>       still occur for when replicating from old masters.
> added:
>   mysql-test/include/wait_for_slave_sql_error_and_skip.inc
>   mysql-test/suite/rpl/r/rpl_filter_tables_not_exist.result
>   mysql-test/suite/rpl/t/rpl_filter_tables_not_exist-slave.opt
>   mysql-test/suite/rpl/t/rpl_filter_tables_not_exist.test
> modified:
>   sql/log_event.cc
>   sql/log_event.h
>   sql/sql_class.cc
>   sql/sql_class.h
>   sql/sql_parse.cc
>   sql/sql_update.cc
> 
> per-file messages:
>   sql/sql_class.h
>     add member table_map_for_update to THD
>   sql/sql_parse.cc
>     check filter rules by using table_map_for_update value
>   sql/sql_update.cc
>     save the value of table_map_for_update
> === added file 'mysql-test/include/wait_for_slave_sql_error_and_skip.inc'
> --- a/mysql-test/include/wait_for_slave_sql_error_and_skip.inc	1970-01-01 00:00:00
> +0000
> +++ b/mysql-test/include/wait_for_slave_sql_error_and_skip.inc	2008-07-31 06:24:27
> +0000
> @@ -0,0 +1,23 @@
> +# ==== Purpose ====
> +#
> +# Wait for slave SQL error, skip the erroneous statement and restart
> +# slave
> +#
> +# ==== Usage ====
> +#
> +# let show_sql_error=0|1;
> +# source include/wait_for_slave_sql_error_and_skip.inc;
> +
> +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)
> +{
> +  let $error= query_get_value("SHOW SLAVE STATUS", Last_SQL_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;
> 
> === added file 'mysql-test/suite/rpl/r/rpl_filter_tables_not_exist.result'
> --- a/mysql-test/suite/rpl/r/rpl_filter_tables_not_exist.result	1970-01-01 00:00:00
> +0000
> +++ b/mysql-test/suite/rpl/r/rpl_filter_tables_not_exist.result	2008-07-31 06:24:27
> +0000
> @@ -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;
> 
> === added file 'mysql-test/suite/rpl/t/rpl_filter_tables_not_exist-slave.opt'
> --- a/mysql-test/suite/rpl/t/rpl_filter_tables_not_exist-slave.opt	1970-01-01
> 00:00:00 +0000
> +++ b/mysql-test/suite/rpl/t/rpl_filter_tables_not_exist-slave.opt	2008-07-31
> 06:24:27 +0000
> @@ -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
> 
> === added file 'mysql-test/suite/rpl/t/rpl_filter_tables_not_exist.test'
> --- a/mysql-test/suite/rpl/t/rpl_filter_tables_not_exist.test	1970-01-01 00:00:00
> +0000
> +++ b/mysql-test/suite/rpl/t/rpl_filter_tables_not_exist.test	2008-07-31 06:24:27
> +0000
> @@ -0,0 +1,205 @@
> +# 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/have_binlog_format_statement.inc;
> +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;
> +
> +# Parameter for include/wait_for_slave_sql_error_and_skip.inc, ask it
> +# to show SQL error message
> +let show_sql_error=1;
> +
> +#
> +# 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;
> 
> === modified file 'sql/log_event.cc'
> --- a/sql/log_event.cc	2008-05-30 09:12:07 +0000
> +++ b/sql/log_event.cc	2008-07-31 06:24:27 +0000
> @@ -1474,6 +1474,11 @@ void Query_log_event::pack_info(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;
> @@ -1493,21 +1498,8 @@ static void write_str_with_code_and_len(
>  
>  bool Query_log_event::write(IO_CACHE* file)
>  {
> -  /**
> -    @todo if catalog can be of length FN_REFLEN==512, then we are not
> -    replicating it correctly, since the length is stored in a byte
> -    /sven
> -  */
> -  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)
> @@ -1613,10 +1605,8 @@ bool Query_log_event::write(IO_CACHE* fi
>    {
>      /* 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)
>    {
> @@ -1632,7 +1622,17 @@ bool Query_log_event::write(IO_CACHE* fi
>      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)
>      {
> @@ -1709,7 +1709,8 @@ Query_log_event::Query_log_event(THD* th
>     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;
>  
> @@ -1838,6 +1839,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;
> @@ -1874,7 +1876,8 @@ Query_log_event::Query_log_event(const c
>     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;
> @@ -2016,6 +2019,11 @@ Query_log_event::Query_log_event(const c
>        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\
> @@ -2425,6 +2433,8 @@ int Query_log_event::do_apply_event(Rela
>        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);
> 
> === modified file 'sql/log_event.h'
> --- a/sql/log_event.h	2008-03-28 13:52:33 +0000
> +++ b/sql/log_event.h	2008-07-31 06:24:27 +0000
> @@ -237,12 +237,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 */   \
> @@ -306,6 +309,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
> @@ -1455,6 +1460,22 @@ protected:
>      This field is written if it is not 0.
>      </td>
>    </tr>
> +  <tr>
> +    <td>table_map_for_update</td>
> +    <td>Q_TABLE_MAP_FOR_UPDATE_CODE == 9</td>
> +    <td>8 byte integer</td>
> +
> +    <td>The value of the table map that is to be updated by the
> +    multi-table update query statement. Every bit of this variable
> +    represents a table, and is set to 1 if the corresponding table is
> +    to be updated by this statement.
> +
> +    The value of this variable is set when executing a multi-table update
> +    statement and used by slave to apply filter rules without opening
> +    all the tables on slave. This is required because some tables may
> +    not exist on slave because of the filter rules.
> +    </td>
> +  </tr>
>    </table>
>  
>    @subsection Query_log_event_notes_on_previous_versions Notes on Previous Versions
> @@ -1471,6 +1492,9 @@ protected:
>  
>    * See Q_CHARSET_DATABASE_CODE in the table above.
>  
> +  * When adding new status vars, please don't forget to update the
> +  MAX_SIZE_LOG_EVENT_STATUS, and update function code_name
> +
>  */
>  class Query_log_event: public Log_event
>  {
> @@ -1548,6 +1572,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
>  
> 
> === modified file 'sql/sql_class.cc'
> --- a/sql/sql_class.cc	2008-06-19 18:47:59 +0000
> +++ b/sql/sql_class.cc	2008-07-31 06:24:27 +0000
> @@ -1113,6 +1113,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;
>  }
>  
>  
> 
> === modified file 'sql/sql_class.h'
> --- a/sql/sql_class.h	2008-05-20 07:38:17 +0000
> +++ b/sql/sql_class.h	2008-07-31 06:24:27 +0000
> @@ -396,7 +396,6 @@ struct system_variables
>    DATE_TIME_FORMAT *datetime_format;
>    DATE_TIME_FORMAT *time_format;
>    my_bool sysdate_is_now;
> -
>  };
>  
>  
> @@ -1446,6 +1445,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;
> +
>    /* Tells if LAST_INSERT_ID(#) was called for the current statement */
>    bool arg_of_last_insert_id_function;
>    /*
> 
> === modified file 'sql/sql_parse.cc'
> --- a/sql/sql_parse.cc	2008-05-20 16:36:26 +0000
> +++ b/sql/sql_parse.cc	2008-07-31 06:24:27 +0000
> @@ -1887,6 +1887,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");
>  #ifdef WITH_PARTITION_STORAGE_ENGINE
> @@ -1952,6 +1956,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
> @@ -2866,7 +2912,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))
>        {
> 
> === modified file 'sql/sql_update.cc'
> --- a/sql/sql_update.cc	2008-05-20 07:38:17 +0000
> +++ b/sql/sql_update.cc	2008-07-31 06:24:27 +0000
> @@ -1000,7 +1000,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
> 
> 

-- 
Sven Sandberg, Software Engineer
MySQL AB, www.mysql.com
Thread
bzr commit into mysql-5.1 branch (hezx:2632) Bug#37051He Zhenxing31 Jul
  • Re: bzr commit into mysql-5.1 branch (hezx:2632) Bug#37051Sven Sandberg31 Jul