Commit 4046ed12 authored by Sergei Golubchik's avatar Sergei Golubchik

rbr and savepoint in a subtatement

Apply MySQL fix for bug#76727:
https://github.com/mysql/mysql-server/commit/69d4e72c

  Bug#20901025: SLAVE ASSERTION IN UNPACK_ROW WITH ROLLBACK TO
  SAVEPOINT IN ERROR HANDLER
parent 33ab30df
# ==== Purpose ====
#
# Waits until the slave SQL thread has been synced, i.e., all events
# have been copied over to slave. This is like mtr's built-in command
# sync_slave_with_master, but more flexible (e.g., you can set a
# custom timeout and you can force it to use GTIDs instead of filename
# and offset).
#
#
# ==== Usage ====
#
# [--let $sync_slave_connection= <connection_name>]
# [--let $use_gtids= 1]
# [--let $slave_timeout= NUMBER]
# [--let $rpl_debug= 1]
# --source include/sync_slave_io_with_master.inc
#
# Must be called on the master. Will change connection to the slave.
#
# Parameters:
#
# $use_gtids
# If set, uses GTIDs instead of filename and offset for positions.
#
# $sync_slave_connection
# By default, this script switches connection to 'slave'. If
# $sync_slave_connection is set, then '$sync_slave_connection' is
# used instead of 'slave'.
#
# $slave_timeout
# See include/wait_for_slave_param.inc.
#
# $rpl_debug
# See include/rpl_init.inc
--let $include_filename= sync_slave_sql_with_master.inc
--source include/begin_include_file.inc
save_master_pos;
--let $rpl_connection_name= slave
if ($sync_slave_connection)
{
--let $rpl_connection_name= $sync_slave_connection
}
--source include/rpl_connection.inc
sync_with_master;
--let $include_filename= sync_slave_sql_with_master.inc
--let $skip_restore_connection= 1
--source include/end_include_file.inc
This diff is collapsed.
###############################################################################
# Bug#76727: SLAVE ASSERTION IN UNPACK_ROW WITH ROLLBACK TO
# SAVEPOINT IN ERROR HANDLER
#
# Problem:
# ========
# "SAVEPOINT", "ROLLBACK TO savepoint" wipe out table map on slave during
# execution binary log events. For trigger the map is written to binary log once
# for all trigger body and if trigger contains "SAVEPOINT" or
# "ROLLBACK TO savepoint" statements any trigger's events after these
# statements will not have table map. This results in an assert on slave.
#
# Test:
# =====
# Test case 1:
# Create a trigger with exception handler which rolls back to a savepoint.
# Test proves that there will not be any assert during execution of rolling
# back to savepoint.
#
# Test case 2:
# Create a trigger which calls a procedure which in turn calls an exception
# handler which rolls back to a savepoint. Prove that it doesn't cause any
# assertion during execution.
#
# Test case 3:
# Create a simple trigger which does SAVEPOINT and ROLLBACK TO SAVEPOINT
# and doesn't follow with any other DML statement. Prove that it doesn't cause
# any assertion during execution.
#
# Test case 4:
# Create a trigger with SAVEPOINT and follows with a DML without ROLLBACK TO
# savepoint. Ensure that data is replicated properly.
#
# Test case 5:
# Create a trigger with SAVEPOINT and it does nothing. Do few DMLs following
# the trigger ensure that the data is replicated properly
#
# Test case 6:
# Create a stored function which does SAVEPOINT and ROLLBACK TO
# SAVEPOINT. Do few inserts following the stored function call and ensure that
# no assert is generated on slave and all the rows are replicated to slave.
#
# Test case 7:
# Create a stored function which creates a SAVEPOINT alone and follows with
# DMLs without ROLLBACK TO savepoint. Ensure that data is replicated properly.
#
# Test case 8:
# Create a stored function which has SAVEPOINT inside it and does noting. It
# should follow with other DMLs. Ensure that data is replicated properly.
###############################################################################
--source include/have_binlog_format_row.inc
--source include/have_innodb.inc
--source include/master-slave.inc
--echo #Test case 1:
CREATE TABLE t1 (f1 INTEGER PRIMARY KEY) ENGINE=INNODB;
CREATE TABLE t2 (f1 INTEGER PRIMARY KEY) ENGINE=INNODB;
CREATE TABLE t3 (f1 INTEGER PRIMARY KEY) ENGINE=INNODB;
DELIMITER |;
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK TO event_logging_1;
INSERT t3 VALUES (1);
END;
SAVEPOINT event_logging_1;
INSERT INTO t2 VALUES (1);
RELEASE SAVEPOINT event_logging_1;
END|
DELIMITER ;|
INSERT INTO t2 VALUES (1);
INSERT INTO t1 VALUES (1);
--source include/show_binlog_events.inc
--sync_slave_with_master
--source include/rpl_connection_master.inc
DROP TRIGGER tr1;
DELETE FROM t1;
DELETE FROM t2;
DELETE FROM t3;
--echo # Test case 2:
DELIMITER |;
CREATE PROCEDURE p1()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK TO event_logging_2;
INSERT t3 VALUES (3);
END;
SAVEPOINT event_logging_2;
INSERT INTO t2 VALUES (1);
RELEASE SAVEPOINT event_logging_2;
END|
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW CALL p1()|
DELIMITER ;|
INSERT INTO t2 VALUES (1);
INSERT INTO t1 VALUES (1);
--source include/show_binlog_events.inc
--sync_slave_with_master
--source include/rpl_connection_master.inc
DROP TABLE t1;
DROP TABLE t2;
DROP TABLE t3;
DROP PROCEDURE p1;
--echo # Test case 3:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t (f1 int(10) unsigned NOT NULL, PRIMARY KEY (f1)) ENGINE=InnoDB;
--delimiter |
CREATE TRIGGER t_insert_trig AFTER INSERT ON t FOR EACH ROW
BEGIN
SAVEPOINT savepoint_1;
ROLLBACK TO savepoint_1;
END |
--delimiter ;
INSERT INTO t VALUES (2);
INSERT INTO t VALUES (3);
--source include/show_binlog_events.inc
SELECT * FROM t;
--source include/sync_slave_sql_with_master.inc
SELECT * FROM t;
--source include/rpl_connection_master.inc
DROP TABLE t;
--echo # Test case 4:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t (f1 int(10) unsigned NOT NULL) ENGINE=InnoDB;
CREATE TABLE t1 (f1 int(10) unsigned NOT NULL) ENGINE=InnoDB;
--delimiter |
CREATE TRIGGER t_insert_trig BEFORE INSERT ON t FOR EACH ROW
BEGIN
SAVEPOINT savepoint_1;
INSERT INTO t1 VALUES (5);
END |
--delimiter ;
INSERT INTO t VALUES (2), (3);
INSERT INTO t1 VALUES (30);
--source include/show_binlog_events.inc
SELECT * FROM t;
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc
SELECT * FROM t;
SELECT * FROM t1;
--source include/rpl_connection_master.inc
DROP TABLE t;
DROP TABLE t1;
--echo # Test case 5:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t (f1 int(10) unsigned NOT NULL) ENGINE=InnoDB;
CREATE TABLE t1 (f1 int(10) unsigned NOT NULL) ENGINE=InnoDB;
--delimiter |
CREATE TRIGGER t_insert_trig BEFORE INSERT ON t
FOR EACH ROW
BEGIN
SAVEPOINT savepoint_1;
END |
--delimiter ;
INSERT INTO t VALUES (2), (3);
INSERT INTO t1 VALUES (30);
--source include/show_binlog_events.inc
SELECT * FROM t;
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc
SELECT * FROM t;
SELECT * FROM t1;
--source include/rpl_connection_master.inc
DROP TABLE t;
DROP TABLE t1;
--echo # Test case 6:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t1 (f1 INTEGER ) ENGINE=INNODB;
CREATE TABLE t2 (f1 INTEGER ) ENGINE=INNODB;
--delimiter |
CREATE FUNCTION f1() RETURNS INT
BEGIN
SAVEPOINT event_logging_2;
INSERT INTO t1 VALUES (1);
ROLLBACK TO event_logging_2;
RETURN 0;
END|
--delimiter ;
BEGIN;
INSERT INTO t2 VALUES (1), (f1()), (2), (4);
COMMIT;
INSERT INTO t2 VALUES (10);
--source include/show_binlog_events.inc
--source include/rpl_connection_master.inc
SELECT * FROM t2;
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc
SELECT * FROM t2;
SELECT * FROM t1;
--source include/rpl_connection_master.inc
DROP TABLE t1;
DROP TABLE t2;
DROP FUNCTION f1;
--echo # Test case 7:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t1 (f1 INTEGER ) ENGINE=INNODB;
CREATE TABLE t2 (f1 INTEGER ) ENGINE=INNODB;
--delimiter |
CREATE FUNCTION f1() RETURNS INT
BEGIN
SAVEPOINT event_logging_2;
INSERT INTO t1 VALUES (1);
RETURN 0;
END|
--delimiter ;
BEGIN;
INSERT INTO t2 VALUES (1), (f1()), (2), (4);
COMMIT;
INSERT INTO t2 VALUES (10);
--source include/show_binlog_events.inc
--source include/rpl_connection_master.inc
SELECT * FROM t2;
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc
SELECT * FROM t2;
SELECT * FROM t1;
--source include/rpl_connection_master.inc
DROP TABLE t1;
DROP TABLE t2;
DROP FUNCTION f1;
--echo # Test case 8:
--source include/rpl_reset.inc
--source include/rpl_connection_master.inc
CREATE TABLE t1 (f1 INTEGER ) ENGINE=INNODB;
--delimiter |
CREATE FUNCTION f1() RETURNS INT
BEGIN
SAVEPOINT event_logging_2;
RETURN 0;
END|
--delimiter ;
BEGIN;
INSERT INTO t1 VALUES (1), (f1()), (2), (4);
COMMIT;
INSERT INTO t1 VALUES (10);
--source include/show_binlog_events.inc
--source include/rpl_connection_master.inc
SELECT * FROM t1;
--source include/sync_slave_sql_with_master.inc
SELECT * FROM t1;
--source include/rpl_connection_master.inc
DROP TABLE t1;
DROP FUNCTION f1;
--source include/rpl_end.inc
......@@ -2218,6 +2218,7 @@ static int binlog_savepoint_set(handlerton *hton, THD *thd, void *sv)
DBUG_RETURN(0);
char buf[1024];
String log_query(buf, sizeof(buf), &my_charset_bin);
if (log_query.copy(STRING_WITH_LEN("SAVEPOINT "), &my_charset_bin) ||
append_identifier(thd, &log_query,
......@@ -2272,6 +2273,17 @@ static int binlog_savepoint_rollback(handlerton *hton, THD *thd, void *sv)
binlog_trans_log_truncate(thd, *(my_off_t*)sv);
/*
When a SAVEPOINT is executed inside a stored function/trigger we force the
pending event to be flushed with a STMT_END_F flag and clear the table maps
as well to ensure that following DMLs will have a clean state to start
with. ROLLBACK inside a stored routine has to finalize possibly existing
current row-based pending event with cleaning up table maps. That ensures
that following DMLs will have a clean state to start with.
*/
if (thd->in_sub_stmt)
thd->clear_binlog_table_maps();
DBUG_RETURN(0);
}
......@@ -6043,10 +6055,17 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate)
/*
We only end the statement if we are in a top-level statement. If
we are inside a stored function, we do not end the statement since
this will close all tables on the slave.
this will close all tables on the slave. But there can be a special case
where we are inside a stored function/trigger and a SAVEPOINT is being
set in side the stored function/trigger. This SAVEPOINT execution will
force the pending event to be flushed without an STMT_END_F flag. This
will result in a case where following DMLs will be considered as part of
same statement and result in data loss on slave. Hence in this case we
force the end_stmt to be true.
*/
bool const end_stmt=
thd->locked_tables_mode && thd->lex->requires_prelocking();
bool const end_stmt= (thd->in_sub_stmt && thd->lex->sql_command ==
SQLCOM_SAVEPOINT) ? true :
(thd->locked_tables_mode && thd->lex->requires_prelocking());
if (thd->binlog_flush_pending_rows_event(end_stmt, using_trans))
DBUG_RETURN(error);
......
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