diff options
-rw-r--r-- | client/client_priv.h | 4 | ||||
-rw-r--r-- | client/mysqlbinlog.cc | 364 | ||||
-rw-r--r-- | man/mysqlbinlog.1 | 67 | ||||
-rw-r--r-- | mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_ignore.result | 122 | ||||
-rw-r--r-- | mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_ignore-master.opt | 2 | ||||
-rw-r--r-- | mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_ignore.test | 448 | ||||
-rw-r--r-- | sql/rpl_gtid.cc | 143 | ||||
-rw-r--r-- | sql/rpl_gtid.h | 101 |
8 files changed, 1210 insertions, 41 deletions
diff --git a/client/client_priv.h b/client/client_priv.h index 23b2e6782a1..d32690c5abb 100644 --- a/client/client_priv.h +++ b/client/client_priv.h @@ -105,6 +105,10 @@ enum options_client OPT_COPY_S3_TABLES, OPT_PRINT_TABLE_METADATA, OPT_ASOF_TIMESTAMP, + OPT_IGNORE_DOMAIN_IDS, + OPT_DO_DOMAIN_IDS, + OPT_IGNORE_SERVER_IDS, + OPT_DO_SERVER_IDS, OPT_MAX_CLIENT_OPTION /* should be always the last */ }; diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc index d4e10a7141a..ec01e556259 100644 --- a/client/mysqlbinlog.cc +++ b/client/mysqlbinlog.cc @@ -82,7 +82,7 @@ extern "C" { char server_version[SERVER_VERSION_LENGTH]; } -ulong server_id = 0; +static char *server_id_str; // needed by net_serv.c ulong bytes_sent = 0L, bytes_received = 0L; @@ -144,6 +144,9 @@ static char *charset= 0; static uint verbose= 0; +static char *ignore_domain_ids_str, *do_domain_ids_str; +static char *ignore_server_ids_str, *do_server_ids_str; + static char *start_pos_str, *stop_pos_str; static ulonglong start_position= BIN_LOG_HEADER_SIZE, stop_position= (longlong)(~(my_off_t)0) ; @@ -151,7 +154,10 @@ static ulonglong start_position= BIN_LOG_HEADER_SIZE, #define stop_position_mot ((my_off_t)stop_position) static Gtid_stream_auditor *gtid_stream_auditor= NULL; -static Domain_gtid_event_filter *domain_gtid_filter= NULL; +static Gtid_event_filter *gtid_event_filter= NULL; +static Domain_gtid_event_filter *domain_window_gtid_filter= NULL; +static Domain_gtid_event_filter *domain_id_exclusivity_gtid_filter= NULL; +static Server_gtid_event_filter *server_gtid_filter= NULL; static char *start_datetime_str, *stop_datetime_str; static my_time_t start_datetime= 0, stop_datetime= MY_TIME_T_MAX; @@ -987,9 +993,25 @@ static bool print_row_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, return result; } +/* + The presence of the domain filter indicates whether or not GTID filtering is + enabled or not (a server filter is still valid using log position) +*/ static inline my_bool is_gtid_filtering_enabled() { - return domain_gtid_filter != NULL; + return domain_window_gtid_filter || domain_id_exclusivity_gtid_filter; +} + +/* + In the absence of a domain id, filtering by server id can still be valid. +*/ +static inline my_bool is_server_id_excluded(uint32 server_id) +{ + static rpl_gtid server_tester_gtid; + server_tester_gtid.server_id= server_id; + return server_gtid_filter == NULL + ? FALSE // No server id filter exists + : server_gtid_filter->exclude(&server_tester_gtid); } /** @@ -1029,11 +1051,11 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, #endif /* Run time estimation of the output window configuration. */ - if (ev_type == GTID_LIST_EVENT && is_gtid_filtering_enabled()) + if (ev_type == GTID_LIST_EVENT && domain_window_gtid_filter) { Gtid_list_log_event *glev= (Gtid_list_log_event *)ev; - size_t n_start_gtid_ranges= domain_gtid_filter->get_num_start_gtids(); - rpl_gtid *start_gtids= domain_gtid_filter->get_start_gtids(); + size_t n_start_gtid_ranges= domain_window_gtid_filter->get_num_start_gtids(); + rpl_gtid *start_gtids= domain_window_gtid_filter->get_start_gtids(); for (size_t i= 0; i < n_start_gtid_ranges; i++) { for (size_t k= 0; k < glev->count; k++) @@ -1052,8 +1074,8 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, } } } - size_t n_stop_gtid_ranges= domain_gtid_filter->get_num_stop_gtids(); - rpl_gtid *stop_gtids= domain_gtid_filter->get_stop_gtids(); + size_t n_stop_gtid_ranges= domain_window_gtid_filter->get_num_stop_gtids(); + rpl_gtid *stop_gtids= domain_window_gtid_filter->get_stop_gtids(); for (size_t i= 0; i < n_stop_gtid_ranges; i++) { for (size_t k= 0; k < glev->count; k++) @@ -1080,21 +1102,21 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, 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 && (domain_gtid_filter || gtid_stream_auditor)) + if (ev_type == GTID_EVENT && (is_gtid_filtering_enabled() || gtid_stream_auditor)) { Gtid_log_event *gle= (Gtid_log_event*) ev; rpl_gtid gtid; set_rpl_gtid(>id, gle->domain_id, gle->server_id, gle->seq_no); - if (domain_gtid_filter) + if (gtid_event_filter) { - if (domain_gtid_filter->has_finished()) + if (gtid_event_filter->has_finished()) { retval= OK_STOP; goto end; } - if (!domain_gtid_filter->exclude(>id)) + if (!gtid_event_filter->exclude(>id)) ev->activate_current_event_group(); else ev->deactivate_current_event_group(); @@ -1131,8 +1153,7 @@ Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, the format_description event so that we can parse subsequent events. */ - if (ev_type != ROTATE_EVENT && - server_id && (server_id != ev->server_id)) + if (ev_type != ROTATE_EVENT && is_server_id_excluded(ev->server_id)) goto end; } if ((ev->when >= stop_datetime) @@ -1719,9 +1740,42 @@ static struct my_option my_options[] = "Print row event positions", &print_row_event_positions, &print_row_event_positions, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, - {"server-id", 0, - "Extract only binlog entries created by the server having the given id.", - &server_id, &server_id, 0, GET_ULONG, + {"ignore-domain-ids", OPT_IGNORE_DOMAIN_IDS, + "A list of positive integers, separated by commas, that form a blacklist " + "of domain ids. Any log event with a GTID that originates from a domain id " + "specified in this list is hidden. Cannot be used with " + "--do-domain-ids. When used with --(ignore|do)-server-ids, the result is the " + "intersection between the two datasets.", + &ignore_domain_ids_str, &ignore_domain_ids_str, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"do-domain-ids", OPT_DO_DOMAIN_IDS, + "A list of positive integers, separated by commas, that form a whitelist " + "of domain ids. Any log event with a GTID that originates from a domain id " + "specified in this list is displayed. Cannot be used with " + "--ignore-domain-ids. When used with --(ignore|do)-server-ids, the result " + "is the intersection between the two datasets.", + &do_domain_ids_str, &do_domain_ids_str, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, + 0, 0, 0, 0, 0}, + {"ignore-server-ids", OPT_IGNORE_SERVER_IDS, + "A list of positive integers, separated by commas, that form a blacklist " + "of server ids. Any log event originating from a server id " + "specified in this list is hidden. Cannot be used with " + "--do-server-ids. When used with --(ignore|do)-domain-ids, the result is " + "the intersection between the two datasets.", + &ignore_server_ids_str, &ignore_server_ids_str, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"do-server-ids", OPT_DO_SERVER_IDS, + "A list of positive integers, separated by commas, that form a whitelist " + "of server ids. Any log event originating from a server id " + "specified in this list is displayed. Cannot be used with " + "--ignore-server-ids. When used with --(ignore|do)-domain-ids, the result " + "is the intersection between the two datasets. Synonymous to --server-id.", + &do_server_ids_str, &do_server_ids_str, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"server-id", OPT_SERVER_ID, + "Extract only binlog entries created by the server having the given id. " + "Synonymous with --do-server-ids.", + &server_id_str, &server_id_str, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"set-charset", OPT_SET_CHARSET, "Add 'SET NAMES character_set' to the output.", &charset, @@ -1924,9 +1978,31 @@ static void cleanup() my_free(stop_datetime_str); my_free(start_pos_str); my_free(stop_pos_str); + my_free(ignore_domain_ids_str); + my_free(do_domain_ids_str); + my_free(ignore_server_ids_str); + my_free(do_server_ids_str); + my_free(server_id_str); free_root(&glob_root, MYF(0)); - delete domain_gtid_filter; + if (gtid_event_filter) + { + delete gtid_event_filter; + } + else + { + /* + If there was an error during input parsing, gtid_event_filter will not + be set, so we need to ensure the comprising filters are cleaned up + properly. + */ + if (domain_id_exclusivity_gtid_filter) + delete domain_id_exclusivity_gtid_filter; + if (domain_window_gtid_filter) + delete domain_window_gtid_filter; + if (server_gtid_filter) + delete server_gtid_filter; + } delete gtid_stream_auditor; delete binlog_filter; @@ -1994,6 +2070,80 @@ static my_time_t convert_str_to_timestamp(const char* str) my_system_gmt_sec(&l_time, &dummy_my_timezone, &dummy_in_dst_time_gap); } +/* + Parse a list of positive numbers separated by commas. + Returns a list of numbers on success, NULL on parsing/resource error +*/ +static uint32 *parse_u32_list(const char *str, size_t str_len, uint32 *n_vals) +{ + const char *str_begin= const_cast<char *>(str); + const char *str_end= str_begin + str_len; + const char *p = str_begin; + uint32 len= 0, alloc_len= 4; + uint32 *list= NULL; + int err; + + for (;;) + { + uint32 val; + + /* + Set it to the end of the string overall, but when parsing, it will be + moved to the end of the element + */ + char *el_end= (char*) str_begin + str_len; + + if (len >= (((uint32)1 << 28)-1)) + { + my_free(list); + list= NULL; + goto end; + } + + val= (uint32)my_strtoll10(p, &el_end, &err); + if (err) + { + my_free(list); + list= NULL; + goto end; + } + p = el_end; + + if ((!list || len >= alloc_len) && + !(list= + (uint32 *)my_realloc(PSI_INSTRUMENT_ME, list, + (alloc_len= alloc_len*2) * sizeof(uint32), + MYF(MY_FREE_ON_ERROR|MY_ALLOW_ZERO_PTR)))) + return NULL; + list[len++]= val; + + if (el_end == str_end) + break; + if (*p != ',') + { + my_free(list); + return NULL; + } + ++p; + } + *n_vals= len; + +end: + return list; +} + +/* + If multiple different types of Gtid_event_filters are used, the result + should be the intersection between the filter types. +*/ +static void extend_main_gtid_event_filter(Gtid_event_filter *new_filter) +{ + if (gtid_event_filter == NULL) + gtid_event_filter= new_filter; + else + gtid_event_filter= + new Intersecting_gtid_event_filter(gtid_event_filter, new_filter); +} extern "C" my_bool get_one_option(const struct my_option *opt, const char *argument, const char *filename) @@ -2181,8 +2331,9 @@ get_one_option(const struct my_option *opt, const char *argument, const char *fi case OPT_STOP_POSITION: { /* Stop position was already specified, so reset it and use the new list */ - if (domain_gtid_filter && domain_gtid_filter->get_num_stop_gtids() > 0) - domain_gtid_filter->clear_stop_gtids(); + if (domain_window_gtid_filter && + domain_window_gtid_filter->get_num_stop_gtids() > 0) + domain_window_gtid_filter->clear_stop_gtids(); uint32 n_stop_gtid_ranges= 0; rpl_gtid *stop_gtids= gtid_parse_string_to_list( @@ -2208,14 +2359,13 @@ get_one_option(const struct my_option *opt, const char *argument, const char *fi else if (n_stop_gtid_ranges > 0) { uint32 gtid_idx; - - if (domain_gtid_filter == NULL) - domain_gtid_filter= new Domain_gtid_event_filter(); + if (domain_window_gtid_filter == NULL) + domain_window_gtid_filter= new Domain_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_gtid_filter->add_stop_gtid(stop_gtid)) + if (domain_window_gtid_filter->add_stop_gtid(stop_gtid)) { my_free(stop_gtids); return 1; @@ -2232,8 +2382,9 @@ get_one_option(const struct my_option *opt, const char *argument, const char *fi case 'j': { /* Start position was already specified, so reset it and use the new list */ - if (domain_gtid_filter && domain_gtid_filter->get_num_start_gtids() > 0) - domain_gtid_filter->clear_start_gtids(); + if (domain_window_gtid_filter && + domain_window_gtid_filter->get_num_start_gtids() > 0) + domain_window_gtid_filter->clear_start_gtids(); uint32 n_start_gtid_ranges= 0; rpl_gtid *start_gtids= gtid_parse_string_to_list( @@ -2247,6 +2398,9 @@ get_one_option(const struct my_option *opt, const char *argument, const char *fi No GTIDs specified in OPT_START_POSITION specification. Treat the value as a singular index. */ + if (domain_window_gtid_filter == NULL) + domain_window_gtid_filter= new Domain_gtid_event_filter(); + start_position= my_strtoll10(start_pos_str, &end_ptr, &err); if (err || *end_ptr) @@ -2260,14 +2414,13 @@ get_one_option(const struct my_option *opt, const char *argument, const char *fi else if (n_start_gtid_ranges > 0) { uint32 gtid_idx; - - if (domain_gtid_filter == NULL) - domain_gtid_filter= new Domain_gtid_event_filter(); + if (domain_window_gtid_filter == NULL) + domain_window_gtid_filter= new Domain_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_gtid_filter->add_start_gtid(start_gtid)) + if (domain_window_gtid_filter->add_start_gtid(start_gtid)) { my_free(start_gtids); return 1; @@ -2281,6 +2434,129 @@ get_one_option(const struct my_option *opt, const char *argument, const char *fi } break; } + case OPT_IGNORE_DOMAIN_IDS: + { + uint32 n_ids= 0; + uint32 *domain_ids= parse_u32_list(ignore_domain_ids_str, + strlen(ignore_domain_ids_str), &n_ids); + + if (domain_ids == NULL || n_ids == 0) + { + sql_print_error("Input for --ignore-domain-ids is invalid. Should be a " + "list of positive integers"); + return 1; + } + + if (domain_id_exclusivity_gtid_filter == NULL) + domain_id_exclusivity_gtid_filter= new Domain_gtid_event_filter(); + + if (domain_id_exclusivity_gtid_filter->set_blacklist(domain_ids, n_ids)) + { + my_free(domain_ids); + /* TODO Set_blacklist should write the specific error*/ + sql_print_error( + "Cannot combine --ignore-domain-ids with --do-domain-ids"); + return 1; + } + my_free(domain_ids); + break; + } + case OPT_DO_DOMAIN_IDS: + { + uint32 n_ids= 0; + uint32 *domain_ids= parse_u32_list(do_domain_ids_str, + strlen(do_domain_ids_str), &n_ids); + + if (domain_ids == NULL || n_ids == 0) + { + sql_print_error("Input for --do-domain-ids is invalid. Should be a " + "list of positive integers"); + return 1; + } + + if (domain_id_exclusivity_gtid_filter == NULL) + domain_id_exclusivity_gtid_filter= new Domain_gtid_event_filter(); + + if (domain_id_exclusivity_gtid_filter->set_whitelist(domain_ids, n_ids)) + { + my_free(domain_ids); + return 1; + } + my_free(domain_ids); + break; + } + case OPT_IGNORE_SERVER_IDS: + { + uint32 n_ids= 0; + uint32 *server_ids= parse_u32_list(ignore_server_ids_str, + strlen(ignore_server_ids_str), &n_ids); + + if (server_ids == NULL || n_ids == 0) + { + sql_print_error("Input for --ignore-server-ids is invalid. Should be a " + "list of positive integers"); + return 1; + } + + if (server_gtid_filter == NULL) + server_gtid_filter= new Server_gtid_event_filter(); + + if (server_gtid_filter->set_blacklist(server_ids, n_ids)) + { + my_free(server_ids); + return 1; + } + my_free(server_ids); + break; + } + case OPT_DO_SERVER_IDS: + { + uint32 n_ids= 0; + uint32 *server_ids= + parse_u32_list(do_server_ids_str, strlen(do_server_ids_str), &n_ids); + + if (server_ids == NULL || n_ids == 0) + { + sql_print_error("Input for --do-server-ids is invalid. Should be a " + "list of positive integers"); + return 1; + } + + if (server_gtid_filter == NULL) + server_gtid_filter= new Server_gtid_event_filter(); + + if (server_gtid_filter->set_whitelist(server_ids, n_ids)) + { + my_free(server_ids); + return 1; + } + my_free(server_ids); + break; + } + case OPT_SERVER_ID: + { + uint32 n_ids= 0; + uint32 *server_ids= + parse_u32_list(server_id_str, strlen(server_id_str), &n_ids); + + if (server_ids == NULL || n_ids == 0) + { + sql_print_error("Input for --server-id is invalid. Should be a " + "list of positive integers"); + return 1; + } + + if (server_gtid_filter == NULL) + server_gtid_filter= new Server_gtid_event_filter(); + + if (server_gtid_filter->set_whitelist(server_ids, n_ids)) + { + my_free(server_ids); + return 1; + } + my_free(server_ids); + break; + } case '?': usage(); opt_version= 1; @@ -2292,7 +2568,6 @@ get_one_option(const struct my_option *opt, const char *argument, const char *fi return 0; } - static int parse_args(int *argc, char*** argv) { int ho_error; @@ -2314,12 +2589,10 @@ static int parse_args(int *argc, char*** argv) start_position= UINT_MAX32; } - if (domain_gtid_filter) - Log_event::enable_event_group_filtering(); - if (opt_gtid_strict_mode) { - if (domain_gtid_filter && domain_gtid_filter->validate_window_filters()) + if (domain_window_gtid_filter && + domain_window_gtid_filter->validate_window_filters()) { /* In strict mode, if any --start/stop-position GTID ranges are invalid, @@ -2331,6 +2604,21 @@ static int parse_args(int *argc, char*** argv) gtid_stream_auditor= new Gtid_stream_auditor(); } + if (domain_window_gtid_filter) + { + Log_event::enable_event_group_filtering(); + extend_main_gtid_event_filter(domain_window_gtid_filter); + } + + if(domain_id_exclusivity_gtid_filter) + { + Log_event::enable_event_group_filtering(); + extend_main_gtid_event_filter(domain_id_exclusivity_gtid_filter); + } + + if(server_gtid_filter) + extend_main_gtid_event_filter(server_gtid_filter); + return 0; } @@ -2512,7 +2800,7 @@ static Exit_status check_master_version() goto err; } - if (domain_gtid_filter && domain_gtid_filter->get_num_start_gtids() > 0) + if (domain_window_gtid_filter && domain_window_gtid_filter->get_num_start_gtids() > 0) { char str_buf[256]; String query_str(str_buf, sizeof(str_buf), system_charset_info); @@ -2520,8 +2808,8 @@ static Exit_status check_master_version() query_str.append(STRING_WITH_LEN("SET @slave_connect_state='"), system_charset_info); - size_t n_start_gtids= domain_gtid_filter->get_num_start_gtids(); - rpl_gtid *start_gtids= domain_gtid_filter->get_start_gtids(); + size_t n_start_gtids= domain_window_gtid_filter->get_num_start_gtids(); + rpl_gtid *start_gtids= domain_window_gtid_filter->get_start_gtids(); for (size_t gtid_idx = 0; gtid_idx < n_start_gtids; gtid_idx++) { diff --git a/man/mysqlbinlog.1 b/man/mysqlbinlog.1 index 17c81809ce2..1e37d8b6830 100644 --- a/man/mysqlbinlog.1 +++ b/man/mysqlbinlog.1 @@ -882,11 +882,78 @@ quote the value (e.g. \fB--rewrite-db="oldname->newname"\fR\&. .sp -1 .IP \(bu 2.3 .\} +.\" mysqlbinlog: ignore-domain-ids option +.\" ignore-domain-ids option: mysqlbinlog +\fB\-\-ignore\-domain\-ids=\fR\fB\fIIDs\fR\fR +.sp +Hide events which are a part of any stream identified by the domain ID +list \fIIDs\fR\&. Cannot be used with \-\-do\-domain\-ids\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +.\" mysqlbinlog: do-domain-ids option +.\" do-domain-ids option: mysqlbinlog +\fB\-\-do\-domain\-ids=\fR\fB\fIIDs\fR\fR +.sp +Display only the events which exist in a stream identified by \fIIDs\fR\&. +Cannot be used with \-\-ignore\-domain\-ids\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +.\" mysqlbinlog: ignore-server-ids option +.\" ignore-server-ids option: mysqlbinlog +\fB\-\-ignore\-server\-ids=\fR\fB\fIIDs\fR\fR +.sp +Hide events which originated from any server whose ID is specified in +\fIIDs\fR\&. Cannot be used with \-\-do\-server\-ids\ or \-\-server\-id\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +.\" mysqlbinlog: do-server-ids option +.\" do-server-ids option: mysqlbinlog +\fB\-\-do\-server\-ids=\fR\fB\fIIDs\fR\fR +.sp +Display only the events that originated from a server identified in +\fIIDs\fR\&. Cannot be used with \-\-ignore\-server\-ids, and +synonymous with \-\-server\-id\&. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} .\" mysqlbinlog: server-id option .\" server-id option: mysqlbinlog \fB\-\-server\-id=\fR\fB\fIid\fR\fR .sp Display only those events created by the server having the given server ID\&. +Cannot be used with \-\-ignore\-server\-ids, and synonymous with +\-\-do\-server\-ids\&. .RE .sp .RS 4 diff --git a/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_ignore.result b/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_ignore.result new file mode 100644 index 00000000000..a0ca7c2f943 --- /dev/null +++ b/mysql-test/suite/binlog/r/binlog_mysqlbinlog_gtid_ignore.result @@ -0,0 +1,122 @@ +############################### +# Test Setup +############################### +SET timestamp=1000000000; +RESET MASTER; +SET @@session.gtid_domain_id= 0; +SET @@session.server_id= 1; +CREATE TABLE t1 (a int); +SET @@session.server_id= 2; +CREATE TABLE t2 (a int); +INSERT INTO t2 values (3); +SET @@session.gtid_domain_id= 1; +SET @@session.server_id= 1; +CREATE TABLE t3 (a int); +INSERT INTO t3 values (4); +SET @@session.server_id= 3; +CREATE TABLE t4 (a int); +INSERT INTO t4 values (5); +SET @@session.gtid_domain_id= 0; +SET @@session.server_id= 1; +INSERT INTO t1 values (1); +SET @@session.gtid_domain_id= 2; +SET @@session.server_id= 1; +CREATE TABLE t5 (a int); +INSERT INTO t5 values (6); +SET @@session.gtid_domain_id= 0; +SET @@session.server_id= 1; +INSERT INTO t1 values (2); +FLUSH LOGS; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +DROP TABLE t4; +DROP TABLE t5; +RESET MASTER; +############################### +# Test Cases +############################### +# +# Test Case 1: +# --ignore-domain-ids standalone +# MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-domain-ids=0,1 | MYSQL +DROP TABLE t5; +# +# Test Case 2: +# --do-domain-ids standalone +# MYSQL_BINLOG BINLOG_FILE_PARAM --do-domain-ids=0,2 | MYSQL +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t5; +# +# Test Case 3: +# --ignore-server-ids standalone +# MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-server-ids=2,3 | MYSQL +DROP TABLE t1; +DROP TABLE t3; +DROP TABLE t5; +# +# Test Case 4: +# --do-server-ids standalone +# MYSQL_BINLOG BINLOG_FILE_PARAM --do-server-ids=2,3 | MYSQL +DROP TABLE t2; +DROP TABLE t4; +# +# Test Case 5: +# --ignore-domain-ids with --ignore-server-ids intersects the datasets +# MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-server-ids=1,3 --ignore-domain-ids=1,2 | MYSQL +DROP TABLE t2; +# +# Test Case 6: +# --do-domain-ids with GTID range intersects the datasets +# MYSQL_BINLOG BINLOG_FILE_PARAM --do-domain-ids=0 --start-position=0-1-0 --stop-position=0-1-4 | MYSQL +DROP TABLE t1; +DROP TABLE t2; +# +# Test Case 7: +# --ignore-server-ids with GTID range intersects the datasets +# MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-server-ids=2,3 --start-position=0-1-0 --stop-position=0-1-4 | MYSQL +DROP TABLE t1; +DROP TABLE t3; +# +# Test Case 8: +# --ignore-domain-ids, --ignore-server-ids, and GTID range +# MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-domain-ids=0 --ignore-server-ids=2,3 --stop-position=0-1-4 | MYSQL +DROP TABLE t3; +# +# Test Case 9: +# If the same domain id rule is specified multiple times, only the values +# from the last specification will be used +# MYSQL_BINLOG BINLOG_FILE_PARAM --do-domain-ids=0,1 --do-domain-ids=2 | MYSQL +DROP TABLE t5; +# +# Test Case 10: +# --server-id and --do-server-ids are aliases and the latter will +# override the former +# MYSQL_BINLOG BINLOG_FILE_PARAM --do-server-ids=1,2 --server-id=3 | MYSQL +DROP TABLE t4; +# +# Test Case 11: +# --ignore-domain-ids, --ignore-server-ids, and multiple GTID ranges +# MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-domain-ids=0 --ignore-server-ids=2,3 --start-position=0-1-0,1-1-0 --stop-position=1-1-2,0-1-4| MYSQL +DROP TABLE t3; +############################## +# Error Cases +############################## +# +# Error Case 1: +# --ignore-domain-ids and --do-domain-ids both specified +# MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-domain-ids=0 --do-domain-ids=1 +# +# Error Case 2: +# --ignore-server-ids and --do-server-ids both specified +# MYSQL_BINLOG BINLOG_FILE_PARAM --do-server-ids=1 --ignore-server-ids=2 +# +# Error Case 3: +# Invalid ID number provided +# MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-server-ids=-1 +############################## +# Cleanup +############################## +SET @@global.gtid_domain_id= 0; +SET @@global.server_id= 1; diff --git a/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_ignore-master.opt b/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_ignore-master.opt new file mode 100644 index 00000000000..c94389cfd56 --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_ignore-master.opt @@ -0,0 +1,2 @@ +--timezone=GMT-8 + diff --git a/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_ignore.test b/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_ignore.test new file mode 100644 index 00000000000..816ec50c55d --- /dev/null +++ b/mysql-test/suite/binlog/t/binlog_mysqlbinlog_gtid_ignore.test @@ -0,0 +1,448 @@ +# +# Purpose: +# This test ensures that the mariadb-binlog CLI tool can use GTIDS to filter +# log events by blacklisting or whitelisting domain ids or server ids. +# Specifically, it tests the new arguments --do-domain-ids, --ignore-domain-ids, +# --ignore-server-ids, and --do-server-ids to ensure they exclude the correct +# events. +# +# Methodology: +# This test validates that the new arguments produce the correct output +# through the following test cases: +# Test Case 1) --ignore-domain-ids standalone +# Test Case 2) --do-domain-ids standalone +# Test Case 3) --ignore-server-ids standalone +# Test Case 4) --do-server-ids standalone +# Test Case 5) --ignore-domain-ids with --ignore-server-ids intersects the +# datasets +# Test Case 6) --ignore-domain-ids with GTID range unions the datasets +# Test Case 7) --ignore-server-ids with GTID range intersects the datasets +# Test Case 8) --ignore-domain-ids, --ignore-server-ids, and GTID range +# Test Case 9) --server-id and --do-server-ids union results together +# +# Additionally, this test validates the following error scenarios: +# Error Case 1) --ignore-domain-ids and --do-domain-ids both specified +# Error Case 1) --ignore-server-ids and --do-server-ids both specified +# Error Case 2) --(ignore|do)-domain-ids and GTID range specified on the same +# domain +# +# References: +# MDEV-20119: Implement the --do-domain-ids, --ignore-domain-ids, and +# --ignore-server-ids options for mysqlbinlog +# + +--source include/have_log_bin.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 BINLOG_FILENAME= query_get_value(SHOW MASTER STATUS, File, 1) +--let BINLOG_FILE_PARAM= $MYSQLD_DATADIR/$BINLOG_FILENAME.orig + +--let table_inconsistent_err= "table data is inconsistent after replaying binlog events"; +--let table_exists_error= "table exists but binlog playback should have excluded its creation"; + +# Initialize test data +SET @@session.gtid_domain_id= 0; +SET @@session.server_id= 1; +CREATE TABLE t1 (a int); + +SET @@session.server_id= 2; +CREATE TABLE t2 (a int); +INSERT INTO t2 values (3); +--let t2_checksum= `CHECKSUM TABLE t2` + +SET @@session.gtid_domain_id= 1; +SET @@session.server_id= 1; +CREATE TABLE t3 (a int); +INSERT INTO t3 values (4); +--let t3_checksum= `CHECKSUM TABLE t3` + +SET @@session.server_id= 3; +CREATE TABLE t4 (a int); +INSERT INTO t4 values (5); +--let t4_checksum= `CHECKSUM TABLE t4` + +SET @@session.gtid_domain_id= 0; +SET @@session.server_id= 1; +INSERT INTO t1 values (1); +--let t1_partial_checksum= `CHECKSUM TABLE t1` + +SET @@session.gtid_domain_id= 2; +SET @@session.server_id= 1; +CREATE TABLE t5 (a int); +INSERT INTO t5 values (6); +--let t5_checksum= `CHECKSUM TABLE t5` + +SET @@session.gtid_domain_id= 0; +SET @@session.server_id= 1; +INSERT INTO t1 values (2); +--let t1_checksum= `CHECKSUM TABLE t1` + + +FLUSH LOGS; + +--copy_file $MYSQLD_DATADIR/$BINLOG_FILENAME $BINLOG_FILE_PARAM + +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +DROP TABLE t4; +DROP TABLE t5; +RESET MASTER; + +--echo ############################### +--echo # Test Cases +--echo ############################### + +--echo # +--echo # Test Case 1: +--echo # --ignore-domain-ids standalone +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-domain-ids=0,1 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --ignore-domain-ids=0,1 | $MYSQL +if ($t5_checksum != `CHECKSUM TABLE t5`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't1'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't2'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't3'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't4'`) +{ + die $table_exists_error; +} +DROP TABLE t5; + +--echo # +--echo # Test Case 2: +--echo # --do-domain-ids standalone +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --do-domain-ids=0,2 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --do-domain-ids=0,2 | $MYSQL +if ($t1_checksum != `CHECKSUM TABLE t1`) +{ + die $table_inconsistent_err; +} +if ($t2_checksum != `CHECKSUM TABLE t2`) +{ + die $table_inconsistent_err; +} +if ($t5_checksum != `CHECKSUM TABLE t5`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't3'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't4'`) +{ + die $table_exists_error; +} +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t5; + +--echo # +--echo # Test Case 3: +--echo # --ignore-server-ids standalone +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-server-ids=2,3 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --ignore-server-ids=2,3 | $MYSQL +if ($t1_checksum != `CHECKSUM TABLE t1`) +{ + die $table_inconsistent_err; +} +if ($t3_checksum != `CHECKSUM TABLE t3`) +{ + die $table_inconsistent_err; +} +if ($t5_checksum != `CHECKSUM TABLE t5`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't2'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't4'`) +{ + die $table_exists_error; +} +DROP TABLE t1; +DROP TABLE t3; +DROP TABLE t5; + +--echo # +--echo # Test Case 4: +--echo # --do-server-ids standalone +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --do-server-ids=2,3 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --do-server-ids=2,3 | $MYSQL +if ($t2_checksum != `CHECKSUM TABLE t2`) +{ + die $table_inconsistent_err; +} +if ($t4_checksum != `CHECKSUM TABLE t4`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't1'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't3'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't5'`) +{ + die $table_exists_error; +} +DROP TABLE t2; +DROP TABLE t4; + +--echo # +--echo # Test Case 5: +--echo # --ignore-domain-ids with --ignore-server-ids intersects the datasets +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-server-ids=1,3 --ignore-domain-ids=1,2 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --ignore-server-ids=1,3 --ignore-domain-ids=1,2 | $MYSQL +if ($t2_checksum != `CHECKSUM TABLE t2`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't1'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't3'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't4'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't5'`) +{ + die $table_exists_error; +} +DROP TABLE t2; + + +--echo # +--echo # Test Case 6: +--echo # --do-domain-ids with GTID range intersects the datasets +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --do-domain-ids=0 --start-position=0-1-0 --stop-position=0-1-4 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --do-domain-ids=0 --start-position=0-1-0 --stop-position=0-1-4 | $MYSQL +if ($t1_partial_checksum != `CHECKSUM TABLE t1`) +{ + die $table_inconsistent_err; +} +if ($t2_checksum != `CHECKSUM TABLE t2`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't3'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't4'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't5'`) +{ + die $table_exists_error; +} +DROP TABLE t1; +DROP TABLE t2; + +--echo # +--echo # Test Case 7: +--echo # --ignore-server-ids with GTID range intersects the datasets +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-server-ids=2,3 --start-position=0-1-0 --stop-position=0-1-4 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --ignore-server-ids=2,3 --start-position=0-1-0 --stop-position=0-1-4 | $MYSQL +if ($t1_partial_checksum != `CHECKSUM TABLE t1`) +{ + die $table_inconsistent_err; +} +if ($t3_checksum != `CHECKSUM TABLE t3`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't2'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't4'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't5'`) +{ + die $table_exists_error; +} +DROP TABLE t1; +DROP TABLE t3; + + +--echo # +--echo # Test Case 8: +--echo # --ignore-domain-ids, --ignore-server-ids, and GTID range +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-domain-ids=0 --ignore-server-ids=2,3 --stop-position=0-1-4 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --ignore-domain-ids=0 --ignore-server-ids=2,3 --stop-position=0-1-4 | $MYSQL +if ($t3_checksum != `CHECKSUM TABLE t3`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't1'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't2'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't4'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't5'`) +{ + die $table_exists_error; +} +DROP TABLE t3; + +--echo # +--echo # Test Case 9: +--echo # If the same domain id rule is specified multiple times, only the values +--echo # from the last specification will be used +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --do-domain-ids=0,1 --do-domain-ids=2 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --do-domain-ids=0,1 --do-domain-ids=2 | $MYSQL +if ($t5_checksum != `CHECKSUM TABLE t5`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't1'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't2'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't3'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't4'`) +{ + die $table_exists_error; +} +DROP TABLE t5; + +--echo # +--echo # Test Case 10: +--echo # --server-id and --do-server-ids are aliases and the latter will +--echo # override the former +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --do-server-ids=1,2 --server-id=3 | MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --do-server-ids=1,2 --server-id=3 | $MYSQL +if ($t4_checksum != `CHECKSUM TABLE t4`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't1'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't2'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't3'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't5'`) +{ + die $table_exists_error; +} +DROP TABLE t4; + +--echo # +--echo # Test Case 11: +--echo # --ignore-domain-ids, --ignore-server-ids, and multiple GTID ranges +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-domain-ids=0 --ignore-server-ids=2,3 --start-position=0-1-0,1-1-0 --stop-position=1-1-2,0-1-4| MYSQL +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --ignore-domain-ids=0 --ignore-server-ids=2,3 --start-position=0-1-0,1-1-0 --stop-position=1-1-2,0-1-4 | $MYSQL +if ($t3_checksum != `CHECKSUM TABLE t3`) +{ + die $table_inconsistent_err; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't1'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't2'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't4'`) +{ + die $table_exists_error; +} +if (`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'test' AND table_name = 't5'`) +{ + die $table_exists_error; +} +DROP TABLE t3; + +--echo ############################## +--echo # Error Cases +--echo ############################## + +--echo # +--echo # Error Case 1: +--echo # --ignore-domain-ids and --do-domain-ids both specified +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-domain-ids=0 --do-domain-ids=1 +--error 1 +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --ignore-domain-ids=0 --do-domain-ids=1 + +--echo # +--echo # Error Case 2: +--echo # --ignore-server-ids and --do-server-ids both specified +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --do-server-ids=1 --ignore-server-ids=2 +--error 1 +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --do-server-ids=1 --ignore-server-ids=2 + +--echo # +--echo # Error Case 3: +--echo # Invalid ID number provided +--echo # MYSQL_BINLOG BINLOG_FILE_PARAM --ignore-server-ids=-1 +--error 1 +--exec $MYSQL_BINLOG $BINLOG_FILE_PARAM --ignore-server-ids=-1 + +--echo ############################## +--echo # Cleanup +--echo ############################## +#DROP TABLE t1; +--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 a7f8298506f..367c8b7a8c5 100644 --- a/sql/rpl_gtid.cc +++ b/sql/rpl_gtid.cc @@ -3308,7 +3308,8 @@ void free_gtid_filter_element(void *p) } Id_delegating_gtid_event_filter::Id_delegating_gtid_event_filter() - : m_num_explicit_filters(0), m_num_completed_filters(0) + : m_num_explicit_filters(0), m_num_completed_filters(0), + m_whitelist_set(FALSE), m_blacklist_set(FALSE) { my_hash_init(PSI_INSTRUMENT_ME, &m_filters_by_id_hash, &my_charset_bin, 32, offsetof(gtid_filter_element, identifier), @@ -3392,6 +3393,140 @@ my_bool Id_delegating_gtid_event_filter::exclude(rpl_gtid *gtid) return ret; } +int Id_delegating_gtid_event_filter::set_blacklist( + gtid_filter_identifier *id_list, size_t n_ids) +{ + size_t id_ctr; + int err; + + if (m_whitelist_set) + { + /* + Whitelist is already set, we can't do a blacklist and whitelist together + */ + err= 1; + sql_print_error("Cannot create exclusion rule for %s ids because an " + "inclusion rule already exists (possibly from specifying " + "both --do-%s-ids and --ignore-%s-ids)", + get_id_type_name(), get_id_type_name(), + get_id_type_name()); + goto err; + } + + /* If a blacklist is specified more than once, only use the latest values */ + if (m_blacklist_set) + my_hash_reset(&m_filters_by_id_hash); + + for (id_ctr= 0; id_ctr < n_ids; id_ctr++) + { + gtid_filter_identifier filter_id= id_list[id_ctr]; + gtid_filter_element *map_element= + find_or_create_filter_element_for_id(filter_id); + + if(map_element == NULL) + { + err= 1; + goto err; + } + else if (map_element->filter == NULL) + { + map_element->filter= new Reject_all_gtid_filter(); + m_num_explicit_filters++; + } + else if (map_element->filter->get_filter_type() != + REJECT_ALL_GTID_FILTER_TYPE) + { + /* + There is a different filter placed on an id that should be blacklisted. + Error. + */ + err= 1; + sql_print_error("Cannot set filter blacklist on %s id %lu because it " + "already has a filtering rule", + get_id_type_name(), filter_id); + goto err; + } + } + + /* + With a blacklist, we by default want to accept everything that is not + specified in the list + */ + set_default_filter(new Accept_all_gtid_filter()); + m_blacklist_set= TRUE; + err= 0; + +err: + return err; +} + +int Id_delegating_gtid_event_filter::set_whitelist( + gtid_filter_identifier *id_list, size_t n_ids) +{ + size_t id_ctr; + int err; + + if (m_blacklist_set) + { + /* + Blacklist is already set, we can't do a blacklist and whitelist together + */ + err= 1; + sql_print_error("Cannot create inclusion rule for %s ids because an " + "exclusion rule already exists (possibly from specifying " + "both --ignore-%s-ids and --do-%s-ids)", + get_id_type_name(), get_id_type_name(), + get_id_type_name()); + goto err; + } + + /* If a whitelist is specified more than once, only use the latest values */ + if (m_whitelist_set) + my_hash_reset(&m_filters_by_id_hash); + + for (id_ctr= 0; id_ctr < n_ids; id_ctr++) + { + gtid_filter_identifier filter_id= id_list[id_ctr]; + gtid_filter_element *map_element= + find_or_create_filter_element_for_id(filter_id); + + if(map_element == NULL) + { + err= 1; + goto err; + } + else if (map_element->filter == NULL) + { + map_element->filter= new Accept_all_gtid_filter(); + m_num_explicit_filters++; + } + else if (map_element->filter->get_filter_type() != + ACCEPT_ALL_GTID_FILTER_TYPE) + { + /* + There is a different filter placed on an id that should be whitelisted. + Error. + */ + err= 1; + sql_print_error("Cannot set filter whitelist on %s id %lu because it " + "already has a filtering rule", + get_id_type_name(), filter_id); + goto err; + } + } + + /* + With a whitelist, we by only want to accept the ids which are specified. + Everything else should be denied. + */ + set_default_filter(new Reject_all_gtid_filter()); + m_whitelist_set= TRUE; + err= 0; + +err: + return err; +} + Window_gtid_event_filter * Domain_gtid_event_filter::find_or_create_window_filter_for_id( uint32 domain_id) @@ -3608,3 +3743,9 @@ void Domain_gtid_event_filter::clear_stop_gtids() reset_dynamic(&m_stop_filters); } + +my_bool Intersecting_gtid_event_filter::exclude(rpl_gtid *gtid) +{ + DBUG_ASSERT(m_filter1 && m_filter2); + return m_filter1->exclude(gtid) || m_filter2->exclude(gtid); +} diff --git a/sql/rpl_gtid.h b/sql/rpl_gtid.h index 927937d5162..fe5286943d9 100644 --- a/sql/rpl_gtid.h +++ b/sql/rpl_gtid.h @@ -458,7 +458,9 @@ public: { DELEGATING_GTID_FILTER_TYPE = 1, WINDOW_GTID_FILTER_TYPE = 2, - ACCEPT_ALL_GTID_FILTER_TYPE = 3 + ACCEPT_ALL_GTID_FILTER_TYPE = 3, + REJECT_ALL_GTID_FILTER_TYPE = 4, + INTERSECTING_GTID_FILTER_TYPE = 5 }; /* @@ -504,6 +506,20 @@ public: }; /* + Filter implementation to exclude all tested GTIDs. +*/ +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; } + my_bool has_finished() { return FALSE; } + void write_warnings(FILE *out) {} +}; + +/* A filter implementation that passes through events between two GTIDs, m_start (exclusive) and m_stop (inclusive). @@ -657,6 +673,21 @@ public: uint32 get_filter_type() { return DELEGATING_GTID_FILTER_TYPE; } virtual gtid_filter_identifier get_id_from_gtid(rpl_gtid *) = 0; + virtual const char* get_id_type_name() = 0; + + /* + Set the default behavior to include all ids except for the ones that are + provided in the input list or overridden with another filter. + Returns 0 on ok, non-zero on error + */ + int set_blacklist(gtid_filter_identifier *id_list, size_t n_ids); + + /* + Set the default behavior to exclude all ids except for the ones that are + provided in the input list or overridden with another filter. + Returns 0 on ok, non-zero on error + */ + int set_whitelist(gtid_filter_identifier *id_list, size_t n_ids); protected: @@ -666,6 +697,8 @@ protected: HASH m_filters_by_id_hash; + my_bool m_whitelist_set, m_blacklist_set; + gtid_filter_element *find_or_create_filter_element_for_id(gtid_filter_identifier); }; @@ -709,6 +742,8 @@ public: return gtid->domain_id; } + const char* get_id_type_name() { return "domain"; } + /* Validates that window filters with both a start and stop GTID satisfy stop_gtid > start_gtid @@ -759,7 +794,69 @@ private: DYNAMIC_ARRAY m_start_filters; DYNAMIC_ARRAY m_stop_filters; - Window_gtid_event_filter *find_or_create_window_filter_for_id(gtid_filter_identifier); + Window_gtid_event_filter * + find_or_create_window_filter_for_id(gtid_filter_identifier); +}; + +/* + A subclass of Id_delegating_gtid_event_filter which identifies filters using the + server id of a GTID. +*/ +class Server_gtid_event_filter : public Id_delegating_gtid_event_filter +{ + +public: + /* + Returns the server id of from the input GTID + */ + gtid_filter_identifier get_id_from_gtid(rpl_gtid *gtid) + { + return gtid->server_id; + } + + const char* get_id_type_name() { return "server"; } +}; + +/* + A Gtid_event_filter implementation that delegates the filtering to two + other filters, where the result is the intersection between the two. +*/ +class Intersecting_gtid_event_filter : public Gtid_event_filter +{ +public: + Intersecting_gtid_event_filter(Gtid_event_filter *filter1, + Gtid_event_filter *filter2) + : m_filter1(filter1), m_filter2(filter2) {} + ~Intersecting_gtid_event_filter() + { + delete m_filter1; + delete m_filter2; + } + + /* + Returns TRUE if either m_filter1 or m_filter1 exclude the gtid, returns + FALSE otherwise, i.e. both m_filter1 and m_filter2 allow the gtid + */ + my_bool exclude(rpl_gtid *gtid); + uint32 get_filter_type() { return INTERSECTING_GTID_FILTER_TYPE; } + + Gtid_event_filter *get_filter_1() { return m_filter1; } + Gtid_event_filter *get_filter_2() { return m_filter2; } + + /* + Returns true if either filter has finished. To elaborate, as this filter + performs an intersection, if either filter has finished, the result would + be excluded regardless. + */ + my_bool has_finished() + { + DBUG_ASSERT(m_filter1 && m_filter2); + return m_filter1->has_finished() || m_filter2->has_finished(); + } + + protected: + Gtid_event_filter *m_filter1; + Gtid_event_filter *m_filter2; }; #endif /* RPL_GTID_H */ |