Commit ee2b7db3 authored by unknown's avatar unknown

MDEV-4478: Implement GTID "strict mode"

When @@GLOBAL.gtid_strict_mode=1, then certain operations result
in error that would otherwise result in out-of-order binlog files
between servers.

GTID sequence numbers are now allocated independently per domain;
this results in less/no holes in GTID sequences, increasing the
likelyhood that diverging binlogs will be caught by the slave when
GTID strict mode is enabled.
parent f5319394
...@@ -207,6 +207,10 @@ The following options may be given as the first argument: ...@@ -207,6 +207,10 @@ The following options may be given as the first argument:
multiple masters), each independent source server must multiple masters), each independent source server must
use a distinct domain_id. For simple tree-shaped use a distinct domain_id. For simple tree-shaped
replication topologies, it can be left at its default, 0. replication topologies, it can be left at its default, 0.
--gtid-strict-mode Enforce strict seq_no ordering of events in the binary
log. Slave stops with an error if it encounters an event
that would cause it to generate an out-of-order binlog if
executed.
-?, --help Display this help and exit. -?, --help Display this help and exit.
--histogram-size=# Number of bytes used for a histogram. If set to 0, no --histogram-size=# Number of bytes used for a histogram. If set to 0, no
histograms are created by ANALYZE. histograms are created by ANALYZE.
...@@ -966,6 +970,7 @@ gdb FALSE ...@@ -966,6 +970,7 @@ gdb FALSE
general-log FALSE general-log FALSE
group-concat-max-len 1024 group-concat-max-len 1024
gtid-domain-id 0 gtid-domain-id 0
gtid-strict-mode FALSE
help TRUE help TRUE
histogram-size 0 histogram-size 0
histogram-type SINGLE_PREC_HB histogram-type SINGLE_PREC_HB
......
...@@ -55,7 +55,6 @@ wait/synch/mutex/sql/LOCK_global_index_stats ...@@ -55,7 +55,6 @@ wait/synch/mutex/sql/LOCK_global_index_stats
wait/synch/mutex/sql/LOCK_global_system_variables wait/synch/mutex/sql/LOCK_global_system_variables
wait/synch/mutex/sql/LOCK_global_table_stats wait/synch/mutex/sql/LOCK_global_table_stats
wait/synch/mutex/sql/LOCK_global_user_client_stats wait/synch/mutex/sql/LOCK_global_user_client_stats
wait/synch/mutex/sql/LOCK_gtid_counter
wait/synch/mutex/sql/LOCK_open wait/synch/mutex/sql/LOCK_open
wait/synch/mutex/sql/LOCK_plugin wait/synch/mutex/sql/LOCK_plugin
wait/synch/mutex/sql/LOCK_prepared_stmt_count wait/synch/mutex/sql/LOCK_prepared_stmt_count
......
...@@ -41,7 +41,7 @@ master-bin.000002 # ...@@ -41,7 +41,7 @@ master-bin.000002 #
master-bin.000003 # master-bin.000003 #
SHOW BINLOG EVENTS IN 'master-bin.000003' LIMIT 1,1; SHOW BINLOG EVENTS IN 'master-bin.000003' LIMIT 1,1;
Log_name Pos Event_type Server_id End_log_pos Info Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000003 # Gtid_list # # [1-1-3,2-1-4,0-1-1] master-bin.000003 # Gtid_list # # [1-1-2,2-1-1,0-1-1]
SET SESSION debug_dbug="+d,crash_dispatch_command_before"; SET SESSION debug_dbug="+d,crash_dispatch_command_before";
SELECT 1; SELECT 1;
Got one of the listed errors Got one of the listed errors
...@@ -53,7 +53,7 @@ master-bin.000003 # ...@@ -53,7 +53,7 @@ master-bin.000003 #
master-bin.000004 # master-bin.000004 #
SHOW BINLOG EVENTS IN 'master-bin.000004' LIMIT 1,1; SHOW BINLOG EVENTS IN 'master-bin.000004' LIMIT 1,1;
Log_name Pos Event_type Server_id End_log_pos Info Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000004 # Gtid_list # # [1-1-3,0-1-1,2-1-4] master-bin.000004 # Gtid_list # # [1-1-2,0-1-1,2-1-1]
SELECT * FROM t1 ORDER BY a; SELECT * FROM t1 ORDER BY a;
a a
1 1
......
...@@ -46,6 +46,12 @@ INSERT INTO t1 VALUES (100); ...@@ -46,6 +46,12 @@ INSERT INTO t1 VALUES (100);
SET GLOBAL gtid_slave_pos = "100-100-100"; SET GLOBAL gtid_slave_pos = "100-100-100";
ERROR 25000: You are not allowed to execute this command in a transaction ERROR 25000: You are not allowed to execute this command in a transaction
ROLLBACK; ROLLBACK;
SET GLOBAL gtid_strict_mode= 1;
SET GLOBAL gtid_slave_pos = "0-1-1";
ERROR HY000: Specified GTID 0-1-1 conflicts with the binary log which contains a more recent GTID 0-2-11. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos.
SET GLOBAL gtid_slave_pos = "";
ERROR HY000: Specified value for @@gtid_slave_pos contains no value for replication domain 0. This conflicts with the binary log which contains GTID 0-2-11. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos.
SET GLOBAL gtid_strict_mode= 0;
SET GLOBAL gtid_slave_pos = "0-1-1"; SET GLOBAL gtid_slave_pos = "0-1-1";
Warnings: Warnings:
Warning 1947 Specified GTID 0-1-1 conflicts with the binary log which contains a more recent GTID 0-2-11. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos. Warning 1947 Specified GTID 0-1-1 conflicts with the binary log which contains a more recent GTID 0-2-11. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos.
......
...@@ -41,10 +41,10 @@ INSERT INTO t4 VALUES (3, 1); ...@@ -41,10 +41,10 @@ INSERT INTO t4 VALUES (3, 1);
INSERT INTO t3 VALUES (3); INSERT INTO t3 VALUES (3);
INSERT INTO t3 VALUES (4); INSERT INTO t3 VALUES (4);
INSERT INTO t4 VALUES (3, 3); INSERT INTO t4 VALUES (3, 3);
START SLAVE UNTIL master_gtid_pos= "1-1-7,2-1-14,3-1-21"; START SLAVE UNTIL master_gtid_pos= "1-1-4,2-1-4,3-1-4";
START SLAVE UNTIL master_gtid_pos= "1-1-4,2-1-14,3-1-24"; START SLAVE UNTIL master_gtid_pos= "1-1-1,2-1-4,3-1-7";
START SLAVE UNTIL master_gtid_pos= "2-1-11,3-1-21,1-1-10"; START SLAVE UNTIL master_gtid_pos= "2-1-1,3-1-4,1-1-7";
START SLAVE UNTIL master_gtid_pos= "3-1-18,1-1-7,2-1-17"; START SLAVE UNTIL master_gtid_pos= "3-1-1,1-1-4,2-1-7";
include/wait_for_slave_to_stop.inc include/wait_for_slave_to_stop.inc
SELECT * FROM t1 ORDER BY a; SELECT * FROM t1 ORDER BY a;
a a
...@@ -119,7 +119,7 @@ a b ...@@ -119,7 +119,7 @@ a b
2 3 2 3
*** Now replicate all extra changes from 3,4,5 to 2, in preparation for making 2 the new master. *** *** Now replicate all extra changes from 3,4,5 to 2, in preparation for making 2 the new master. ***
CHANGE MASTER TO master_host = '127.0.0.1', master_port = SERVER_MYPORT_3; CHANGE MASTER TO master_host = '127.0.0.1', master_port = SERVER_MYPORT_3;
START SLAVE UNTIL master_gtid_pos = "1-1-4,0-1-3,3-1-24,2-1-14"; START SLAVE UNTIL master_gtid_pos = "1-1-1,0-1-3,3-1-7,2-1-4";
include/wait_for_slave_to_stop.inc include/wait_for_slave_to_stop.inc
SELECT * FROM t1 ORDER BY a; SELECT * FROM t1 ORDER BY a;
a a
...@@ -142,7 +142,7 @@ a b ...@@ -142,7 +142,7 @@ a b
3 1 3 1
3 3 3 3
CHANGE MASTER TO master_host = '127.0.0.1', master_port = SERVER_MYPORT_4; CHANGE MASTER TO master_host = '127.0.0.1', master_port = SERVER_MYPORT_4;
START SLAVE UNTIL master_gtid_pos = "1-1-10,0-1-3,3-1-21,2-1-11"; START SLAVE UNTIL master_gtid_pos = "1-1-7,0-1-3,3-1-4,2-1-1";
include/wait_for_slave_to_stop.inc include/wait_for_slave_to_stop.inc
SELECT * FROM t1 ORDER BY a; SELECT * FROM t1 ORDER BY a;
a a
...@@ -168,7 +168,7 @@ a b ...@@ -168,7 +168,7 @@ a b
3 1 3 1
3 3 3 3
CHANGE MASTER TO master_host = '127.0.0.1', master_port = SERVER_MYPORT_5; CHANGE MASTER TO master_host = '127.0.0.1', master_port = SERVER_MYPORT_5;
START SLAVE UNTIL master_gtid_pos = "1-1-7,0-1-3,3-1-18,2-1-17"; START SLAVE UNTIL master_gtid_pos = "1-1-4,0-1-3,3-1-1,2-1-7";
include/wait_for_slave_to_stop.inc include/wait_for_slave_to_stop.inc
SELECT * FROM t1 ORDER BY a; SELECT * FROM t1 ORDER BY a;
a a
......
...@@ -32,10 +32,10 @@ master-bin.000003 # Gtid_list # # [0-1-3] ...@@ -32,10 +32,10 @@ master-bin.000003 # Gtid_list # # [0-1-3]
FLUSH LOGS; FLUSH LOGS;
SHOW BINLOG EVENTS IN 'master-bin.000004' LIMIT 1,1; SHOW BINLOG EVENTS IN 'master-bin.000004' LIMIT 1,1;
Log_name Pos Event_type Server_id End_log_pos Info Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000004 # Gtid_list # # [1-1-5,0-1-4] master-bin.000004 # Gtid_list # # [1-1-1,0-1-4]
SHOW BINLOG EVENTS IN 'master-bin.000005' LIMIT 1,1; SHOW BINLOG EVENTS IN 'master-bin.000005' LIMIT 1,1;
Log_name Pos Event_type Server_id End_log_pos Info Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000005 # Gtid_list # # [1-1-5,0-1-4] master-bin.000005 # Gtid_list # # [1-1-1,0-1-4]
show binary logs; show binary logs;
Log_name File_size Log_name File_size
master-bin.000002 # master-bin.000002 #
......
include/rpl_init.inc [topology=1->2]
ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB;
SET sql_log_bin= 0;
call mtr.add_suppression("Error writing file .*errno: 1950");
SET sql_log_bin= 1;
SET @old_gtid_strict_mode= @@GLOBAL.gtid_strict_mode;
SET GLOBAL gtid_strict_mode= 1;
include/stop_slave.inc
SET @old_gtid_strict_mode= @@GLOBAL.gtid_strict_mode;
SET GLOBAL gtid_strict_mode=1;
CHANGE MASTER TO master_use_gtid=slave_pos;
include/start_slave.inc
CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1);
show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Gtid # # GTID #-#-#
master-bin.000001 # Query # # use `test`; ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB
master-bin.000001 # Gtid # # GTID #-#-#
master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB
master-bin.000001 # Gtid # # BEGIN GTID #-#-#
master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (1)
master-bin.000001 # Xid # # COMMIT /* XID */
SET server_id= 3;
SET gtid_seq_no= 3;
ERROR HY000: An attempt was made to binlog GTID 0-3-3 which would create an out-of-order sequence number with existing GTID 0-1-3, and gtid strict mode is enabled.
SET SESSION debug_dbug="+d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 3;
SET SESSION debug_dbug="-d,ignore_set_gtid_seq_no_check";
INSERT INTO t1 VALUES (2);
ERROR HY000: An attempt was made to binlog GTID 0-3-3 which would create an out-of-order sequence number with existing GTID 0-1-3, and gtid strict mode is enabled.
SET gtid_seq_no= 2;
ERROR HY000: An attempt was made to binlog GTID 0-3-2 which would create an out-of-order sequence number with existing GTID 0-1-3, and gtid strict mode is enabled.
SET SESSION debug_dbug="+d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 2;
SET SESSION debug_dbug="-d,ignore_set_gtid_seq_no_check";
INSERT INTO t1 VALUES (3);
ERROR HY000: An attempt was made to binlog GTID 0-3-2 which would create an out-of-order sequence number with existing GTID 0-1-3, and gtid strict mode is enabled.
SET server_id= 1;
SET gtid_seq_no= 4;
INSERT INTO t1 VALUES (4);
SELECT * FROM t1 ORDER BY 1;
a
1
4
show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Gtid # # GTID #-#-#
master-bin.000001 # Query # # use `test`; ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB
master-bin.000001 # Gtid # # GTID #-#-#
master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB
master-bin.000001 # Gtid # # BEGIN GTID #-#-#
master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (1)
master-bin.000001 # Xid # # COMMIT /* XID */
master-bin.000001 # Gtid # # BEGIN GTID #-#-#
master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (4)
master-bin.000001 # Xid # # COMMIT /* XID */
*** Test non-transactional GTID error (cannot be rolled back). ***
SET server_id= 3;
SET gtid_seq_no= 1;
ERROR HY000: An attempt was made to binlog GTID 0-3-1 which would create an out-of-order sequence number with existing GTID 0-1-4, and gtid strict mode is enabled.
SET SESSION debug_dbug="+d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 1;
SET SESSION debug_dbug="-d,ignore_set_gtid_seq_no_check";
CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=MyISAM;
ERROR HY000: An attempt was made to binlog GTID 0-3-1 which would create an out-of-order sequence number with existing GTID 0-1-4, and gtid strict mode is enabled.
SET sql_log_bin= 0;
DROP TABLE t2;
SET sql_log_bin= 1;
CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=MyISAM;
SET gtid_seq_no= 1;
ERROR HY000: An attempt was made to binlog GTID 0-3-1 which would create an out-of-order sequence number with existing GTID 0-3-5, and gtid strict mode is enabled.
SET SESSION debug_dbug="+d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 1;
SET SESSION debug_dbug="-d,ignore_set_gtid_seq_no_check";
INSERT INTO t2 VALUES (1);
ERROR HY000: An attempt was made to binlog GTID 0-3-1 which would create an out-of-order sequence number with existing GTID 0-3-5, and gtid strict mode is enabled.
SET server_id= 1;
SET gtid_seq_no= 6;
INSERT INTO t2 VALUES (2);
SELECT * FROM t2 ORDER BY a;
a
1
2
show binlog events from <binlog_start>;
Log_name Pos Event_type Server_id End_log_pos Info
master-bin.000001 # Gtid # # GTID #-#-#
master-bin.000001 # Query # # use `test`; ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB
master-bin.000001 # Gtid # # GTID #-#-#
master-bin.000001 # Query # # use `test`; CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB
master-bin.000001 # Gtid # # BEGIN GTID #-#-#
master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (1)
master-bin.000001 # Xid # # COMMIT /* XID */
master-bin.000001 # Gtid # # BEGIN GTID #-#-#
master-bin.000001 # Query # # use `test`; INSERT INTO t1 VALUES (4)
master-bin.000001 # Xid # # COMMIT /* XID */
master-bin.000001 # Gtid # # GTID #-#-#
master-bin.000001 # Query # # use `test`; CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=MyISAM
master-bin.000001 # Gtid # # BEGIN GTID #-#-#
master-bin.000001 # Query # # use `test`; INSERT INTO t2 VALUES (2)
master-bin.000001 # Query # # COMMIT
*** Test that slave stops if it tries to apply a GTID that would create out-of-order binlog GTID sequence numbers. ***
SELECT * FROM t1 ORDER BY a;
a
1
4
SELECT * FROM t2 ORDER BY a;
a
2
SET sql_log_bin= 0;
call mtr.add_suppression("An attempt was made to binlog GTID .* which would create an out-of-order sequence number with existing GTID .*, and gtid strict mode is enabled");
call mtr.add_suppression("The binlog on the master is missing the GTID [-0-9]+ requested by the slave");
SET sql_log_bin= 1;
INSERT INTO t1 VALUES (5);
INSERT INTO t1 VALUES (6);
include/wait_for_slave_sql_error.inc [errno=1950]
STOP SLAVE IO_THREAD;
SET GLOBAL gtid_strict_mode=0;
include/start_slave.inc
SET GLOBAL gtid_strict_mode=1;
SELECT * FROM t1 ORDER BY a;
a
1
4
5
6
INSERT INTO t1 VALUES (7);
CREATE TABLE t3 (a INT PRIMARY KEY);
include/wait_for_slave_sql_error.inc [errno=1950]
SHOW CREATE TABLE t3;
ERROR 42S02: Table 'test.t3' doesn't exist
STOP SLAVE IO_THREAD;
SET GLOBAL gtid_strict_mode=0;
include/start_slave.inc
SET GLOBAL gtid_strict_mode=1;
SHOW CREATE TABLE t3;
Table t3
Create Table CREATE TABLE `t3` (
`a` int(11) NOT NULL,
PRIMARY KEY (`a`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
INSERT INTO t1 VALUES (8);
INSERT INTO t2 VALUES (3);
include/wait_for_slave_sql_error.inc [errno=1950]
SELECT * FROM t2 ORDER BY a;
a
2
STOP SLAVE IO_THREAD;
SET GLOBAL gtid_strict_mode=0;
include/start_slave.inc
SET GLOBAL gtid_strict_mode=1;
SELECT * FROM t2 ORDER BY a;
a
2
3
*** Check slave requests starting from a hole on the master. ***
include/stop_slave.inc
INSERT INTO t1 VALUES (10);
SET gtid_seq_no= 100;
INSERT INTO t1 VALUES (11);
INSERT INTO t1 VALUES (12);
SET GLOBAL gtid_slave_pos= "0-1-50";
START SLAVE;
include/wait_for_slave_io_error.inc [errno=1236]
STOP SLAVE SQL_THREAD;
SET GLOBAL gtid_strict_mode= 0;
include/start_slave.inc
SELECT * FROM t1 ORDER BY a;
a
1
4
5
6
7
8
11
12
SET GLOBAL gtid_strict_mode= 1;
DROP TABLE t1, t2, t3;
SET GLOBAL gtid_strict_mode= @old_gtid_strict_mode;
SET GLOBAL gtid_strict_mode= @old_gtid_strict_mode;
include/rpl_end.inc
...@@ -79,7 +79,7 @@ master-bin.000001 # ...@@ -79,7 +79,7 @@ master-bin.000001 #
master-bin.000002 # master-bin.000002 #
master-bin.000003 # master-bin.000003 #
master-bin.000004 # master-bin.000004 #
START SLAVE UNTIL master_gtid_pos='1-1-11,2-1-12'; START SLAVE UNTIL master_gtid_pos='1-1-3,2-1-4';
include/wait_for_slave_to_stop.inc include/wait_for_slave_to_stop.inc
SELECT * FROM t1 ORDER BY a; SELECT * FROM t1 ORDER BY a;
a a
...@@ -93,7 +93,7 @@ a ...@@ -93,7 +93,7 @@ a
3 3
4 4
5 5
START SLAVE UNTIL master_gtid_pos='1-1-13,2-1-8'; START SLAVE UNTIL master_gtid_pos='1-1-4,2-1-2';
include/wait_for_slave_to_stop.inc include/wait_for_slave_to_stop.inc
SELECT * FROM t1 ORDER BY a; SELECT * FROM t1 ORDER BY a;
a a
...@@ -108,7 +108,7 @@ a ...@@ -108,7 +108,7 @@ a
3 3
4 4
5 5
START SLAVE UNTIL master_gtid_pos='1-1-11'; START SLAVE UNTIL master_gtid_pos='1-1-3';
include/wait_for_slave_to_stop.inc include/wait_for_slave_to_stop.inc
SELECT * FROM t1 ORDER BY a; SELECT * FROM t1 ORDER BY a;
a a
...@@ -144,7 +144,7 @@ FLUSH LOGS; ...@@ -144,7 +144,7 @@ FLUSH LOGS;
SET gtid_domain_id = 1; SET gtid_domain_id = 1;
INSERT INTO t1 VALUES (7); INSERT INTO t1 VALUES (7);
SET gtid_domain_id = 0; SET gtid_domain_id = 0;
START SLAVE UNTIL master_gtid_pos='1-1-13'; START SLAVE UNTIL master_gtid_pos='1-1-4';
include/wait_for_slave_to_stop.inc include/wait_for_slave_to_stop.inc
SELECT * FROM t1 ORDER BY a; SELECT * FROM t1 ORDER BY a;
a a
...@@ -168,7 +168,7 @@ a ...@@ -168,7 +168,7 @@ a
include/stop_slave.inc include/stop_slave.inc
CREATE TABLE t3 (a INT); CREATE TABLE t3 (a INT);
DROP TABLE t3; DROP TABLE t3;
START SLAVE UNTIL master_gtid_pos='1-1-15,2-1-14,0-1-16'; START SLAVE UNTIL master_gtid_pos='1-1-5,2-1-5,0-1-6';
include/wait_for_slave_to_stop.inc include/wait_for_slave_to_stop.inc
SHOW CREATE TABLE t3; SHOW CREATE TABLE t3;
Table Create Table Table Create Table
......
...@@ -80,7 +80,13 @@ SET GLOBAL gtid_slave_pos = "100-100-100"; ...@@ -80,7 +80,13 @@ SET GLOBAL gtid_slave_pos = "100-100-100";
ROLLBACK; ROLLBACK;
# In gtid non-strict mode, we get warnings for setting @@gtid_slave_pos back # In gtid non-strict mode, we get warnings for setting @@gtid_slave_pos back
# to earlier than what is in the binlog. # to earlier than what is in the binlog. In strict mode, we get an error.
SET GLOBAL gtid_strict_mode= 1;
--error ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG
SET GLOBAL gtid_slave_pos = "0-1-1";
--error ER_MASTER_GTID_POS_MISSING_DOMAIN
SET GLOBAL gtid_slave_pos = "";
SET GLOBAL gtid_strict_mode= 0;
SET GLOBAL gtid_slave_pos = "0-1-1"; SET GLOBAL gtid_slave_pos = "0-1-1";
SET GLOBAL gtid_slave_pos = ""; SET GLOBAL gtid_slave_pos = "";
RESET MASTER; RESET MASTER;
......
--source include/have_debug.inc
--source include/have_innodb.inc
--source include/have_binlog_format_statement.inc
--let $rpl_topology=1->2
--source include/rpl_init.inc
--connection server_1
ALTER TABLE mysql.gtid_slave_pos ENGINE=InnoDB;
SET sql_log_bin= 0;
call mtr.add_suppression("Error writing file .*errno: 1950");
SET sql_log_bin= 1;
SET @old_gtid_strict_mode= @@GLOBAL.gtid_strict_mode;
SET GLOBAL gtid_strict_mode= 1;
--connection server_2
--source include/stop_slave.inc
SET @old_gtid_strict_mode= @@GLOBAL.gtid_strict_mode;
SET GLOBAL gtid_strict_mode=1;
CHANGE MASTER TO master_use_gtid=slave_pos;
--source include/start_slave.inc
--connection server_1
CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB;
INSERT INTO t1 VALUES (1);
--source include/show_binlog_events.inc
SET server_id= 3;
--error ER_GTID_STRICT_OUT_OF_ORDER
SET gtid_seq_no= 3;
SET SESSION debug_dbug="+d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 3;
SET SESSION debug_dbug="-d,ignore_set_gtid_seq_no_check";
--error ER_GTID_STRICT_OUT_OF_ORDER
INSERT INTO t1 VALUES (2);
--error ER_GTID_STRICT_OUT_OF_ORDER
SET gtid_seq_no= 2;
SET SESSION debug_dbug="+d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 2;
SET SESSION debug_dbug="-d,ignore_set_gtid_seq_no_check";
--error ER_GTID_STRICT_OUT_OF_ORDER
INSERT INTO t1 VALUES (3);
SET server_id= 1;
SET gtid_seq_no= 4;
INSERT INTO t1 VALUES (4);
SELECT * FROM t1 ORDER BY 1;
--source include/show_binlog_events.inc
--echo *** Test non-transactional GTID error (cannot be rolled back). ***
SET server_id= 3;
--error ER_GTID_STRICT_OUT_OF_ORDER
SET gtid_seq_no= 1;
SET SESSION debug_dbug="+d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 1;
SET SESSION debug_dbug="-d,ignore_set_gtid_seq_no_check";
--error ER_GTID_STRICT_OUT_OF_ORDER
CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=MyISAM;
# The table is still created, DDL cannot be rolled back.
# Fix it up for replication.
SET sql_log_bin= 0;
DROP TABLE t2;
SET sql_log_bin= 1;
CREATE TABLE t2 (a INT PRIMARY KEY) ENGINE=MyISAM;
--error ER_GTID_STRICT_OUT_OF_ORDER
SET gtid_seq_no= 1;
SET SESSION debug_dbug="+d,ignore_set_gtid_seq_no_check";
SET gtid_seq_no= 1;
SET SESSION debug_dbug="-d,ignore_set_gtid_seq_no_check";
--error ER_GTID_STRICT_OUT_OF_ORDER
INSERT INTO t2 VALUES (1);
# The value is still inserted, cannot be rolled back.
SET server_id= 1;
SET gtid_seq_no= 6;
INSERT INTO t2 VALUES (2);
SELECT * FROM t2 ORDER BY a;
--source include/show_binlog_events.inc
--echo *** Test that slave stops if it tries to apply a GTID that would create out-of-order binlog GTID sequence numbers. ***
--save_master_pos
--connection server_2
--sync_with_master
SELECT * FROM t1 ORDER BY a;
SELECT * FROM t2 ORDER BY a;
SET sql_log_bin= 0;
call mtr.add_suppression("An attempt was made to binlog GTID .* which would create an out-of-order sequence number with existing GTID .*, and gtid strict mode is enabled");
call mtr.add_suppression("The binlog on the master is missing the GTID [-0-9]+ requested by the slave");
SET sql_log_bin= 1;
# Create some out-of-order stuff on slave.
INSERT INTO t1 VALUES (5);
--connection server_1
INSERT INTO t1 VALUES (6);
--save_master_pos
--connection server_2
--let $slave_sql_errno=1950
--source include/wait_for_slave_sql_error.inc
STOP SLAVE IO_THREAD;
SET GLOBAL gtid_strict_mode=0;
--source include/start_slave.inc
--sync_with_master
SET GLOBAL gtid_strict_mode=1;
SELECT * FROM t1 ORDER BY a;
INSERT INTO t1 VALUES (7);
--connection server_1
CREATE TABLE t3 (a INT PRIMARY KEY);
--save_master_pos
--connection server_2
--let $slave_sql_errno=1950
--source include/wait_for_slave_sql_error.inc
--error ER_NO_SUCH_TABLE
--query_vertical SHOW CREATE TABLE t3
STOP SLAVE IO_THREAD;
SET GLOBAL gtid_strict_mode=0;
--source include/start_slave.inc
--sync_with_master
SET GLOBAL gtid_strict_mode=1;
--query_vertical SHOW CREATE TABLE t3
INSERT INTO t1 VALUES (8);
--connection server_1
INSERT INTO t2 VALUES (3);
--save_master_pos
--connection server_2
--let $slave_sql_errno=1950
--source include/wait_for_slave_sql_error.inc
SELECT * FROM t2 ORDER BY a;
STOP SLAVE IO_THREAD;
SET GLOBAL gtid_strict_mode=0;
--source include/start_slave.inc
--sync_with_master
SET GLOBAL gtid_strict_mode=1;
SELECT * FROM t2 ORDER BY a;
--echo *** Check slave requests starting from a hole on the master. ***
--connection server_2
--source include/stop_slave.inc
--connection server_1
INSERT INTO t1 VALUES (10);
SET gtid_seq_no= 100;
INSERT INTO t1 VALUES (11);
INSERT INTO t1 VALUES (12);
--save_master_pos
--connection server_2
SET GLOBAL gtid_slave_pos= "0-1-50";
START SLAVE;
--let $slave_io_errno=1236
--source include/wait_for_slave_io_error.inc
STOP SLAVE SQL_THREAD;
SET GLOBAL gtid_strict_mode= 0;
--source include/start_slave.inc
--sync_with_master
SELECT * FROM t1 ORDER BY a;
SET GLOBAL gtid_strict_mode= 1;
# Clean up.
--connection server_1
DROP TABLE t1, t2, t3;
SET GLOBAL gtid_strict_mode= @old_gtid_strict_mode;
--connection server_2
SET GLOBAL gtid_strict_mode= @old_gtid_strict_mode;
--source include/rpl_end.inc
select @@global.gtid_strict_mode;
@@global.gtid_strict_mode
0
select @@session.gtid_strict_mode;
ERROR HY000: Variable 'gtid_strict_mode' is a GLOBAL variable
show global variables like 'gtid_strict_mode';
Variable_name Value
gtid_strict_mode OFF
show session variables like 'gtid_strict_mode';
Variable_name Value
gtid_strict_mode OFF
select * from information_schema.global_variables where variable_name='gtid_strict_mode';
VARIABLE_NAME VARIABLE_VALUE
GTID_STRICT_MODE OFF
select * from information_schema.session_variables where variable_name='gtid_strict_mode';
VARIABLE_NAME VARIABLE_VALUE
GTID_STRICT_MODE OFF
SET @old= @@GLOBAL.gtid_strict_mode;
set global gtid_strict_mode=1;
select @@global.gtid_strict_mode;
@@global.gtid_strict_mode
1
set global gtid_strict_mode=0;
select @@global.gtid_strict_mode;
@@global.gtid_strict_mode
0
set global gtid_strict_mode=@old;
set session gtid_strict_mode=1;
ERROR HY000: Variable 'gtid_strict_mode' is a GLOBAL variable and should be set with SET GLOBAL
--source include/not_embedded.inc
#
# only global
#
select @@global.gtid_strict_mode;
--error ER_INCORRECT_GLOBAL_LOCAL_VAR
select @@session.gtid_strict_mode;
show global variables like 'gtid_strict_mode';
show session variables like 'gtid_strict_mode';
select * from information_schema.global_variables where variable_name='gtid_strict_mode';
select * from information_schema.session_variables where variable_name='gtid_strict_mode';
SET @old= @@GLOBAL.gtid_strict_mode;
set global gtid_strict_mode=1;
select @@global.gtid_strict_mode;
set global gtid_strict_mode=0;
select @@global.gtid_strict_mode;
set global gtid_strict_mode=@old;
--error ER_GLOBAL_VARIABLE
set session gtid_strict_mode=1;
...@@ -3841,9 +3841,6 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log) ...@@ -3841,9 +3841,6 @@ bool MYSQL_BIN_LOG::reset_logs(THD* thd, bool create_new_log)
if (!is_relay_log) if (!is_relay_log)
{ {
rpl_global_gtid_binlog_state.reset(); rpl_global_gtid_binlog_state.reset();
mysql_mutex_lock(&LOCK_gtid_counter);
global_gtid_counter= 0;
mysql_mutex_unlock(&LOCK_gtid_counter);
} }
/* Start logging with a new file */ /* Start logging with a new file */
...@@ -5367,9 +5364,11 @@ MYSQL_BIN_LOG::write_gtid_event(THD *thd, bool standalone, ...@@ -5367,9 +5364,11 @@ MYSQL_BIN_LOG::write_gtid_event(THD *thd, bool standalone,
bool is_transactional) bool is_transactional)
{ {
rpl_gtid gtid; rpl_gtid gtid;
uint64 seq_no; uint32 domain_id= thd->variables.gtid_domain_id;
uint32 server_id= thd->variables.server_id;
uint64 seq_no= thd->variables.gtid_seq_no;
int err;
seq_no= thd->variables.gtid_seq_no;
/* /*
Reset the session variable gtid_seq_no, to reduce the risk of accidentally Reset the session variable gtid_seq_no, to reduce the risk of accidentally
producing a duplicate GTID. producing a duplicate GTID.
...@@ -5377,34 +5376,36 @@ MYSQL_BIN_LOG::write_gtid_event(THD *thd, bool standalone, ...@@ -5377,34 +5376,36 @@ MYSQL_BIN_LOG::write_gtid_event(THD *thd, bool standalone,
thd->variables.gtid_seq_no= 0; thd->variables.gtid_seq_no= 0;
if (seq_no != 0) if (seq_no != 0)
{ {
/* /* Use the specified sequence number. */
If we see a higher sequence number, use that one as the basis of any gtid.domain_id= domain_id;
later generated sequence numbers. gtid.server_id= server_id;
*/ gtid.seq_no= seq_no;
bump_seq_no_counter_if_needed(seq_no); mysql_mutex_lock(&LOCK_rpl_gtid_state);
err= rpl_global_gtid_binlog_state.update(&gtid, opt_gtid_strict_mode);
mysql_mutex_unlock(&LOCK_rpl_gtid_state);
if (err && thd->stmt_da->sql_errno()==ER_GTID_STRICT_OUT_OF_ORDER)
errno= ER_GTID_STRICT_OUT_OF_ORDER;
} }
else else
{ {
mysql_mutex_lock(&LOCK_gtid_counter); /* Allocate the next sequence number for the GTID. */
seq_no= ++global_gtid_counter; mysql_mutex_lock(&LOCK_rpl_gtid_state);
mysql_mutex_unlock(&LOCK_gtid_counter); err= rpl_global_gtid_binlog_state.update_with_next_gtid(domain_id,
server_id, &gtid);
mysql_mutex_unlock(&LOCK_rpl_gtid_state);
seq_no= gtid.seq_no;
} }
gtid.seq_no= seq_no; if (err)
gtid.domain_id= thd->variables.gtid_domain_id; return true;
Gtid_log_event gtid_event(thd, gtid.seq_no, gtid.domain_id, standalone, Gtid_log_event gtid_event(thd, seq_no, domain_id, standalone,
LOG_EVENT_SUPPRESS_USE_F, is_transactional); LOG_EVENT_SUPPRESS_USE_F, is_transactional);
gtid.server_id= gtid_event.server_id;
/* Write the event to the binary log. */ /* Write the event to the binary log. */
if (gtid_event.write(&mysql_bin_log.log_file)) if (gtid_event.write(&mysql_bin_log.log_file))
return true; return true;
status_var_add(thd->status_var.binlog_bytes_written, gtid_event.data_written); status_var_add(thd->status_var.binlog_bytes_written, gtid_event.data_written);
/* Update the replication state (last GTID in each replication domain). */
mysql_mutex_lock(&LOCK_rpl_gtid_state);
rpl_global_gtid_binlog_state.update(&gtid);
mysql_mutex_unlock(&LOCK_rpl_gtid_state);
return false; return false;
} }
...@@ -5505,9 +5506,6 @@ end: ...@@ -5505,9 +5506,6 @@ end:
end_io_cache(&cache); end_io_cache(&cache);
if (opened) if (opened)
mysql_file_close(file_no, MYF(0)); mysql_file_close(file_no, MYF(0));
/* Pick the next unused seq_no from the loaded binlog state. */
bump_seq_no_counter_if_needed(
rpl_global_gtid_binlog_state.seq_no_from_state());
return err; return err;
} }
...@@ -5549,33 +5547,44 @@ bool ...@@ -5549,33 +5547,44 @@ bool
MYSQL_BIN_LOG::lookup_domain_in_binlog_state(uint32 domain_id, MYSQL_BIN_LOG::lookup_domain_in_binlog_state(uint32 domain_id,
rpl_gtid *out_gtid) rpl_gtid *out_gtid)
{ {
rpl_binlog_state::element *elem; rpl_gtid *found_gtid;
bool res; bool res= false;
mysql_mutex_lock(&rpl_global_gtid_binlog_state.LOCK_binlog_state); mysql_mutex_lock(&rpl_global_gtid_binlog_state.LOCK_binlog_state);
elem= (rpl_binlog_state::element *) if ((found_gtid= rpl_global_gtid_binlog_state.find_most_recent(domain_id)))
my_hash_search(&rpl_global_gtid_binlog_state.hash,
(const uchar *)&domain_id, 0);
if (elem)
{ {
*out_gtid= *found_gtid;
res= true; res= true;
*out_gtid= *elem->last_gtid;
} }
else
res= false;
mysql_mutex_unlock(&rpl_global_gtid_binlog_state.LOCK_binlog_state); mysql_mutex_unlock(&rpl_global_gtid_binlog_state.LOCK_binlog_state);
return res; return res;
} }
void int
MYSQL_BIN_LOG::bump_seq_no_counter_if_needed(uint64 seq_no) MYSQL_BIN_LOG::bump_seq_no_counter_if_needed(uint32 domain_id, uint64 seq_no)
{ {
mysql_mutex_lock(&LOCK_gtid_counter); int err;
if (global_gtid_counter < seq_no)
global_gtid_counter= seq_no; mysql_mutex_lock(&rpl_global_gtid_binlog_state.LOCK_binlog_state);
mysql_mutex_unlock(&LOCK_gtid_counter); err= rpl_global_gtid_binlog_state.bump_seq_no_if_needed(domain_id, seq_no);
mysql_mutex_unlock(&rpl_global_gtid_binlog_state.LOCK_binlog_state);
return err;
}
bool
MYSQL_BIN_LOG::check_strict_gtid_sequence(uint32 domain_id, uint32 server_id,
uint64 seq_no)
{
bool err;
mysql_mutex_lock(&rpl_global_gtid_binlog_state.LOCK_binlog_state);
err= rpl_global_gtid_binlog_state.check_strict_sequence(domain_id, server_id,
seq_no);
mysql_mutex_unlock(&rpl_global_gtid_binlog_state.LOCK_binlog_state);
return err;
} }
...@@ -5648,7 +5657,8 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate) ...@@ -5648,7 +5657,8 @@ bool MYSQL_BIN_LOG::write(Log_event *event_info, my_bool *with_annotate)
my_org_b_tell= my_b_tell(file); my_org_b_tell= my_b_tell(file);
mysql_mutex_lock(&LOCK_log); mysql_mutex_lock(&LOCK_log);
prev_binlog_id= current_binlog_id; prev_binlog_id= current_binlog_id;
write_gtid_event(thd, true, using_trans); if (write_gtid_event(thd, true, using_trans))
goto err;
} }
else else
{ {
...@@ -6717,8 +6727,8 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader) ...@@ -6717,8 +6727,8 @@ MYSQL_BIN_LOG::trx_group_commit_leader(group_commit_entry *leader)
*/ */
DBUG_ASSERT(!cache_mngr->stmt_cache.empty() || !cache_mngr->trx_cache.empty()); DBUG_ASSERT(!cache_mngr->stmt_cache.empty() || !cache_mngr->trx_cache.empty());
current->error= write_transaction_or_stmt(current); if ((current->error= write_transaction_or_stmt(current)))
current->commit_errno= errno;
strmake(cache_mngr->last_commit_pos_file, log_file_name, strmake(cache_mngr->last_commit_pos_file, log_file_name,
sizeof(cache_mngr->last_commit_pos_file)-1); sizeof(cache_mngr->last_commit_pos_file)-1);
commit_offset= my_b_write_tell(&log_file); commit_offset= my_b_write_tell(&log_file);
...@@ -8264,9 +8274,6 @@ int TC_LOG_BINLOG::open(const char *opt_name) ...@@ -8264,9 +8274,6 @@ int TC_LOG_BINLOG::open(const char *opt_name)
error= recover(&log_info, log_name, &log, error= recover(&log_info, log_name, &log,
(Format_description_log_event *)ev); (Format_description_log_event *)ev);
state_read= true; state_read= true;
/* Pick the next unused seq_no from the recovered binlog state. */
bump_seq_no_counter_if_needed(
rpl_global_gtid_binlog_state.seq_no_from_state());
} }
else else
error= read_state_from_file(); error= read_state_from_file();
...@@ -8748,7 +8755,7 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, ...@@ -8748,7 +8755,7 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name,
gtid.domain_id= gev->domain_id; gtid.domain_id= gev->domain_id;
gtid.server_id= gev->server_id; gtid.server_id= gev->server_id;
gtid.seq_no= gev->seq_no; gtid.seq_no= gev->seq_no;
if (rpl_global_gtid_binlog_state.update(&gtid)) if (rpl_global_gtid_binlog_state.update(&gtid, false))
goto err2; goto err2;
} }
break; break;
......
...@@ -783,7 +783,9 @@ public: ...@@ -783,7 +783,9 @@ public:
bool find_in_binlog_state(uint32 domain_id, uint32 server_id, bool find_in_binlog_state(uint32 domain_id, uint32 server_id,
rpl_gtid *out_gtid); rpl_gtid *out_gtid);
bool lookup_domain_in_binlog_state(uint32 domain_id, rpl_gtid *out_gtid); bool lookup_domain_in_binlog_state(uint32 domain_id, rpl_gtid *out_gtid);
void bump_seq_no_counter_if_needed(uint64 seq_no); int bump_seq_no_counter_if_needed(uint32 domain_id, uint64 seq_no);
bool check_strict_gtid_sequence(uint32 domain_id, uint32 server_id,
uint64 seq_no);
}; };
class Log_event_handler class Log_event_handler
......
...@@ -6214,6 +6214,15 @@ Gtid_log_event::do_apply_event(Relay_log_info const *rli) ...@@ -6214,6 +6214,15 @@ Gtid_log_event::do_apply_event(Relay_log_info const *rli)
thd->variables.gtid_domain_id= this->domain_id; thd->variables.gtid_domain_id= this->domain_id;
thd->variables.gtid_seq_no= this->seq_no; thd->variables.gtid_seq_no= this->seq_no;
if (opt_gtid_strict_mode && opt_bin_log && opt_log_slave_updates)
{
/* Need to reset prior "ok" status to give an error. */
thd->clear_error();
thd->stmt_da->reset_diagnostics_area();
if (mysql_bin_log.check_strict_gtid_sequence(this->domain_id,
this->server_id, this->seq_no))
return 1;
}
if (flags2 & FL_STANDALONE) if (flags2 & FL_STANDALONE)
return 0; return 0;
......
...@@ -677,7 +677,7 @@ mysql_mutex_t ...@@ -677,7 +677,7 @@ mysql_mutex_t
mysql_mutex_t LOCK_stats, LOCK_global_user_client_stats, mysql_mutex_t LOCK_stats, LOCK_global_user_client_stats,
LOCK_global_table_stats, LOCK_global_index_stats; LOCK_global_table_stats, LOCK_global_index_stats;
mysql_mutex_t LOCK_gtid_counter, LOCK_rpl_gtid_state; mysql_mutex_t LOCK_rpl_gtid_state;
/** /**
The below lock protects access to two global server variables: The below lock protects access to two global server variables:
...@@ -776,7 +776,7 @@ PSI_mutex_key key_LOCK_stats, ...@@ -776,7 +776,7 @@ PSI_mutex_key key_LOCK_stats,
key_LOCK_global_index_stats, key_LOCK_global_index_stats,
key_LOCK_wakeup_ready; key_LOCK_wakeup_ready;
PSI_mutex_key key_LOCK_gtid_counter, key_LOCK_rpl_gtid_state; PSI_mutex_key key_LOCK_rpl_gtid_state;
PSI_mutex_key key_LOCK_prepare_ordered, key_LOCK_commit_ordered; PSI_mutex_key key_LOCK_prepare_ordered, key_LOCK_commit_ordered;
...@@ -821,7 +821,6 @@ static PSI_mutex_info all_server_mutexes[]= ...@@ -821,7 +821,6 @@ static PSI_mutex_info all_server_mutexes[]=
{ &key_LOCK_global_table_stats, "LOCK_global_table_stats", PSI_FLAG_GLOBAL}, { &key_LOCK_global_table_stats, "LOCK_global_table_stats", PSI_FLAG_GLOBAL},
{ &key_LOCK_global_index_stats, "LOCK_global_index_stats", PSI_FLAG_GLOBAL}, { &key_LOCK_global_index_stats, "LOCK_global_index_stats", PSI_FLAG_GLOBAL},
{ &key_LOCK_wakeup_ready, "THD::LOCK_wakeup_ready", 0}, { &key_LOCK_wakeup_ready, "THD::LOCK_wakeup_ready", 0},
{ &key_LOCK_gtid_counter, "LOCK_gtid_counter", PSI_FLAG_GLOBAL},
{ &key_LOCK_rpl_gtid_state, "LOCK_rpl_gtid_state", PSI_FLAG_GLOBAL}, { &key_LOCK_rpl_gtid_state, "LOCK_rpl_gtid_state", PSI_FLAG_GLOBAL},
{ &key_LOCK_thd_data, "THD::LOCK_thd_data", 0}, { &key_LOCK_thd_data, "THD::LOCK_thd_data", 0},
{ &key_LOCK_user_conn, "LOCK_user_conn", PSI_FLAG_GLOBAL}, { &key_LOCK_user_conn, "LOCK_user_conn", PSI_FLAG_GLOBAL},
...@@ -1294,10 +1293,7 @@ struct st_VioSSLFd *ssl_acceptor_fd; ...@@ -1294,10 +1293,7 @@ struct st_VioSSLFd *ssl_acceptor_fd;
*/ */
uint connection_count= 0, extra_connection_count= 0; uint connection_count= 0, extra_connection_count= 0;
/** my_bool opt_gtid_strict_mode= FALSE;
Running counter for generating new GTIDs locally.
*/
uint64 global_gtid_counter= 0;
/* Function declarations */ /* Function declarations */
...@@ -1977,7 +1973,6 @@ static void clean_up_mutexes() ...@@ -1977,7 +1973,6 @@ static void clean_up_mutexes()
mysql_mutex_destroy(&LOCK_global_user_client_stats); mysql_mutex_destroy(&LOCK_global_user_client_stats);
mysql_mutex_destroy(&LOCK_global_table_stats); mysql_mutex_destroy(&LOCK_global_table_stats);
mysql_mutex_destroy(&LOCK_global_index_stats); mysql_mutex_destroy(&LOCK_global_index_stats);
mysql_mutex_destroy(&LOCK_gtid_counter);
mysql_mutex_destroy(&LOCK_rpl_gtid_state); mysql_mutex_destroy(&LOCK_rpl_gtid_state);
#ifdef HAVE_OPENSSL #ifdef HAVE_OPENSSL
mysql_mutex_destroy(&LOCK_des_key_file); mysql_mutex_destroy(&LOCK_des_key_file);
...@@ -4089,8 +4084,6 @@ static int init_thread_environment() ...@@ -4089,8 +4084,6 @@ static int init_thread_environment()
&LOCK_global_table_stats, MY_MUTEX_INIT_FAST); &LOCK_global_table_stats, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_global_index_stats, mysql_mutex_init(key_LOCK_global_index_stats,
&LOCK_global_index_stats, MY_MUTEX_INIT_FAST); &LOCK_global_index_stats, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_gtid_counter,
&LOCK_gtid_counter, MY_MUTEX_INIT_FAST);
mysql_mutex_init(key_LOCK_rpl_gtid_state, mysql_mutex_init(key_LOCK_rpl_gtid_state,
&LOCK_rpl_gtid_state, MY_MUTEX_INIT_SLOW); &LOCK_rpl_gtid_state, MY_MUTEX_INIT_SLOW);
mysql_mutex_init(key_LOCK_prepare_ordered, &LOCK_prepare_ordered, mysql_mutex_init(key_LOCK_prepare_ordered, &LOCK_prepare_ordered,
......
...@@ -253,7 +253,7 @@ extern PSI_mutex_key key_LOCK_stats, ...@@ -253,7 +253,7 @@ extern PSI_mutex_key key_LOCK_stats,
key_LOCK_global_user_client_stats, key_LOCK_global_table_stats, key_LOCK_global_user_client_stats, key_LOCK_global_table_stats,
key_LOCK_global_index_stats, key_LOCK_wakeup_ready; key_LOCK_global_index_stats, key_LOCK_wakeup_ready;
extern PSI_mutex_key key_LOCK_gtid_counter, key_LOCK_rpl_gtid_state; extern PSI_mutex_key key_LOCK_rpl_gtid_state;
extern PSI_rwlock_key key_rwlock_LOCK_grant, key_rwlock_LOCK_logger, extern PSI_rwlock_key key_rwlock_LOCK_grant, key_rwlock_LOCK_logger,
key_rwlock_LOCK_sys_init_connect, key_rwlock_LOCK_sys_init_slave, key_rwlock_LOCK_sys_init_connect, key_rwlock_LOCK_sys_init_slave,
...@@ -345,7 +345,7 @@ extern mysql_mutex_t ...@@ -345,7 +345,7 @@ extern mysql_mutex_t
LOCK_slave_list, LOCK_active_mi, LOCK_manager, LOCK_slave_list, LOCK_active_mi, LOCK_manager,
LOCK_global_system_variables, LOCK_user_conn, LOCK_global_system_variables, LOCK_user_conn,
LOCK_prepared_stmt_count, LOCK_error_messages, LOCK_connection_count; LOCK_prepared_stmt_count, LOCK_error_messages, LOCK_connection_count;
extern mysql_mutex_t LOCK_gtid_counter, LOCK_rpl_gtid_state; extern mysql_mutex_t LOCK_rpl_gtid_state;
extern MYSQL_PLUGIN_IMPORT mysql_mutex_t LOCK_thread_count; extern MYSQL_PLUGIN_IMPORT mysql_mutex_t LOCK_thread_count;
#ifdef HAVE_OPENSSL #ifdef HAVE_OPENSSL
extern mysql_mutex_t LOCK_des_key_file; extern mysql_mutex_t LOCK_des_key_file;
...@@ -541,6 +541,7 @@ extern handlerton *maria_hton; ...@@ -541,6 +541,7 @@ extern handlerton *maria_hton;
extern uint extra_connection_count; extern uint extra_connection_count;
extern uint64 global_gtid_counter; extern uint64 global_gtid_counter;
extern my_bool opt_gtid_strict_mode;
extern my_bool opt_userstat_running, debug_assert_if_crashed_table; extern my_bool opt_userstat_running, debug_assert_if_crashed_table;
extern uint mysqld_extra_port; extern uint mysqld_extra_port;
extern ulong opt_progress_report_time; extern ulong opt_progress_report_time;
......
...@@ -370,7 +370,10 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id, ...@@ -370,7 +370,10 @@ rpl_slave_state::record_gtid(THD *thd, const rpl_gtid *gtid, uint64 sub_id,
} }
table->file->ha_index_end(); table->file->ha_index_end();
mysql_bin_log.bump_seq_no_counter_if_needed(gtid->seq_no); if(!err && opt_bin_log &&
(err= mysql_bin_log.bump_seq_no_counter_if_needed(gtid->domain_id,
gtid->seq_no)))
my_error(ER_OUT_OF_RESOURCES, MYF(0));
end: end:
...@@ -719,7 +722,7 @@ rpl_binlog_state::load(struct rpl_gtid *list, uint32 count) ...@@ -719,7 +722,7 @@ rpl_binlog_state::load(struct rpl_gtid *list, uint32 count)
reset(); reset();
for (i= 0; i < count; ++i) for (i= 0; i < count; ++i)
{ {
if (update(&(list[i]))) if (update(&(list[i]), false))
return true; return true;
} }
return false; return false;
...@@ -741,31 +744,87 @@ rpl_binlog_state::~rpl_binlog_state() ...@@ -741,31 +744,87 @@ rpl_binlog_state::~rpl_binlog_state()
Returns 0 for ok, 1 for error. Returns 0 for ok, 1 for error.
*/ */
int int
rpl_binlog_state::update(const struct rpl_gtid *gtid) rpl_binlog_state::update(const struct rpl_gtid *gtid, bool strict)
{ {
rpl_gtid *lookup_gtid;
element *elem; element *elem;
elem= (element *)my_hash_search(&hash, (const uchar *)(&gtid->domain_id), 0); if ((elem= (element *)my_hash_search(&hash,
if (elem) (const uchar *)(&gtid->domain_id), 0)))
{
if (strict && elem->last_gtid && elem->last_gtid->seq_no >= gtid->seq_no)
{
my_error(ER_GTID_STRICT_OUT_OF_ORDER, MYF(0), gtid->domain_id,
gtid->server_id, gtid->seq_no, elem->last_gtid->domain_id,
elem->last_gtid->server_id, elem->last_gtid->seq_no);
return 1;
}
if (elem->seq_no_counter < gtid->seq_no)
elem->seq_no_counter= gtid->seq_no;
if (!elem->update_element(gtid))
return 0;
}
else if (!alloc_element(gtid))
return 0;
my_error(ER_OUT_OF_RESOURCES, MYF(0));
return 1;
}
/*
Fill in a new GTID, allocating next sequence number, and update state
accordingly.
*/
int
rpl_binlog_state::update_with_next_gtid(uint32 domain_id, uint32 server_id,
rpl_gtid *gtid)
{
element *elem;
gtid->domain_id= domain_id;
gtid->server_id= server_id;
if ((elem= (element *)my_hash_search(&hash, (const uchar *)(&domain_id), 0)))
{ {
gtid->seq_no= ++elem->seq_no_counter;
if (!elem->update_element(gtid))
return 0;
}
else
{
gtid->seq_no= 1;
if (!alloc_element(gtid))
return 0;
}
my_error(ER_OUT_OF_RESOURCES, MYF(0));
return 1;
}
/* Helper functions for update. */
int
rpl_binlog_state::element::update_element(const rpl_gtid *gtid)
{
rpl_gtid *lookup_gtid;
/* /*
By far the most common case is that successive events within same By far the most common case is that successive events within same
replication domain have the same server id (it changes only when replication domain have the same server id (it changes only when
switching to a new master). So save a hash lookup in this case. switching to a new master). So save a hash lookup in this case.
*/ */
if (likely(elem->last_gtid->server_id == gtid->server_id)) if (likely(last_gtid && last_gtid->server_id == gtid->server_id))
{ {
elem->last_gtid->seq_no= gtid->seq_no; last_gtid->seq_no= gtid->seq_no;
return 0; return 0;
} }
lookup_gtid= (rpl_gtid *) lookup_gtid= (rpl_gtid *)
my_hash_search(&elem->hash, (const uchar *)&gtid->server_id, 0); my_hash_search(&hash, (const uchar *)&gtid->server_id, 0);
if (lookup_gtid) if (lookup_gtid)
{ {
lookup_gtid->seq_no= gtid->seq_no; lookup_gtid->seq_no= gtid->seq_no;
elem->last_gtid= lookup_gtid; last_gtid= lookup_gtid;
return 0; return 0;
} }
...@@ -774,14 +833,21 @@ rpl_binlog_state::update(const struct rpl_gtid *gtid) ...@@ -774,14 +833,21 @@ rpl_binlog_state::update(const struct rpl_gtid *gtid)
if (!lookup_gtid) if (!lookup_gtid)
return 1; return 1;
memcpy(lookup_gtid, gtid, sizeof(*lookup_gtid)); memcpy(lookup_gtid, gtid, sizeof(*lookup_gtid));
if (my_hash_insert(&elem->hash, (const uchar *)lookup_gtid)) if (my_hash_insert(&hash, (const uchar *)lookup_gtid))
{ {
my_free(lookup_gtid); my_free(lookup_gtid);
return 1; return 1;
} }
elem->last_gtid= lookup_gtid; last_gtid= lookup_gtid;
return 0; return 0;
} }
int
rpl_binlog_state::alloc_element(const rpl_gtid *gtid)
{
element *elem;
rpl_gtid *lookup_gtid;
/* First time we see this domain_id; allocate a new element. */ /* First time we see this domain_id; allocate a new element. */
elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME)); elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME));
...@@ -793,6 +859,7 @@ rpl_binlog_state::update(const struct rpl_gtid *gtid) ...@@ -793,6 +859,7 @@ rpl_binlog_state::update(const struct rpl_gtid *gtid)
offsetof(rpl_gtid, server_id), sizeof(uint32), NULL, my_free, offsetof(rpl_gtid, server_id), sizeof(uint32), NULL, my_free,
HASH_UNIQUE); HASH_UNIQUE);
elem->last_gtid= lookup_gtid; elem->last_gtid= lookup_gtid;
elem->seq_no_counter= gtid->seq_no;
memcpy(lookup_gtid, gtid, sizeof(*lookup_gtid)); memcpy(lookup_gtid, gtid, sizeof(*lookup_gtid));
if (0 == my_hash_insert(&elem->hash, (const uchar *)lookup_gtid)) if (0 == my_hash_insert(&elem->hash, (const uchar *)lookup_gtid))
{ {
...@@ -812,23 +879,64 @@ rpl_binlog_state::update(const struct rpl_gtid *gtid) ...@@ -812,23 +879,64 @@ rpl_binlog_state::update(const struct rpl_gtid *gtid)
} }
uint64 /*
rpl_binlog_state::seq_no_from_state() Check that a new GTID can be logged without creating an out-of-order
sequence number with existing GTIDs.
*/
bool
rpl_binlog_state::check_strict_sequence(uint32 domain_id, uint32 server_id,
uint64 seq_no)
{ {
ulong i, j; element *elem;
uint64 seq_no= 0;
for (i= 0; i < hash.records; ++i) if ((elem= (element *)my_hash_search(&hash,
(const uchar *)(&domain_id), 0)) &&
elem->last_gtid && elem->last_gtid->seq_no >= seq_no)
{ {
element *e= (element *)my_hash_element(&hash, i); my_error(ER_GTID_STRICT_OUT_OF_ORDER, MYF(0), domain_id, server_id, seq_no,
for (j= 0; j < e->hash.records; ++j) elem->last_gtid->domain_id, elem->last_gtid->server_id,
{ elem->last_gtid->seq_no);
const rpl_gtid *gtid= (const rpl_gtid *)my_hash_element(&e->hash, j); return 1;
if (gtid->seq_no > seq_no)
seq_no= gtid->seq_no;
} }
return 0;
}
/*
When we see a new GTID that will not be binlogged (eg. slave thread
with --log-slave-updates=0), then we need to remember to allocate any
GTID seq_no of our own within that domain starting from there.
Returns 0 if ok, non-zero if out-of-memory.
*/
int
rpl_binlog_state::bump_seq_no_if_needed(uint32 domain_id, uint64 seq_no)
{
element *elem;
if ((elem= (element *)my_hash_search(&hash, (const uchar *)(&domain_id), 0)))
{
if (elem->seq_no_counter < seq_no)
elem->seq_no_counter= seq_no;
return 0;
} }
return seq_no;
/* We need to allocate a new, empty element to remember the next seq_no. */
if (!(elem= (element *)my_malloc(sizeof(*elem), MYF(MY_WME))))
return 1;
elem->domain_id= domain_id;
my_hash_init(&elem->hash, &my_charset_bin, 32,
offsetof(rpl_gtid, server_id), sizeof(uint32), NULL, my_free,
HASH_UNIQUE);
elem->last_gtid= NULL;
elem->seq_no_counter= seq_no;
if (0 == my_hash_insert(&hash, (const uchar *)elem))
return 0;
my_hash_free(&elem->hash);
my_free(elem);
return 1;
} }
...@@ -849,6 +957,11 @@ rpl_binlog_state::write_to_iocache(IO_CACHE *dest) ...@@ -849,6 +957,11 @@ rpl_binlog_state::write_to_iocache(IO_CACHE *dest)
{ {
size_t res; size_t res;
element *e= (element *)my_hash_element(&hash, i); element *e= (element *)my_hash_element(&hash, i);
if (!e->last_gtid)
{
DBUG_ASSERT(e->hash.records == 0);
continue;
}
for (j= 0; j <= e->hash.records; ++j) for (j= 0; j <= e->hash.records; ++j)
{ {
const rpl_gtid *gtid; const rpl_gtid *gtid;
...@@ -890,7 +1003,7 @@ rpl_binlog_state::read_from_iocache(IO_CACHE *src) ...@@ -890,7 +1003,7 @@ rpl_binlog_state::read_from_iocache(IO_CACHE *src)
end= buf + res; end= buf + res;
if (gtid_parser_helper(&p, end, &gtid)) if (gtid_parser_helper(&p, end, &gtid))
return 1; return 1;
if (update(&gtid)) if (update(&gtid, false))
return 1; return 1;
} }
return 0; return 0;
...@@ -906,6 +1019,17 @@ rpl_binlog_state::find(uint32 domain_id, uint32 server_id) ...@@ -906,6 +1019,17 @@ rpl_binlog_state::find(uint32 domain_id, uint32 server_id)
return (rpl_gtid *)my_hash_search(&elem->hash, (const uchar *)&server_id, 0); return (rpl_gtid *)my_hash_search(&elem->hash, (const uchar *)&server_id, 0);
} }
rpl_gtid *
rpl_binlog_state::find_most_recent(uint32 domain_id)
{
element *elem;
elem= (element *)my_hash_search(&hash, (const uchar *)&domain_id, 0);
if (elem && elem->last_gtid)
return elem->last_gtid;
return NULL;
}
uint32 uint32
rpl_binlog_state::count() rpl_binlog_state::count()
...@@ -929,6 +1053,11 @@ rpl_binlog_state::get_gtid_list(rpl_gtid *gtid_list, uint32 list_size) ...@@ -929,6 +1053,11 @@ rpl_binlog_state::get_gtid_list(rpl_gtid *gtid_list, uint32 list_size)
for (i= 0; i < hash.records; ++i) for (i= 0; i < hash.records; ++i)
{ {
element *e= (element *)my_hash_element(&hash, i); element *e= (element *)my_hash_element(&hash, i);
if (!e->last_gtid)
{
DBUG_ASSERT(e->hash.records==0);
continue;
}
for (j= 0; j <= e->hash.records; ++j) for (j= 0; j <= e->hash.records; ++j)
{ {
const rpl_gtid *gtid; const rpl_gtid *gtid;
...@@ -965,16 +1094,22 @@ int ...@@ -965,16 +1094,22 @@ int
rpl_binlog_state::get_most_recent_gtid_list(rpl_gtid **list, uint32 *size) rpl_binlog_state::get_most_recent_gtid_list(rpl_gtid **list, uint32 *size)
{ {
uint32 i; uint32 i;
uint32 alloc_size, out_size;
*size= hash.records; alloc_size= hash.records;
if (!(*list= (rpl_gtid *)my_malloc(*size * sizeof(rpl_gtid), MYF(MY_WME)))) if (!(*list= (rpl_gtid *)my_malloc(alloc_size * sizeof(rpl_gtid),
MYF(MY_WME))))
return 1; return 1;
for (i= 0; i < *size; ++i) out_size= 0;
for (i= 0; i < alloc_size; ++i)
{ {
element *e= (element *)my_hash_element(&hash, i); element *e= (element *)my_hash_element(&hash, i);
memcpy(&((*list)[i]), e->last_gtid, sizeof(rpl_gtid)); if (!e->last_gtid)
continue;
memcpy(&((*list)[out_size++]), e->last_gtid, sizeof(rpl_gtid));
} }
*size= out_size;
return 0; return 0;
} }
...@@ -988,7 +1123,8 @@ rpl_binlog_state::append_pos(String *str) ...@@ -988,7 +1123,8 @@ rpl_binlog_state::append_pos(String *str)
for (i= 0; i < hash.records; ++i) for (i= 0; i < hash.records; ++i)
{ {
element *e= (element *)my_hash_element(&hash, i); element *e= (element *)my_hash_element(&hash, i);
if (rpl_slave_state_tostring_helper(str, e->last_gtid, &first)) if (e->last_gtid &&
rpl_slave_state_tostring_helper(str, e->last_gtid, &first))
return true; return true;
} }
......
...@@ -131,6 +131,10 @@ struct rpl_binlog_state ...@@ -131,6 +131,10 @@ struct rpl_binlog_state
HASH hash; /* Containing all server_id for one domain_id */ HASH hash; /* Containing all server_id for one domain_id */
/* The most recent entry in the hash. */ /* The most recent entry in the hash. */
rpl_gtid *last_gtid; rpl_gtid *last_gtid;
/* Counter to allocate next seq_no for this domain. */
uint64 seq_no_counter;
int update_element(const rpl_gtid *gtid);
}; };
/* Mapping from domain_id to collection of elements. */ /* Mapping from domain_id to collection of elements. */
HASH hash; HASH hash;
...@@ -144,8 +148,12 @@ struct rpl_binlog_state ...@@ -144,8 +148,12 @@ struct rpl_binlog_state
void reset(); void reset();
void free(); void free();
bool load(struct rpl_gtid *list, uint32 count); bool load(struct rpl_gtid *list, uint32 count);
int update(const struct rpl_gtid *gtid); int update(const struct rpl_gtid *gtid, bool strict);
uint64 seq_no_from_state(); int update_with_next_gtid(uint32 domain_id, uint32 server_id,
rpl_gtid *gtid);
int alloc_element(const rpl_gtid *gtid);
bool check_strict_sequence(uint32 domain_id, uint32 server_id, uint64 seq_no);
int bump_seq_no_if_needed(uint32 domain_id, uint64 seq_no);
int write_to_iocache(IO_CACHE *dest); int write_to_iocache(IO_CACHE *dest);
int read_from_iocache(IO_CACHE *src); int read_from_iocache(IO_CACHE *src);
uint32 count(); uint32 count();
...@@ -153,6 +161,7 @@ struct rpl_binlog_state ...@@ -153,6 +161,7 @@ struct rpl_binlog_state
int get_most_recent_gtid_list(rpl_gtid **list, uint32 *size); int get_most_recent_gtid_list(rpl_gtid **list, uint32 *size);
bool append_pos(String *str); bool append_pos(String *str);
rpl_gtid *find(uint32 domain_id, uint32 server_id); rpl_gtid *find(uint32 domain_id, uint32 server_id);
rpl_gtid *find_most_recent(uint32 domain_id);
}; };
......
...@@ -1397,7 +1397,6 @@ rpl_load_gtid_slave_state(THD *thd) ...@@ -1397,7 +1397,6 @@ rpl_load_gtid_slave_state(THD *thd)
HASH hash; HASH hash;
int err= 0; int err= 0;
uint32 i; uint32 i;
uint64 highest_seq_no= 0;
DBUG_ENTER("rpl_load_gtid_slave_state"); DBUG_ENTER("rpl_load_gtid_slave_state");
rpl_global_gtid_slave_state.lock(); rpl_global_gtid_slave_state.lock();
...@@ -1450,8 +1449,6 @@ rpl_load_gtid_slave_state(THD *thd) ...@@ -1450,8 +1449,6 @@ rpl_load_gtid_slave_state(THD *thd)
DBUG_PRINT("info", ("Read slave state row: %u-%u-%lu sub_id=%lu\n", DBUG_PRINT("info", ("Read slave state row: %u-%u-%lu sub_id=%lu\n",
(unsigned)domain_id, (unsigned)server_id, (unsigned)domain_id, (unsigned)server_id,
(ulong)seq_no, (ulong)sub_id)); (ulong)seq_no, (ulong)sub_id));
if (seq_no > highest_seq_no)
highest_seq_no= seq_no;
if ((rec= my_hash_search(&hash, (const uchar *)&domain_id, 0))) if ((rec= my_hash_search(&hash, (const uchar *)&domain_id, 0)))
{ {
...@@ -1495,6 +1492,14 @@ rpl_load_gtid_slave_state(THD *thd) ...@@ -1495,6 +1492,14 @@ rpl_load_gtid_slave_state(THD *thd)
rpl_global_gtid_slave_state.unlock(); rpl_global_gtid_slave_state.unlock();
goto end; goto end;
} }
if (opt_bin_log &&
mysql_bin_log.bump_seq_no_counter_if_needed(entry->gtid.domain_id,
entry->gtid.seq_no))
{
my_error(ER_OUT_OF_RESOURCES, MYF(0));
rpl_global_gtid_slave_state.unlock();
goto end;
}
} }
rpl_global_gtid_slave_state.loaded= true; rpl_global_gtid_slave_state.loaded= true;
rpl_global_gtid_slave_state.unlock(); rpl_global_gtid_slave_state.unlock();
...@@ -1514,7 +1519,6 @@ end: ...@@ -1514,7 +1519,6 @@ end:
thd->mdl_context.release_transactional_locks(); thd->mdl_context.release_transactional_locks();
} }
my_hash_free(&hash); my_hash_free(&hash);
mysql_bin_log.bump_seq_no_counter_if_needed(highest_seq_no);
DBUG_RETURN(err); DBUG_RETURN(err);
} }
......
...@@ -6545,3 +6545,7 @@ ER_MASTER_GTID_POS_MISSING_DOMAIN ...@@ -6545,3 +6545,7 @@ ER_MASTER_GTID_POS_MISSING_DOMAIN
eng "Specified value for @@gtid_slave_pos contains no value for replication domain %u. This conflicts with the binary log which contains GTID %u-%u-%llu. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos." eng "Specified value for @@gtid_slave_pos contains no value for replication domain %u. This conflicts with the binary log which contains GTID %u-%u-%llu. If MASTER_GTID_POS=CURRENT_POS is used, the binlog position will override the new value of @@gtid_slave_pos."
ER_UNTIL_REQUIRES_USING_GTID ER_UNTIL_REQUIRES_USING_GTID
eng "START SLAVE UNTIL master_gtid_pos requires that slave is using GTID" eng "START SLAVE UNTIL master_gtid_pos requires that slave is using GTID"
ER_GTID_STRICT_OUT_OF_ORDER
eng "An attempt was made to binlog GTID %u-%u-%llu which would create an out-of-order sequence number with existing GTID %u-%u-%llu, and gtid strict mode is enabled."
ER_GTID_START_FROM_BINLOG_HOLE
eng "The binlog on the master is missing the GTID %u-%u-%llu requested by the slave (even though both a prior and a subsequent sequence number does exist), and GTID strict mode is enabled"
...@@ -1824,8 +1824,8 @@ after_set_capability: ...@@ -1824,8 +1824,8 @@ after_set_capability:
{ {
int rc; int rc;
char str_buf[256]; char str_buf[256];
String connect_state(str_buf, sizeof(str_buf), system_charset_info); String query_str(str_buf, sizeof(str_buf), system_charset_info);
connect_state.length(0); query_str.length(0);
/* /*
Read the master @@GLOBAL.gtid_domain_id variable. Read the master @@GLOBAL.gtid_domain_id variable.
...@@ -1848,9 +1848,9 @@ after_set_capability: ...@@ -1848,9 +1848,9 @@ after_set_capability:
mysql_free_result(master_res); mysql_free_result(master_res);
master_res= NULL; master_res= NULL;
connect_state.append(STRING_WITH_LEN("SET @slave_connect_state='"), query_str.append(STRING_WITH_LEN("SET @slave_connect_state='"),
system_charset_info); system_charset_info);
if (rpl_append_gtid_state(&connect_state, if (rpl_append_gtid_state(&query_str,
mi->using_gtid == mi->using_gtid ==
Master_info::USE_GTID_CURRENT_POS)) Master_info::USE_GTID_CURRENT_POS))
{ {
...@@ -1860,9 +1860,9 @@ after_set_capability: ...@@ -1860,9 +1860,9 @@ after_set_capability:
sprintf(err_buff, "%s Error: Out of memory", errmsg); sprintf(err_buff, "%s Error: Out of memory", errmsg);
goto err; goto err;
} }
connect_state.append(STRING_WITH_LEN("'"), system_charset_info); query_str.append(STRING_WITH_LEN("'"), system_charset_info);
rc= mysql_real_query(mysql, connect_state.ptr(), connect_state.length()); rc= mysql_real_query(mysql, query_str.ptr(), query_str.length());
if (rc) if (rc)
{ {
err_code= mysql_errno(mysql); err_code= mysql_errno(mysql);
...@@ -1883,12 +1883,45 @@ after_set_capability: ...@@ -1883,12 +1883,45 @@ after_set_capability:
} }
} }
query_str.length(0);
if (query_str.append(STRING_WITH_LEN("SET @slave_gtid_strict_mode="),
system_charset_info) ||
query_str.append_ulonglong(opt_gtid_strict_mode != false))
{
err_code= ER_OUTOFMEMORY;
errmsg= "The slave I/O thread stops because a fatal out-of-memory "
"error is encountered when it tries to set @slave_gtid_strict_mode.";
sprintf(err_buff, "%s Error: Out of memory", errmsg);
goto err;
}
rc= mysql_real_query(mysql, query_str.ptr(), query_str.length());
if (rc)
{
err_code= mysql_errno(mysql);
if (is_network_error(err_code))
{
mi->report(ERROR_LEVEL, err_code,
"Setting @slave_gtid_strict_mode failed with error: %s",
mysql_error(mysql));
goto network_err;
}
else
{
/* Fatal error */
errmsg= "The slave I/O thread stops because a fatal error is "
"encountered when it tries to set @slave_gtid_strict_mode.";
sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql));
goto err;
}
}
if (mi->rli.until_condition == Relay_log_info::UNTIL_GTID) if (mi->rli.until_condition == Relay_log_info::UNTIL_GTID)
{ {
connect_state.length(0); query_str.length(0);
connect_state.append(STRING_WITH_LEN("SET @slave_until_gtid='"), query_str.append(STRING_WITH_LEN("SET @slave_until_gtid='"),
system_charset_info); system_charset_info);
if (mi->rli.until_gtid_pos.append_to_string(&connect_state)) if (mi->rli.until_gtid_pos.append_to_string(&query_str))
{ {
err_code= ER_OUTOFMEMORY; err_code= ER_OUTOFMEMORY;
errmsg= "The slave I/O thread stops because a fatal out-of-memory " errmsg= "The slave I/O thread stops because a fatal out-of-memory "
...@@ -1896,9 +1929,9 @@ after_set_capability: ...@@ -1896,9 +1929,9 @@ after_set_capability:
sprintf(err_buff, "%s Error: Out of memory", errmsg); sprintf(err_buff, "%s Error: Out of memory", errmsg);
goto err; goto err;
} }
connect_state.append(STRING_WITH_LEN("'"), system_charset_info); query_str.append(STRING_WITH_LEN("'"), system_charset_info);
rc= mysql_real_query(mysql, connect_state.ptr(), connect_state.length()); rc= mysql_real_query(mysql, query_str.ptr(), query_str.length());
if (rc) if (rc)
{ {
err_code= mysql_errno(mysql); err_code= mysql_errno(mysql);
......
...@@ -614,6 +614,19 @@ get_slave_connect_state(THD *thd, String *out_str) ...@@ -614,6 +614,19 @@ get_slave_connect_state(THD *thd, String *out_str)
} }
static bool
get_slave_gtid_strict_mode(THD *thd)
{
bool null_value;
const LEX_STRING name= { C_STRING_WITH_LEN("slave_gtid_strict_mode") };
user_var_entry *entry=
(user_var_entry*) my_hash_search(&thd->user_vars, (uchar*) name.str,
name.length);
return entry && entry->val_int(&null_value) && !null_value;
}
/* /*
Get the value of the @slave_until_gtid user variable into the supplied Get the value of the @slave_until_gtid user variable into the supplied
String (this is the GTID position specified for START SLAVE UNTIL String (this is the GTID position specified for START SLAVE UNTIL
...@@ -876,8 +889,6 @@ contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev) ...@@ -876,8 +889,6 @@ contains_all_slave_gtid(slave_connection_state *st, Gtid_list_log_event *glev)
Give an error if the slave requests something that we do not have in our Give an error if the slave requests something that we do not have in our
binlog. binlog.
T
*/ */
static int static int
...@@ -1465,13 +1476,13 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, ...@@ -1465,13 +1476,13 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags,
enum_gtid_skip_type *gtid_skip_group, enum_gtid_skip_type *gtid_skip_group,
slave_connection_state *until_gtid_state, slave_connection_state *until_gtid_state,
enum_gtid_until_state *gtid_until_group, enum_gtid_until_state *gtid_until_group,
rpl_binlog_state *until_binlog_state) rpl_binlog_state *until_binlog_state,
bool slave_gtid_strict_mode, rpl_gtid *error_gtid)
{ {
my_off_t pos; my_off_t pos;
size_t len= packet->length(); size_t len= packet->length();
if (event_type == GTID_LIST_EVENT && using_gtid_state && if (event_type == GTID_LIST_EVENT && using_gtid_state && until_gtid_state)
(gtid_state->count() > 0 || until_gtid_state))
{ {
rpl_gtid *gtid_list; rpl_gtid *gtid_list;
uint32 list_len; uint32 list_len;
...@@ -1481,12 +1492,18 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, ...@@ -1481,12 +1492,18 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags,
Gtid_list_log_event::peek(packet->ptr()+ev_offset, len - ev_offset, Gtid_list_log_event::peek(packet->ptr()+ev_offset, len - ev_offset,
current_checksum_alg, current_checksum_alg,
&gtid_list, &list_len)) &gtid_list, &list_len))
{
my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
return "Failed to read Gtid_list_log_event: corrupt binlog"; return "Failed to read Gtid_list_log_event: corrupt binlog";
}
err= until_binlog_state->load(gtid_list, list_len); err= until_binlog_state->load(gtid_list, list_len);
my_free(gtid_list); my_free(gtid_list);
if (err) if (err)
{
my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
return "Failed in internal GTID book-keeping: Out of memory"; return "Failed in internal GTID book-keeping: Out of memory";
} }
}
/* Skip GTID event groups until we reach slave position within a domain_id. */ /* Skip GTID event groups until we reach slave position within a domain_id. */
if (event_type == GTID_EVENT && using_gtid_state) if (event_type == GTID_EVENT && using_gtid_state)
...@@ -1503,10 +1520,16 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, ...@@ -1503,10 +1520,16 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags,
current_checksum_alg, current_checksum_alg,
&event_gtid.domain_id, &event_gtid.server_id, &event_gtid.domain_id, &event_gtid.server_id,
&event_gtid.seq_no, &flags2)) &event_gtid.seq_no, &flags2))
{
my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
return "Failed to read Gtid_log_event: corrupt binlog"; return "Failed to read Gtid_log_event: corrupt binlog";
}
if (until_binlog_state->update(&event_gtid)) if (until_binlog_state->update(&event_gtid, false))
{
my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
return "Failed in internal GTID book-keeping: Out of memory"; return "Failed in internal GTID book-keeping: Out of memory";
}
if (gtid_state->count() > 0) if (gtid_state->count() > 0)
{ {
...@@ -1518,15 +1541,32 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, ...@@ -1518,15 +1541,32 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags,
event_gtid.seq_no <= gtid->seq_no) event_gtid.seq_no <= gtid->seq_no)
*gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ? *gtid_skip_group = (flags2 & Gtid_log_event::FL_STANDALONE ?
GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION); GTID_SKIP_STANDALONE : GTID_SKIP_TRANSACTION);
/*
Delete this entry if we have reached slave start position (so we will
not skip subsequent events and won't have to look them up and check).
*/
if (event_gtid.server_id == gtid->server_id && if (event_gtid.server_id == gtid->server_id &&
event_gtid.seq_no >= gtid->seq_no) event_gtid.seq_no >= gtid->seq_no)
{
/*
In strict mode, it is an error if the slave requests to start in
a "hole" in the master's binlog: a GTID that does not exist, even
though both the prior and subsequent seq_no exists for same
domain_id and server_id.
*/
if (slave_gtid_strict_mode && event_gtid.seq_no > gtid->seq_no)
{
my_errno= ER_GTID_START_FROM_BINLOG_HOLE;
*error_gtid= *gtid;
return "The binlog on the master is missing the GTID requested "
"by the slave (even though both a prior and a subsequent "
"sequence number does exist), and GTID strict mode is enabled.";
}
/*
Delete this entry if we have reached slave start position (so we
will not skip subsequent events and won't have to look them up
and check).
*/
gtid_state->remove(gtid); gtid_state->remove(gtid);
} }
} }
}
if (until_gtid_state) if (until_gtid_state)
{ {
...@@ -1626,9 +1666,12 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, ...@@ -1626,9 +1666,12 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags,
a no-operation on the slave. a no-operation on the slave.
*/ */
if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg)) if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg))
{
my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
return "Failed to replace row annotate event with dummy: too small event."; return "Failed to replace row annotate event with dummy: too small event.";
} }
} }
}
/* /*
Replace GTID events with old-style BEGIN events for slaves that do not Replace GTID events with old-style BEGIN events for slaves that do not
...@@ -1645,8 +1688,11 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, ...@@ -1645,8 +1688,11 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags,
ev_offset, ev_offset,
current_checksum_alg); current_checksum_alg);
if (err) if (err)
{
my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
return "Failed to replace GTID event with backwards-compatible event: " return "Failed to replace GTID event with backwards-compatible event: "
"currupt event."; "currupt event.";
}
if (!need_dummy) if (!need_dummy)
return NULL; return NULL;
} }
...@@ -1673,10 +1719,13 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, ...@@ -1673,10 +1719,13 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags,
binlog positions. binlog positions.
*/ */
if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg)) if (Query_log_event::dummy_event(packet, ev_offset, current_checksum_alg))
{
my_errno= ER_MASTER_FATAL_ERROR_READING_BINLOG;
return "Failed to replace binlog checkpoint or gtid list event with " return "Failed to replace binlog checkpoint or gtid list event with "
"dummy: too small event."; "dummy: too small event.";
} }
} }
}
/* /*
Skip events with the @@skip_replication flag set, if slave requested Skip events with the @@skip_replication flag set, if slave requested
...@@ -1698,20 +1747,32 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags, ...@@ -1698,20 +1747,32 @@ send_event_to_slave(THD *thd, NET *net, String* const packet, ushort flags,
pos= my_b_tell(log); pos= my_b_tell(log);
if (RUN_HOOK(binlog_transmit, before_send_event, if (RUN_HOOK(binlog_transmit, before_send_event,
(thd, flags, packet, log_file_name, pos))) (thd, flags, packet, log_file_name, pos)))
{
my_errno= ER_UNKNOWN_ERROR;
return "run 'before_send_event' hook failed"; return "run 'before_send_event' hook failed";
}
if (my_net_write(net, (uchar*) packet->ptr(), len)) if (my_net_write(net, (uchar*) packet->ptr(), len))
{
my_errno= ER_UNKNOWN_ERROR;
return "Failed on my_net_write()"; return "Failed on my_net_write()";
}
DBUG_PRINT("info", ("log event code %d", (*packet)[LOG_EVENT_OFFSET+1] )); DBUG_PRINT("info", ("log event code %d", (*packet)[LOG_EVENT_OFFSET+1] ));
if (event_type == LOAD_EVENT) if (event_type == LOAD_EVENT)
{ {
if (send_file(thd)) if (send_file(thd))
{
my_errno= ER_UNKNOWN_ERROR;
return "failed in send_file()"; return "failed in send_file()";
} }
}
if (RUN_HOOK(binlog_transmit, after_send_event, (thd, flags, packet))) if (RUN_HOOK(binlog_transmit, after_send_event, (thd, flags, packet)))
{
my_errno= ER_UNKNOWN_ERROR;
return "Failed to run hook 'after_send_event'"; return "Failed to run hook 'after_send_event'";
}
return NULL; /* Success */ return NULL; /* Success */
} }
...@@ -1747,6 +1808,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, ...@@ -1747,6 +1808,7 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos,
enum_gtid_skip_type gtid_skip_group= GTID_SKIP_NOT; enum_gtid_skip_type gtid_skip_group= GTID_SKIP_NOT;
enum_gtid_until_state gtid_until_group= GTID_UNTIL_NOT_DONE; enum_gtid_until_state gtid_until_group= GTID_UNTIL_NOT_DONE;
rpl_binlog_state until_binlog_state; rpl_binlog_state until_binlog_state;
bool slave_gtid_strict_mode;
uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; uint8 current_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF;
int old_max_allowed_packet= thd->variables.max_allowed_packet; int old_max_allowed_packet= thd->variables.max_allowed_packet;
...@@ -1778,9 +1840,12 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos, ...@@ -1778,9 +1840,12 @@ void mysql_binlog_send(THD* thd, char* log_ident, my_off_t pos,
connect_gtid_state.length(0); connect_gtid_state.length(0);
using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state); using_gtid_state= get_slave_connect_state(thd, &connect_gtid_state);
DBUG_EXECUTE_IF("simulate_non_gtid_aware_master", using_gtid_state= false;); DBUG_EXECUTE_IF("simulate_non_gtid_aware_master", using_gtid_state= false;);
if (using_gtid_state && if (using_gtid_state)
get_slave_until_gtid(thd, &slave_until_gtid_str)) {
slave_gtid_strict_mode= get_slave_gtid_strict_mode(thd);
if(get_slave_until_gtid(thd, &slave_until_gtid_str))
until_gtid_state= &until_gtid_state_obj; until_gtid_state= &until_gtid_state_obj;
}
/* /*
We want to corrupt the first event, in Log_event::read_log_event(). We want to corrupt the first event, in Log_event::read_log_event().
...@@ -2134,10 +2199,10 @@ impossible position"; ...@@ -2134,10 +2199,10 @@ impossible position";
current_checksum_alg, using_gtid_state, current_checksum_alg, using_gtid_state,
&gtid_state, &gtid_skip_group, &gtid_state, &gtid_skip_group,
until_gtid_state, &gtid_until_group, until_gtid_state, &gtid_until_group,
&until_binlog_state))) &until_binlog_state,
slave_gtid_strict_mode, &error_gtid)))
{ {
errmsg= tmp_msg; errmsg= tmp_msg;
my_errno= ER_UNKNOWN_ERROR;
goto err; goto err;
} }
if (until_gtid_state && if (until_gtid_state &&
...@@ -2315,10 +2380,10 @@ impossible position"; ...@@ -2315,10 +2380,10 @@ impossible position";
current_checksum_alg, current_checksum_alg,
using_gtid_state, &gtid_state, using_gtid_state, &gtid_state,
&gtid_skip_group, until_gtid_state, &gtid_skip_group, until_gtid_state,
&gtid_until_group, &until_binlog_state))) &gtid_until_group, &until_binlog_state,
slave_gtid_strict_mode, &error_gtid)))
{ {
errmsg= tmp_msg; errmsg= tmp_msg;
my_errno= ER_UNKNOWN_ERROR;
goto err; goto err;
} }
if ( if (
...@@ -2430,6 +2495,17 @@ err: ...@@ -2430,6 +2495,17 @@ err:
/* Use this error code so slave will know not to try reconnect. */ /* Use this error code so slave will know not to try reconnect. */
my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG; my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG;
} }
else if (my_errno == ER_GTID_START_FROM_BINLOG_HOLE)
{
my_snprintf(error_text, sizeof(error_text),
"The binlog on the master is missing the GTID %u-%u-%llu "
"requested by the slave (even though both a prior and a "
"subsequent sequence number does exist), and GTID strict mode "
"is enabled",
error_gtid.domain_id, error_gtid.server_id, error_gtid.seq_no);
/* Use this error code so slave will know not to try reconnect. */
my_errno = ER_MASTER_FATAL_ERROR_READING_BINLOG;
}
else if (my_errno == ER_CANNOT_LOAD_SLAVE_GTID_STATE) else if (my_errno == ER_CANNOT_LOAD_SLAVE_GTID_STATE)
{ {
my_snprintf(error_text, sizeof(error_text), my_snprintf(error_text, sizeof(error_text),
...@@ -3721,8 +3797,6 @@ rpl_append_gtid_state(String *dest, bool use_binlog) ...@@ -3721,8 +3797,6 @@ rpl_append_gtid_state(String *dest, bool use_binlog)
bool bool
rpl_gtid_pos_check(THD *thd, char *str, size_t len) rpl_gtid_pos_check(THD *thd, char *str, size_t len)
{ {
/* ToDo: Use gtid_strict_mode sysvar, when implemented. */
static const bool gtid_strict_mode= false;
slave_connection_state tmp_slave_state; slave_connection_state tmp_slave_state;
bool gave_conflict_warning= false, gave_missing_warning= false; bool gave_conflict_warning= false, gave_missing_warning= false;
...@@ -3759,7 +3833,7 @@ rpl_gtid_pos_check(THD *thd, char *str, size_t len) ...@@ -3759,7 +3833,7 @@ rpl_gtid_pos_check(THD *thd, char *str, size_t len)
continue; continue;
if (!(slave_gtid= tmp_slave_state.find(binlog_gtid->domain_id))) if (!(slave_gtid= tmp_slave_state.find(binlog_gtid->domain_id)))
{ {
if (gtid_strict_mode) if (opt_gtid_strict_mode)
{ {
my_error(ER_MASTER_GTID_POS_MISSING_DOMAIN, MYF(0), my_error(ER_MASTER_GTID_POS_MISSING_DOMAIN, MYF(0),
binlog_gtid->domain_id, binlog_gtid->domain_id, binlog_gtid->domain_id, binlog_gtid->domain_id,
...@@ -3778,7 +3852,7 @@ rpl_gtid_pos_check(THD *thd, char *str, size_t len) ...@@ -3778,7 +3852,7 @@ rpl_gtid_pos_check(THD *thd, char *str, size_t len)
} }
else if (slave_gtid->seq_no < binlog_gtid->seq_no) else if (slave_gtid->seq_no < binlog_gtid->seq_no)
{ {
if (gtid_strict_mode) if (opt_gtid_strict_mode)
{ {
my_error(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, MYF(0), my_error(ER_MASTER_GTID_POS_CONFLICTS_WITH_BINLOG, MYF(0),
slave_gtid->domain_id, slave_gtid->server_id, slave_gtid->domain_id, slave_gtid->server_id,
......
...@@ -1215,6 +1215,26 @@ static Sys_var_uint Sys_gtid_domain_id( ...@@ -1215,6 +1215,26 @@ static Sys_var_uint Sys_gtid_domain_id(
BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG,
ON_CHECK(check_has_super)); ON_CHECK(check_has_super));
static bool check_gtid_seq_no(sys_var *self, THD *thd, set_var *var)
{
uint32 domain_id, server_id;
uint64_t seq_no;
if (check_has_super(self, thd, var))
return true;
domain_id= thd->variables.gtid_domain_id;
server_id= thd->variables.server_id;
seq_no= (uint64)var->value->val_uint();
DBUG_EXECUTE_IF("ignore_set_gtid_seq_no_check", return 0;);
if (opt_gtid_strict_mode && opt_bin_log &&
mysql_bin_log.check_strict_gtid_sequence(domain_id, server_id, seq_no))
return true;
return false;
}
static Sys_var_ulonglong Sys_gtid_seq_no( static Sys_var_ulonglong Sys_gtid_seq_no(
"gtid_seq_no", "gtid_seq_no",
"Internal server usage, for replication with global transaction id. " "Internal server usage, for replication with global transaction id. "
...@@ -1224,7 +1244,7 @@ static Sys_var_ulonglong Sys_gtid_seq_no( ...@@ -1224,7 +1244,7 @@ static Sys_var_ulonglong Sys_gtid_seq_no(
SESSION_ONLY(gtid_seq_no), SESSION_ONLY(gtid_seq_no),
NO_CMD_LINE, VALID_RANGE(0, ULONGLONG_MAX), DEFAULT(0), NO_CMD_LINE, VALID_RANGE(0, ULONGLONG_MAX), DEFAULT(0),
BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG, BLOCK_SIZE(1), NO_MUTEX_GUARD, NOT_IN_BINLOG,
ON_CHECK(check_has_super)); ON_CHECK(check_gtid_seq_no));
#ifdef HAVE_REPLICATION #ifdef HAVE_REPLICATION
...@@ -1364,6 +1384,15 @@ static Sys_var_gtid_slave_pos Sys_gtid_slave_pos( ...@@ -1364,6 +1384,15 @@ static Sys_var_gtid_slave_pos Sys_gtid_slave_pos(
"The list of global transaction IDs that were last replicated on the " "The list of global transaction IDs that were last replicated on the "
"server, one for each replication domain.", "server, one for each replication domain.",
GLOBAL_VAR(opt_gtid_slave_pos_dummy), NO_CMD_LINE); GLOBAL_VAR(opt_gtid_slave_pos_dummy), NO_CMD_LINE);
static Sys_var_mybool Sys_gtid_strict_mode(
"gtid_strict_mode",
"Enforce strict seq_no ordering of events in the binary log. Slave "
"stops with an error if it encounters an event that would cause it to "
"generate an out-of-order binlog if executed.",
GLOBAL_VAR(opt_gtid_strict_mode),
CMD_LINE(OPT_ARG), DEFAULT(FALSE));
#endif #endif
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment