summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client/mysqlbinlog.cc331
-rw-r--r--mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_window.result226
-rw-r--r--mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_window-master.opt1
-rw-r--r--mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_window.test242
-rw-r--r--sql/rpl_gtid.cc402
-rw-r--r--sql/rpl_gtid.h224
-rw-r--r--sql/sql_parse.cc36
-rw-r--r--sql/sql_repl.cc44
-rw-r--r--sql/sql_repl.h3
9 files changed, 1471 insertions, 38 deletions
diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc
index d65828ea71c..dcb362e6b3a 100644
--- a/client/mysqlbinlog.cc
+++ b/client/mysqlbinlog.cc
@@ -143,10 +143,18 @@ static char *charset= 0;
static uint verbose= 0;
-static ulonglong start_position, stop_position;
+static char *start_pos_str, *stop_pos_str;
+static ulonglong start_position= BIN_LOG_HEADER_SIZE,
+ stop_position= (longlong)(~(my_off_t)0) ;
#define start_position_mot ((my_off_t)start_position)
#define stop_position_mot ((my_off_t)stop_position)
+static Delegating_gtid_event_filter *gtid_event_filter= NULL;
+static rpl_gtid *start_gtids, *stop_gtids;
+static my_bool is_event_gtid_active= FALSE;
+static uint32 n_start_gtid_ranges= 0;
+static uint32 n_stop_gtid_ranges= 0;
+
static char *start_datetime_str, *stop_datetime_str;
static my_time_t start_datetime= 0, stop_datetime= MY_TIME_T_MAX;
static ulonglong rec_count= 0;
@@ -981,6 +989,40 @@ static bool print_row_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev,
return result;
}
+static inline my_bool is_gtid_filtering_enabled()
+{
+ return gtid_event_filter != NULL;
+}
+
+/*
+ Where the binlog is processed sequentially, the variable is_event_gtid_active
+ keeps track of the state of active event groups. When a new Gtid_log_event is
+ read, if it should be output, this function will return true. Otherwise, this
+ function will return false.
+*/
+static inline my_bool is_event_group_active()
+{
+ return is_event_gtid_active;
+}
+
+/*
+ When a Gtid_log_event marks a GTID that should be output, this function is
+ invoked to change the program state to start writing binlog events until
+ the event group has ended.
+*/
+static inline void activate_current_event_group()
+{
+ is_event_gtid_active= TRUE;
+}
+
+/*
+ When an active event group has written its last event, this function is
+ invoked to change the program state to stop writing events.
+*/
+static inline void deactivate_current_event_group()
+{
+ is_event_gtid_active= FALSE;
+}
/**
Print the given event, and either delete it or delegate the deletion
@@ -1019,11 +1061,34 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev,
#endif
/*
+ If the binlog output should be filtered using GTIDs, test the new event
+ group to see if its events should be written or ignored.
+ */
+ if (ev_type == GTID_EVENT && is_gtid_filtering_enabled())
+ {
+ Gtid_log_event *gle= (Gtid_log_event*) ev;
+ rpl_gtid gtid;
+ gtid.domain_id= gle->domain_id;
+ gtid.server_id= gle->server_id;
+ gtid.seq_no= gle->seq_no;
+ if (!gtid_event_filter->exclude(&gtid))
+ {
+ activate_current_event_group();
+ }
+ else
+ {
+ deactivate_current_event_group();
+ }
+
+ }
+
+ /*
Format events are not concerned by --offset and such, we always need to
read them to be able to process the wanted events.
*/
if (((rec_count >= offset) &&
- (ev->when >= start_datetime)) ||
+ (ev->when >= start_datetime) &&
+ (!is_gtid_filtering_enabled() || is_event_group_active())) ||
(ev_type == FORMAT_DESCRIPTION_EVENT))
{
if (ev_type != FORMAT_DESCRIPTION_EVENT)
@@ -1500,6 +1565,13 @@ end:
}
}
+ /* Xid_log_events or Query_log_events mark the end of a GTID event group. */
+ if ((ev_type == XID_EVENT || ev_type == QUERY_EVENT) &&
+ is_event_group_active())
+ {
+ deactivate_current_event_group();
+ }
+
if (destroy_evt) /* destroy it later if not set (ignored table map) */
delete ev;
}
@@ -1658,15 +1730,13 @@ static struct my_option my_options[] =
&start_datetime_str, &start_datetime_str,
0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"start-position", 'j',
- "Start reading the binlog at position N. Applies to the first binlog "
- "passed on the command line.",
- &start_position, &start_position, 0, GET_ULL,
- REQUIRED_ARG, BIN_LOG_HEADER_SIZE, BIN_LOG_HEADER_SIZE,
- /*
- COM_BINLOG_DUMP accepts only 4 bytes for the position
- so remote log reading has lower limit.
- */
- (ulonglong)(0xffffffffffffffffULL), 0, 0, 0},
+ "Start reading the binlog at this position. Type can either be a positive "
+ "integer or a GTID. When using a positive integer, the value only "
+ "applies to the first binlog passed on the command line. In GTID mode, "
+ "multiple GTIDs can be passed as a comma separated list, where each must "
+ "have a unique domain id.",
+ &start_pos_str, &start_pos_str, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0,
+ 0, 0, 0},
{"stop-datetime", OPT_STOP_DATETIME,
"Stop reading the binlog at first event having a datetime equal or "
"posterior to the argument; the argument must be a date and time "
@@ -1684,11 +1754,13 @@ static struct my_option my_options[] =
&opt_stop_never_slave_server_id, &opt_stop_never_slave_server_id, 0,
GET_ULONG, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"stop-position", OPT_STOP_POSITION,
- "Stop reading the binlog at position N. Applies to the last binlog "
- "passed on the command line.",
- &stop_position, &stop_position, 0, GET_ULL,
- REQUIRED_ARG, (longlong)(~(my_off_t)0), BIN_LOG_HEADER_SIZE,
- (ulonglong)(~(my_off_t)0), 0, 0, 0},
+ "Stop reading the binlog at this position. Type can either be a positive "
+ "integer or a GTID. When using a positive integer, the value only "
+ "applies to the last binlog passed on the command line. In GTID mode, "
+ "multiple GTIDs can be passed as a comma separated list, where each must "
+ "have a unique domain id.",
+ &stop_pos_str, &stop_pos_str, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0,
+ 0, 0},
{"table", 'T', "List entries for just this table (local log only).",
&table, &table, 0, GET_STR_ALLOC, REQUIRED_ARG,
0, 0, 0, 0, 0, 0},
@@ -1824,8 +1896,14 @@ static void cleanup()
my_free(const_cast<char*>(dirname_for_local_load));
my_free(start_datetime_str);
my_free(stop_datetime_str);
+ my_free(start_pos_str);
+ my_free(stop_pos_str);
+ my_free(start_gtids);
+ my_free(stop_gtids);
free_root(&glob_root, MYF(0));
+ delete gtid_event_filter;
+
delete binlog_filter;
delete glob_description_event;
if (mysql)
@@ -2075,6 +2153,123 @@ get_one_option(const struct my_option *opt, const char *argument, const char *fi
print_version();
opt_version= 1;
break;
+ case OPT_STOP_POSITION:
+ {
+ stop_gtids= gtid_parse_string_to_list(stop_pos_str, strlen(stop_pos_str),
+ &n_stop_gtid_ranges);
+ if (stop_gtids == NULL)
+ {
+ int err= 0;
+ char *end_ptr= NULL;
+ /*
+ No GTIDs specified in OPT_STOP_POSITION specification. Treat the value
+ as a singular index.
+ */
+ stop_position= my_strtoll10(stop_pos_str, &end_ptr, &err);
+
+ if (err || *end_ptr)
+ {
+ // Can't parse the position from the user
+ sql_print_error("Stop position argument value is invalid. Should be "
+ "either a position or GTID.");
+ return 1;
+ }
+ }
+ else if (n_stop_gtid_ranges > 0)
+ {
+ uint32 gtid_idx;
+ Domain_gtid_event_filter *domain_filter;
+
+ if (gtid_event_filter == NULL)
+ {
+ domain_filter= new Domain_gtid_event_filter();
+ gtid_event_filter= domain_filter;
+ }
+ else
+ {
+ domain_filter= (Domain_gtid_event_filter *) gtid_event_filter;
+ }
+
+ for (gtid_idx = 0; gtid_idx < n_stop_gtid_ranges; gtid_idx++)
+ {
+ rpl_gtid *stop_gtid= &stop_gtids[gtid_idx];
+ if (!domain_filter->add_stop_gtid(stop_gtid))
+ {
+ sql_print_error("Cannot add stop position; domain id %u "
+ "already has a stop position",
+ stop_gtid->domain_id);
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ // Can't parse the position from the user
+ sql_print_error("Stop position argument value is invalid. Should be "
+ "either a position or GTID.");
+ return 1;
+ }
+ break;
+ }
+ case 'j':
+ {
+ start_gtids= gtid_parse_string_to_list(
+ start_pos_str, strlen(start_pos_str), &n_start_gtid_ranges);
+
+ if (start_gtids == NULL)
+ {
+ int err= 0;
+ char *end_ptr= NULL;
+ /*
+ No GTIDs specified in OPT_START_POSITION specification. Treat the value
+ as a singular index.
+ */
+ start_position= my_strtoll10(start_pos_str, &end_ptr, &err);
+
+ if (err || *end_ptr)
+ {
+ // Can't parse the position from the user
+ sql_print_error("Start position argument value is invalid. Should be "
+ "either a position or GTID.");
+ return 1;
+ }
+ }
+ else if (n_start_gtid_ranges > 0)
+ {
+ uint32 gtid_idx;
+ Domain_gtid_event_filter *domain_filter;
+
+ if (gtid_event_filter == NULL)
+ {
+ domain_filter= new Domain_gtid_event_filter();
+ gtid_event_filter= domain_filter;
+ }
+ else
+ {
+ domain_filter= (Domain_gtid_event_filter *) gtid_event_filter;
+ }
+
+ for (gtid_idx = 0; gtid_idx < n_start_gtid_ranges; gtid_idx++)
+ {
+ rpl_gtid *start_gtid= &start_gtids[gtid_idx];
+ if (!domain_filter->add_start_gtid(start_gtid))
+ {
+ sql_print_error("Cannot add start position; domain id %u "
+ "already has a start position",
+ start_gtid->domain_id);
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ // Can't parse the position from the user
+ sql_print_error("Start position argument value is invalid. Should be "
+ "either a position or GTID.");
+ return 1;
+ }
+ break;
+ }
case '?':
usage();
opt_version= 1;
@@ -2237,11 +2432,12 @@ static Exit_status dump_log_entries(const char* logname)
@retval ERROR_STOP An error occurred - the program should terminate.
@retval OK_CONTINUE No error, the program should continue.
*/
-static Exit_status check_master_version()
+static Exit_status check_master_version(uint *major, uint *minor, uint *patch)
{
MYSQL_RES* res = 0;
MYSQL_ROW row;
uint version;
+ size_t version_iter;
if (mysql_query(mysql, "SELECT VERSION()") ||
!(res = mysql_store_result(mysql)))
@@ -2257,12 +2453,52 @@ static Exit_status check_master_version()
goto err;
}
- if (!(version = atoi(row[0])))
+ if (!(*major = atoi(row[0])))
{
error("Could not find server version: "
"Master reported NULL for the version.");
goto err;
}
+
+ /* Try to save the minor version, if supplied with a storage location */
+ if (minor != NULL)
+ {
+ for (version_iter= 0; version_iter < strlen((const char *) row);
+ version_iter++)
+ {
+ if (row[0][version_iter] == '.')
+ {
+ version_iter= version_iter + 1;
+ break;
+ }
+ }
+ if (!(*minor= atoi(&row[0][version_iter])))
+ {
+ error("Could not find server minor version: "
+ "Master reported NULL for the minor version.");
+ }
+ }
+
+ /* Try to save the patch version, if supplied with a storage location */
+ if (patch != NULL)
+ {
+ for (; version_iter < strlen((const char *) row); version_iter++)
+ {
+ if (row[0][version_iter] == '.')
+ {
+ version_iter= version_iter + 1;
+ break;
+ }
+ }
+ if (!(*patch= atoi(&row[0][version_iter])))
+ {
+ error("Could not find server patch version: "
+ "Master reported NULL for the patch version.");
+ }
+ }
+
+ version= *major;
+
/*
Make a notice to the server that this client
is checksum-aware. It does not need the first fake Rotate
@@ -2606,21 +2842,56 @@ static Exit_status dump_remote_log_entries(PRINT_EVENT_INFO *print_event_info,
DBUG_RETURN(retval);
net= &mysql->net;
- if ((retval= check_master_version()) != OK_CONTINUE)
+ uint major_version, minor_version;
+ if ((retval= check_master_version(&major_version, &minor_version, NULL)) !=
+ OK_CONTINUE)
DBUG_RETURN(retval);
/*
COM_BINLOG_DUMP accepts only 4 bytes for the position, so we are forced to
cast to uint32.
*/
- DBUG_ASSERT(start_position <= UINT_MAX32);
- int4store(buf, (uint32)start_position);
+ size_t buf_idx= 0;
+ if (is_gtid_filtering_enabled())
+ {
+ if (major_version < 10 || (major_version == 10 && minor_version < 7))
+ {
+ error("Master does not support GTID filtering. This feature was added "
+ "in MariaDB version 10.7");
+ DBUG_RETURN(ERROR_STOP);
+ }
+
+ size_t i;
+ for (i = 0; i < n_start_gtid_ranges; i++)
+ {
+ if (i > 0)
+ {
+ buf[buf_idx]= ',';
+ buf_idx += 1;
+ }
+
+
+ int4store(buf + buf_idx, start_gtids[i].domain_id);
+ buf[buf_idx+4]= '-';
+ int4store(buf + buf_idx + 5, start_gtids[i].server_id);
+ buf[buf_idx+9]= '-';
+ int8store(buf + buf_idx + 10, start_gtids[i].seq_no);
+ buf_idx += 18;
+ }
+ }
+ else
+ {
+ DBUG_ASSERT(start_position <= UINT_MAX32);
+ int4store(buf, (uint32) start_position);
+ buf_idx= BIN_LOG_HEADER_SIZE;
+ }
if (!opt_skip_annotate_row_events)
binlog_flags|= BINLOG_SEND_ANNOTATE_ROWS_EVENT;
if (!opt_stop_never)
binlog_flags|= BINLOG_DUMP_NON_BLOCK;
- int2store(buf + BIN_LOG_HEADER_SIZE, binlog_flags);
+ int2store(buf + buf_idx, binlog_flags);
+ buf_idx += 2;
size_t tlen = strlen(logname);
if (tlen > sizeof(buf) - 10)
@@ -2637,9 +2908,10 @@ static Exit_status dump_remote_log_entries(PRINT_EVENT_INFO *print_event_info,
}
else
slave_id= 0;
- int4store(buf + 6, slave_id);
- memcpy(buf + 10, logname, logname_len);
- if (simple_command(mysql, COM_BINLOG_DUMP, buf, logname_len + 10, 1))
+ int4store(buf + buf_idx, slave_id);
+ buf_idx += 4;
+ memcpy(buf + buf_idx, logname, logname_len);
+ if (simple_command(mysql, COM_BINLOG_DUMP, buf, logname_len + buf_idx, 1))
{
error("Got fatal error sending the log dump command.");
DBUG_RETURN(ERROR_STOP);
@@ -3210,6 +3482,14 @@ int main(int argc, char** argv)
"/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n");
fprintf(result_file, "/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;\n");
+
+ if (is_gtid_filtering_enabled())
+ {
+ fprintf(result_file,
+ "/*!100001 SET @@SESSION.SERVER_ID=@@GLOBAL.SERVER_ID */;\n"
+ "/*!100001 SET @@SESSION.GTID_DOMAIN_ID=@@GLOBAL.GTID_DOMAIN_ID "
+ "*/;\n");
+ }
}
if (tmpdir.list)
@@ -3271,3 +3551,4 @@ struct encryption_service_st encryption_handler=
#include "sql_list.cc"
#include "rpl_filter.cc"
#include "compat56.cc"
+#include "rpl_gtid.cc" \ No newline at end of file
diff --git a/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_window.result b/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_window.result
new file mode 100644
index 00000000000..d3fd17b7a18
--- /dev/null
+++ b/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_window.result
@@ -0,0 +1,226 @@
+###############################
+# Test Setup
+###############################
+SET timestamp=1000000000;
+RESET MASTER;
+SET @@session.gtid_domain_id= 0;
+SET @@session.server_id= 1;
+CREATE TABLE t1 (a int);
+CREATE TABLE t2 (a int);
+INSERT INTO t1 values (1), (2);
+SET @@session.gtid_domain_id= 1;
+SET @@session.server_id= 2;
+INSERT INTO t2 values (1);
+INSERT INTO t2 values (2);
+SET @@session.gtid_domain_id= 0;
+SET @@session.server_id= 1;
+INSERT INTO t1 values (3), (4);
+SET @@session.gtid_domain_id= 1;
+SET @@session.server_id= 2;
+INSERT INTO t2 values (3);
+INSERT INTO t2 values (4);
+INSERT INTO t2 values (5);
+SET @@session.server_id= 3;
+INSERT INTO t2 values (6);
+INSERT INTO t2 values (7);
+SET @@session.server_id= 2;
+INSERT INTO t2 values (8);
+FLUSH LOGS;
+###############################
+# Test Cases
+###############################
+#
+# Test Case 1:
+# The end of the binlog file resets the server and domain id of the
+# session
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1 --stop-position=0-1-4 --base64-output=NEVER > OUT_FILE
+SET @@SESSION.SERVER_ID=@@GLOBAL.SERVER_ID
+SET @@SESSION.GTID_DOMAIN_ID=@@GLOBAL.GTID_DOMAIN_ID
+#
+# Test Case 2:
+# Local file, single GTID range specified
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1 --stop-position=0-1-4 --base64-output=NEVER > OUT_FILE
+end_log_pos # CRC32 XXX Start: binlog v 4, server v #.##.## created 010909 9:46:40 at startup
+end_log_pos # CRC32 XXX GTID 0-1-2 ddl
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 0-1-3
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 0-1-4
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+#
+# Test Case 3:
+# Local file, single GTID range with different server_ids
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=1-2-2 --stop-position=1-3-6 --base64-output=NEVER > OUT_FILE
+end_log_pos # CRC32 XXX Start: binlog v 4, server v #.##.## created 010909 9:46:40 at startup
+end_log_pos # CRC32 XXX GTID 1-2-3
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t2` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 1-2-4
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t2` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 1-2-5
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t2` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 1-3-6
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t2` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+#
+# Test Case 4:
+# Local file, multiple GTID ranges specified
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1,1-2-0 --stop-position=0-1-4,1-2-3 --base64-output=NEVER > OUT_FILE
+end_log_pos # CRC32 XXX Start: binlog v 4, server v #.##.## created 010909 9:46:40 at startup
+end_log_pos # CRC32 XXX GTID 0-1-2 ddl
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 0-1-3
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 1-2-1
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t2` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 1-2-2
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t2` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 0-1-4
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 1-2-3
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t2` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+#
+# Test Case 5:
+# Local file, multiple GTID ranges specified where the domain ids are
+# listed in different orders between start/stop position
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --stop-position=0-1-4,1-2-3 --start-position=1-2-0,0-1-1 --base64-output=NEVER > MYSQLTEST_VARDIR/tmp/binlog2.out
+# --diff_files OUT_FILE MYSQLTEST_VARDIR/tmp/binlog2.out
+#
+# Test Case 6:
+# Local file, only start position specified
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-2 --base64-output=NEVER > OUT_FILE
+end_log_pos # CRC32 XXX Start: binlog v 4, server v #.##.## created 010909 9:46:40 at startup
+end_log_pos # CRC32 XXX GTID 0-1-3
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 0-1-4
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+#
+# Test Case 7:
+# Local file, only stop position specified
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --stop-position=0-1-2 --base64-output=NEVER > OUT_FILE
+end_log_pos # CRC32 XXX Start: binlog v 4, server v #.##.## created 010909 9:46:40 at startup
+end_log_pos # CRC32 XXX GTID 0-1-1 ddl
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 0-1-2 ddl
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+#
+# Test Case 8:
+# Remote host, single GTID range specified
+# MYSQL_BINLOG master-bin.000001 --read-from-remote-server --start-position=0-1-1 --stop-position=0-1-4 --base64-output=NEVER > OUT_FILE
+end_log_pos # CRC32 XXX Start: binlog v 4, server v #.##.## created 010909 9:46:40 at startup
+end_log_pos # CRC32 XXX GTID 0-1-2 ddl
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 0-1-3
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+end_log_pos # CRC32 XXX GTID 0-1-4
+end_log_pos # CRC32 XXX Annotate_rows:
+end_log_pos # CRC32 XXX Table_map: `test`.`t1` mapped to number #
+end_log_pos # CRC32 XXX Write_rows: table id # flags: STMT_END_F
+end_log_pos # CRC32 XXX Query thread_id=# exec_time=# error_code=0 xid=#
+SET @@SESSION.SERVER_ID=@@GLOBAL.SERVER_ID
+SET @@SESSION.GTID_DOMAIN_ID=@@GLOBAL.GTID_DOMAIN_ID
+#
+# Test Case 9:
+# The output filtered by GTID ranges can be piped back into the
+# MariaDB client
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --stop-position=0-1-4,1-2-8 > MYSQLTEST_VARDIR/tmp/ir.out
+DROP TABLE t1;
+DROP TABLE t2;
+RESET MASTER;
+# MYSQL < MYSQLTEST_VARDIR/tmp/ir.out
+FLUSH LOGS;
+show tables;
+Tables_in_test
+t1
+t2
+SELECT * FROM t1;
+a
+1
+2
+3
+4
+SELECT * FROM t2;
+a
+1
+2
+3
+4
+5
+6
+7
+8
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --base64-output=NEVER > OUT_FILE
+GTID 0-1-1
+GTID 0-1-2
+GTID 0-1-3
+GTID 1-2-1
+GTID 1-2-2
+GTID 0-1-4
+GTID 1-2-3
+GTID 1-2-4
+GTID 1-2-5
+GTID 1-3-6
+GTID 1-3-7
+GTID 1-2-8
+##############################
+# Error Cases
+##############################
+#
+# Error Case 1:
+# User provides invalid positions
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=z --base64-output=NEVER > OUT_FILE
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=1- --base64-output=NEVER > OUT_FILE
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=1-2 --base64-output=NEVER > OUT_FILE
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=1-2- --base64-output=NEVER > OUT_FILE
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=-1 --base64-output=NEVER > OUT_FILE
+#
+# Error Case 2:
+# User provides GTID ranges with repeated domain ids
+# MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1,0-1-8 --stop-position=0-1-4,0-1-12 --base64-output=NEVER > OUT_FILE
+##############################
+# Cleanup
+##############################
+DROP TABLE t1;
+DROP TABLE t2;
+SET @@global.gtid_domain_id= 0;
+SET @@global.server_id= 1;
diff --git a/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_window-master.opt b/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_window-master.opt
new file mode 100644
index 00000000000..d17999c07c1
--- /dev/null
+++ b/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_window-master.opt
@@ -0,0 +1 @@
+--timezone=GMT-8 \ No newline at end of file
diff --git a/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_window.test b/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_window.test
new file mode 100644
index 00000000000..9611cb381da
--- /dev/null
+++ b/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_window.test
@@ -0,0 +1,242 @@
+#
+# Purpose:
+# This test ensures that the mariadb-binlog CLI tool can filter log events
+# using GTID ranges. More specifically, this test ensures the following
+# capabilities:
+# 1) GTIDs can be used to filter results on local binlog files
+# 2) GTIDs can be used to filter results from remote servers
+# 3) For a given GTID range, its start-position is exclusive and its
+# stop-position is inclusive
+# 4) After the events have been written, the session server id and domain id
+# are reset to their former values
+# 5) Output filtered by GTID ranges can be piped to the MariaDB client
+#
+# Methodology:
+# This test validates the expected capabilities using the following test
+# cases:
+# Test Case 1) The end of the binlog file resets the server and domain id of
+# the session
+# Test Case 2) Local file, single GTID range specified
+# Test Case 3) Local file, single GTID range with different server_ids
+# Test Case 4) Local file, multiple GTID ranges specified
+# Test Case 5) Local file, multiple GTID ranges specified where the domain
+# ids are listed in different orders between start/stop position
+# Test Case 6) Local file, only start position specified
+# Test Case 7) Local file, only stop position specified
+# Test Case 8) Remote host, single GTID range specified
+# Test Case 9) The output filtered by GTID ranges can be piped back into the
+# MariaDB client
+#
+# Additionally, this test validates the following error scenarios:
+# Error Case 1) User provides invalid positions
+# Error Case 2: User provides GTID ranges with repeated domain ids
+#
+# References:
+# MDEV-4989: Support for GTID in mysqlbinlog
+#
+
+--source include/have_log_bin.inc
+--source include/have_binlog_format_row.inc
+
+--echo ###############################
+--echo # Test Setup
+--echo ###############################
+
+## Fix timestamp to avoid varying results.
+#
+SET timestamp=1000000000;
+RESET MASTER;
+
+## Save old state
+#
+let $ORIG_GTID_DOMAIN_ID = `select @@session.gtid_domain_id`;
+let $ORIG_SERVER_ID = `select @@session.server_id`;
+
+## Configure test variables
+#
+--let $MYSQLD_DATADIR=`select @@datadir`
+--let OUT_FILE=$MYSQLTEST_VARDIR/tmp/binlog.out
+--let SEARCH_OUTPUT=matches
+--let SEARCH_FILE=$OUT_FILE
+
+## Initialize test data
+#
+SET @@session.gtid_domain_id= 0;
+SET @@session.server_id= 1;
+
+CREATE TABLE t1 (a int);
+CREATE TABLE t2 (a int);
+
+INSERT INTO t1 values (1), (2);
+
+SET @@session.gtid_domain_id= 1;
+SET @@session.server_id= 2;
+INSERT INTO t2 values (1);
+INSERT INTO t2 values (2);
+
+SET @@session.gtid_domain_id= 0;
+SET @@session.server_id= 1;
+INSERT INTO t1 values (3), (4);
+
+SET @@session.gtid_domain_id= 1;
+SET @@session.server_id= 2;
+INSERT INTO t2 values (3);
+INSERT INTO t2 values (4);
+INSERT INTO t2 values (5);
+
+SET @@session.server_id= 3;
+INSERT INTO t2 values (6);
+INSERT INTO t2 values (7);
+
+SET @@session.server_id= 2;
+INSERT INTO t2 values (8);
+
+FLUSH LOGS;
+
+
+--echo ###############################
+--echo # Test Cases
+--echo ###############################
+
+--echo #
+--echo # Test Case 1:
+--echo # The end of the binlog file resets the server and domain id of the
+--echo # session
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1 --stop-position=0-1-4 --base64-output=NEVER > OUT_FILE
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1 --stop-position=0-1-4 --base64-output=NEVER > $OUT_FILE
+--let SEARCH_PATTERN=SET @@SESSION\.[\w]*_ID[\h]*=[\h]*@@GLOBAL\.[\w]+_ID
+--source include/search_pattern_in_file.inc
+
+--echo #
+--echo # Test Case 2:
+--echo # Local file, single GTID range specified
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1 --stop-position=0-1-4 --base64-output=NEVER > OUT_FILE
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1 --stop-position=0-1-4 --base64-output=NEVER > $OUT_FILE
+--let SEARCH_PATTERN=end_log_pos[^\n]+
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ /exec_time=[0-9]*/exec_time=#/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ /Xid = [0-9]*/Xid = #/ /xid=[0-9]*/xid=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /server v [^ ]*/server v #.##.##/ /CRC32 0x[0-9a-f]*/CRC32 XXX/ /collation_server=[0-9]+/collation_server=X/ /character_set_client=[0-9]+/character_set_client=X/ /collation_connection=[0-9]+/collation_connection=X/
+--source include/search_pattern_in_file.inc
+
+--echo #
+--echo # Test Case 3:
+--echo # Local file, single GTID range with different server_ids
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=1-2-2 --stop-position=1-3-6 --base64-output=NEVER > OUT_FILE
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=1-2-2 --stop-position=1-3-6 --base64-output=NEVER > $OUT_FILE
+--let SEARCH_PATTERN=end_log_pos[^\n]+
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ /exec_time=[0-9]*/exec_time=#/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ /Xid = [0-9]*/Xid = #/ /xid=[0-9]*/xid=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /server v [^ ]*/server v #.##.##/ /CRC32 0x[0-9a-f]*/CRC32 XXX/ /collation_server=[0-9]+/collation_server=X/ /character_set_client=[0-9]+/character_set_client=X/ /collation_connection=[0-9]+/collation_connection=X/
+--source include/search_pattern_in_file.inc
+
+--echo #
+--echo # Test Case 4:
+--echo # Local file, multiple GTID ranges specified
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1,1-2-0 --stop-position=0-1-4,1-2-3 --base64-output=NEVER > OUT_FILE
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1,1-2-0 --stop-position=0-1-4,1-2-3 --base64-output=NEVER > $OUT_FILE
+--let SEARCH_PATTERN=end_log_pos[^\n]+
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ /exec_time=[0-9]*/exec_time=#/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ /Xid = [0-9]*/Xid = #/ /xid=[0-9]*/xid=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /server v [^ ]*/server v #.##.##/ /CRC32 0x[0-9a-f]*/CRC32 XXX/ /collation_server=[0-9]+/collation_server=X/ /character_set_client=[0-9]+/character_set_client=X/ /collation_connection=[0-9]+/collation_connection=X/
+--source include/search_pattern_in_file.inc
+
+--echo #
+--echo # Test Case 5:
+--echo # Local file, multiple GTID ranges specified where the domain ids are
+--echo # listed in different orders between start/stop position
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --stop-position=0-1-4,1-2-3 --start-position=1-2-0,0-1-1 --base64-output=NEVER > MYSQLTEST_VARDIR/tmp/binlog2.out
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --stop-position=0-1-4,1-2-3 --start-position=1-2-0,0-1-1 --base64-output=NEVER > $MYSQLTEST_VARDIR/tmp/binlog2.out
+--let SEARCH_PATTERN=end_log_pos[^\n]+
+--echo # --diff_files OUT_FILE MYSQLTEST_VARDIR/tmp/binlog2.out
+--diff_files $OUT_FILE $MYSQLTEST_VARDIR/tmp/binlog2.out
+
+--echo #
+--echo # Test Case 6:
+--echo # Local file, only start position specified
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-2 --base64-output=NEVER > OUT_FILE
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-2 --base64-output=NEVER > $OUT_FILE
+--let SEARCH_PATTERN=end_log_pos[^\n]+
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ /exec_time=[0-9]*/exec_time=#/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ /Xid = [0-9]*/Xid = #/ /xid=[0-9]*/xid=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /server v [^ ]*/server v #.##.##/ /CRC32 0x[0-9a-f]*/CRC32 XXX/ /collation_server=[0-9]+/collation_server=X/ /character_set_client=[0-9]+/character_set_client=X/ /collation_connection=[0-9]+/collation_connection=X/
+--source include/search_pattern_in_file.inc
+
+--echo #
+--echo # Test Case 7:
+--echo # Local file, only stop position specified
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --stop-position=0-1-2 --base64-output=NEVER > OUT_FILE
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --stop-position=0-1-2 --base64-output=NEVER > $OUT_FILE
+--let SEARCH_PATTERN=end_log_pos[^\n]+
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ /exec_time=[0-9]*/exec_time=#/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ /Xid = [0-9]*/Xid = #/ /xid=[0-9]*/xid=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /server v [^ ]*/server v #.##.##/ /CRC32 0x[0-9a-f]*/CRC32 XXX/ /collation_server=[0-9]+/collation_server=X/ /character_set_client=[0-9]+/character_set_client=X/ /collation_connection=[0-9]+/collation_connection=X/
+--source include/search_pattern_in_file.inc
+
+--echo #
+--echo # Test Case 8:
+--echo # Remote host, single GTID range specified
+--echo # MYSQL_BINLOG master-bin.000001 --read-from-remote-server --start-position=0-1-1 --stop-position=0-1-4 --base64-output=NEVER > OUT_FILE
+--exec $MYSQL_BINLOG master-bin.000001 --read-from-remote-server --start-position=0-1-1 --stop-position=0-1-4 --base64-output=NEVER > $OUT_FILE
+--let SEARCH_PATTERN=SET @@SESSION\.[\w]*_ID[\h]*=[\h]*@@GLOBAL\.[\w]+_ID|end_log_pos[^\n]+
+--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
+--replace_regex /SQL_LOAD_MB-[0-9]-[0-9]/SQL_LOAD_MB-#-#/ /exec_time=[0-9]*/exec_time=#/ /end_log_pos [0-9]*/end_log_pos #/ /# at [0-9]*/# at #/ /Xid = [0-9]*/Xid = #/ /xid=[0-9]*/xid=#/ /thread_id=[0-9]*/thread_id=#/ /table id [0-9]*/table id #/ /mapped to number [0-9]*/mapped to number #/ /server v [^ ]*/server v #.##.##/ /CRC32 0x[0-9a-f]*/CRC32 XXX/ /collation_server=[0-9]+/collation_server=X/ /character_set_client=[0-9]+/character_set_client=X/ /collation_connection=[0-9]+/collation_connection=X/
+--source include/search_pattern_in_file.inc
+
+--echo #
+--echo # Test Case 9:
+--echo # The output filtered by GTID ranges can be piped back into the
+--echo # MariaDB client
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --stop-position=0-1-4,1-2-8 > MYSQLTEST_VARDIR/tmp/ir.out
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --stop-position=0-1-4,1-2-8 > $MYSQLTEST_VARDIR/tmp/ir.out
+DROP TABLE t1;
+DROP TABLE t2;
+RESET MASTER;
+--echo # MYSQL < MYSQLTEST_VARDIR/tmp/ir.out
+--exec $MYSQL < $MYSQLTEST_VARDIR/tmp/ir.out
+FLUSH LOGS;
+show tables;
+SELECT * FROM t1;
+SELECT * FROM t2;
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --base64-output=NEVER > OUT_FILE
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --base64-output=NEVER > $OUT_FILE
+--let SEARCH_PATTERN=GTID \d+-\d+-\d+
+--source include/search_pattern_in_file.inc
+
+
+--echo ##############################
+--echo # Error Cases
+--echo ##############################
+
+--echo #
+--echo # Error Case 1:
+--echo # User provides invalid positions
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=z --base64-output=NEVER > OUT_FILE
+--error 1
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=z --base64-output=NEVER > $OUT_FILE
+
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=1- --base64-output=NEVER > OUT_FILE
+--error 1
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=1- --base64-output=NEVER > $OUT_FILE
+
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=1-2 --base64-output=NEVER > OUT_FILE
+--error 1
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=1-2 --base64-output=NEVER > $OUT_FILE
+
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=1-2- --base64-output=NEVER > OUT_FILE
+--error 1
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=1-2- --base64-output=NEVER > $OUT_FILE
+
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=-1 --base64-output=NEVER > OUT_FILE
+--error 1
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=-1 --base64-output=NEVER > $OUT_FILE
+
+--echo #
+--echo # Error Case 2:
+--echo # User provides GTID ranges with repeated domain ids
+--echo # MYSQL_BINLOG MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1,0-1-8 --stop-position=0-1-4,0-1-12 --base64-output=NEVER > OUT_FILE
+--error 1
+--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 --start-position=0-1-1,0-1-8 --stop-position=0-1-4,0-1-12 --base64-output=NEVER > $OUT_FILE
+
+--echo ##############################
+--echo # Cleanup
+--echo ##############################
+DROP TABLE t1;
+DROP TABLE t2;
+--eval SET @@global.gtid_domain_id= $ORIG_GTID_DOMAIN_ID
+--eval SET @@global.server_id= $ORIG_SERVER_ID
+
diff --git a/sql/rpl_gtid.cc b/sql/rpl_gtid.cc
index 8b10703fdc2..52e14714a9b 100644
--- a/sql/rpl_gtid.cc
+++ b/sql/rpl_gtid.cc
@@ -17,6 +17,7 @@
/* Definitions for MariaDB global transaction ID (GTID). */
+#ifndef MYSQL_CLIENT
#include "mariadb.h"
#include "sql_priv.h"
#include "unireg.h"
@@ -1268,6 +1269,7 @@ rpl_slave_state::domain_to_gtid(uint32 domain_id, rpl_gtid *out_gtid)
return true;
}
+#endif
/*
Parse a GTID at the start of a string, and update the pointer to point
@@ -1305,9 +1307,32 @@ gtid_parser_helper(const char **ptr, const char *end, rpl_gtid *out_gtid)
return 0;
}
+/*
+ Unpack a GTID at the start of a string, and update the pointer to point
+ at the first character after the unpacked GTID.
+
+ Returns 0 on ok, non-zero on parse error.
+*/
+static int
+gtid_unpack_helper(const char **ptr, const char *end, rpl_gtid *out_gtid)
+{
+ const char *p= *ptr;
+
+ if (p[4] != '-' || p[9] != '-')
+ return 1;
+
+ out_gtid->domain_id= (uint32)uint4korr(p);
+ out_gtid->server_id= (uint32)uint4korr(&p[5]);
+ out_gtid->seq_no= (uint64)uint8korr(&p[10]);
+
+ *ptr= p + 18;
+ return 0;
+}
rpl_gtid *
-gtid_parse_string_to_list(const char *str, size_t str_len, uint32 *out_len)
+gtid_read_to_list(const char *str, size_t str_len, uint32 *out_len,
+ int reader_f(const char **ptr, const char *end,
+ rpl_gtid *out_gtid))
{
const char *p= const_cast<char *>(str);
const char *end= p + str_len;
@@ -1318,7 +1343,7 @@ gtid_parse_string_to_list(const char *str, size_t str_len, uint32 *out_len)
{
rpl_gtid gtid;
- if (len >= (((uint32)1 << 28)-1) || gtid_parser_helper(&p, end, &gtid))
+ if (len >= (((uint32)1 << 28)-1) || reader_f(&p, end, &gtid))
{
my_free(list);
return NULL;
@@ -1345,6 +1370,20 @@ gtid_parse_string_to_list(const char *str, size_t str_len, uint32 *out_len)
}
+rpl_gtid *
+gtid_parse_string_to_list(const char *str, size_t str_len, uint32 *out_len)
+{
+ return gtid_read_to_list(str, str_len, out_len, gtid_parser_helper);
+}
+
+rpl_gtid *
+gtid_unpack_string_to_list(const char *str, size_t str_len, uint32 *out_len)
+{
+ return gtid_read_to_list(str, str_len, out_len, gtid_unpack_helper);
+}
+
+#ifndef MYSQL_CLIENT
+
/*
Update the slave replication state with the GTID position obtained from
master when connecting with old-style (filename,offset) position.
@@ -2952,3 +2991,362 @@ gtid_waiting::remove_from_wait_queue(gtid_waiting::hash_element *he,
queue_remove(&he->queue, elem->queue_idx);
}
+
+#endif
+
+Window_gtid_event_filter::Window_gtid_event_filter() :
+ m_has_start(FALSE),
+ m_has_stop(FALSE),
+ m_is_active(FALSE),
+ m_has_passed(FALSE)
+ {
+ // m_start and m_stop do not need initial values if unused
+ }
+
+Window_gtid_event_filter::Window_gtid_event_filter(rpl_gtid *start, rpl_gtid *stop) :
+ m_is_active(FALSE),
+ m_has_passed(FALSE)
+{
+ DBUG_ASSERT(start->domain_id == stop->domain_id);
+
+ m_is_active= FALSE;
+ m_has_passed= FALSE;
+
+ m_has_start= TRUE;
+ m_start.domain_id= start->domain_id;
+ m_start.server_id= start->server_id;
+ m_start.seq_no= start->seq_no;
+
+ m_has_stop= TRUE;
+ m_stop.domain_id= stop->domain_id;
+ m_stop.server_id= stop->server_id;
+ m_stop.seq_no= stop->seq_no;
+}
+
+my_bool Window_gtid_event_filter::set_start_gtid(rpl_gtid *start)
+{
+ if (m_has_start)
+ return FALSE;
+
+ // Copy values
+ m_has_start= TRUE;
+ m_start.domain_id= start->domain_id;
+ m_start.server_id= start->server_id;
+ m_start.seq_no= start->seq_no;
+
+ return TRUE;
+}
+
+my_bool Window_gtid_event_filter::set_stop_gtid(rpl_gtid *stop)
+{
+ if (m_has_stop)
+ return FALSE;
+
+ // Copy values
+ m_has_stop= TRUE;
+ m_stop.domain_id= stop->domain_id;
+ m_stop.server_id= stop->server_id;
+ m_stop.seq_no= stop->seq_no;
+
+ return TRUE;
+}
+
+gtid_filter_identifier Window_gtid_event_filter::get_filter_identifier()
+{
+ DBUG_ASSERT(m_has_start || m_has_stop);
+ if (m_has_start)
+ return m_start.domain_id;
+ else
+ return m_stop.domain_id;
+}
+
+static inline my_bool is_gtid_at_or_after(rpl_gtid *boundary,
+ rpl_gtid *test_gtid)
+{
+ return test_gtid->domain_id == boundary->domain_id &&
+ test_gtid->server_id == boundary->server_id &&
+ test_gtid->seq_no >= boundary->seq_no;
+}
+
+static inline my_bool is_gtid_before(rpl_gtid *boundary,
+ rpl_gtid *test_gtid)
+{
+ return test_gtid->domain_id == boundary->domain_id &&
+ test_gtid->server_id == boundary->server_id &&
+ test_gtid->seq_no <= boundary->seq_no;
+}
+
+my_bool Window_gtid_event_filter::exclude(rpl_gtid *gtid)
+{
+ /* Assume result should be excluded to start */
+ my_bool should_exclude= TRUE;
+
+ DBUG_ASSERT((m_has_start || m_has_stop) &&
+ (gtid->domain_id == m_start.domain_id ||
+ gtid->domain_id == m_stop.domain_id));
+
+ if (!m_is_active && !m_has_passed)
+ {
+ /*
+ This filter has not yet been activated. Check if the gtid is within the
+ bounds of this window.
+ */
+
+ if (!m_has_start)
+ {
+ /*
+ Start GTID was not provided, so we want to include everything up to m_stop
+ */
+ m_is_active= TRUE;
+ should_exclude= FALSE;
+ }
+ else if (is_gtid_at_or_after(&m_start, gtid))
+ {
+ m_is_active= TRUE;
+
+ DBUG_PRINT("gtid-event-filter",
+ ("Window: Begin (%d-%d-%d, %d-%d-%llu]", m_start.domain_id,
+ m_start.server_id, m_start.seq_no, m_stop.domain_id,
+ m_stop.server_id, m_stop.seq_no));
+
+ /*
+ As the start of the range is exclusive, if this gtid is the start of
+ the range, exclude it
+ */
+ if (gtid->seq_no == m_start.seq_no &&
+ gtid->server_id == m_start.server_id)
+ should_exclude= TRUE;
+ else
+ should_exclude= FALSE;
+ }
+ } /* if (!m_is_active && !m_has_passed) */
+ else if (m_is_active && !m_has_passed)
+ {
+ /*
+ This window is currently active so we want the event group to be included
+ in the results. Additionally check if we are at the end of the window.
+ If no end of the window is provided, go indefinitely
+ */
+ should_exclude= FALSE;
+
+ if (m_has_stop && is_gtid_at_or_after(&m_stop, gtid))
+ {
+ DBUG_PRINT("gtid-event-filter",
+ ("Window: End (%d-%d-%d, %d-%d-%llu]", m_start.domain_id,
+ m_start.server_id, m_start.seq_no, m_stop.domain_id,
+ m_stop.server_id, m_stop.seq_no));
+ m_is_active= FALSE;
+ m_has_passed= TRUE;
+
+ if (gtid->server_id == m_stop.server_id && gtid->seq_no > m_stop.seq_no)
+ {
+ /*
+ The GTID is after the finite stop of the window, don't let it pass
+ through
+ */
+ should_exclude= TRUE;
+ }
+ }
+ else if (m_has_start && is_gtid_before(&m_start, gtid))
+ {
+ /*
+ Out of order check, the window is active but this GTID takes place
+ before the window begins. keep the window active, but exclude it from
+ passing through.
+ */
+ should_exclude= TRUE;
+ }
+ }
+ else if (m_has_passed && m_has_stop && is_gtid_before(&m_stop, gtid))
+ {
+ /* Test if events are out of order */
+ if (!m_has_start || (m_has_start && is_gtid_at_or_after(&m_start, gtid)))
+ {
+ /*
+ The filter window has closed because it has seen a GTID higher than its
+ end boundary; however, this GTID is out of order and should be passed
+ through.
+ */
+ should_exclude= TRUE;
+ }
+ }
+
+ return should_exclude;
+}
+
+Delegating_gtid_event_filter::Delegating_gtid_event_filter()
+{
+ uint32 i;
+
+ m_filter_id_mask= 0xf;
+
+ m_filters_by_id= (gtid_filter_element **) my_malloc(
+ PSI_NOT_INSTRUMENTED,
+ (m_filter_id_mask + 1) * sizeof(gtid_filter_element *),
+ MYF(MY_WME)
+ );
+
+ DBUG_ASSERT(m_filters_by_id != NULL);
+
+ for (i = 0; i <= m_filter_id_mask; i++)
+ {
+ m_filters_by_id[i]= NULL;
+ }
+
+ m_default_filter= new Reject_all_gtid_filter();
+}
+
+/*
+ Deconstructor deletes:
+ 1) All Identifiable_gtid_event_filters added
+ 2) All gtid_filter_element allocations
+*/
+Delegating_gtid_event_filter::~Delegating_gtid_event_filter()
+{
+ uint32 i;
+ for (i = 0; i <= m_filter_id_mask; i++)
+ {
+ gtid_filter_element *filter_element= m_filters_by_id[i],
+ *filter_element_to_del= NULL;
+ while(filter_element)
+ {
+ filter_element_to_del= filter_element;
+ filter_element= filter_element->next;
+ delete filter_element_to_del->filter;
+ my_free(filter_element_to_del);
+ }
+ }
+ my_free(m_filters_by_id);
+
+ delete m_default_filter;
+}
+
+void Delegating_gtid_event_filter::set_default_filter(Gtid_event_filter *filter)
+{
+ if (m_default_filter)
+ delete m_default_filter;
+
+ m_default_filter= filter;
+}
+
+gtid_filter_element *
+Delegating_gtid_event_filter::try_find_filter_element_for_id(
+ gtid_filter_identifier filter_id)
+{
+ // Add this into the domain id list
+ uint32 map_idx= filter_id & m_filter_id_mask;
+ gtid_filter_element *filter_idx= m_filters_by_id[map_idx];
+
+ /* Find list index to add this filter */
+ while (filter_idx)
+ {
+ if (filter_idx->identifier == filter_id)
+ break;
+ filter_idx= filter_idx->next;
+ }
+
+ return filter_idx;
+}
+
+gtid_filter_element *
+Delegating_gtid_event_filter::find_or_create_filter_element_for_id(
+ gtid_filter_identifier filter_id)
+{
+ // Add this into the domain id list
+ uint32 map_idx= filter_id & m_filter_id_mask;
+ gtid_filter_element *filter_idx= m_filters_by_id[map_idx],
+ *prev_idx= NULL;
+
+ /* Find list index to add this filter */
+ while (filter_idx)
+ {
+ prev_idx= filter_idx;
+ if (filter_idx->identifier == filter_id)
+ {
+ break;
+ }
+ prev_idx= filter_idx;
+ filter_idx= filter_idx->next;
+ }
+
+ if (filter_idx == NULL)
+ {
+ // No other domain ids have filters that index here, create this one
+ filter_idx= (gtid_filter_element *) my_malloc(
+ PSI_NOT_INSTRUMENTED, sizeof(gtid_filter_element), MYF(MY_WME));
+ filter_idx->identifier= filter_id;
+ filter_idx->next= NULL;
+ filter_idx->filter= NULL;
+
+ if (prev_idx == NULL)
+ {
+ // This is the first filter in the bucket
+ m_filters_by_id[map_idx]= filter_idx;
+ }
+ else
+ {
+ // End of list, append filter list to tail
+ prev_idx->next= filter_idx;
+ }
+ }
+
+ return filter_idx;
+}
+
+my_bool Delegating_gtid_event_filter::exclude(rpl_gtid *gtid)
+{
+ Gtid_event_filter *filter;
+ gtid_filter_identifier filter_id= get_id_from_gtid(gtid);
+ gtid_filter_element *filter_element= try_find_filter_element_for_id(filter_id);
+ if (filter_element)
+ {
+ filter= filter_element->filter;
+ }
+ else
+ {
+ filter= m_default_filter;
+ }
+
+ return filter->exclude(gtid);
+}
+
+Window_gtid_event_filter *
+Domain_gtid_event_filter::find_or_create_window_filter_for_id(
+ uint32 domain_id)
+{
+ gtid_filter_element *filter_element=
+ find_or_create_filter_element_for_id(domain_id);
+ Window_gtid_event_filter *wgef= NULL;
+
+ if (filter_element->filter == NULL)
+ {
+ // New filter
+ wgef= new Window_gtid_event_filter();
+ filter_element->filter= wgef;
+ }
+ else if (filter_element->filter->get_filter_type() == WINDOW_GTID_FILTER_TYPE)
+ {
+ // We have an existing window filter here
+ wgef= (Window_gtid_event_filter *) filter_element->filter;
+ }
+ /*
+ Else: We have an existing filter but it is not of window type so propogate
+ NULL filter
+ */
+
+ return wgef;
+}
+
+my_bool Domain_gtid_event_filter::add_start_gtid(rpl_gtid *gtid)
+{
+ Window_gtid_event_filter *filter_to_update=
+ find_or_create_window_filter_for_id(gtid->domain_id);
+ return filter_to_update->set_start_gtid(gtid);
+}
+
+my_bool Domain_gtid_event_filter::add_stop_gtid(rpl_gtid *gtid)
+{
+ Window_gtid_event_filter *filter_to_update=
+ find_or_create_window_filter_for_id(gtid->domain_id);
+ return filter_to_update->set_stop_gtid(gtid);
+} \ No newline at end of file
diff --git a/sql/rpl_gtid.h b/sql/rpl_gtid.h
index 11541c8000c..9f5058196b8 100644
--- a/sql/rpl_gtid.h
+++ b/sql/rpl_gtid.h
@@ -380,5 +380,229 @@ extern bool rpl_slave_state_tostring_helper(String *dest, const rpl_gtid *gtid,
extern int gtid_check_rpl_slave_state_table(TABLE *table);
extern rpl_gtid *gtid_parse_string_to_list(const char *p, size_t len,
uint32 *out_len);
+extern rpl_gtid *gtid_unpack_string_to_list(const char *p, size_t len,
+ uint32 *out_len);
+
+/*
+ Interface to support different methods of filtering log events by GTID
+*/
+class Gtid_event_filter
+{
+public:
+ Gtid_event_filter() {};
+ virtual ~Gtid_event_filter() {};
+
+ enum gtid_event_filter_type
+ {
+ DELEGATING_GTID_FILTER_TYPE = 1,
+ WINDOW_GTID_FILTER_TYPE = 2,
+ REJECT_ALL_GTID_FILTER_TYPE = 3
+ };
+
+ /*
+ Run the filter on an input gtid to test if the corresponding log events
+ should be excluded from a result
+
+ Returns TRUE when the event group corresponding to the input GTID should be
+ excluded.
+ Returns FALSE when the event group should be included.
+ */
+ virtual my_bool exclude(rpl_gtid *) = 0;
+
+ /*
+ The gtid_event_filter_type that corresponds to the underlying filter
+ implementation
+ */
+ virtual uint32 get_filter_type() = 0;
+};
+
+/*
+ Filter implementation which will exclude any and all input GTIDs. This is
+ used to set default behavior for GTIDs that do not have explicit filters
+ set on their domain_id, e.g. when a Window_gtid_event_filter is used for
+ a specific domain, then all other domain_ids will be rejected using this
+ filter implementation.
+*/
+class Reject_all_gtid_filter : public Gtid_event_filter
+{
+public:
+ Reject_all_gtid_filter() {}
+ ~Reject_all_gtid_filter() {}
+ my_bool exclude(rpl_gtid *gtid) { return TRUE; }
+ uint32 get_filter_type() { return REJECT_ALL_GTID_FILTER_TYPE; }
+};
+
+/*
+ A virtual sub-class of Gtid_event_filter which allows for quick identification
+ of potentially applicable filters for arbitrary GTIDs.
+*/
+typedef uint32 gtid_filter_identifier;
+class Identifiable_gtid_event_filter : public Gtid_event_filter
+{
+
+public:
+ Identifiable_gtid_event_filter() {};
+ virtual ~Identifiable_gtid_event_filter() {};
+
+ enum gtid_filter_lookup_flags
+ {
+ BY_DOMAIN_ID= 0x1
+ };
+
+ virtual my_bool exclude(rpl_gtid *) = 0;
+ virtual uint32 get_filter_type() = 0;
+ virtual int get_lookup_flags() = 0;
+ virtual gtid_filter_identifier get_filter_identifier() = 0;
+};
+
+/*
+ A filter implementation that passes through events between two GTIDs, m_start
+ (exclusive) and m_stop (inclusive).
+
+ This filter is stateful, such that it expects GTIDs to be a sequential
+ stream, and internally, the window will activate/deactivate when the start
+ and stop positions of the event stream have passed through, respectively.
+
+ Window activation is used to pass through events from arbitrary servers that
+ were not mentioned within m_start or m_stop, yet still fall within the
+ boundary.
+*/
+class Window_gtid_event_filter : public Identifiable_gtid_event_filter
+{
+public:
+ Window_gtid_event_filter();
+ Window_gtid_event_filter(rpl_gtid *start, rpl_gtid *stop);
+ ~Window_gtid_event_filter() {}
+
+ my_bool exclude(rpl_gtid*);
+ gtid_filter_identifier get_filter_identifier();
+
+ my_bool set_start_gtid(rpl_gtid *start);
+ my_bool set_stop_gtid(rpl_gtid *stop);
+
+ /*
+ Windows are indexed by the domain_id of a GTID
+ */
+ int get_lookup_flags()
+ {
+ return BY_DOMAIN_ID;
+ }
+
+ uint32 get_filter_type() { return WINDOW_GTID_FILTER_TYPE; }
+
+private:
+ /*
+ m_has_start : Indicates if a start to this window has been explicitly
+ provided. A window starts immediately if not provided.
+ */
+ my_bool m_has_start;
+
+ /*
+ m_has_stop : Indicates if a stop to this window has been explicitly
+ provided. A window continues indefinitely if not provided.
+ */
+ my_bool m_has_stop;
+
+ /*
+ m_is_active : Indicates whether or not the program is currently reading
+ events from within this window. When TRUE, events with
+ different server ids than those specified by m_start or
+ m_stop will be passed through.
+ */
+ my_bool m_is_active;
+
+ /*
+ m_has_passed : Indicates whether or not the program is currently reading
+ events from within this window.
+ */
+ my_bool m_has_passed;
+
+ /* m_start : marks the GTID that begins the window (exclusive). */
+ rpl_gtid m_start;
+
+ /* m_stop : marks the GTID that ends the range (inclusive). */
+ rpl_gtid m_stop;
+
+ /* last_gtid_seen: saves the last */
+ rpl_gtid last_gtid_seen;
+};
+
+/*
+ Data structure to help with quick lookup for filters. More specifically,
+ if two filters have identifiers that lead to the same hash, they will be
+ put into a linked list.
+*/
+typedef struct _gtid_filter_element
+{
+ gtid_filter_identifier identifier;
+ Identifiable_gtid_event_filter *filter;
+ struct _gtid_filter_element *next;
+} gtid_filter_element;
+
+/*
+ Gtid_event_filter subclass which has no specific implementation, but rather
+ delegates the filtering to specific identifiable/mapped implementations.
+
+ A default filter is used for GTIDs that are passed through which no explicit
+ filter can be identified.
+
+ This class should be subclassed, where the get_id_from_gtid function
+ specifies how to extract the filter identifier from a GTID.
+*/
+class Delegating_gtid_event_filter : public Gtid_event_filter
+{
+public:
+ Delegating_gtid_event_filter();
+ ~Delegating_gtid_event_filter();
+
+ my_bool exclude(rpl_gtid *gtid);
+ void set_default_filter(Gtid_event_filter *default_filter);
+
+ uint32 get_filter_type() { return DELEGATING_GTID_FILTER_TYPE; }
+
+ virtual gtid_filter_identifier get_id_from_gtid(rpl_gtid *) = 0;
+
+protected:
+
+ uint32 m_filter_id_mask;
+ Gtid_event_filter *m_default_filter;
+
+ /*
+ To reduce time to find a gtid window, they are indexed by domain_id. More
+ specifically, domain_ids are arranged into m_filter_id_mask+1 buckets, and
+ each bucket is a linked list of gtid_filter_elements that share the same
+ index. The index itself is found by a bitwise and, i.e.
+ some_rpl_gtid.domain_id & m_filter_id_mask
+ */
+ gtid_filter_element **m_filters_by_id;
+
+ gtid_filter_element *try_find_filter_element_for_id(gtid_filter_identifier);
+ gtid_filter_element *find_or_create_filter_element_for_id(gtid_filter_identifier);
+};
+
+/*
+ A subclass of Delegating_gtid_event_filter which identifies filters using the
+ domain id of a GTID.
+
+ Additional helper functions include:
+ add_start_gtid(GTID) : adds a start GTID position to this filter, to be
+ identified by its domain id
+ add_stop_gtid(GTID) : adds a stop GTID position to this filter, to be
+ identified by its domain id
+*/
+class Domain_gtid_event_filter : public Delegating_gtid_event_filter
+{
+public:
+ gtid_filter_identifier get_id_from_gtid(rpl_gtid *gtid)
+ {
+ return gtid->domain_id;
+ }
+
+ my_bool add_start_gtid(rpl_gtid *gtid);
+ my_bool add_stop_gtid(rpl_gtid *gtid);
+
+private:
+ Window_gtid_event_filter *find_or_create_window_filter_for_id(gtid_filter_identifier);
+};
#endif /* RPL_GTID_H */
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index e46e46f803c..9f23f537cb1 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -2123,8 +2123,11 @@ dispatch_command_return dispatch_command(enum enum_server_command command, THD *
case COM_BINLOG_DUMP:
{
ulong pos;
+ uint32 n_start_gtids;
+ rpl_gtid *start_gtids= NULL;
ushort flags;
uint32 slave_server_id;
+ uint32 unpack_idx= 0;
status_var_increment(thd->status_var.com_other);
@@ -2133,19 +2136,42 @@ dispatch_command_return dispatch_command(enum enum_server_command command, THD *
break;
/* TODO: The following has to be changed to an 8 byte integer */
- pos = uint4korr(packet);
- flags = uint2korr(packet + 4);
+ if (packet[4] == '-' && packet[9] == '-')
+ {
+ unpack_idx= 18;
+ while (packet[unpack_idx] == ',')
+ unpack_idx += 19; // 18 chars for gtid + 1 for comma
+ start_gtids= gtid_unpack_string_to_list(packet, unpack_idx, &n_start_gtids);
+
+ /*
+ Set pos to the start of the binlog file for scanning
+
+ TODO: When GTID indexing is complete (MDEV-4991), update pos by
+ looking it up in the index
+ */
+ pos= 4;
+ } /* if (packet[4] == '-' && packet[9] == '-') */
+ else
+ {
+ /* Single numeric log position case */
+ pos = uint4korr(packet);
+ unpack_idx += 4;
+ }
+ flags = uint2korr(packet + unpack_idx);
+ unpack_idx += 2;
thd->variables.server_id=0; /* avoid suicide */
- if ((slave_server_id= uint4korr(packet+6))) // mysqlbinlog.server_id==0
+ if ((slave_server_id= uint4korr(packet+unpack_idx))) // mysqlbinlog.server_id==0
kill_zombie_dump_threads(slave_server_id);
thd->variables.server_id = slave_server_id;
+ unpack_idx += 4;
- const char *name= packet + 10;
+ const char *name= packet + unpack_idx;
size_t nlen= strlen(name);
general_log_print(thd, command, "Log: '%s' Pos: %lu", name, pos);
if (nlen < FN_REFLEN)
- mysql_binlog_send(thd, thd->strmake(name, nlen), (my_off_t)pos, flags);
+ mysql_binlog_send(thd, thd->strmake(name, nlen), (my_off_t)pos, flags,
+ start_gtids, n_start_gtids);
thd->unregister_slave(); // todo: can be extraneous
/* fake COM_QUIT -- if we get here, the thread needs to terminate */
error = TRUE;
diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc
index 7bcff12a735..00fc65cf82f 100644
--- a/sql/sql_repl.cc
+++ b/sql/sql_repl.cc
@@ -163,6 +163,8 @@ struct binlog_send_info {
bool should_stop;
size_t dirlen;
+ Gtid_event_filter *gtid_event_filter;
+
binlog_send_info(THD *thd_arg, String *packet_arg, ushort flags_arg,
char *lfn)
: thd(thd_arg), net(&thd_arg->net), packet(packet_arg),
@@ -185,6 +187,8 @@ struct binlog_send_info {
error_text[0] = 0;
bzero(&error_gtid, sizeof(error_gtid));
until_binlog_state.init();
+
+ gtid_event_filter= NULL;
}
};
@@ -1751,6 +1755,7 @@ send_event_to_slave(binlog_send_info *info, Log_event_type event_type,
}
}
+
/* Skip GTID event groups until we reach slave position within a domain_id. */
if (event_type == GTID_EVENT && info->using_gtid_state)
{
@@ -1758,7 +1763,7 @@ send_event_to_slave(binlog_send_info *info, Log_event_type event_type,
slave_connection_state::entry *gtid_entry;
rpl_gtid *gtid;
- if (gtid_state->count() > 0 || until_gtid_state)
+ if (gtid_state->count() > 0 || until_gtid_state || info->gtid_event_filter)
{
rpl_gtid event_gtid;
@@ -1899,6 +1904,17 @@ send_event_to_slave(binlog_send_info *info, Log_event_type event_type,
}
}
}
+
+ /*
+ Should this result be excluded from the output?
+ */
+ if (info->gtid_event_filter &&
+ info->gtid_event_filter->exclude(&event_gtid))
+ {
+ info->gtid_skip_group=
+ (flags2 & Gtid_log_event::FL_STANDALONE ? GTID_SKIP_STANDALONE
+ : GTID_SKIP_TRANSACTION);
+ }
}
}
@@ -2110,7 +2126,9 @@ err:
static int init_binlog_sender(binlog_send_info *info,
LOG_INFO *linfo,
const char *log_ident,
- my_off_t *pos)
+ my_off_t *pos,
+ rpl_gtid *start_gtids,
+ size_t n_start_gtids)
{
THD *thd= info->thd;
int error;
@@ -2130,7 +2148,8 @@ static int init_binlog_sender(binlog_send_info *info,
info->current_checksum_alg= get_binlog_checksum_value_at_connect(thd);
info->mariadb_slave_capability= get_mariadb_slave_capability(thd);
- info->using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state);
+ info->using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state) ||
+ start_gtids != NULL;
DBUG_EXECUTE_IF("simulate_non_gtid_aware_master",
info->using_gtid_state= false;);
@@ -2247,6 +2266,17 @@ static int init_binlog_sender(binlog_send_info *info,
info->clear_initial_log_pos= true;
}
+ if (start_gtids != NULL)
+ {
+ Domain_gtid_event_filter *filter= new Domain_gtid_event_filter();
+ my_off_t i;
+ for(i = 0; i < n_start_gtids; i++)
+ {
+ filter->add_start_gtid(&start_gtids[i]);
+ }
+ info->gtid_event_filter= filter;
+ }
+
return 0;
}
@@ -2840,7 +2870,8 @@ static int send_one_binlog_file(binlog_send_info *info,
}
void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos,
- ushort flags)
+ ushort flags, rpl_gtid *start_gtids,
+ uint32 n_start_gtids)
{
LOG_INFO linfo;
@@ -2860,7 +2891,8 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos,
bzero((char*) &log,sizeof(log));
- if (init_binlog_sender(info, &linfo, log_ident, &pos))
+ if (init_binlog_sender(info, &linfo, log_ident, &pos, start_gtids,
+ n_start_gtids))
goto err;
has_transmit_started= true;
@@ -3022,6 +3054,8 @@ err:
thd->reset_current_linfo();
thd->variables.max_allowed_packet= old_max_allowed_packet;
delete info->fdev;
+ delete info->gtid_event_filter;
+ my_free(start_gtids);
if (likely(info->error == 0))
{
diff --git a/sql/sql_repl.h b/sql/sql_repl.h
index 95916e31abf..bfc35ea5456 100644
--- a/sql/sql_repl.h
+++ b/sql/sql_repl.h
@@ -57,7 +57,8 @@ struct LOAD_FILE_IO_CACHE : public IO_CACHE
int log_loaded_block(IO_CACHE* file, uchar *Buffer, size_t Count);
int init_replication_sys_vars();
-void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, ushort flags);
+void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, ushort flags,
+ rpl_gtid *start_gtids, uint32 n_start_gtids);
#ifdef HAVE_PSI_INTERFACE
extern PSI_mutex_key key_LOCK_slave_state, key_LOCK_binlog_state;