Commit e6ac8830 authored by He Zhenxing's avatar He Zhenxing

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.
parent 62ee3132
# ==== 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;
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;
--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
# 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;
......@@ -1474,6 +1474,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;
......@@ -1493,21 +1498,8 @@ static void write_str_with_code_and_len(char **dst, const char *src,
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* 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)
{
......@@ -1632,7 +1622,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)
{
......@@ -1709,7 +1709,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;
......@@ -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 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;
......@@ -2016,6 +2019,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\
......@@ -2425,6 +2433,8 @@ int Query_log_event::do_apply_event(Relay_log_info const *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);
......
......@@ -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 @@ class Log_event
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 @@ class Log_event
* 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 @@ class Query_log_event: public Log_event
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
......
......@@ -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;
}
......
......@@ -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 @@ class THD :public Statement,
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;
/*
......
......@@ -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
......@@ -1953,6 +1957,48 @@ mysql_execute_command(THD *thd)
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
rules
......@@ -2866,7 +2912,7 @@ mysql_execute_command(THD *thd)
#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))
{
......
......@@ -1000,7 +1000,7 @@ int mysql_multi_update_prepare(THD *thd)
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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment