Commit 0c1bf5e2 authored by Brandon Nesterenko's avatar Brandon Nesterenko

MDEV-27247: Add keywords "SQL_BEFORE_GTIDS" and "SQL_AFTER_GTIDS" for START SLAVE UNTIL

New Feature:
============
This patch extends the START SLAVE UNTIL command with options
SQL_BEFORE_GTIDS and SQL_AFTER_GTIDS to allow user control of
whether the replica stops before or after a provided GTID state. Its
syntax is:

START SLAVE UNTIL (SQL_BEFORE_GTIDS|SQL_AFTER_GTIDS)=”<gtid_list>”

When providing SQL_BEFORE_GTIDS=”<gtid_list>”, for each domain
specified in the gtid_list, the replica will execute transactions up
to the GTID found, and immediately stop processing events in that
domain (without executing the transaction of the specified GTID).
Once all domains have stopped, the replica will stop. Events
originating from domains that are not specified in the list are not
replicated.

START SLAVE UNTIL SQL_AFTER_GTIDS=”<gtid_list>” is an alias to the
default behavior of START SLAVE UNTIL master_gtid_pos=”<gtid_list>”.
That is, the replica will only execute transactions originating from
domain ids provided in the list, and will stop once all transactions
provided in the UNTIL list have all been executed.

Example:
=========
If a primary server has a binary log consisting of the following GTIDs:

0-1-1
1-1-1
0-1-2
1-1-2
0-1-3
1-1-3

If a fresh replica (i.e. one with an empty GTID position,
@@gtid_slave_pos='') is started with SQL_BEFORE_GTIDS, i.e.

START SLAVE UNTIL SQL_BEFORE_GTIDS=”1-1-2”

The resulting gtid_slave_pos of the replica will be “1-1-1”.
This is because the replica will execute only events from domain 1
until it sees the transaction with sequence number 2, and
immediately stop without executing it.

If the replica is started with SQL_AFTER_GTIDS, i.e.

START SLAVE UNTIL SQL_AFTER_GTIDS=”1-1-2”

then the resulting gtid_slave_pos of the replica will be “1-1-2”.
This is because it will only execute events from domain 1 until it
has executed the provided GTID.

Reviewed By:
============
Kristian Nielson <knielsen@knielsen-hq.org>
parent 0e8dfcfd
......@@ -191,17 +191,17 @@ SELECT SCHEMA_NAME, DIGEST, DIGEST_TEXT, COUNT_STAR
FROM performance_schema.events_statements_summary_by_digest
ORDER BY DIGEST_TEXT;
SCHEMA_NAME DIGEST DIGEST_TEXT COUNT_STAR
test 78b80220002834f612d11b0663f64d59 EXPLAIN SELECT * FROM `test` . `v1` 1
test 9649c572f7c7b927e314d31620294e34 EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 1
test 304c0393779f7b183065e7b577f1be26 EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 1
test 6e400ce1796d40cfefa45333d6e5895c EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 1
test 36c8726233a5c621742d35107d72e5e0 EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 1
test 0aeb23572eed79a9e05cafe0e9cd1909 SELECT * FROM `test` . `v1` 1
test 28bd92caf5c189316fab14a67b622203 SELECT * FROM `test` . `v1` WHERE `a` = ? 1
test 637dba52704594bc4275ba3f37b8f851 SELECT * FROM `test` . `v1` WHERE `b` > ? 1
test fd8e83e523b0eec97a94eef612154591 SELECT `a` , `b` FROM `test` . `v1` 1
test c58ed156113959965ebf619b6dd3a8b2 SELECT `b` , `a` FROM `test` . `v1` 1
test 4d9d22440ce86533e3fac764ab259bbd TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1
test b662d7ab4e8aa67e3aaeb81957a38e97 EXPLAIN SELECT * FROM `test` . `v1` 1
test 4a6602212e0e1386dada25a12dc2315f EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 1
test 7b3a13b2c268ba0a72aade33fb3b9d7d EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 1
test 840a987076e3bd7b25603240595f47b9 EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 1
test 955679ae1c2068ade1003c820f1cdd58 EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 1
test 3fe51e497b828a521e0b404f7c0e0ac6 SELECT * FROM `test` . `v1` 1
test 2bec69e086533e335e311b159eee104a SELECT * FROM `test` . `v1` WHERE `a` = ? 1
test 4e995969bf7999998047ed967d8ebf45 SELECT * FROM `test` . `v1` WHERE `b` > ? 1
test 51f783c563f3af0cdca3065baace1236 SELECT `a` , `b` FROM `test` . `v1` 1
test be34ebc622c2d81afca10cc511a06af9 SELECT `b` , `a` FROM `test` . `v1` 1
test af6cd26b7f88eafb1ff05602a49ce39e TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1
DROP TABLE test.v1;
CREATE VIEW test.v1 AS SELECT * FROM test.t1;
EXPLAIN SELECT * from test.v1;
......@@ -248,19 +248,19 @@ SELECT SCHEMA_NAME, DIGEST, DIGEST_TEXT, COUNT_STAR
FROM performance_schema.events_statements_summary_by_digest
ORDER BY DIGEST_TEXT;
SCHEMA_NAME DIGEST DIGEST_TEXT COUNT_STAR
test 4ccb56972e9c19941d4928d31502e796 CREATE VIEW `test` . `v1` AS SELECT * FROM `test` . `t1` 1
test a087be31e6440102676ef0171b8b2734 DROP TABLE `test` . `v1` 1
test 78b80220002834f612d11b0663f64d59 EXPLAIN SELECT * FROM `test` . `v1` 2
test 9649c572f7c7b927e314d31620294e34 EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 2
test 304c0393779f7b183065e7b577f1be26 EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 2
test 6e400ce1796d40cfefa45333d6e5895c EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 2
test 36c8726233a5c621742d35107d72e5e0 EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 2
test 0aeb23572eed79a9e05cafe0e9cd1909 SELECT * FROM `test` . `v1` 2
test 28bd92caf5c189316fab14a67b622203 SELECT * FROM `test` . `v1` WHERE `a` = ? 2
test 637dba52704594bc4275ba3f37b8f851 SELECT * FROM `test` . `v1` WHERE `b` > ? 2
test 765bf27a2d45249dcc6377bcc02e1c4b SELECT SCHEMA_NAME , `DIGEST` , `DIGEST_TEXT` , `COUNT_STAR` FROM `performance_schema` . `events_statements_summary_by_digest` ORDER BY `DIGEST_TEXT` 1
test fd8e83e523b0eec97a94eef612154591 SELECT `a` , `b` FROM `test` . `v1` 2
test c58ed156113959965ebf619b6dd3a8b2 SELECT `b` , `a` FROM `test` . `v1` 2
test 4d9d22440ce86533e3fac764ab259bbd TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1
test 1c3d8fb894f91a3cca115966b8c99921 CREATE VIEW `test` . `v1` AS SELECT * FROM `test` . `t1` 1
test 6b0df2c35a3d2ac98a4e4b43a6e78fe2 DROP TABLE `test` . `v1` 1
test b662d7ab4e8aa67e3aaeb81957a38e97 EXPLAIN SELECT * FROM `test` . `v1` 2
test 4a6602212e0e1386dada25a12dc2315f EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 2
test 7b3a13b2c268ba0a72aade33fb3b9d7d EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 2
test 840a987076e3bd7b25603240595f47b9 EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 2
test 955679ae1c2068ade1003c820f1cdd58 EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 2
test 3fe51e497b828a521e0b404f7c0e0ac6 SELECT * FROM `test` . `v1` 2
test 2bec69e086533e335e311b159eee104a SELECT * FROM `test` . `v1` WHERE `a` = ? 2
test 4e995969bf7999998047ed967d8ebf45 SELECT * FROM `test` . `v1` WHERE `b` > ? 2
test 68543d0eacb67ded0fe3280a7be99f73 SELECT SCHEMA_NAME , `DIGEST` , `DIGEST_TEXT` , `COUNT_STAR` FROM `performance_schema` . `events_statements_summary_by_digest` ORDER BY `DIGEST_TEXT` 1
test 51f783c563f3af0cdca3065baace1236 SELECT `a` , `b` FROM `test` . `v1` 2
test be34ebc622c2d81afca10cc511a06af9 SELECT `b` , `a` FROM `test` . `v1` 2
test af6cd26b7f88eafb1ff05602a49ce39e TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1
DROP VIEW test.v1;
DROP TABLE test.t1;
......@@ -8,5 +8,5 @@ SELECT 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1
####################################
SELECT event_name, digest, digest_text, sql_text FROM events_statements_history_long;
event_name digest digest_text sql_text
statement/sql/select be5dd6a08f2e34c86168c154f61cbf8c SELECT ? + ? + SELECT ...
statement/sql/truncate e2b84d4b47baf073fa24b65f724e9431 TRUNCATE TABLE truncat...
statement/sql/select 141772a2d94a8dd7d4795ffcf2cb1e6c SELECT ? + ? + SELECT ...
statement/sql/truncate 188ff5124e67b3e6d57b8d5b7fd10694 TRUNCATE TABLE truncat...
#
# Helper file to run through test cases to validate that the replica will stop
# at the correct place when running STOP SLAVE UNTIL with options
# SQL_BEFORE_GTIDS and SQL_AFTER_GTIDS.
#
# MTR Parameters:
# ssu_before_gtids (Boolean): Indicates whether to test SQL_BEFORE_GTIDS,
# (when true), or SQL_AFTER_GTIDS (when false).
#
--let $include_filename= rpl_gtid_until_before_after_gtids.inc
--source include/begin_include_file.inc
if ($ssu_before_gtids)
{
--let $ssu_opt=SQL_BEFORE_GTIDS
}
if (!$ssu_before_gtids)
{
--let $ssu_opt=SQL_AFTER_GTIDS
}
--echo #
--echo # Test Setup ($ssu_opt)
--echo # Clean primary and replica states
--connection master
--source include/save_master_gtid.inc
--connection slave
--source include/stop_slave.inc
--source include/start_slave.inc
--source include/sync_with_master_gtid.inc
--source include/stop_slave.inc
--source include/reset_slave.inc
--connection master
RESET MASTER;
set session gtid_domain_id=0;
--echo # Initialize test data
--connection master
create table t1 (a int);
create table t2 (a int);
# Set the value counters to use on insertions. Note they are only set once per
# test, subsequent invocations of this .inc file continue to increment the
# previous values.
if (!$t1_ctr)
{
--let $t1_ctr= 100
--let $t2_ctr= 200
}
--source include/save_master_gtid.inc
--connection slave
--source include/start_slave.inc
--source include/sync_with_master_gtid.inc
--echo #
--echo # Test Case 1 ($ssu_opt): For a single-dimensional binlog state and a
--echo # STOP SLAVE UNTIL gtid position with one GTID, the replica should
if ($ssu_before_gtids)
{
--echo # execute events up until the GTID is encountered, and immediately stop
}
if (!$ssu_before_gtids)
{
--echo # execute events up until the GTID is encountered, finish replicating
--echo # that event group, and then stop
}
--connection slave
--source include/stop_slave.inc
--let $initial_slave_pos= query_get_value(SHOW ALL SLAVES STATUS, Exec_Master_Log_Pos, 1)
--connection master
set session gtid_domain_id=0;
--eval INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
if ($ssu_before_gtids)
{
--let $expected_stop_gtid= `SELECT @@gtid_binlog_pos`
}
--eval INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
--let $until_gtid= `SELECT @@gtid_binlog_pos`
if (!$ssu_before_gtids)
{
--let $expected_stop_gtid= `SELECT @@gtid_binlog_pos`
}
--eval INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
--connection slave
--eval START SLAVE UNTIL $ssu_opt="$until_gtid"
--echo # Ensure the slave started
--let $slave_param= Exec_Master_Log_Pos
--let $slave_param_comparison= !=
--let $slave_param_value= $initial_slave_pos
--source include/wait_for_slave_param.inc
--let $slave_param_comparison= =
--source include/wait_for_slave_to_stop.inc
--let $actual_stop_gtid= `SELECT @@gtid_slave_pos`
if (`SELECT strcmp("$expected_stop_gtid","$actual_stop_gtid") != 0`)
{
--echo # Expected stop gtid != actual stop gtid ($expected_stop_gtid != $actual_stop_gtid)
--die Expected stop gtid != actual stop gtid
}
--echo # Clean replica state
--connection master
--source include/save_master_gtid.inc
--connection slave
--source include/start_slave.inc
--source include/sync_with_master_gtid.inc
--echo #
--echo # Test Case 2 ($ssu_opt): If a provided until GTID doesn't exist in the
--echo # binary log due to a gap, once an event is seen that is beyond the
--echo # until GTID, the slave should immediately stop. Note the behavior of
--echo # this test case should be the same between SQL_BEFORE_GTIDS and
--echo # SQL_AFTER_GTIDS.
--connection slave
--let $initial_slave_pos= query_get_value(SHOW ALL SLAVES STATUS, Exec_Master_Log_Pos, 1)
--source include/stop_slave.inc
--connection master
set session gtid_domain_id=0;
--eval INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
--eval INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
--echo # Skip a seq_no
--let $binlog_pos= `SELECT @@gtid_binlog_pos`
--let $domain_id= `SELECT @@gtid_domain_id`
--let $server_id= `SELECT @@server_id`
--let $last_seq_no= `SELECT REGEXP_SUBSTR('$binlog_pos','[0-9]+\\\$')`
--let $skipped_seq_no= `SELECT ($last_seq_no + 1)`
--let $new_seq_no= `SELECT ($skipped_seq_no + 1)`
--eval set @@session.gtid_seq_no= $new_seq_no
--let $until_gtid= $domain_id-$server_id-$skipped_seq_no
--let $expected_stop_gtid= $binlog_pos
--eval INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
--connection slave
--eval START SLAVE UNTIL $ssu_opt="$until_gtid"
--echo # Ensure the slave started
--let $slave_param= Exec_Master_Log_Pos
--let $slave_param_comparison= !=
--let $slave_param_value= $initial_slave_pos
--source include/wait_for_slave_param.inc
--let $slave_param_comparison= =
--source include/wait_for_slave_to_stop.inc
--let $actual_stop_gtid= `SELECT @@gtid_slave_pos`
if (`SELECT strcmp("$expected_stop_gtid","$actual_stop_gtid") != 0`)
{
--echo # Expected stop gtid != actual stop gtid ($expected_stop_gtid != $actual_stop_gtid)
--die Expected stop gtid != actual stop gtid
}
--connection slave
--source include/start_slave.inc
--connection master
--sync_slave_with_master
--echo #
--echo # Test Case 3 ($ssu_opt): For a multi-dimensional binlog state and a
--echo # STOP SLAVE UNTIL gtid position with one GTID, the replica should
--echo # execute events from only the specified domain until the provided GTID
if ($ssu_before_gtids)
{
--echo # is encountered, and immediately stop
}
if (!$ssu_before_gtids)
{
--echo # is encountered, finish replicating that event group, and then stop
}
--connection slave
--source include/stop_slave.inc
--let $initial_slave_pos= query_get_value(SHOW ALL SLAVES STATUS, Exec_Master_Log_Pos, 1)
--connection master
set session gtid_domain_id=0;
--eval INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
set session gtid_domain_id=1;
--eval INSERT INTO t2 VALUES ($t2_ctr)
--inc $t2_ctr
--eval INSERT INTO t2 VALUES ($t2_ctr)
--inc $t2_ctr
if ($ssu_before_gtids)
{
# Will have GTIDs for both domains 0 and 1
--let $binlog_pos= `SELECT @@gtid_binlog_pos`
--let $expected_stop_gtid= `SELECT REGEXP_SUBSTR('$binlog_pos','0-[0-9]+-[0-9]+')`
}
set session gtid_domain_id=0;
--eval INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
--let $binlog_pos= `SELECT @@gtid_binlog_pos`
# Just the GTID for domain 0
--let $until_gtid= `SELECT REGEXP_SUBSTR('$binlog_pos','0-[0-9]+-[0-9]+')`
if (!$ssu_before_gtids)
{
--let $expected_stop_gtid= $until_gtid
}
set session gtid_domain_id=1;
--eval INSERT INTO t2 VALUES ($t2_ctr)
--inc $t2_ctr
set session gtid_domain_id=0;
--connection slave
--eval START SLAVE UNTIL $ssu_opt="$until_gtid"
--echo # Ensure the slave started
--let $slave_param= Exec_Master_Log_Pos
--let $slave_param_comparison= !=
--let $slave_param_value= $initial_slave_pos
--source include/wait_for_slave_param.inc
--let $slave_param_comparison= =
--source include/wait_for_slave_to_stop.inc
--let $actual_stop_gtid= `SELECT @@gtid_slave_pos`
if (`SELECT strcmp("$expected_stop_gtid","$actual_stop_gtid") != 0`)
{
--echo # Expected stop gtid != actual stop gtid ($expected_stop_gtid != $actual_stop_gtid)
--die Expected stop gtid != actual stop gtid
}
--connection slave
--source include/start_slave.inc
--connection master
--sync_slave_with_master
--echo #
--echo # Test Case 4 ($ssu_opt): For a multi-dimensional binlog state and a
--echo # STOP SLAVE UNTIL gtid position with multiple GTIDs, the replica should
if ($ssu_before_gtids)
{
--echo # for each domain, execute events only up until its provided GTID, and
--echo # once all domains have hit their end point, immediately stop.
}
if (!$ssu_before_gtids)
{
--echo # stop executing events as soon as all listed GTIDs in the UNTIL list
--echo # have been executed.
}
--connection slave
--source include/stop_slave.inc
--let $initial_slave_pos= query_get_value(SHOW ALL SLAVES STATUS, Exec_Master_Log_Pos, 1)
--connection master
--eval SET STATEMENT gtid_domain_id=0 FOR INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
if ($ssu_before_gtids)
{
# Save binlog pos for domain 0
--let $expected_stop_gtid_d0= `SELECT REGEXP_SUBSTR(@@global.gtid_binlog_pos,'0-[0-9]+-[0-9]+')`
--echo # Tagging domain 0 stop: $expected_stop_gtid_d0
}
--eval SET STATEMENT gtid_domain_id=0 FOR INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
--eval SET STATEMENT gtid_domain_id=1 FOR INSERT INTO t2 VALUES ($t2_ctr)
--inc $t2_ctr
if ($ssu_before_gtids)
{
# Save binlog pos for domain 1
--let $expected_stop_gtid_d1= `SELECT REGEXP_SUBSTR(@@global.gtid_binlog_pos,'1-[0-9]+-[0-9]+')`
--let $expected_stop_gtid= $expected_stop_gtid_d0,$expected_stop_gtid_d1
}
--eval SET STATEMENT gtid_domain_id=1 FOR INSERT INTO t2 VALUES ($t2_ctr)
--inc $t2_ctr
--let $until_gtid= `SELECT @@gtid_binlog_pos`
if (!$ssu_before_gtids)
{
--let $expected_stop_gtid= $until_gtid
}
--eval SET STATEMENT gtid_domain_id=0 FOR INSERT INTO t1 VALUES ($t1_ctr)
--inc $t1_ctr
--eval SET STATEMENT gtid_domain_id=1 FOR INSERT INTO t2 VALUES ($t2_ctr)
--inc $t2_ctr
--connection slave
--eval START SLAVE UNTIL $ssu_opt="$until_gtid"
--echo # Ensure the slave started
--let $slave_param= Exec_Master_Log_Pos
--let $slave_param_comparison= !=
--let $slave_param_value= $initial_slave_pos
--source include/wait_for_slave_param.inc
--let $slave_param_comparison= =
--source include/wait_for_slave_to_stop.inc
--let $actual_stop_gtid= `SELECT @@gtid_slave_pos`
if (`SELECT strcmp("$expected_stop_gtid","$actual_stop_gtid") != 0`)
{
--echo # Expected stop gtid != actual stop gtid ($expected_stop_gtid != $actual_stop_gtid)
--die Expected stop gtid != actual stop gtid
}
--connection slave
--source include/start_slave.inc
--connection master
--sync_slave_with_master
--echo #
--echo # Error Case 1: Not providing a valid GTID should result in a syntax
--echo # error
--connection slave
--source include/stop_slave.inc
--error ER_INCORRECT_GTID_STATE
--eval START SLAVE UNTIL $ssu_opt="a"
--error ER_INCORRECT_GTID_STATE
--eval START SLAVE UNTIL $ssu_opt="0"
--error ER_INCORRECT_GTID_STATE
--eval START SLAVE UNTIL $ssu_opt="0-1"
--error ER_INCORRECT_GTID_STATE
--eval START SLAVE UNTIL $ssu_opt="0-1-"
--error ER_INCORRECT_GTID_STATE
--eval START SLAVE UNTIL $ssu_opt="a-b-c"
--source include/start_slave.inc
--echo #
--echo # Cleanup test data
--connection master
DROP TABLE t1, t2;
--source include/save_master_gtid.inc
--connection slave
--source include/sync_with_master_gtid.inc
--let $include_filename= rpl_gtid_until_before_after_gtids.inc
--source include/end_include_file.inc
include/master-slave.inc
[connection master]
include/rpl_gtid_until_before_after_gtids.inc
#
# Test Setup (SQL_BEFORE_GTIDS)
# Clean primary and replica states
connection master;
connection slave;
connection master;
RESET MASTER;
set session gtid_domain_id=0;
# Initialize test data
connection master;
create table t1 (a int);
create table t2 (a int);
connection slave;
#
# Test Case 1 (SQL_BEFORE_GTIDS): For a single-dimensional binlog state and a
# STOP SLAVE UNTIL gtid position with one GTID, the replica should
# execute events up until the GTID is encountered, and immediately stop
connection slave;
connection master;
set session gtid_domain_id=0;
INSERT INTO t1 VALUES (100);
INSERT INTO t1 VALUES (101);
INSERT INTO t1 VALUES (102);
connection slave;
START SLAVE UNTIL SQL_BEFORE_GTIDS="0-1-4";
# Ensure the slave started
# Clean replica state
connection master;
connection slave;
#
# Test Case 2 (SQL_BEFORE_GTIDS): If a provided until GTID doesn't exist in the
# binary log due to a gap, once an event is seen that is beyond the
# until GTID, the slave should immediately stop. Note the behavior of
# this test case should be the same between SQL_BEFORE_GTIDS and
# SQL_AFTER_GTIDS.
connection slave;
connection master;
set session gtid_domain_id=0;
INSERT INTO t1 VALUES (103);
INSERT INTO t1 VALUES (104);
# Skip a seq_no
set @@session.gtid_seq_no= 9;
INSERT INTO t1 VALUES (105);
connection slave;
START SLAVE UNTIL SQL_BEFORE_GTIDS="0-1-8";
# Ensure the slave started
connection slave;
connection master;
connection slave;
#
# Test Case 3 (SQL_BEFORE_GTIDS): For a multi-dimensional binlog state and a
# STOP SLAVE UNTIL gtid position with one GTID, the replica should
# execute events from only the specified domain until the provided GTID
# is encountered, and immediately stop
connection slave;
connection master;
set session gtid_domain_id=0;
INSERT INTO t1 VALUES (106);
set session gtid_domain_id=1;
INSERT INTO t2 VALUES (200);
INSERT INTO t2 VALUES (201);
set session gtid_domain_id=0;
INSERT INTO t1 VALUES (107);
set session gtid_domain_id=1;
INSERT INTO t2 VALUES (202);
set session gtid_domain_id=0;
connection slave;
START SLAVE UNTIL SQL_BEFORE_GTIDS="0-1-11";
# Ensure the slave started
connection slave;
connection master;
connection slave;
#
# Test Case 4 (SQL_BEFORE_GTIDS): For a multi-dimensional binlog state and a
# STOP SLAVE UNTIL gtid position with multiple GTIDs, the replica should
# for each domain, execute events only up until its provided GTID, and
# once all domains have hit their end point, immediately stop.
connection slave;
connection master;
SET STATEMENT gtid_domain_id=0 FOR INSERT INTO t1 VALUES (108);
# Tagging domain 0 stop: 0-1-12
SET STATEMENT gtid_domain_id=0 FOR INSERT INTO t1 VALUES (109);
SET STATEMENT gtid_domain_id=1 FOR INSERT INTO t2 VALUES (203);
SET STATEMENT gtid_domain_id=1 FOR INSERT INTO t2 VALUES (204);
SET STATEMENT gtid_domain_id=0 FOR INSERT INTO t1 VALUES (110);
SET STATEMENT gtid_domain_id=1 FOR INSERT INTO t2 VALUES (205);
connection slave;
START SLAVE UNTIL SQL_BEFORE_GTIDS="0-1-13,1-1-5";
# Ensure the slave started
connection slave;
connection master;
connection slave;
#
# Error Case 1: Not providing a valid GTID should result in a syntax
# error
connection slave;
START SLAVE UNTIL SQL_BEFORE_GTIDS="a";
ERROR HY000: Could not parse GTID list
START SLAVE UNTIL SQL_BEFORE_GTIDS="0";
ERROR HY000: Could not parse GTID list
START SLAVE UNTIL SQL_BEFORE_GTIDS="0-1";
ERROR HY000: Could not parse GTID list
START SLAVE UNTIL SQL_BEFORE_GTIDS="0-1-";
ERROR HY000: Could not parse GTID list
START SLAVE UNTIL SQL_BEFORE_GTIDS="a-b-c";
ERROR HY000: Could not parse GTID list
#
# Cleanup test data
connection master;
DROP TABLE t1, t2;
connection slave;
include/rpl_gtid_until_before_after_gtids.inc
#
# Test Setup (SQL_AFTER_GTIDS)
# Clean primary and replica states
connection master;
connection slave;
connection master;
RESET MASTER;
set session gtid_domain_id=0;
# Initialize test data
connection master;
create table t1 (a int);
create table t2 (a int);
connection slave;
#
# Test Case 1 (SQL_AFTER_GTIDS): For a single-dimensional binlog state and a
# STOP SLAVE UNTIL gtid position with one GTID, the replica should
# execute events up until the GTID is encountered, finish replicating
# that event group, and then stop
connection slave;
connection master;
set session gtid_domain_id=0;
INSERT INTO t1 VALUES (111);
INSERT INTO t1 VALUES (112);
INSERT INTO t1 VALUES (113);
connection slave;
START SLAVE UNTIL SQL_AFTER_GTIDS="0-1-4";
# Ensure the slave started
# Clean replica state
connection master;
connection slave;
#
# Test Case 2 (SQL_AFTER_GTIDS): If a provided until GTID doesn't exist in the
# binary log due to a gap, once an event is seen that is beyond the
# until GTID, the slave should immediately stop. Note the behavior of
# this test case should be the same between SQL_BEFORE_GTIDS and
# SQL_AFTER_GTIDS.
connection slave;
connection master;
set session gtid_domain_id=0;
INSERT INTO t1 VALUES (114);
INSERT INTO t1 VALUES (115);
# Skip a seq_no
set @@session.gtid_seq_no= 9;
INSERT INTO t1 VALUES (116);
connection slave;
START SLAVE UNTIL SQL_AFTER_GTIDS="0-1-8";
# Ensure the slave started
connection slave;
connection master;
connection slave;
#
# Test Case 3 (SQL_AFTER_GTIDS): For a multi-dimensional binlog state and a
# STOP SLAVE UNTIL gtid position with one GTID, the replica should
# execute events from only the specified domain until the provided GTID
# is encountered, finish replicating that event group, and then stop
connection slave;
connection master;
set session gtid_domain_id=0;
INSERT INTO t1 VALUES (117);
set session gtid_domain_id=1;
INSERT INTO t2 VALUES (206);
INSERT INTO t2 VALUES (207);
set session gtid_domain_id=0;
INSERT INTO t1 VALUES (118);
set session gtid_domain_id=1;
INSERT INTO t2 VALUES (208);
set session gtid_domain_id=0;
connection slave;
START SLAVE UNTIL SQL_AFTER_GTIDS="0-1-11";
# Ensure the slave started
connection slave;
connection master;
connection slave;
#
# Test Case 4 (SQL_AFTER_GTIDS): For a multi-dimensional binlog state and a
# STOP SLAVE UNTIL gtid position with multiple GTIDs, the replica should
# stop executing events as soon as all listed GTIDs in the UNTIL list
# have been executed.
connection slave;
connection master;
SET STATEMENT gtid_domain_id=0 FOR INSERT INTO t1 VALUES (119);
SET STATEMENT gtid_domain_id=0 FOR INSERT INTO t1 VALUES (120);
SET STATEMENT gtid_domain_id=1 FOR INSERT INTO t2 VALUES (209);
SET STATEMENT gtid_domain_id=1 FOR INSERT INTO t2 VALUES (210);
SET STATEMENT gtid_domain_id=0 FOR INSERT INTO t1 VALUES (121);
SET STATEMENT gtid_domain_id=1 FOR INSERT INTO t2 VALUES (211);
connection slave;
START SLAVE UNTIL SQL_AFTER_GTIDS="0-1-13,1-1-5";
# Ensure the slave started
connection slave;
connection master;
connection slave;
#
# Error Case 1: Not providing a valid GTID should result in a syntax
# error
connection slave;
START SLAVE UNTIL SQL_AFTER_GTIDS="a";
ERROR HY000: Could not parse GTID list
START SLAVE UNTIL SQL_AFTER_GTIDS="0";
ERROR HY000: Could not parse GTID list
START SLAVE UNTIL SQL_AFTER_GTIDS="0-1";
ERROR HY000: Could not parse GTID list
START SLAVE UNTIL SQL_AFTER_GTIDS="0-1-";
ERROR HY000: Could not parse GTID list
START SLAVE UNTIL SQL_AFTER_GTIDS="a-b-c";
ERROR HY000: Could not parse GTID list
#
# Cleanup test data
connection master;
DROP TABLE t1, t2;
connection slave;
#
# Error Case 2: Providing both SQL_BEFORE_GTIDS and SQL_AFTER_GTIDS
# should result in a syntax error
connection slave;
START SLAVE UNTIL SQL_AFTER_GTIDS="0-1-1" SQL_BEFORE_GTIDS="0-1-1";
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'SQL_BEFORE_GTIDS="0-1-1"' at line 1
#
# Cleanup
include/save_master_gtid.inc
connection slave;
include/sync_with_master_gtid.inc
include/rpl_end.inc
# End of rpl_gtid_until_before_gtids.test
#
# This test validates the behavior of SQL_BEFORE_GTIDS and SQL_AFTER_GTIDS
# of a slave's START SLAVE UNTIL command. Notably, it tests the following
# scenarios:
# 1. Single domain id in binary log with a single domain id in the UNTIL
# condition
# 2. Multiple domain ids in binary log with a single domain id in the UNTIL
# condition
# 3. Multiple domain ids in binary log with multiple domain ids in the UNTIL
# condition
# 4. A gap in the binary log with the UNTIL condition GTID pointed to the
# missing transaction
# 5. Syntax errors using the new options
#
#
# References:
# MDEV-27247: Add keywords "exclusive" and "inclusive" for START SLAVE UNTIL
#
--source include/have_innodb.inc
--source include/have_log_bin.inc
--source include/master-slave.inc
--let $ssu_before_gtids=1
--source include/rpl_gtid_until_before_after_gtids.test
--let $ssu_before_gtids=0
--source include/rpl_gtid_until_before_after_gtids.test
--echo #
--echo # Error Case 2: Providing both SQL_BEFORE_GTIDS and SQL_AFTER_GTIDS
--echo # should result in a syntax error
--connection slave
--error ER_PARSE_ERROR
START SLAVE UNTIL SQL_AFTER_GTIDS="0-1-1" SQL_BEFORE_GTIDS="0-1-1";
--echo #
--echo # Cleanup
--source include/save_master_gtid.inc
--connection slave
--source include/sync_with_master_gtid.inc
--source include/rpl_end.inc
--echo # End of rpl_gtid_until_before_gtids.test
......@@ -618,6 +618,8 @@ SYMBOL symbols[] = {
{ "SQLEXCEPTION", SYM(SQLEXCEPTION_SYM)},
{ "SQLSTATE", SYM(SQLSTATE_SYM)},
{ "SQLWARNING", SYM(SQLWARNING_SYM)},
{ "SQL_AFTER_GTIDS", SYM(SQL_AFTER_GTIDS_SYM)},
{ "SQL_BEFORE_GTIDS", SYM(SQL_BEFORE_GTIDS_SYM)},
{ "SQL_BIG_RESULT", SYM(SQL_BIG_RESULT)},
{ "SQL_BUFFER_RESULT", SYM(SQL_BUFFER_RESULT)},
{ "SQL_CACHE", SYM(SQL_CACHE_SYM)},
......
......@@ -60,7 +60,8 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery, const char* thread_name)
abort_pos_wait(0), slave_run_id(0), sql_driver_thd(),
gtid_skip_flag(GTID_SKIP_NOT), inited(0), abort_slave(0), stop_for_until(0),
slave_running(MYSQL_SLAVE_NOT_RUN), until_condition(UNTIL_NONE),
until_log_pos(0), retried_trans(0), executed_entries(0),
until_log_pos(0), is_until_before_gtids(false),
retried_trans(0), executed_entries(0),
last_trans_retry_count(0), sql_delay(0), sql_delay_end(0),
until_relay_log_names_defer(false),
m_flags(0)
......
......@@ -338,6 +338,8 @@ class Relay_log_info : public Slave_reporting_capability
/* Condition for UNTIL master_gtid_pos. */
slave_connection_state until_gtid_pos;
bool is_until_before_gtids;
/*
retried_trans is a cumulative counter: how many times the slave
has retried a transaction (any) since slave started.
......
......@@ -2525,6 +2525,42 @@ when it try to get the value of TIME_ZONE global variable from master.";
goto err;
}
}
query_str.length(0);
if (query_str.append(
STRING_WITH_LEN("SET @slave_gtid_until_before_gtids="),
system_charset_info) ||
query_str.append_ulonglong(mi->rli.is_until_before_gtids))
{
err_code= ER_OUTOFMEMORY;
errmsg=
"The slave I/O thread stops because a fatal out-of-memory error "
"is encountered when it tries to set "
"@slave_gtid_until_before_gtids.";
sprintf(err_buff, "%s Error: Out of memory", errmsg);
goto err;
}
rc= mysql_real_query(mysql, query_str.ptr(), query_str.length());
if (unlikely(rc))
{
err_code= mysql_errno(mysql);
if (is_network_error(err_code))
{
mi->report(ERROR_LEVEL, err_code, NULL,
"Setting @slave_gtid_until_before_gtids failed with "
"error: %s", mysql_error(mysql));
goto network_err;
}
else
{
/* Fatal error */
errmsg= "The slave I/O thread stops because a fatal error is "
"encountered when it tries to set @slave_gtid_until_before_gtids.";
sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
goto err;
}
}
}
}
else
......
......@@ -494,6 +494,7 @@ struct LEX_MASTER_INFO
float heartbeat_period;
int sql_delay;
bool is_demotion_opt;
bool is_until_before_gtids;
/*
Enum is used for making it possible to detect if the user
changed variable or if it should be left at old value
......@@ -537,6 +538,7 @@ struct LEX_MASTER_INFO
use_gtid_opt= LEX_GTID_UNCHANGED;
sql_delay= -1;
is_demotion_opt= 0;
is_until_before_gtids= false;
}
};
......
......@@ -162,6 +162,7 @@ struct binlog_send_info {
bool clear_initial_log_pos;
bool should_stop;
size_t dirlen;
bool is_until_before_gtids;
binlog_send_info(THD *thd_arg, String *packet_arg, ushort flags_arg,
char *lfn)
......@@ -180,7 +181,7 @@ struct binlog_send_info {
hb_info_counter(0),
#endif
clear_initial_log_pos(false),
should_stop(false)
should_stop(false), is_until_before_gtids(false)
{
error_text[0] = 0;
bzero(&error_gtid, sizeof(error_gtid));
......@@ -808,6 +809,18 @@ get_slave_until_gtid(THD *thd, String *out_str)
return entry && entry->val_str(&null_value, out_str, 0) && !null_value;
}
static bool
get_slave_gtid_until_before_gtids(THD *thd)
{
bool null_value;
const LEX_CSTRING name= { STRING_WITH_LEN("slave_gtid_until_before_gtids") };
user_var_entry *entry=
(user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str,
name.length);
return entry && entry->val_int(&null_value) && !null_value;
}
/*
Function prepares and sends repliation heartbeat event.
......@@ -1867,8 +1880,9 @@ send_event_to_slave(binlog_send_info *info, Log_event_type event_type,
This domain already reached the START SLAVE UNTIL stop condition,
so skip this event group.
*/
info->gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ?
GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION);
info->gtid_skip_group= (flags2 & Gtid_log_event::FL_STANDALONE
? GTID_SKIP_STANDALONE
: GTID_SKIP_TRANSACTION);
}
else if (event_gtid.server_id == gtid->server_id &&
event_gtid.seq_no >= gtid->seq_no)
......@@ -1885,14 +1899,19 @@ send_event_to_slave(binlog_send_info *info, Log_event_type event_type,
info->gtid_until_group= (flags2 & Gtid_log_event::FL_STANDALONE ?
GTID_UNTIL_STOP_AFTER_STANDALONE :
GTID_UNTIL_STOP_AFTER_TRANSACTION);
if (event_gtid.seq_no > until_seq_no)
if (event_gtid.seq_no > until_seq_no ||
info->is_until_before_gtids)
{
/*
Stop processing events now and skip the current event group
because either:
The GTID in START SLAVE UNTIL condition is missing in our binlog.
This should normally not happen (user error), but since we can be
sure that we are now beyond the position that the UNTIL condition
should be in, we can just stop now. And we also need to skip this
event group (as it is beyond the UNTIL condition).
should be in, we can just stop now.
Or the until condition is specified as SQL_BEFORE_GTIDS
*/
info->gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ?
GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION);
......@@ -2139,7 +2158,10 @@ static int init_binlog_sender(binlog_send_info *info,
info->slave_gtid_strict_mode= get_slave_gtid_strict_mode(thd);
info->slave_gtid_ignore_duplicates= get_slave_gtid_ignore_duplicates(thd);
if (get_slave_until_gtid(thd, &slave_until_gtid_str))
{
info->until_gtid_state= &info->until_gtid_state_obj;
info->is_until_before_gtids= get_slave_gtid_until_before_gtids(thd);
}
}
DBUG_EXECUTE_IF("binlog_force_reconnect_after_22_events",
......@@ -3228,6 +3250,7 @@ int start_slave(THD* thd , Master_info* mi, bool net_report)
goto err;
}
mi->rli.until_condition= Relay_log_info::UNTIL_GTID;
mi->rli.is_until_before_gtids= thd->lex->mi.is_until_before_gtids;
}
else
mi->rli.clear_until_condition();
......
......@@ -1090,6 +1090,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize);
%token <kwd> SONAME_SYM
%token <kwd> SOUNDS_SYM
%token <kwd> SOURCE_SYM
%token <kwd> SQL_AFTER_GTIDS_SYM
%token <kwd> SQL_BEFORE_GTIDS_SYM
%token <kwd> SQL_BUFFER_RESULT
%token <kwd> SQL_CACHE_SYM
%token <kwd> SQL_CALC_FOUND_ROWS
......@@ -8062,6 +8064,17 @@ slave_until:
| UNTIL_SYM MASTER_GTID_POS_SYM '=' TEXT_STRING_sys
{
Lex->mi.gtid_pos_str = $4;
Lex->mi.is_until_before_gtids= false;
}
| UNTIL_SYM SQL_AFTER_GTIDS_SYM '=' TEXT_STRING_sys
{
Lex->mi.gtid_pos_str = $4;
Lex->mi.is_until_before_gtids= false;
}
| UNTIL_SYM SQL_BEFORE_GTIDS_SYM '=' TEXT_STRING_sys
{
Lex->mi.gtid_pos_str = $4;
Lex->mi.is_until_before_gtids= true;
}
;
......
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