diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 2e49fc9b0821f..2244428a1c353 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -57,7 +57,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/field.cc ../sql/field_conv.cc ../sql/field_comp.cc ../sql/filesort_utils.cc ../sql/sql_digest.cc ../sql/filesort.cc ../sql/grant.cc - ../sql/gstream.cc ../sql/slave.cc + ../sql/gstream.cc ../sql/signal_handler.cc ../sql/handler.cc ../sql/hash_filo.cc ../sql/hostname.cc ../sql/init.cc ../sql/item_buff.cc ../sql/item_cmpfunc.cc diff --git a/mysql-test/main/change_master_default.result b/mysql-test/main/change_master_default.result new file mode 100644 index 0000000000000..e7add783f6b3d --- /dev/null +++ b/mysql-test/main/change_master_default.result @@ -0,0 +1,266 @@ +# Start of main.change_master_default +CREATE PROCEDURE show_defaultable_fields() +SELECT connection_name, +connect_retry, +master_ssl_allowed, +master_ssl_ca_file, +master_ssl_ca_path, +master_ssl_cert, +master_ssl_cipher, +master_ssl_key, +`master_ssl_verify_server_cert`, # MDEV-38194 +master_ssl_crl, +master_ssl_crlpath, +using_gtid, +master_retry_count, +slave_heartbeat_period +FROM information_schema.slave_status ORDER BY connection_name; +CHANGE MASTER 'unset' TO master_host='127.0.1.1'; +CHANGE MASTER 'defaulted' TO +master_connect_retry= DEFAULT, +master_ssl= DEFAULT, +master_ssl_ca= DEFAULT, +master_ssl_capath= DEFAULT, +master_ssl_cert= DEFAULT, +master_ssl_cipher= DEFAULT, +master_ssl_key= DEFAULT, +master_ssl_verify_server_cert= DEFAULT, +master_ssl_crl= DEFAULT, +master_ssl_crlpath= DEFAULT, +master_use_gtid= DEFAULT, +master_retry_count= DEFAULT, +master_heartbeat_period= DEFAULT, +master_host= '127.0.1.2'; +CHANGE MASTER TO # Default master does not replace named masters +master_connect_retry= 90, +master_ssl= FALSE, +master_ssl_ca= 'specified_ca', +master_ssl_capath= 'specified_capath', +master_ssl_cert= 'specified_cert', +master_ssl_cipher= 'specified_cipher', +master_ssl_key= 'specified_key', +master_ssl_verify_server_cert= FALSE, +master_ssl_crl= 'specified_crl', +master_ssl_crlpath= 'specified_crlpath', +master_use_gtid= NO, +master_retry_count= 150000, +master_heartbeat_period= 45, +master_host='127.0.0.1'; +CALL show_defaultable_fields(); +connection_name +connect_retry 90 +master_ssl_allowed No +master_ssl_ca_file specified_ca +master_ssl_ca_path specified_capath +master_ssl_cert specified_cert +master_ssl_cipher specified_cipher +master_ssl_key specified_key +master_ssl_verify_server_cert No +master_ssl_crl specified_crl +master_ssl_crlpath specified_crlpath +using_gtid No +master_retry_count 150000 +slave_heartbeat_period 45.000 +connection_name defaulted +connect_retry 60 +master_ssl_allowed Yes +master_ssl_ca_file +master_ssl_ca_path +master_ssl_cert +master_ssl_cipher +master_ssl_key +master_ssl_verify_server_cert Yes +master_ssl_crl +master_ssl_crlpath +using_gtid Slave_Pos +master_retry_count 100000 +slave_heartbeat_period 60.000 +connection_name unset +connect_retry 60 +master_ssl_allowed Yes +master_ssl_ca_file +master_ssl_ca_path +master_ssl_cert +master_ssl_cipher +master_ssl_key +master_ssl_verify_server_cert Yes +master_ssl_crl +master_ssl_crlpath +using_gtid Slave_Pos +master_retry_count 100000 +slave_heartbeat_period 60.000 +# Those set or left as `DEFAULT` should pick up changes to defaults. +# restart: --skip-slave-start --master-connect-retry=30 --skip-master-ssl --master-ssl-ca=default_ca --master-ssl-capath=default_capath --master-ssl-cert=default_cert --master-ssl-cipher=default_cipher --master-ssl-key=default_key --skip-master-ssl-verify-server-cert --master-ssl-crl=default_crl --master-ssl-crlpath=default_crlpath --master-use-gtid=CURRENT_POS --master-retry-count=50000 --master-heartbeat-period=15 +CALL show_defaultable_fields(); +connection_name +connect_retry 90 +master_ssl_allowed No +master_ssl_ca_file specified_ca +master_ssl_ca_path specified_capath +master_ssl_cert specified_cert +master_ssl_cipher specified_cipher +master_ssl_key specified_key +master_ssl_verify_server_cert No +master_ssl_crl specified_crl +master_ssl_crlpath specified_crlpath +using_gtid No +master_retry_count 150000 +slave_heartbeat_period 45.000 +connection_name defaulted +connect_retry 30 +master_ssl_allowed No +master_ssl_ca_file default_ca +master_ssl_ca_path default_capath +master_ssl_cert default_cert +master_ssl_cipher default_cipher +master_ssl_key default_key +master_ssl_verify_server_cert No +master_ssl_crl default_crl +master_ssl_crlpath default_crlpath +using_gtid Current_Pos +master_retry_count 50000 +slave_heartbeat_period 15.000 +connection_name unset +connect_retry 30 +master_ssl_allowed No +master_ssl_ca_file default_ca +master_ssl_ca_path default_capath +master_ssl_cert default_cert +master_ssl_cipher default_cipher +master_ssl_key default_key +master_ssl_verify_server_cert No +master_ssl_crl default_crl +master_ssl_crlpath default_crlpath +using_gtid Current_Pos +master_retry_count 50000 +slave_heartbeat_period 15.000 +SET @@GLOBAL.slave_net_timeout= 100; +SELECT connection_name, slave_heartbeat_period +FROM information_schema.slave_status ORDER BY connection_name; +connection_name slave_heartbeat_period + 45.000 +defaulted 15.000 +unset 15.000 +CHANGE MASTER TO +master_connect_retry= DEFAULT, +master_ssl= DEFAULT, +master_ssl_ca= DEFAULT, +master_ssl_capath= DEFAULT, +master_ssl_cert= DEFAULT, +master_ssl_cipher= DEFAULT, +master_ssl_key= DEFAULT, +master_ssl_verify_server_cert= DEFAULT, +master_ssl_crl= DEFAULT, +master_ssl_crlpath= DEFAULT, +master_use_gtid= DEFAULT, +master_retry_count= DEFAULT, +master_heartbeat_period= DEFAULT; +CALL show_defaultable_fields(); +connection_name +connect_retry 30 +master_ssl_allowed No +master_ssl_ca_file default_ca +master_ssl_ca_path default_capath +master_ssl_cert default_cert +master_ssl_cipher default_cipher +master_ssl_key default_key +master_ssl_verify_server_cert No +master_ssl_crl default_crl +master_ssl_crlpath default_crlpath +using_gtid Current_Pos +master_retry_count 50000 +slave_heartbeat_period 15.000 +connection_name defaulted +connect_retry 30 +master_ssl_allowed No +master_ssl_ca_file default_ca +master_ssl_ca_path default_capath +master_ssl_cert default_cert +master_ssl_cipher default_cipher +master_ssl_key default_key +master_ssl_verify_server_cert No +master_ssl_crl default_crl +master_ssl_crlpath default_crlpath +using_gtid Current_Pos +master_retry_count 50000 +slave_heartbeat_period 15.000 +connection_name unset +connect_retry 30 +master_ssl_allowed No +master_ssl_ca_file default_ca +master_ssl_ca_path default_capath +master_ssl_cert default_cert +master_ssl_cipher default_cipher +master_ssl_key default_key +master_ssl_verify_server_cert No +master_ssl_crl default_crl +master_ssl_crlpath default_crlpath +using_gtid Current_Pos +master_retry_count 50000 +slave_heartbeat_period 15.000 +RESET REPLICA 'unset' ALL; +CHANGE MASTER 'unset' TO master_host='127.0.1.3'; +# Validate command line options +# restart_abort: --master-heartbeat-period='' +# restart_abort: --master-heartbeat-period=123abc +# restart_abort: --master-heartbeat-period=-1 +# restart_abort: --master-heartbeat-period=4294967.296 +# restart: --skip-slave-start --master-heartbeat-period=0.000499 +SELECT connection_name, slave_heartbeat_period +FROM information_schema.slave_status ORDER BY connection_name; +connection_name slave_heartbeat_period + 0.000 +defaulted 0.000 +unset 0.000 +CALL mtr.add_suppression('.*master-heartbeat-period.+0.*disabl.+'); +FOUND 1 /\[Warning\] .*master-heartbeat-period.+0.*disabl.+/ in mysqld.1.err +# restart: --skip-slave-start --skip-master-ssl --master-ssl --skip-master-ssl-verify-server-cert --master-ssl-verify-server-cert --master-use-gtid=NO --autoset-master-use-gtid --master-heartbeat-period=45 --autoset-master-heartbeat-period +CALL show_defaultable_fields(); +connection_name +connect_retry 60 +master_ssl_allowed Yes +master_ssl_ca_file +master_ssl_ca_path +master_ssl_cert +master_ssl_cipher +master_ssl_key +master_ssl_verify_server_cert Yes +master_ssl_crl +master_ssl_crlpath +using_gtid Slave_Pos +master_retry_count 100000 +slave_heartbeat_period 60.000 +connection_name defaulted +connect_retry 60 +master_ssl_allowed Yes +master_ssl_ca_file +master_ssl_ca_path +master_ssl_cert +master_ssl_cipher +master_ssl_key +master_ssl_verify_server_cert Yes +master_ssl_crl +master_ssl_crlpath +using_gtid Slave_Pos +master_retry_count 100000 +slave_heartbeat_period 60.000 +connection_name unset +connect_retry 60 +master_ssl_allowed Yes +master_ssl_ca_file +master_ssl_ca_path +master_ssl_cert +master_ssl_cipher +master_ssl_key +master_ssl_verify_server_cert Yes +master_ssl_crl +master_ssl_crlpath +using_gtid Slave_Pos +master_retry_count 100000 +slave_heartbeat_period 60.000 +# Clean-up +DROP PROCEDURE show_defaultable_fields; +RESET REPLICA 'unset' ALL; +RESET REPLICA 'defaulted' ALL; +RESET REPLICA ALL; +# End of main.change_master_default diff --git a/mysql-test/main/change_master_default.test b/mysql-test/main/change_master_default.test new file mode 100644 index 0000000000000..c1fff6e07adb3 --- /dev/null +++ b/mysql-test/main/change_master_default.test @@ -0,0 +1,149 @@ +--echo # Start of main.change_master_default + +# MDEV-28302: Test how `CHANGE MASTER [named]` reads server +# options for its default values and the lack of those options. +# The test creates multiple CHANGE MASTER connections, +# where the defaultable fields of a connection are: +# * left unset +# * set to `DEFAULT` +# * configured with values + +# only need CHANGE MASTER and IS.slave_status +--source include/have_binlog_format_mixed.inc + +CREATE PROCEDURE show_defaultable_fields() +SELECT connection_name, + connect_retry, + master_ssl_allowed, + master_ssl_ca_file, + master_ssl_ca_path, + master_ssl_cert, + master_ssl_cipher, + master_ssl_key, + `master_ssl_verify_server_cert`, # MDEV-38194 + master_ssl_crl, + master_ssl_crlpath, + using_gtid, + master_retry_count, + slave_heartbeat_period +FROM information_schema.slave_status ORDER BY connection_name; + +CHANGE MASTER 'unset' TO master_host='127.0.1.1'; + +CHANGE MASTER 'defaulted' TO + master_connect_retry= DEFAULT, + master_ssl= DEFAULT, + master_ssl_ca= DEFAULT, + master_ssl_capath= DEFAULT, + master_ssl_cert= DEFAULT, + master_ssl_cipher= DEFAULT, + master_ssl_key= DEFAULT, + master_ssl_verify_server_cert= DEFAULT, + master_ssl_crl= DEFAULT, + master_ssl_crlpath= DEFAULT, + master_use_gtid= DEFAULT, + master_retry_count= DEFAULT, + master_heartbeat_period= DEFAULT, + master_host= '127.0.1.2'; + +CHANGE MASTER TO # Default master does not replace named masters + master_connect_retry= 90, + master_ssl= FALSE, + master_ssl_ca= 'specified_ca', + master_ssl_capath= 'specified_capath', + master_ssl_cert= 'specified_cert', + master_ssl_cipher= 'specified_cipher', + master_ssl_key= 'specified_key', + master_ssl_verify_server_cert= FALSE, + master_ssl_crl= 'specified_crl', + master_ssl_crlpath= 'specified_crlpath', + master_use_gtid= NO, + master_retry_count= 150000, + master_heartbeat_period= 45, + master_host='127.0.0.1'; + +# Show freshly created configurations +--query_vertical CALL show_defaultable_fields() + + +--echo # Those set or left as `DEFAULT` should pick up changes to defaults. + +--let $restart_parameters= --skip-slave-start --master-connect-retry=30 --skip-master-ssl --master-ssl-ca=default_ca --master-ssl-capath=default_capath --master-ssl-cert=default_cert --master-ssl-cipher=default_cipher --master-ssl-key=default_key --skip-master-ssl-verify-server-cert --master-ssl-crl=default_crl --master-ssl-crlpath=default_crlpath --master-use-gtid=CURRENT_POS --master-retry-count=50000 --master-heartbeat-period=15 +--source include/restart_mysqld.inc # not_embedded +--query_vertical CALL show_defaultable_fields() + +# The `DEFAULT` of `master_heartbeat_period` changes with `@@slave_net_timeout`. +SET @@GLOBAL.slave_net_timeout= 100; +SELECT connection_name, slave_heartbeat_period +FROM information_schema.slave_status ORDER BY connection_name; + +# `DEFAULT` overwrites the existing configs for only the CHANGEd connection. +CHANGE MASTER TO + master_connect_retry= DEFAULT, + master_ssl= DEFAULT, + master_ssl_ca= DEFAULT, + master_ssl_capath= DEFAULT, + master_ssl_cert= DEFAULT, + master_ssl_cipher= DEFAULT, + master_ssl_key= DEFAULT, + master_ssl_verify_server_cert= DEFAULT, + master_ssl_crl= DEFAULT, + master_ssl_crlpath= DEFAULT, + master_use_gtid= DEFAULT, + master_retry_count= DEFAULT, + master_heartbeat_period= DEFAULT; +--query_vertical CALL show_defaultable_fields() + +# Recreate 'unset' to see later that it saves `DEFAULT`, not the options' values +--disable_warnings + RESET REPLICA 'unset' ALL; +--enable_warnings +CHANGE MASTER 'unset' TO master_host='127.0.1.3'; + + +--echo # Validate command line options + +# Invalid `--master-heartbeat-period` values should abort the server +# (`restart_abort` includes a wait for the server to exit on its own, +# e.g., due to a startup error.) +--source include/shutdown_mysqld.inc +--let $restart_parameters= restart_abort: --master-heartbeat-period='' +--echo # $restart_parameters +--write_line "$restart_parameters" $_expect_file_name +--let $restart_parameters= restart_abort: --master-heartbeat-period=123abc +--echo # $restart_parameters +--write_line "$restart_parameters" $_expect_file_name +--let $restart_parameters= restart_abort: --master-heartbeat-period=-1 +--echo # $restart_parameters +--write_line "$restart_parameters" $_expect_file_name +--let $restart_parameters= restart_abort: --master-heartbeat-period=4294967.296 +--echo # $restart_parameters +--write_line "$restart_parameters" $_expect_file_name + +# Numbers between 0 and 0.5 exclusive should warn about rounding to 0 (disabled) +--let $restart_parameters= --skip-slave-start --master-heartbeat-period=0.000499 +--source include/start_mysqld.inc +SELECT connection_name, slave_heartbeat_period +FROM information_schema.slave_status ORDER BY connection_name; +--let $regexp= .*master-heartbeat-period.+0.*disabl.+ +--eval CALL mtr.add_suppression('$regexp') +--let SEARCH_FILE= `SELECT @@log_error` +--let SEARCH_PATTERN= \[Warning\] $regexp +--source include/search_pattern_in_file.inc + +# Test prefixes: adding `auto-` and omitting `skip-` +--let $restart_parameters= --skip-slave-start --skip-master-ssl --master-ssl --skip-master-ssl-verify-server-cert --master-ssl-verify-server-cert --master-use-gtid=NO --autoset-master-use-gtid --master-heartbeat-period=45 --autoset-master-heartbeat-period +--source include/restart_mysqld.inc + +--query_vertical CALL show_defaultable_fields() + + +--echo # Clean-up + +DROP PROCEDURE show_defaultable_fields; + +RESET REPLICA 'unset' ALL; +RESET REPLICA 'defaulted' ALL; +RESET REPLICA ALL; + +--echo # End of main.change_master_default diff --git a/mysql-test/main/master_retry_count_basic.result b/mysql-test/main/master_retry_count_basic.result index b274d285b32d6..3e8b33326aba6 100644 --- a/mysql-test/main/master_retry_count_basic.result +++ b/mysql-test/main/master_retry_count_basic.result @@ -1,14 +1,7 @@ -# Use `--master-retry-count` when not specified -CHANGE MASTER 'named' TO master_host='example.com'; -CHANGE MASTER TO master_host='127.0.0.1', master_ssl_verify_server_cert=0; -SELECT Connection_name, Master_Retry_Count -FROM information_schema.SLAVE_STATUS; -Connection_name Master_Retry_Count - 1 -named 1 -# Replace when specified -CHANGE MASTER 'named' TO master_retry_count=11; -CHANGE MASTER TO master_retry_count=10; +# Replace `--master-retry-count` when specified +CHANGE MASTER 'named' TO master_retry_count=11, master_host='example.com'; +CHANGE MASTER TO master_retry_count=10, +master_host='127.0.0.1', master_ssl_verify_server_cert=0; SELECT Connection_name, Master_Retry_Count FROM information_schema.SLAVE_STATUS; Connection_name Master_Retry_Count @@ -49,17 +42,17 @@ FROM information_schema.SLAVE_STATUS; Connection_name Master_Retry_Count 10 named 11 -# 0 internally means "not specified" +# 0 does not mean "not specified" CHANGE MASTER TO master_retry_count=0; CHANGE MASTER 'named' TO master_retry_count=0; SELECT Connection_name, Master_Retry_Count FROM information_schema.SLAVE_STATUS; Connection_name Master_Retry_Count - 10 -named 11 + 0 +named 0 # Truncates decimals -CHANGE MASTER TO master_retry_count=0.5; -CHANGE MASTER 'named' TO master_retry_count=0.5; +CHANGE MASTER TO master_retry_count=10.75; +CHANGE MASTER 'named' TO master_retry_count=11.25; SELECT Connection_name, Master_Retry_Count FROM information_schema.SLAVE_STATUS; Connection_name Master_Retry_Count @@ -68,12 +61,11 @@ named 11 # Caps values (such as UINT64_MAX + 1) to `--master-retry-count`'s max CHANGE MASTER TO master_retry_count=18446744073709551616; CHANGE MASTER 'named' TO master_retry_count=18446744073709551616; -SELECT Connection_name -FROM information_schema.SLAVE_STATUS -WHERE Master_Retry_Count IN (4294967295, 18446744073709551615); -Connection_name - -named +SELECT Connection_name, Master_Retry_Count +FROM information_schema.SLAVE_STATUS; +Connection_name Master_Retry_Count + 18446744073709551615 +named 18446744073709551615 # Negative CHANGE MASTER TO master_retry_count=-1; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '-1' at line 1 diff --git a/mysql-test/main/master_retry_count_basic.test b/mysql-test/main/master_retry_count_basic.test index 8e7fc110ed53c..1aaf61d9fc6c9 100644 --- a/mysql-test/main/master_retry_count_basic.test +++ b/mysql-test/main/master_retry_count_basic.test @@ -1,18 +1,15 @@ # MDEV-25674: Test the `Master_Retry_Count` field of -# CHANGE MASTER [name] TO and SHOW SLAVE [name] STATUS & co. (no feature testing) -# Two connections tests that the field is now per-connection. +# `CHANGE MASTER [name] TO` and `SHOW SLAVE [name] STATUS` & co.. +# feature testing is in `multi_source.connects_tried` and +# `--master-retry-count` testing is in `main.change_master_default`. +# The use of two connections tests that the field is now per-connection. --source include/have_perfschema.inc ---echo # Use `--master-retry-count` when not specified -CHANGE MASTER 'named' TO master_host='example.com'; -CHANGE MASTER TO master_host='127.0.0.1', master_ssl_verify_server_cert=0; -SELECT Connection_name, Master_Retry_Count - FROM information_schema.SLAVE_STATUS; - ---echo # Replace when specified -CHANGE MASTER 'named' TO master_retry_count=11; +--echo # Replace `--master-retry-count` when specified +CHANGE MASTER 'named' TO master_retry_count=11, master_host='example.com'; # Default master does not replace named master -CHANGE MASTER TO master_retry_count=10; +CHANGE MASTER TO master_retry_count=10, + master_host='127.0.0.1', master_ssl_verify_server_cert=0; # check-testcase SELECT Connection_name, Master_Retry_Count FROM information_schema.SLAVE_STATUS; @@ -46,24 +43,23 @@ CHANGE MASTER 'named' TO master_user='root'; SELECT Connection_name, Master_Retry_Count FROM information_schema.SLAVE_STATUS; ---echo # 0 internally means "not specified" +--echo # 0 does not mean "not specified" CHANGE MASTER TO master_retry_count=0; CHANGE MASTER 'named' TO master_retry_count=0; SELECT Connection_name, Master_Retry_Count FROM information_schema.SLAVE_STATUS; --echo # Truncates decimals -CHANGE MASTER TO master_retry_count=0.5; -CHANGE MASTER 'named' TO master_retry_count=0.5; +CHANGE MASTER TO master_retry_count=10.75; +CHANGE MASTER 'named' TO master_retry_count=11.25; SELECT Connection_name, Master_Retry_Count FROM information_schema.SLAVE_STATUS; --echo # Caps values (such as UINT64_MAX + 1) to `--master-retry-count`'s max CHANGE MASTER TO master_retry_count=18446744073709551616; CHANGE MASTER 'named' TO master_retry_count=18446744073709551616; -SELECT Connection_name - FROM information_schema.SLAVE_STATUS - WHERE Master_Retry_Count IN (4294967295, 18446744073709551615); +SELECT Connection_name, Master_Retry_Count + FROM information_schema.SLAVE_STATUS; --echo # Negative --error ER_PARSE_ERROR diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index c0c96e25905b3..0924d11626b8d 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -623,13 +623,68 @@ The following specify which files/extra groups are read (specified before remain If set to 1 table names are stored in lowercase on disk and table names will be case-insensitive. Should be set to 2 if you are using a case insensitive file system + --master-connect-retry=# + The DEFAULT value for the CHANGE MASTER option + MASTER_CONNECT_RETRY, the interval in integer seconds + between each try to connect to the master + --master-heartbeat-period=name + The DEFAULT value for the CHANGE MASTER option + MASTER_HEARTBEAT_PERIOD, the interval in DECIMAL(10, 3) + seconds between replication heartbeats; the autoset value + is @@slave_net_timeout/2 calculated on use (Automatically + configured unless set explicitly) --master-info-file=name The location and name of the file that remembers the master and where the I/O replication thread is in the master's binlogs. Defaults to master.info --master-retry-count=# - The number of tries the slave will make to connect to the + The DEFAULT value for the CHANGE MASTER option + MASTER_RETRY_COUNT, the number of tries to connect to the master before giving up + --master-ssl The DEFAULT value for the CHANGE MASTER option + MASTER_SSL, which is whether to use TLS to connect to the + master + (Defaults to on; use --skip-master-ssl to disable.) + --master-ssl-ca=name + The DEFAULT value for the CHANGE MASTER option + MASTER_SSL_CA, an optional path to a Certificate + Authorities' certificates file for TLS replication + --master-ssl-capath=name + The DEFAULT value for the CHANGE MASTER option + MASTER_SSL_CAPATH, an optional path to a directory of + Certificate Authority's certificate files for TLS + replication, + --master-ssl-cert=name + The DEFAULT value for the CHANGE MASTER option + MASTER_SSL_CERT, an optional path to the master's + certificate for TLS replication + --master-ssl-cipher=name + The DEFAULT value for the CHANGE MASTER option + MASTER_SSL_CIPHER, a list of permitted ciphers for TLS + replication + --master-ssl-crl=name + The DEFAULT value for the CHANGE MASTER option + MASTER_SSL_CRL, an optional path to a revoked + certificates file for TLS replication + --master-ssl-crlpath=name + The DEFAULT value for the CHANGE MASTER option + MASTER_SSL_CRLPATH, an optional path to a directory of + revoked certificate files for TLS replication + --master-ssl-key=name + The DEFAULT value for the CHANGE MASTER option + MASTER_SSL_KEY, an optional path to the master's private + key for TLS replication + --master-ssl-verify-server-cert + The DEFAULT value for the CHANGE MASTER option + MASTER_SSL_VERIFY_SERVER_CERT, which is whether to + validate the master's certificate in TLS replication + (Defaults to on; use --skip-master-ssl-verify-server-cert to disable.) + --master-use-gtid=name + The DEFAULT value for the CHANGE MASTER option + MASTER_USE_GTID, which specifies which GTID record (or + neither) to start replicating from; the autoset value is + Slave_Pos, or No if that master does not support GTIDs + (Automatically configured unless set explicitly) --master-verify-checksum Force checksum verification of logged events in the binary log before sending them to slaves or printing them @@ -1828,8 +1883,20 @@ log-warnings 2 long-query-time 10 low-priority-updates FALSE lower-case-table-names 1 +master-connect-retry 60 +master-heartbeat-period auto master-info-file master.info master-retry-count 100000 +master-ssl TRUE +master-ssl-ca +master-ssl-capath +master-ssl-cert +master-ssl-cipher +master-ssl-crl +master-ssl-crlpath +master-ssl-key +master-ssl-verify-server-cert TRUE +master-use-gtid ? master-verify-checksum FALSE max-allowed-packet 16777216 max-binlog-cache-size 18446744073709547520 diff --git a/mysql-test/suite/rpl/r/rpl_heartbeat.result b/mysql-test/suite/rpl/r/rpl_heartbeat.result index 42cf90bc73804..7525cb5d0abef 100644 --- a/mysql-test/suite/rpl/r/rpl_heartbeat.result +++ b/mysql-test/suite/rpl/r/rpl_heartbeat.result @@ -6,41 +6,6 @@ connection slave; include/stop_slave.inc set @restore_slave_net_timeout= @@global.slave_net_timeout; set @@global.slave_net_timeout= 10; -change master to master_host='127.0.0.1',master_port=MASTER_PORT, master_user='root'; -show status like 'Slave_heartbeat_period';; -Variable_name Slave_heartbeat_period -Value 5.000 -change master to master_host='127.0.0.1',master_port=MASTER_PORT, master_user='root', master_heartbeat_period= 4294968; -ERROR HY000: The requested value for the heartbeat period is either negative or exceeds the maximum allowed (4294967 seconds) -show status like 'Slave_heartbeat_period';; -Variable_name Slave_heartbeat_period -Value 5.000 -connection slave; -change master to master_host='127.0.0.1',master_port=MASTER_PORT, master_user='root', master_heartbeat_period= 0.0009999; -Warnings: -Warning 1703 The requested value for the heartbeat period is less than 1 millisecond. The value is reset to 0, meaning that heartbeating will effectively be disabled -show status like 'Slave_heartbeat_period';; -Variable_name Slave_heartbeat_period -Value 0.000 -change master to master_host='127.0.0.1',master_port=MASTER_PORT, master_user='root', master_heartbeat_period= 4294967; -Warnings: -Warning 1704 The requested value for the heartbeat period exceeds the value of `slave_net_timeout' seconds. A sensible value for the period should be less than the timeout -show status like 'Slave_heartbeat_period';; -Variable_name Slave_heartbeat_period -Value 4294967.000 -change master to master_host='127.0.0.1',master_port=MASTER_PORT, master_user='root', master_heartbeat_period= 0.001; -show status like 'Slave_heartbeat_period';; -Variable_name Slave_heartbeat_period -Value 0.001 -reset slave; -set @@global.slave_net_timeout= 5; -change master to master_host='127.0.0.1',master_port=MASTER_PORT, master_user='root', master_heartbeat_period= 5.001; -Warnings: -Warning 1704 The requested value for the heartbeat period exceeds the value of `slave_net_timeout' seconds. A sensible value for the period should be less than the timeout -show status like 'Slave_heartbeat_period';; -Variable_name Slave_heartbeat_period -Value 5.001 -reset slave; set @@global.slave_net_timeout= 5; change master to master_host='127.0.0.1',master_port=MASTER_PORT, master_user='root', master_heartbeat_period= 4; show status like 'Slave_heartbeat_period';; diff --git a/mysql-test/suite/rpl/r/rpl_heartbeat_basic.result b/mysql-test/suite/rpl/r/rpl_heartbeat_basic.result index a9bd16cc85ad2..fa82f30d58e5f 100644 --- a/mysql-test/suite/rpl/r/rpl_heartbeat_basic.result +++ b/mysql-test/suite/rpl/r/rpl_heartbeat_basic.result @@ -145,7 +145,7 @@ SHOW GLOBAL STATUS LIKE 'slave_heartbeat_period'; Variable_name Value Slave_heartbeat_period 0.001 RESET SLAVE; -CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_PORT, MASTER_USER='root', MASTER_CONNECT_RETRY=20, MASTER_HEARTBEAT_PERIOD=0.0009; +CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_PORT, MASTER_USER='root', MASTER_CONNECT_RETRY=20, MASTER_HEARTBEAT_PERIOD=0.00049; Warnings: Warning 1703 The requested value for the heartbeat period is less than 1 millisecond. The value is reset to 0, meaning that heartbeating will effectively be disabled SHOW GLOBAL STATUS LIKE 'slave_heartbeat_period'; @@ -154,21 +154,21 @@ Slave_heartbeat_period 0.000 RESET SLAVE; *** Max slave_heartbeat_timeout *** -CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_PORT, MASTER_USER='root', MASTER_CONNECT_RETRY=20, MASTER_HEARTBEAT_PERIOD=4294967; +CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_PORT, MASTER_USER='root', MASTER_CONNECT_RETRY=20, MASTER_HEARTBEAT_PERIOD=4294967.295; Warnings: Warning 1704 The requested value for the heartbeat period exceeds the value of `slave_net_timeout' seconds. A sensible value for the period should be less than the timeout SHOW GLOBAL STATUS LIKE 'slave_heartbeat_period'; Variable_name Value -Slave_heartbeat_period 4294967.000 +Slave_heartbeat_period 4294967.295 RESET SLAVE; CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_PORT, MASTER_USER='root', MASTER_CONNECT_RETRY=20, MASTER_HEARTBEAT_PERIOD=4294968; -ERROR HY000: The requested value for the heartbeat period is either negative or exceeds the maximum allowed (4294967 seconds) +ERROR HY000: The requested value for the heartbeat period is either negative or exceeds the maximum allowed (4294967.295 seconds) RESET SLAVE; CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_PORT, MASTER_USER='root', MASTER_CONNECT_RETRY=20, MASTER_HEARTBEAT_PERIOD=8589935; -ERROR HY000: The requested value for the heartbeat period is either negative or exceeds the maximum allowed (4294967 seconds) +ERROR HY000: The requested value for the heartbeat period is either negative or exceeds the maximum allowed (4294967.295 seconds) RESET SLAVE; CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=MASTER_PORT, MASTER_USER='root', MASTER_CONNECT_RETRY=20, MASTER_HEARTBEAT_PERIOD=4294967296; -ERROR HY000: The requested value for the heartbeat period is either negative or exceeds the maximum allowed (4294967 seconds) +ERROR HY000: The requested value for the heartbeat period is either negative or exceeds the maximum allowed (4294967.295 seconds) RESET SLAVE; *** Misc incorrect values *** diff --git a/mysql-test/suite/rpl/r/rpl_heartbeat_debug.result b/mysql-test/suite/rpl/r/rpl_heartbeat_debug.result index a89010cd43232..d5ed3d331511e 100644 --- a/mysql-test/suite/rpl/r/rpl_heartbeat_debug.result +++ b/mysql-test/suite/rpl/r/rpl_heartbeat_debug.result @@ -6,7 +6,7 @@ set @restore_slave_net_timeout= @@global.slave_net_timeout; set @@global.slave_net_timeout= 10; show status like 'Slave_heartbeat_period';; Variable_name Slave_heartbeat_period -Value 60.000 +Value 5.000 SET @saved_dbug= @@GLOBAL.debug_dbug; SET GLOBAL debug_dbug="+d,simulate_slave_heartbeat_network_error"; include/start_slave.inc diff --git a/mysql-test/suite/rpl/t/rpl_heartbeat.test b/mysql-test/suite/rpl/t/rpl_heartbeat.test index a34cf761c8a11..f7c766b3af744 100644 --- a/mysql-test/suite/rpl/t/rpl_heartbeat.test +++ b/mysql-test/suite/rpl/t/rpl_heartbeat.test @@ -21,58 +21,6 @@ set @@global.slave_net_timeout= 10; --enable_warnings --enable_prepare_warnings -### -### Checking the range -### - -# -# default period slave_net_timeout/2 -# ---replace_result $MASTER_MYPORT MASTER_PORT -eval change master to master_host='127.0.0.1',master_port=$MASTER_MYPORT, master_user='root'; ---query_vertical show status like 'Slave_heartbeat_period'; - -# -# the max for the period is ULONG_MAX/1000; an attempt to exceed it is denied -# ---replace_result $MASTER_MYPORT MASTER_PORT ---error ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE -eval change master to master_host='127.0.0.1',master_port=$MASTER_MYPORT, master_user='root', master_heartbeat_period= 4294968; ---query_vertical show status like 'Slave_heartbeat_period'; - -# -# the min value for the period is 1 millisecond an attempt to assign a -# lesser will be warned with treating the value as zero -# -connection slave; ---replace_result $MASTER_MYPORT MASTER_PORT -### 5.1 mtr does not have --warning ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE -eval change master to master_host='127.0.0.1',master_port=$MASTER_MYPORT, master_user='root', master_heartbeat_period= 0.0009999; ---query_vertical show status like 'Slave_heartbeat_period'; - -# -# the actual max and min must be accepted -# ---replace_result $MASTER_MYPORT MASTER_PORT -eval change master to master_host='127.0.0.1',master_port=$MASTER_MYPORT, master_user='root', master_heartbeat_period= 4294967; ---query_vertical show status like 'Slave_heartbeat_period'; - ---replace_result $MASTER_MYPORT MASTER_PORT -eval change master to master_host='127.0.0.1',master_port=$MASTER_MYPORT, master_user='root', master_heartbeat_period= 0.001; ---query_vertical show status like 'Slave_heartbeat_period'; - -reset slave; - -# -# A warning if period greater than slave_net_timeout -# -set @@global.slave_net_timeout= 5; ---replace_result $MASTER_MYPORT MASTER_PORT -eval change master to master_host='127.0.0.1',master_port=$MASTER_MYPORT, master_user='root', master_heartbeat_period= 5.001; ---query_vertical show status like 'Slave_heartbeat_period'; - -reset slave; - # # A warning if slave_net_timeout is set to less than the current HB period # diff --git a/mysql-test/suite/rpl/t/rpl_heartbeat_basic.test b/mysql-test/suite/rpl/t/rpl_heartbeat_basic.test index fb67ad770d57a..e06655935cca7 100644 --- a/mysql-test/suite/rpl/t/rpl_heartbeat_basic.test +++ b/mysql-test/suite/rpl/t/rpl_heartbeat_basic.test @@ -197,14 +197,14 @@ eval CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=$MASTER_MYPORT, MASTE SHOW GLOBAL STATUS LIKE 'slave_heartbeat_period'; RESET SLAVE; --replace_result $MASTER_MYPORT MASTER_PORT -eval CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=$MASTER_MYPORT, MASTER_USER='root', MASTER_CONNECT_RETRY=$connect_retry, MASTER_HEARTBEAT_PERIOD=0.0009; +eval CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=$MASTER_MYPORT, MASTER_USER='root', MASTER_CONNECT_RETRY=$connect_retry, MASTER_HEARTBEAT_PERIOD=0.00049; SHOW GLOBAL STATUS LIKE 'slave_heartbeat_period'; RESET SLAVE; --echo --echo *** Max slave_heartbeat_timeout *** --replace_result $MASTER_MYPORT MASTER_PORT -eval CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=$MASTER_MYPORT, MASTER_USER='root', MASTER_CONNECT_RETRY=$connect_retry, MASTER_HEARTBEAT_PERIOD=4294967; +eval CHANGE MASTER TO MASTER_HOST='127.0.0.1', MASTER_PORT=$MASTER_MYPORT, MASTER_USER='root', MASTER_CONNECT_RETRY=$connect_retry, MASTER_HEARTBEAT_PERIOD=4294967.295; SHOW GLOBAL STATUS LIKE 'slave_heartbeat_period'; RESET SLAVE; --replace_result $MASTER_MYPORT MASTER_PORT diff --git a/mysql-test/suite/rpl/t/rpl_rotate_logs.test b/mysql-test/suite/rpl/t/rpl_rotate_logs.test index 3ea78dbc174ab..c9d968b6f5a63 100644 --- a/mysql-test/suite/rpl/t/rpl_rotate_logs.test +++ b/mysql-test/suite/rpl/t/rpl_rotate_logs.test @@ -39,7 +39,7 @@ start slave; chmod 0600 $MYSQLD_SLAVE_DATADIR/master.info; # It will fail again because the file is empty so the slave cannot get valuable # info about how to connect to the master from it (failure in -# init_strvar_from_file() in init_master_info()). +# InfoFile::Persistent::load_from() in init_master_info()). --error 1201 start slave; --replace_result $MASTER_MYPORT MASTER_PORT diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index b5e18017597c4..d6c4cbbb90179 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -421,6 +421,7 @@ ENDIF() IF(NOT CMAKE_CROSSCOMPILING OR DEFINED CMAKE_CROSSCOMPILING_EMULATOR) ADD_EXECUTABLE(gen_lex_token gen_lex_token.cc ${CMAKE_CURRENT_BINARY_DIR}/yy_mariadb.hh) + TARGET_LINK_LIBRARIES(gen_lex_token mysys dbug) # required by `yy_mariadb.hh` ADD_EXECUTABLE(gen_lex_hash gen_lex_hash.cc) ENDIF() diff --git a/sql/mysqld.cc b/sql/mysqld.cc index ecb69d84ed006..75a6804049b77 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -303,6 +303,10 @@ static TYPELIB tc_heuristic_recover_typelib= array_elements(tc_heuristic_recover_names)-1,"", tc_heuristic_recover_names, NULL, NULL }; +#ifdef HAVE_REPLICATION +static TYPELIB master_use_gtid_typelib= + CREATE_TYPELIB_FOR(master_use_gtid_names); +#endif const char *first_keyword= "first"; const char *my_localhost= "localhost", @@ -782,8 +786,9 @@ File_parser_dummy_hook file_parser_dummy_hook; /* replication parameters */ uint report_port= 0; -ulong master_retry_count=100000; char *master_info_file; +// Options do not reset to default if the default is `nullptr`, so use `auto`. +char *master_heartbeat_period_str= autoset_my_option; char *relay_log_info_file, *report_user, *report_password, *report_host; char *opt_relay_logname = 0, *opt_relaylog_index_name=0; char *opt_logname, *opt_slow_logname, *opt_bin_logname; @@ -6822,11 +6827,86 @@ struct my_option my_long_options[]= "more than one storage engine, when binary log is disabled)", &opt_tc_log_file, &opt_tc_log_file, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, - {"master-retry-count", 0, - "The number of tries the slave will make to connect to the master before giving up", - &master_retry_count, &master_retry_count, 0, GET_ULONG, - REQUIRED_ARG, static_cast(master_retry_count), 0, 0, 0, 0, 0}, #ifdef HAVE_REPLICATION + {"master-connect-retry", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_CONNECT_RETRY, " + "the interval in integer seconds between each try to connect to the master", + &master_connect_retry, nullptr, nullptr, GET_UINT, + REQUIRED_ARG, master_connect_retry, 0, 0, nullptr, 0, nullptr}, + {"master-heartbeat-period", OPT_MASTER_HEARTBEAT_PERIOD, + "The DEFAULT value for the CHANGE MASTER option MASTER_HEARTBEAT_PERIOD, " + "the interval in DECIMAL(10, 3) seconds between replication heartbeats; " + "the autoset value is @@slave_net_timeout/2 calculated on use", + /*TODO + Like the filters, it is easier to parse from a string than to implement + new option types. Compromises would not be necessary if the options + parser isn't stuck with the lack of heterogenous types back in your day, + let alone the (in)accessiblity to C++'s @std::optional. + */ + &master_heartbeat_period_str, nullptr, nullptr, GET_STR|GET_AUTO, + REQUIRED_ARG, reinterpret_cast(master_heartbeat_period_str), + /* ignored for @ref GET_STR */ 0, 0, nullptr, 0, nullptr}, + {"master-use-gtid", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_USE_GTID, which " + "specifies which GTID record (or neither) to start replicating from; the " + "autoset value is Slave_Pos, or No if that master does not support GTIDs", + &master_use_gtid, nullptr, + &master_use_gtid_typelib, GET_ENUM|GET_AUTO, REQUIRED_ARG, + static_cast(master_use_gtid), 0, 0, nullptr, 0, nullptr}, + {"master-retry-count", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_RETRY_COUNT, " + "the number of tries to connect to the master before giving up", + &master_retry_count, nullptr, 0, GET_ULL, REQUIRED_ARG, + static_cast(master_retry_count), 0, 0, nullptr, 0, nullptr}, + {"master-ssl", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_SSL, " + "which is whether to use TLS to connect to the master", + &master_ssl, nullptr, nullptr, GET_BOOL, NO_ARG, + master_ssl, 0, 0, nullptr, 0, nullptr}, + {"master-ssl-ca", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_SSL_CA, " + "an optional path to a Certificate Authorities' " + "certificates file for TLS replication", + &master_ssl_ca, nullptr, nullptr, GET_STR, REQUIRED_ARG, + reinterpret_cast(master_ssl_ca), 0, 0, nullptr, 0, nullptr}, + {"master-ssl-capath", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_SSL_CAPATH, " + "an optional path to a directory of Certificate Authority's " + "certificate files for TLS replication, ", + &master_ssl_capath, nullptr, nullptr, GET_STR, REQUIRED_ARG, + reinterpret_cast(master_ssl_capath), 0, 0, nullptr, 0, nullptr}, + {"master-ssl-cert", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_SSL_CERT, " + "an optional path to the master's certificate for TLS replication", + &master_ssl_cert, nullptr, nullptr, GET_STR, REQUIRED_ARG, + reinterpret_cast(master_ssl_cert), 0, 0, nullptr, 0, nullptr}, + {"master-ssl-cipher", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_SSL_CIPHER, " + "a list of permitted ciphers for TLS replication", + &master_ssl_cipher, nullptr, nullptr, GET_STR, REQUIRED_ARG, + reinterpret_cast(master_ssl_cipher), 0, 0, nullptr, 0, nullptr}, + {"master-ssl-crl", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_SSL_CRL, " + "an optional path to a revoked certificates file for TLS replication", + &master_ssl_crl, nullptr, nullptr, GET_STR, REQUIRED_ARG, + reinterpret_cast(master_ssl_crl), 0, 0, nullptr, 0, nullptr}, + {"master-ssl-crlpath", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_SSL_CRLPATH, " + "an optional path to a directory of revoked " + "certificate files for TLS replication", + &master_ssl_crlpath, nullptr, nullptr, GET_STR, REQUIRED_ARG, + reinterpret_cast(master_ssl_crlpath), 0, 0, nullptr, 0, nullptr}, + {"master-ssl-key", 0, + "The DEFAULT value for the CHANGE MASTER option MASTER_SSL_KEY, " + "an optional path to the master's private key for TLS replication", + &master_ssl_key, nullptr, nullptr, GET_STR, REQUIRED_ARG, + reinterpret_cast(master_ssl_key), 0, 0, nullptr, 0, nullptr}, + {"master-ssl-verify-server-cert", 0, + "The DEFAULT value for the CHANGE MASTER " + "option MASTER_SSL_VERIFY_SERVER_CERT, " + "which is whether to validate the master's certificate in TLS replication", + &master_ssl_verify_server_cert, nullptr, nullptr, GET_BOOL, NO_ARG, + master_ssl_verify_server_cert, 0, 0, nullptr, 0, nullptr}, {"init-rpl-role", 0, "Set the replication role", &rpl_status, &rpl_status, &rpl_role_typelib, GET_ENUM, REQUIRED_ARG, RPL_AUTH_MASTER, 0, 0, 0, 0, 0}, @@ -7113,7 +7193,8 @@ static int show_heartbeat_period(THD *thd, SHOW_VAR *var, void *buff, get_master_info(&thd->variables.default_master_connection, Sql_condition::WARN_LEVEL_NOTE)) { - sprintf(static_cast(buff), "%.3f", mi->heartbeat_period); + sprintf(static_cast(buff), "%.3lf", + mi->master_heartbeat_period/1000.0); mi->release(); var->type= SHOW_CHAR; var->value= buff; @@ -8186,6 +8267,9 @@ mysqld_get_one_option(const struct my_option *opt, const char *argument, else var->value_origin= sys_var::COMMAND_LINE; } + // Handle `GET_AUTO` for `--master-heartbeat-period` and `--master-use-gtid` + if (argument == autoset_my_option) + my_getopt_init_one_value(opt, opt->value, opt->def_value); switch(opt->id) { case '#': @@ -8420,6 +8504,31 @@ mysqld_get_one_option(const struct my_option *opt, const char *argument, } break; } + case OPT_MASTER_HEARTBEAT_PERIOD: + if (master_heartbeat_period_str == autoset_my_option) + master_heartbeat_period.reset(); + else + { + bool overprecise; + if (Master_info_file::Heartbeat_period_value::from_chars( + master_heartbeat_period, master_heartbeat_period_str, + strchr(master_heartbeat_period_str, '\0'), overprecise, '\0') + ) + { + sql_print_error( + "Bad value for master-heartbeat-period; " + "should be between 0 and %s seconds inclusive.", + Master_info_file::Heartbeat_period_value::MAX + ); + return true; + } + if (unlikely(!*master_heartbeat_period && overprecise)) + sql_print_warning( + "master-heartbeat-period rounded to 0, " + "meaning that heartbeating will effectively be disabled." + ); + } + break; #endif /* HAVE_REPLICATION */ case (int) OPT_SAFE: opt_specialflag|= SPECIAL_SAFE_MODE | SPECIAL_NO_NEW_FUNC; diff --git a/sql/mysqld.h b/sql/mysqld.h index dc5d75bfe1a01..12acd9adaba8a 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -808,6 +808,7 @@ enum options_mysqld OPT_TLS_VERSION, OPT_SECURE_AUTH, OPT_MYSQL_TO_BE_IMPLEMENTED, OPT_SEQURE_FILE_PRIV, + OPT_MASTER_HEARTBEAT_PERIOD, OPT_which_is_always_the_last }; #endif diff --git a/sql/rpl_info_file.h b/sql/rpl_info_file.h new file mode 100644 index 0000000000000..1b16330e0bc2a --- /dev/null +++ b/sql/rpl_info_file.h @@ -0,0 +1,336 @@ +/* + Copyright (c) 2025 MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA. +*/ + +#ifndef RPL_INFO_FILE_H +#define RPL_INFO_FILE_H + +#include // uintN_t +#include // std::from/to_chars and other helpers +#include // superclass of Info_file::Mem_fn +#include // IO_CACHE, FN_REFLEN, ... + + +/** Helpers for reading and writing integers to and from @ref IO_CACHE + TODO: Other components, if you find these useful, + feel free to move these out of this Replication module. +*/ +namespace Int_IO_CACHE +{ + /** Number of fully-utilized decimal digits plus + * the partially-utilized digit (e.g., the 2's place in "2147483647") + * The sign, if signed (: + */ + template static constexpr size_t BUF_SIZE= + std::numeric_limits::digits10 + 1 + std::numeric_limits::is_signed; + static constexpr auto ERRC_OK= std::errc(); + + /** + @ref IO_CACHE (reading one line with the `\n`) version of std::from_chars() + @tparam I integer type + @return `false` if the line has parsed successfully or `true` if error + */ + template static bool from_chars(IO_CACHE *file, I &value) + { + /** + +2 for the terminating `\n\0` (They are ignored by + std::from_chars(), but my_b_gets() includes them.) + */ + char buf[BUF_SIZE + 2]; + /// includes the `\n` but excludes the `\0` + size_t length= my_b_gets(file, buf, sizeof(buf)); + if (!length) // EOF + return true; + // SFINAE if `I` is not a numeric type + std::from_chars_result result= std::from_chars(buf, &(buf[length]), value); + // Return `true` if the conversion failed or if the number ended early + return result.ec != ERRC_OK || *(result.ptr) != '\n'; + } + /** + Convenience overload of from_chars(IO_CACHE *, I &) for `operator=` types + @tparam I inner integer type + @tparam T wrapper type + */ + template static bool from_chars(IO_CACHE *file, T *self) + { + I value; + if (from_chars(file, value)) + return true; + (*self)= value; + return false; + } + + /** + @ref IO_CACHE (writing *without* a `\n`) version of std::to_chars() + @tparam I (inner) integer type + */ + template static void to_chars(IO_CACHE *file, I value) + { + /** + my_b_printf() uses a buffer too, + so we might as well save on format parsing and buffer resizing + */ + char buf[BUF_SIZE]; + std::to_chars_result result= std::to_chars(buf, &buf[sizeof(buf)], value); + DBUG_ASSERT(result.ec == ERRC_OK); + my_b_write(file, reinterpret_cast(buf), result.ptr - buf); + } +}; + + +/** + This common superclass of @ref Master_info_file and + @ref Relay_log_info_file provides them common code for saving + and loading values in their MySQL line-based sections. + As only the @ref Master_info_file has a MariaDB `key=value` + section with a mix of explicit and `DEFAULT`-able values, + code for those are in @ref Master_info_file instead. + + Each value is an instance of an implementation of the + @ref Info_file::Persistent interface. For convenience, they also have + assignment and implicit conversion operators for their underlying types. + + C++ templates enables code reuse for those implementation structs, but + templates are not suitable for the conventional header/implementation split. + Thus, this and derived files are header-only units (methods are `inline`). + Other files may include these files directly. + [C++20 modules](https://en.cppreference.com/w/cpp/language/modules.html) + can supercede the header-only design as well as headers' `#include` guards. +*/ +struct Info_file +{ + IO_CACHE file; + + + /// Persistence interface for an unspecified item + struct Persistent + { + virtual ~Persistent()= default; + // for save_to_file() + virtual bool is_default() { return false; } + /// @return `true` if the item is mandatory and couldn't provide a default + virtual bool set_default() { return true; } + /** set the value by reading a line from the IO and consume the `\n` + @return `false` if the line has parsed successfully or `true` if error + @post is_default() is `false` + */ + virtual bool load_from(IO_CACHE *file)= 0; + /** write the *effective* value to the IO **without** a `\n` + (The caller will separately determine how + to represent using the default value.) + */ + virtual void save_to(IO_CACHE *file)= 0; + }; + + /** Integer Value + @tparam I signed or unsigned integer type + @see Master_info_file::Optional_int_value + version with `DEFAULT` (not a subclass) + */ + template struct Int_value: Persistent + { + I value; + operator I() { return value; } + auto &operator=(I value) + { + this->value= value; + return *this; + } + virtual bool load_from(IO_CACHE *file) override + { return Int_IO_CACHE::from_chars(file, value); } + virtual void save_to(IO_CACHE *file) override + { return Int_IO_CACHE::to_chars(file, value); } + }; + + /// Null-Terminated String (usually file name) Value + template struct String_value: Persistent + { + char buf[size]; + /** + Reads should consider this an immutable '\0'-terminated string (especially + with @ref Optional_path_value where a `DEFAULT` may substitute the value). + Writes may prefers to directly address the underlying @ref buf. + */ + virtual operator const char *() { return buf; } + /// @param other non-`nullptr` `\0`-terminated string + auto &operator=(const char *other) + { + strmake(buf, other, size-1); + return *this; + } + virtual bool load_from(IO_CACHE *file) override + { + size_t length= my_b_gets(file, buf, size); + if (!length) // EOF + return true; + /// If we stopped on a newline, kill it. + char &last_char= buf[length-1]; + if (last_char == '\n') + { + last_char= '\0'; + return false; + } + /* + Consume the lost line break, + or error if the line overflows the @ref buf. + */ + return my_b_get(file) != '\n'; + } + virtual void save_to(IO_CACHE *file) override + { + const char *buf= *this; + my_b_write(file, reinterpret_cast(buf), strlen(buf)); + } + }; + + + virtual ~Info_file()= default; + virtual bool load_from_file()= 0; + virtual void save_to_file()= 0; + +protected: + + /** + std::Mem_fn()-like nullable replacement for + [member pointer upcasting](https://wg21.link/P0149R3) + */ + struct Mem_fn: std::function + { + using List= const std::initializer_list; + /// Null Constructor + Mem_fn(std::nullptr_t null= nullptr): + std::function(null) {} + /** Non-Null Constructor + @tparam T CRTP subclass of Info_file + @tparam M @ref Persistent subclass of the member + @param pm member pointer + */ + template Mem_fn(M T::* pm): + std::function( + [pm](Info_file *self) -> Persistent & + { return self->*static_cast(pm); } + ) {} + }; + + /** + (Re)load the MySQL line-based section from the @ref file + @param value_list + List of wrapped member pointers to values. The first element must be a + file name @ref String_value to be unambiguous with the line count line. + @param default_line_count + We cannot simply read lines until EOF as all versions + of MySQL/MariaDB may generate more lines than needed. + Therefore, starting with MySQL/MariaDB 4.1.x for @ref Master_info_file and + 5.6.x for @ref Relay_log_info_file, the first line of the file is number + of one-line-per-value lines in the file, including this line count itself. + This parameter specifies the number of effective lines before those + versions (i.e., not counting the line count line if it was to have one), + where the first line is a filename with extension + (either contains a `.` or is entirely empty) rather than an integer. + @return `false` if the file has parsed successfully or `true` if error + */ + bool load_from_file(Mem_fn::List value_list, size_t default_line_count) + { + /** + The first row is temporarily stored in the first value. If it is a line + count and not a log name (new format), the second row will overwrite it. + */ + auto &line1= dynamic_cast &>((*(value_list.begin()))(this)); + if (line1.load_from(&file)) + return true; + size_t line_count; + std::from_chars_result result= std::from_chars( + line1.buf, &line1.buf[sizeof(line1.buf)], line_count); + /** + If this first line was not a number - the line count, + then it was the first value for real, + so the for loop should then skip over it, the index 0 of the list. + */ + size_t i= result.ec != Int_IO_CACHE::ERRC_OK || *(result.ptr) != '\0'; + /* + Set the default after parsing: While std::from_chars() does not replace + the output if it failed, it does replace if the line is not fully spent. + */ + if (i) + line_count= default_line_count; + for (; i < line_count; ++i) + { + int c; + if (i < value_list.size()) // line known in the `value_list` + { + const Mem_fn &pm= value_list.begin()[i]; + if (pm) + { + if (pm(this).load_from(&file)) + return true; + continue; + } + } + /* + Count and discard unrecognized lines. + This is especially to prepare for @ref Master_info_file for MariaDB 10.0+, + which reserves a bunch of lines before its unique `key=value` section + to accomodate any future line-based (old-style) additions in MySQL. + (This will make moving from MariaDB to MySQL easier by not + requiring MySQL to recognize MariaDB `key=value` lines.) + */ + while ((c= my_b_get(&file)) != '\n') + if (c == my_b_EOF) + return true; // EOF already? + } + return false; + } + + /** + Flush the MySQL line-based section to the @ref file + @param value_list List of wrapped member pointers to values. + @param total_line_count + The number of lines to describe the file as on the first line of the file. + If this is larger than `value_list.size()`, suffix the file with empty + lines until the line count (including the line count line) is this many. + This reservation provides compatibility with MySQL, + who has added more old-style lines while MariaDB innovated. + */ + void save_to_file(Mem_fn::List value_list, size_t total_line_count) + { + DBUG_ASSERT(total_line_count > value_list.size()); + my_b_seek(&file, 0); + /* + If the new contents take less space than the previous file contents, + then this code would write the file with unerased trailing garbage lines. + But these garbage don't matter thanks to the number + of effective lines in the first line of the file. + */ + Int_IO_CACHE::to_chars(&file, total_line_count); + my_b_write_byte(&file, '\n'); + for (const Mem_fn &pm: value_list) + { + if (pm) + pm(this).save_to(&file); + my_b_write_byte(&file, '\n'); + } + /* + Pad additional reserved lines: + (1 for the line count line + line count) inclusive -> max line inclusive + = line count exclusive <- max line inclusive + */ + for (; total_line_count > value_list.size(); --total_line_count) + my_b_write_byte(&file, '\n'); + } + +}; + +#endif diff --git a/sql/rpl_master_info_file.h b/sql/rpl_master_info_file.h new file mode 100644 index 0000000000000..e84ff7badb64e --- /dev/null +++ b/sql/rpl_master_info_file.h @@ -0,0 +1,729 @@ +/* + Copyright (c) 2025 MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA. +*/ + +#ifndef RPL_MASTER_INFO_FILE_H +#define RPL_MASTER_INFO_FILE_H + +/*FIXME MDEV-38213: + `rpl_master_info_file.h` requires C++17, but RocksDB, + which transitively includes this file, is still on C++11. +*/ +#if __cplusplus < 201703L && _MSVC_LANG < 201703L +struct Master_info_file; +enum struct enum_master_use_gtid {}; +enum struct trilean {}; +#else + +#include "rpl_info_file.h" +#include // Type of Master_info_file::VALUE_MAP +#include // Key type of Master_info_file::VALUE_MAP +#include // Storage type of Master_info_file::Optional_int_value +#include // Used by Master_info_file::load_from_file() to dedup +#include "sql_const.h" // MAX_PASSWORD_LENGTH +// Interface type of Master_info_file::master_heartbeat_period +#include "my_decimal.h" + + +/** + A three-way comparison function for using + sort_dynamic() and bsearch() on ID_array_value::array. + @return -1 if first argument is less, 0 if it equal to, or 1 if it is greater + than the second + @deprecated Use a sorted set, such as @ref std::set, + to save on explicitly calling those functions. +*/ +inline static int change_master_id_cmp(const void *arg1, const void *arg2) +{ + const ulong &id1= *(const ulong *)arg1, &id2= *(const ulong *)arg2; + return (id1 > id2) - (id1 < id2); +} + +/// enum for @ref Master_info_file::Optional_bool_value +/*TODO: + `UNKNOWN` is the general term in ternary logic, but this name is `#define`d in + `item_cmpfunc.h`, which is used by target RocksDB, whose *C++11* requirement + doesn't recognize `inline` constants (whereas the server is on C++17). +*/ +enum struct trilean { NO, YES, DEFAULT= -1 }; +/// enum for @ref Master_info_file::master_use_gtid +enum struct enum_master_use_gtid { NO, CURRENT_POS, SLAVE_POS, DEFAULT }; +/// String names for non-@ref enum_master_use_gtid::DEFAULT values +inline const char *master_use_gtid_names[]= + {"No", "Current_Pos", "Slave_Pos", nullptr}; + +/** + `mariadbd` Options for the `DEFAULT` values of @ref Master_info_file values + @{ +*/ +/// Computes the `DEFAULT` value of @ref ::master_heartbeat_period +extern uint slave_net_timeout; +inline uint32_t master_connect_retry= 60; +inline std::optional master_heartbeat_period= std::nullopt; +inline bool master_ssl= true; +inline const char *master_ssl_ca = ""; +inline const char *master_ssl_capath = ""; +inline const char *master_ssl_cert = ""; +inline const char *master_ssl_crl = ""; +inline const char *master_ssl_crlpath= ""; +inline const char *master_ssl_key = ""; +inline const char *master_ssl_cipher = ""; +inline bool master_ssl_verify_server_cert= true; +/// `ulong` is the data type `my_getopt` expects. +inline auto master_use_gtid= static_cast(enum_master_use_gtid::DEFAULT); +inline uint64_t master_retry_count= 100000; +/// }@ + + +struct Master_info_file: Info_file +{ + + /** General Optional Value + @tparam T wrapped type + */ + template struct Optional_value: Persistent + { + std::optional optional; + virtual operator T()= 0; + /// Fowards to @ref optional perfectly + template auto &operator=(O&& other) + { + optional= std::forward(other); + return *this; + } + bool is_default() override { return !optional.has_value(); } + bool set_default() override + { + optional.reset(); + return false; + } + }; + /** Integer Value with `DEFAULT` + @tparam mariadbd_option + server options variable that determines the value of `DEFAULT` + @tparam I integer type (auto-deduced from `mariadbd_option`) + @see Int_value version without `DEFAULT` (not a superclass) + */ + template> + struct Optional_int_value: Optional_value + { + using Optional_value::operator=; + operator I() override + { return Optional_value::optional.value_or(mariadbd_option); } + virtual bool load_from(IO_CACHE *file) override + { return Int_IO_CACHE::from_chars(file, this); } + virtual void save_to(IO_CACHE *file) override + { return Int_IO_CACHE::to_chars(file, operator I()); } + }; + + /** + Optional Path Value (for SSL): @ref FN_REFLEN-sized '\0'- + terminated string with a `mariadbd` option for the `DEFAULT`. + @note This reuses the @ref String_value::buf to track the `DEFAULT`ed state, + which is a bit more efficient and convenient than + `std::optional>`. + Specifically, when the strlen() is 0, the value is an empty string if + the index 1 char is also '\0', or is set_default() if it is '\1'. + */ + template + struct Optional_path_value: String_value<> + { + operator const char *() override + { + if (is_default()) + return mariadbd_option; + return String_value<>::operator const char *(); + } + /// @param other `\0`-terminated string, or `nullptr` to call set_default() + auto &operator=(const char *other) + { + if (other) + { + buf[1]= false; // not default + String_value<>::operator=(other); + } + else + set_default(); + return *this; + } + bool is_default() override { return /* strlen() == 0 */ !buf[0] && buf[1]; } + bool set_default() override + { + buf[0]= false; + buf[1]= true; + return false; + } + bool load_from(IO_CACHE *file) override + { + buf[1]= false; // not default + return String_value<>::load_from(file); + } + }; + + /** Boolean Value with `DEFAULT`. + @note + * This uses the @ref trilean enum, + which is more efficient than `std::optional`. + * load_from() and save_to() are also engineered + to make use of the range of only two cases. + */ + template struct Optional_bool_value: Persistent + { + trilean value; + operator bool() + { return is_default() ? mariadbd_option : (value != trilean::NO); } + bool is_default() override { return value <= trilean::DEFAULT; } + bool set_default() override + { + value= trilean::DEFAULT; + return false; + } + auto &operator=(trilean other) + { + this->value= other; + return *this; + } + auto &operator=(bool value) + { return operator=(value ? trilean::YES : trilean::NO); } + /// @return `true` if the line is `0` or `1`, `false` otherwise or on error + bool load_from(IO_CACHE *file) override + { + /** Only three chars are required: + * One digit + (When base prefixes are not recognized in integer parsing, + anything with a leading `0` stops parsing + after converting the `0` to zero anyway.) + * the terminating `\n\0` as in IntegerLike::from_chars(IO_CACHE *, I &) + */ + char buf[3]; + if (my_b_gets(file, buf, 3) && buf[1] == '\n') + switch (buf[0]) { + case '0': + value= trilean::NO; + return false; + case '1': + value= trilean::YES; + return false; + } + return true; + } + void save_to(IO_CACHE *file) override + { my_b_write_byte(file, operator bool() ? '1' : '0'); } + }; + + + /** @ref uint32_t Array value + @deprecated + Only one of `DO_DOMAIN_IDS` and `IGNORE_DOMAIN_IDS` can be active + at a time, so giving them separate arrays, let alone value instances, + is wasteful. Until we refactor this pair, this will only reference + to existing arrays to reduce changes that will be obsolete by then. + As references, the struct does not manage (construct/destruct) the array. + */ + struct ID_array_value: Persistent + { + /// Array of `long`s (FIXME: Domain and Server IDs should be `uint32_t`s.) + DYNAMIC_ARRAY &array; + ID_array_value(DYNAMIC_ARRAY &array): array(array) {} + operator DYNAMIC_ARRAY &() { return array; } + /// @pre @ref array is initialized + + bool load_from(IO_CACHE *file) override + { + uint32_t count; + size_t i; + /// +1 for the terminating delimiter + char buf[Int_IO_CACHE::BUF_SIZE + 1]; + for (i= 0; i < sizeof(buf); ++i) + { + int c= my_b_get(file); + if (c == my_b_EOF) + return true; + buf[i]= static_cast(c); + if (c == /* End of Line */ '\n' || c == /* End of Count */ ' ') + break; + } + /* + * std::from_chars() fails if `count` will overflow in any way. + * exclusive end index of the string = size + */ + std::from_chars_result result= std::from_chars(buf, &buf[i], count); + // Reserve enough elements ahead of time. + if (result.ec != Int_IO_CACHE::ERRC_OK || allocate_dynamic(&array, count)) + return true; + while (count--) + { + uint32_t value; + /* + Check that the previous number ended with a ` `, + not `\n` or anything else. + */ + if (*(result.ptr) != ' ') + return true; + for (i= 0; i < sizeof(buf); ++i) + { + /* + Bottlenecks from repeated IO does not affect the + performance of reading char by char thanks to the cache. + */ + int c= my_b_get(file); + if (c == my_b_EOF) + return true; + buf[i]= static_cast(c); + if (c == /* End of Count */ ' ' || c == /* End of Line */ '\n') + break; + } + result= std::from_chars(buf, &buf[i], value); + if (result.ec != Int_IO_CACHE::ERRC_OK) + return true; + ulong id= value; + bool oom= insert_dynamic(&array, (uchar *)&id); + /* + This should not error because enough + memory was already allocate_dynamic()-ed. + */ + DBUG_ASSERT(!oom); + if (oom) + return true; + } + // Check that the last number ended with a `\n`, not ` ` or anything else. + if (*(result.ptr) != '\n') + return true; + sort_dynamic(&array, change_master_id_cmp); // to be safe + return false; + } + + /// Store the total number of elements followed by the individual elements. + void save_to(IO_CACHE *file) override + { + Int_IO_CACHE::to_chars(file, array.elements); + for (size_t i= 0; i < array.elements; ++i) + { + ulong id; + get_dynamic(&array, &id, i); + my_b_write_byte(file, ' '); + Int_IO_CACHE::to_chars(file, id); + } + } + }; + + + /** + `@@master_info_file` values, in SHOW SLAVE STATUS order where applicable + @{ + */ + + String_value master_host; + String_value master_user; + // Not in SHOW SLAVE STATUS + String_value master_password; + Int_value master_port; + /// Connect_Retry + Optional_int_value<::master_connect_retry> master_connect_retry; + String_value<> master_log_file; + /// Read_Master_Log_Pos + Int_value master_log_pos; + /// Master_SSL_Allowed + Optional_bool_value<::master_ssl> master_ssl; + /// Master_SSL_CA_File + Optional_path_value<::master_ssl_ca> master_ssl_ca; + /// Master_SSL_CA_Path + Optional_path_value<::master_ssl_capath> master_ssl_capath; + Optional_path_value<::master_ssl_cert> master_ssl_cert; + Optional_path_value<::master_ssl_cipher> master_ssl_cipher; + Optional_path_value<::master_ssl_key> master_ssl_key; + Optional_bool_value<::master_ssl_verify_server_cert> + master_ssl_verify_server_cert; + /// Replicate_Ignore_Server_Ids + ID_array_value ignore_server_ids; + Optional_path_value<::master_ssl_crl> master_ssl_crl; + Optional_path_value<::master_ssl_crlpath> master_ssl_crlpath; + + /** Singleton class of @ref Master_info_file::master_use_gtid: + It is a @ref enum_master_use_gtid value + with a `DEFAULT` value of @ref ::master_use_gtid, + which in turn has a `DEFAULT` value based on @ref gtid_supported. + */ + struct: Persistent + { + enum_master_use_gtid mode; + /** + The default `master_use_gtid` is normally `SLAVE_POS`; however, if the + master does not supports GTIDs, we fall back to `NO`. This value caches + the check so future RESET SLAVE commands don't revert to `SLAVE_POS`. + load_from() and save_to() are engineered (that is, hard-coded) + on the single-digit range of @ref enum_master_use_gtid, + similar to Optional_bool_value. + */ + bool gtid_supported= true; + operator enum_master_use_gtid() + { + if (is_default()) + { + auto default_use_gtid= + static_cast(::master_use_gtid); + return default_use_gtid >= enum_master_use_gtid::DEFAULT ? ( + gtid_supported ? + enum_master_use_gtid::SLAVE_POS : enum_master_use_gtid::NO + ) : default_use_gtid; + } + return mode; + } + operator bool() + { return operator enum_master_use_gtid() != enum_master_use_gtid::NO; } + auto &operator=(enum_master_use_gtid mode) + { + this->mode= mode; + return *this; + } + bool is_default() override + { return mode >= enum_master_use_gtid::DEFAULT; } + bool set_default() override + { + mode= enum_master_use_gtid::DEFAULT; + return false; + } + /** @return + `true` if the line is a @ref enum_master_use_gtid, + `false` otherwise or on error + */ + bool load_from(IO_CACHE *file) override + { + /** + Only 3 chars are required for the enum, + similar to @ref Optional_bool_value::load_from() + */ + char buf[3]; + if (!my_b_gets(file, buf, 3) || + buf[1] != '\n' || + buf[0] > /* SLAVE_POS */ '2' || buf[0] < /* NO */ '0') + return true; + operator=(static_cast(buf[0] - '0')); + return false; + } + void save_to(IO_CACHE *file) override + { + my_b_write_byte(file, + '0' + static_cast(operator enum_master_use_gtid())); + } + } + /// Using_Gtid + master_use_gtid; + + /// Replicate_Do_Domain_Ids + ID_array_value do_domain_ids; + /// Replicate_Ignore_Domain_Ids + ID_array_value ignore_domain_ids; + Optional_int_value<::master_retry_count> master_retry_count; + + /** Singleton class of Master_info_file::master_heartbeat_period: + It is a non-negative `DECIMAL(10,3)` seconds value internally + calculated as an unsigned integer milliseconds value. + It has a `DEFAULT` value of @ref ::master_heartbeat_period, + which in turn has a `DEFAULT` value of `@@slave_net_timeout / 2` seconds. + */ + struct Heartbeat_period_value: Optional_value + { + /** + @return std::numeric_limits::max() / 1000.0 + as a constant '\0'-terminated string + */ + static constexpr char MAX[]= "4294967.295"; + using Optional_value::operator=; + operator uint32_t() override + { + return is_default() ? ::master_heartbeat_period.value_or( + MY_MIN(slave_net_timeout*500ULL, std::numeric_limits::max()) + ) : *(Optional_value::optional); + } + /** Load from a `DECIMAL(10,3)` + @param overprecise + set to `true` if the decimal has more than 3 decimal digits + @return whether the decimal is out of range + @post Output arguments are set on success and + not changed if the decimal is out of range. + */ + static uint from_decimal( + uint32_t &result, const decimal_t &decimal, bool &overprecise + ) + { + /// Wrapper to enable only-once static const construction + struct Decimal_from_str: my_decimal + { + Decimal_from_str(const char *str, size_t strlen): my_decimal() + { + const char *end= &(str[strlen]); + [[maybe_unused]] int unexpected_error= str2my_decimal( + E_DEC_ERROR, str, this, const_cast(&end)); + DBUG_ASSERT(!unexpected_error && !*end); + } + }; + /* + The ideal use would work with `double`s, including the `*1000` step, + but disappointingly, the double interfaces of @ref decimal_t are + implemented by printing into a string and parsing that char array. + */ + static const auto MAX_PERIOD= Decimal_from_str(STRING_WITH_LEN(MAX)), + THOUSAND = Decimal_from_str(STRING_WITH_LEN("1000")); + ulonglong decimal_out; + if (decimal.sign || decimal_cmp(&MAX_PERIOD, &decimal) < 0) + return true; // ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE + overprecise= decimal.frac > 3; + // decomposed from my_decimal2int() to reduce a bit of computations + auto rounded= my_decimal(), product= my_decimal(); + int unexpected_error= + decimal_round(&decimal, &rounded, 3, HALF_UP) | + decimal_mul(&rounded, &THOUSAND, &product) | + decimal2ulonglong(&product, &decimal_out); + DBUG_ASSERT(!unexpected_error); + result= static_cast(decimal_out); + return unexpected_error; + } + /** Load from a '\0'-terminated string + @param expected_end This function also checks that the exclusive end + of the decimal *(which may be `str_end` itself)* is this delimiter. + @return from_decimal(), or `true` on unexpected contents + @post Output arguments are set on success and not changed on error. + */ + static uint from_chars( + std::optional &self, const char *str, + const char *str_end, bool &overprecise, char expected_end= '\n' + ) + { + uint32_t result; + auto decimal= my_decimal(); + if (str2my_decimal( + E_DEC_ERROR, str, &decimal, const_cast(&str_end) + ) || *str_end != expected_end || + from_decimal(result, decimal, overprecise)) + return true; + self.emplace(result); + return false; + } + bool load_from(IO_CACHE *file) override + { + /** + Number of chars Optional_int_value::load_from() uses plus + 1 for the decimal point; truncate the excess precision, + which there should not be unless the file is edited externally. + */ + char buf[Int_IO_CACHE::BUF_SIZE + 3]; + bool overprecise; + size_t length= my_b_gets(file, buf, sizeof(buf)); + return !length || + from_chars(optional, buf, &(buf[length]), overprecise) || overprecise; + } + /** + This method is engineered (that is, hard-coded) to take + full advantage of the non-negative `DECIMAL(10,3)` format. + */ + void save_to(IO_CACHE *file) override { + char buf[Int_IO_CACHE::BUF_SIZE - /* decimal part */ 3]; + auto[integer_part, decimal_part]= div(operator uint32_t(), 1000); + std::to_chars_result result= + std::to_chars(buf, &buf[sizeof(buf)], integer_part); + DBUG_ASSERT(result.ec == Int_IO_CACHE::ERRC_OK); + my_b_write(file, reinterpret_cast(buf), result.ptr - buf); + my_b_write_byte(file, '.'); + result= std::to_chars(buf, &buf[sizeof(buf)], decimal_part); + DBUG_ASSERT(result.ec == Int_IO_CACHE::ERRC_OK); + for (ptrdiff_t digits= result.ptr - buf; digits < 3; ++digits) + my_b_write_byte(file, '0'); + my_b_write(file, reinterpret_cast(buf), result.ptr - buf); + } + } + /// `Slave_heartbeat_period` of SHOW ALL SLAVES STATUS + master_heartbeat_period; + + /// }@ + + + inline static Mem_fn::List VALUE_LIST= { + &Master_info_file::master_log_file, + &Master_info_file::master_log_pos, + &Master_info_file::master_host, + &Master_info_file::master_user, + &Master_info_file::master_password, + &Master_info_file::master_port, + &Master_info_file::master_connect_retry, + &Master_info_file::master_ssl, + &Master_info_file::master_ssl_ca, + &Master_info_file::master_ssl_capath, + &Master_info_file::master_ssl_cert, + &Master_info_file::master_ssl_cipher, + &Master_info_file::master_ssl_key, + &Master_info_file::master_ssl_verify_server_cert, + &Master_info_file::master_heartbeat_period, + nullptr, // &Master_info_file::master_bind, // MDEV-19248 + &Master_info_file::ignore_server_ids, + nullptr, // MySQL `master_uuid`, which MariaDB ignores. + &Master_info_file::master_retry_count, + &Master_info_file::master_ssl_crl, + &Master_info_file::master_ssl_crlpath + }; + + /** + Guard agaist extra left-overs at the end of file in case a later update + causes the effective content to shrink compared to earlier contents + */ + static constexpr const char END_MARKER[]= "END_MARKER"; + /** A keyed iterable for the `key=value` section of `@@master_info_file`. + For bidirectional compatibility with MySQL + (codenames only at this writing) and earlier versions of MariaDB, + keys should match the corresponding old property name in @ref Master_info. + */ + // C++ default allocator to match that `mysql_execute_command()` uses `new` + inline static const std::unordered_map VALUE_MAP= { + /* + These are here to annotate whether they are `DEFAULT`. + They are repeated from @ref VALUE_LIST to enable bidirectional + compatibility with MySQL and earlier versions of MariaDB + (where unrecognized keys, such as those from the future, are ignored). + */ + {"connect_retry" , &Master_info_file::master_connect_retry }, + {"ssl" , &Master_info_file::master_ssl }, + {"ssl_ca" , &Master_info_file::master_ssl_ca }, + {"ssl_capath" , &Master_info_file::master_ssl_capath }, + {"ssl_cert" , &Master_info_file::master_ssl_cert }, + {"ssl_cipher" , &Master_info_file::master_ssl_cipher }, + {"ssl_key" , &Master_info_file::master_ssl_key }, + {"ssl_crl" , &Master_info_file::master_ssl_crl }, + {"ssl_crlpath" , &Master_info_file::master_ssl_crlpath }, + {"ssl_verify_server_cert", + &Master_info_file::master_ssl_verify_server_cert}, + {"heartbeat_period" , &Master_info_file::master_heartbeat_period }, + {"retry_count" , &Master_info_file::master_retry_count }, + // These are the ones new in MariaDB. + {"using_gtid", &Master_info_file::master_use_gtid }, + {"do_domain_ids", &Master_info_file::do_domain_ids }, + {"ignore_domain_ids", &Master_info_file::ignore_domain_ids}, + {END_MARKER, nullptr} + }; + + + Master_info_file( + DYNAMIC_ARRAY &ignore_server_ids, + DYNAMIC_ARRAY &do_domain_ids, DYNAMIC_ARRAY &ignore_domain_ids + ): + ignore_server_ids(ignore_server_ids), + do_domain_ids(do_domain_ids), ignore_domain_ids(ignore_domain_ids) + { + for(auto &[_, mem_fn]: VALUE_MAP) + if (mem_fn) + mem_fn(this).set_default(); + } + + bool load_from_file() override + { + /// Repurpose the trailing `\0` spot to prepare for the `=` or `\n` + static constexpr size_t LONGEST_KEY_SIZE= sizeof("ssl_verify_server_cert"); + if (Info_file::load_from_file(VALUE_LIST, /* MASTER_CONNECT_RETRY */ 7)) + return true; + /* + Info_file::load_from_file() is only for fixed-position entries. + Proceed with `key=value` lines for MariaDB 10.0 and above: + The "value" can then be read individually after consuming the`key=`. + */ + /** + MariaDB 10.0 does not have the `END_MARKER` before any left-overs at + the end of the file, so ignore any non-first occurrences of a key. + @note + This set only "contains" the static strings of @ref VALUE_MAP's keys, + which means it can simply compare pointers by face values rather than + their pointed content, in contrast with how `HASH` of `include/hash.h` + is designed for string contents in a specified charset. + */ + auto seen= std::unordered_set(); + while (true) + { + /** + A `key=value` line might not actually have the `=value` part; + in this case, it means this value was set_default(). + */ + bool found_equal= false; + char key[LONGEST_KEY_SIZE]; + for (size_t i= 0; i < LONGEST_KEY_SIZE; ++i) + { + switch (int c= my_b_get(&file)) { + case my_b_EOF: + return i; // OK if no chars were read, or error if the line hits EOF. + case '=': + found_equal= true; + [[fallthrough]]; + case '\n': + { + decltype(VALUE_MAP)::const_iterator kv= + VALUE_MAP.find(std::string_view( + key, + i // size = exclusive end index of the string + )); + // The "unknown" lines would be ignored to facilitate downgrades. + if (kv != VALUE_MAP.cend()) // found + { + const char *key= kv->first.data(); + if (key == END_MARKER) + return false; + /** + The `second` member of std::unordered_set::insert()'s return + is `true` for a new insertion or `false` for a duplicate. + */ + else if (seen.insert(key).second) + { + Persistent &value= kv->second(this); + if (found_equal ? value.load_from(&file) : value.set_default()) + return true; + } + } + goto break_for; + } + default: + key[i]= static_cast(c); + } + } +break_for:; + } + } + + void save_to_file() override + { + // Write the line-based section with some reservations for MySQL additions + Info_file::save_to_file(VALUE_LIST, 33); + /* Write MariaDB `key=value` lines: + The "value" can then be written individually after generating the`key=`. + */ + for (auto &[key, pm]: VALUE_MAP) + if (pm) + { + Persistent &value= pm(this); + my_b_write(&file, + reinterpret_cast(key.data()), key.size()); + if (!value.is_default()) + { + my_b_write_byte(&file, '='); + value.save_to(&file); + } + my_b_write_byte(&file, '\n'); + } + my_b_write(&file, reinterpret_cast(END_MARKER), + sizeof(END_MARKER) - /* the '\0' */ 1); + my_b_write_byte(&file, '\n'); + } + +}; + +#endif // C++ standard guard +#endif // include guard diff --git a/sql/rpl_mi.cc b/sql/rpl_mi.cc index 18d4222ec4ec4..a1f7dc69cbd77 100644 --- a/sql/rpl_mi.cc +++ b/sql/rpl_mi.cc @@ -26,23 +26,20 @@ #ifdef HAVE_REPLICATION -#define DEFAULT_CONNECT_RETRY 60 - static void init_master_log_pos(Master_info* mi); Master_info::Master_info(LEX_CSTRING *connection_name_arg, - bool is_slave_recovery) - :Slave_reporting_capability("I/O"), - ssl(1), ssl_verify_server_cert(1), fd(-1), io_thd(0), - rli(is_slave_recovery), port(MYSQL_PORT), + bool is_slave_recovery): + Master_info_file(ignore_server_ids, domain_id_filter.m_domain_ids[0], + domain_id_filter.m_domain_ids[1]), + Slave_reporting_capability("I/O"), fd(-1), io_thd(0), rli(is_slave_recovery), checksum_alg_before_fd(BINLOG_CHECKSUM_ALG_UNDEF), - connect_retry(DEFAULT_CONNECT_RETRY), retry_count(master_retry_count), connects_tried(0), inited(0), abort_slave(0), slave_running(MYSQL_SLAVE_NOT_RUN), slave_run_id(0), clock_diff_with_master(0), - sync_counter(0), heartbeat_period(0), received_heartbeats(0), + sync_counter(0), received_heartbeats(0), master_id(0), prev_master_id(0), - using_gtid(USE_GTID_SLAVE_POS), events_queued_since_last_gtid(0), + events_queued_since_last_gtid(0), gtid_reconnect_event_skip_count(0), gtid_event_seen(false), in_start_all_slaves(0), in_stop_all_slaves(0), in_flush_all_relay_logs(0), users(0), killed(0), @@ -50,10 +47,8 @@ Master_info::Master_info(LEX_CSTRING *connection_name_arg, semi_sync_reply_enabled(0) { char *tmp; + port= MYSQL_PORT; host[0] = 0; user[0] = 0; password[0] = 0; - ssl_ca[0]= 0; ssl_capath[0]= 0; ssl_cert[0]= 0; - ssl_cipher[0]= 0; ssl_key[0]= 0; - ssl_crl[0]= 0; ssl_crlpath[0]= 0; /* Store connection name and lower case connection name @@ -149,18 +144,6 @@ Master_info::~Master_info() free_root(&mem_root, MYF(0)); } -/** - A comparison function to be supplied as argument to @c sort_dynamic() - and @c bsearch() - - @return -1 if first argument is less, 0 if it equal to, 1 if it is greater - than the second -*/ -static int change_master_id_cmp(const void *id1, const void *id2) -{ - return (*(ulong *) id1 - *(ulong *) id2); -} - /** Reports if the s_id server has been configured to ignore events it generates with @@ -197,142 +180,20 @@ void Master_info::clear_in_memory_info(bool all) } } - -const char * -Master_info::using_gtid_astext(enum enum_using_gtid arg) -{ - switch (arg) - { - case USE_GTID_NO: - return "No"; - case USE_GTID_SLAVE_POS: - return "Slave_Pos"; - default: - DBUG_ASSERT(arg == USE_GTID_CURRENT_POS); - return "Current_Pos"; - } -} - - void init_master_log_pos(Master_info* mi) { DBUG_ENTER("init_master_log_pos"); - mi->master_log_name[0] = 0; mi->master_log_pos = BIN_LOG_HEADER_SIZE; // skip magic number - if (mi->master_supports_gtid) - { - mi->using_gtid= Master_info::USE_GTID_SLAVE_POS; - } + mi->master_use_gtid.set_default(); + mi->master_heartbeat_period.set_default(); mi->gtid_current_pos.reset(); mi->events_queued_since_last_gtid= 0; mi->gtid_reconnect_event_skip_count= 0; mi->gtid_event_seen= false; - - /* - always request heartbeat unless master_heartbeat_period is set - explicitly zero. Here is the default value for heartbeat period - if CHANGE MASTER did not specify it. (no data loss in conversion - as hb period has a max) - */ - mi->heartbeat_period= (float) MY_MIN(SLAVE_MAX_HEARTBEAT_PERIOD, - (slave_net_timeout/2.0)); - DBUG_ASSERT(mi->heartbeat_period > (float) 0.001 - || mi->heartbeat_period == 0); - DBUG_VOID_RETURN; } -/** - Parses the IO_CACHE for "key=" and returns the "key". - If no '=' found, returns the whole line (for END_MARKER). - - @param key [OUT] Key buffer - @param max_size [IN] Maximum buffer size - @param f [IN] IO_CACHE file - @param found_equal [OUT] Set true if a '=' was found. - - @retval 0 Either "key=" or '\n' found - @retval 1 EOF -*/ -static int -read_mi_key_from_file(char *key, int max_size, IO_CACHE *f, bool *found_equal) -{ - int i= 0, c; - - DBUG_ENTER("read_key_from_file"); - - *found_equal= false; - if (max_size <= 0) - DBUG_RETURN(1); - for (;;) - { - if (i >= max_size-1) - { - key[i] = '\0'; - DBUG_RETURN(0); - } - c= my_b_get(f); - if (c == my_b_EOF) - { - DBUG_RETURN(1); - } - else if (c == '\n') - { - key[i]= '\0'; - DBUG_RETURN(0); - } - else if (c == '=') - { - key[i]= '\0'; - *found_equal= true; - DBUG_RETURN(0); - } - else - { - key[i]= c; - ++i; - } - } - /* NotReached */ -} - -enum { - LINES_IN_MASTER_INFO_WITH_SSL= 14, - - /* 5.1.16 added value of master_ssl_verify_server_cert */ - LINE_FOR_MASTER_SSL_VERIFY_SERVER_CERT= 15, - - /* 5.5 added value of master_heartbeat_period */ - LINE_FOR_MASTER_HEARTBEAT_PERIOD= 16, - - /* MySQL Cluster 6.3 added master_bind */ - LINE_FOR_MASTER_BIND = 17, - - /* 6.0 added value of master_ignore_server_id */ - LINE_FOR_REPLICATE_IGNORE_SERVER_IDS= 18, - - /* 6.0 added value of master_uuid */ - LINE_FOR_MASTER_UUID= 19, - - /* line for master_retry_count */ - LINE_FOR_MASTER_RETRY_COUNT= 20, - - /* line for ssl_crl */ - LINE_FOR_SSL_CRL= 21, - - /* line for ssl_crl */ - LINE_FOR_SSL_CRLPATH= 22, - - /* MySQL 5.6 fixed-position lines. */ - LINE_FOR_FIRST_MYSQL_5_6=23, - LINE_FOR_LAST_MYSQL_5_6=23, - /* Reserved lines for MySQL future versions. */ - LINE_FOR_LAST_MYSQL_FUTURE=33, - /* Number of (fixed-position) lines used when saving master info file */ - LINES_IN_MASTER_INFO= LINE_FOR_LAST_MYSQL_FUTURE -}; - int init_master_info(Master_info* mi, const char* master_info_fname, const char* slave_info_fname, bool abort_if_no_master_info_file, @@ -454,236 +315,15 @@ file '%s')", fname); } mi->fd = fd; - int port, connect_retry, master_log_pos, lines; - int ssl= 0, ssl_verify_server_cert= 0; - float master_heartbeat_period= 0.0; - char *first_non_digit; - char buf[HOSTNAME_LENGTH+1]; - - /* - Starting from 4.1.x master.info has new format. Now its - first line contains number of lines in file. By reading this - number we will be always distinguish to which version our - master.info corresponds to. We can't simply count lines in - file since versions before 4.1.x could generate files with more - lines than needed. - If first line doesn't contain a number or contain number less than - LINES_IN_MASTER_INFO_WITH_SSL then such file is treated like file - from pre 4.1.1 version. - There is no ambiguity when reading an old master.info, as before - 4.1.1, the first line contained the binlog's name, which is either - empty or has an extension (contains a '.'), so can't be confused - with an integer. - - So we're just reading first line and trying to figure which version - is this. - */ - - /* - The first row is temporarily stored in mi->master_log_name, - if it is line count and not binlog name (new format) it will be - overwritten by the second row later. - */ - if (init_strvar_from_file(mi->master_log_name, - sizeof(mi->master_log_name), &mi->file, - "")) + if (mi->load_from_file()) goto errwithmsg; - lines= strtoul(mi->master_log_name, &first_non_digit, 10); - - if (mi->master_log_name[0]!='\0' && - *first_non_digit=='\0' && lines >= LINES_IN_MASTER_INFO_WITH_SSL) - { - /* Seems to be new format => read master log name from next line */ - if (init_strvar_from_file(mi->master_log_name, - sizeof(mi->master_log_name), &mi->file, "")) - goto errwithmsg; - } - else - lines= 7; - - if (init_intvar_from_file(&master_log_pos, &mi->file, 4) || - init_strvar_from_file(mi->host, sizeof(mi->host), &mi->file, 0) || - init_strvar_from_file(mi->user, sizeof(mi->user), &mi->file, "test") || - init_strvar_from_file(mi->password, sizeof(mi->password), - &mi->file, 0) || - init_intvar_from_file(&port, &mi->file, MYSQL_PORT) || - init_intvar_from_file(&connect_retry, &mi->file, - DEFAULT_CONNECT_RETRY)) - goto errwithmsg; - - /* - If file has ssl part use it even if we have server without - SSL support. But these options will be ignored later when - slave will try connect to master, so in this case warning - is printed. - */ - if (lines >= LINES_IN_MASTER_INFO_WITH_SSL) - { - if (init_intvar_from_file(&ssl, &mi->file, 0) || - init_strvar_from_file(mi->ssl_ca, sizeof(mi->ssl_ca), - &mi->file, 0) || - init_strvar_from_file(mi->ssl_capath, sizeof(mi->ssl_capath), - &mi->file, 0) || - init_strvar_from_file(mi->ssl_cert, sizeof(mi->ssl_cert), - &mi->file, 0) || - init_strvar_from_file(mi->ssl_cipher, sizeof(mi->ssl_cipher), - &mi->file, 0) || - init_strvar_from_file(mi->ssl_key, sizeof(mi->ssl_key), - &mi->file, 0)) - goto errwithmsg; - - /* - Starting from 5.1.16 ssl_verify_server_cert might be - in the file - */ - if (lines >= LINE_FOR_MASTER_SSL_VERIFY_SERVER_CERT && - init_intvar_from_file(&ssl_verify_server_cert, &mi->file, 0)) - goto errwithmsg; - /* - Starting from 6.0 master_heartbeat_period might be - in the file - */ - if (lines >= LINE_FOR_MASTER_HEARTBEAT_PERIOD && - init_floatvar_from_file(&master_heartbeat_period, &mi->file, 0.0)) - goto errwithmsg; - /* - Starting from MySQL Cluster 6.3 master_bind might be in the file - (this is just a reservation to avoid future upgrade problems) - */ - if (lines >= LINE_FOR_MASTER_BIND && - init_strvar_from_file(buf, sizeof(buf), &mi->file, "")) - goto errwithmsg; - /* - Starting from 6.0 list of server_id of ignorable servers might be - in the file - */ - if (lines >= LINE_FOR_REPLICATE_IGNORE_SERVER_IDS && - init_dynarray_intvar_from_file(&mi->ignore_server_ids, &mi->file)) - { - sql_print_error("Failed to initialize master info ignore_server_ids"); - goto errwithmsg; - } - - /* reserved */ - if (lines >= LINE_FOR_MASTER_UUID && - init_strvar_from_file(buf, sizeof(buf), &mi->file, "")) - goto errwithmsg; - - /* Starting from 5.5 the master_retry_count may be in the repository. */ - if (lines >= LINE_FOR_MASTER_RETRY_COUNT) - { - if (init_strvar_from_file(buf, sizeof(buf), &mi->file, "")) - goto errwithmsg; - mi->retry_count = atol(buf); - } - - if (lines >= LINE_FOR_SSL_CRLPATH && - (init_strvar_from_file(mi->ssl_crl, sizeof(mi->ssl_crl), - &mi->file, "") || - init_strvar_from_file(mi->ssl_crlpath, sizeof(mi->ssl_crlpath), - &mi->file, ""))) - goto errwithmsg; - - /* - Starting with MariaDB 10.0, we use a key=value syntax, which is nicer - in several ways. But we leave a bunch of empty lines to accomodate - any future old-style additions in MySQL (this will make it easier for - users moving from MariaDB to MySQL, to not have MySQL try to - interpret a MariaDB key=value line.) - */ - if (lines >= LINE_FOR_LAST_MYSQL_FUTURE) - { - uint i; - bool got_eq; - bool seen_using_gtid= false; - bool seen_do_domain_ids=false, seen_ignore_domain_ids=false; - - /* Skip lines used by / reserved for MySQL >= 5.6. */ - for (i= LINE_FOR_FIRST_MYSQL_5_6; i <= LINE_FOR_LAST_MYSQL_FUTURE; ++i) - { - if (init_strvar_from_file(buf, sizeof(buf), &mi->file, "")) - goto errwithmsg; - } - - /* - Parse any extra key=value lines. read_key_from_file() parses the file - for "key=" and returns the "key" if found. The "value" can then the - parsed on case by case basis. The "unknown" lines would be ignored to - facilitate downgrades. - 10.0 does not have the END_MARKER before any left-overs at the end - of the file. So ignore any but the first occurrence of a key. - */ - while (!read_mi_key_from_file(buf, sizeof(buf), &mi->file, &got_eq)) - { - if (got_eq && !seen_using_gtid && !strcmp(buf, "using_gtid")) - { - int val; - if (!init_intvar_from_file(&val, &mi->file, 0)) - { - if (val == Master_info::USE_GTID_CURRENT_POS) - mi->using_gtid= Master_info::USE_GTID_CURRENT_POS; - else if (val == Master_info::USE_GTID_SLAVE_POS) - mi->using_gtid= Master_info::USE_GTID_SLAVE_POS; - else - mi->using_gtid= Master_info::USE_GTID_NO; - seen_using_gtid= true; - } else { - sql_print_error("Failed to initialize master info using_gtid"); - goto errwithmsg; - } - } - else if (got_eq && !seen_do_domain_ids && !strcmp(buf, "do_domain_ids")) - { - if (mi->domain_id_filter.init_ids(&mi->file, - Domain_id_filter::DO_DOMAIN_IDS)) - { - sql_print_error("Failed to initialize master info do_domain_ids"); - goto errwithmsg; - } - seen_do_domain_ids= true; - } - else if (got_eq && !seen_ignore_domain_ids && - !strcmp(buf, "ignore_domain_ids")) - { - if (mi->domain_id_filter.init_ids(&mi->file, - Domain_id_filter::IGNORE_DOMAIN_IDS)) - { - sql_print_error("Failed to initialize master info " - "ignore_domain_ids"); - goto errwithmsg; - } - seen_ignore_domain_ids= true; - } - else if (!got_eq && !strcmp(buf, "END_MARKER")) - { - /* - Guard agaist extra left-overs at the end of file, in case a later - update causes the file to shrink compared to earlier contents. - */ - break; - } - } - } - } - #ifndef HAVE_OPENSSL - if (ssl) + if (mi->master_ssl) sql_print_warning("SSL information in the master info file " "('%s') are ignored because this MySQL slave was " "compiled without SSL support.", fname); #endif /* HAVE_OPENSSL */ - - /* - This has to be handled here as init_intvar_from_file can't handle - my_off_t types - */ - mi->master_log_pos= (my_off_t) master_log_pos; - mi->port= (uint) port; - mi->connect_retry= (uint) connect_retry; - mi->ssl= (my_bool) ssl; - mi->ssl_verify_server_cert= ssl_verify_server_cert; - mi->heartbeat_period= MY_MIN(SLAVE_MAX_HEARTBEAT_PERIOD, master_heartbeat_period); } DBUG_PRINT("master_info",("log_file_name: %s position: %ld", mi->master_log_name, @@ -728,7 +368,6 @@ int flush_master_info(Master_info* mi, bool need_lock_relay_log) { IO_CACHE* file = &mi->file; - char lbuf[22]; int err= 0; DBUG_ENTER("flush_master_info"); @@ -764,45 +403,6 @@ int flush_master_info(Master_info* mi, DBUG_RETURN(2); } - /* - produce a line listing the total number and all the ignored server_id:s - */ - char* ignore_server_ids_buf; - { - ignore_server_ids_buf= - (char *) my_malloc(PSI_INSTRUMENT_ME, - (sizeof(global_system_variables.server_id) * 3 + 1) * - (1 + mi->ignore_server_ids.elements), MYF(MY_WME)); - if (!ignore_server_ids_buf) - DBUG_RETURN(1); /* error */ - ulong cur_len= sprintf(ignore_server_ids_buf, "%zu", - mi->ignore_server_ids.elements); - for (ulong i= 0; i < mi->ignore_server_ids.elements; i++) - { - ulong s_id; - get_dynamic(&mi->ignore_server_ids, (uchar*) &s_id, i); - cur_len+= sprintf(ignore_server_ids_buf + cur_len, " %lu", s_id); - } - } - - char *do_domain_ids_buf= 0, *ignore_domain_ids_buf= 0; - - do_domain_ids_buf= - mi->domain_id_filter.as_string(Domain_id_filter::DO_DOMAIN_IDS); - if (do_domain_ids_buf == NULL) - { - err= 1; /* error */ - goto done; - } - - ignore_domain_ids_buf= - mi->domain_id_filter.as_string(Domain_id_filter::IGNORE_DOMAIN_IDS); - if (ignore_domain_ids_buf == NULL) - { - err= 1; /* error */ - goto done; - } - /* We flushed the relay log BEFORE the master.info file, because if we crash now, we will get a duplicate event in the relay log at restart. If we @@ -811,34 +411,7 @@ int flush_master_info(Master_info* mi, can add detection and scrap one event; with a hole there's nothing we can do). */ - - /* - In certain cases this code may create master.info files that seems - corrupted, because of extra lines filled with garbage in the end - file (this happens if new contents take less space than previous - contents of file). But because of number of lines in the first line - of file we don't care about this garbage. - */ - char heartbeat_buf[FLOATING_POINT_BUFFER]; - my_fcvt(mi->heartbeat_period, 3, heartbeat_buf, NULL); - my_b_seek(file, 0L); - my_b_printf(file, - "%u\n%s\n%s\n%s\n%s\n%s\n%d\n%d\n%d\n%s\n%s\n%s\n%s\n%s\n%d\n%s\n%s\n%s\n%s\n%d\n%s\n%s\n" - "\n\n\n\n\n\n\n\n\n\n\n" - "using_gtid=%d\n" - "do_domain_ids=%s\n" - "ignore_domain_ids=%s\n" - "END_MARKER\n", - LINES_IN_MASTER_INFO, - mi->master_log_name, llstr(mi->master_log_pos, lbuf), - mi->host, mi->user, - mi->password, mi->port, mi->connect_retry, - (int)(mi->ssl), mi->ssl_ca, mi->ssl_capath, mi->ssl_cert, - mi->ssl_cipher, mi->ssl_key, mi->ssl_verify_server_cert, - heartbeat_buf, "", ignore_server_ids_buf, - "", mi->retry_count, - mi->ssl_crl, mi->ssl_crlpath, mi->using_gtid, - do_domain_ids_buf, ignore_domain_ids_buf); + mi->save_to_file(); err= flush_io_cache(file); if (sync_masterinfo_period && !err && ++(mi->sync_counter) >= sync_masterinfo_period) @@ -849,11 +422,6 @@ int flush_master_info(Master_info* mi, /* Fix err; flush_io_cache()/my_sync() may return -1 */ err= (err != 0) ? 1 : 0; - -done: - my_free(ignore_server_ids_buf); - my_free(do_domain_ids_buf); - my_free(ignore_domain_ids_buf); DBUG_RETURN(err); } @@ -1105,7 +673,7 @@ bool Master_info_index::init_all_master_info() { int thread_mask; int err_num= 0, succ_num= 0; // The number of success read Master_info - char sign[MAX_CONNECTION_NAME+1]; + Info_file::String_value sign; File index_file_nr; THD *thd; DBUG_ENTER("init_all_master_info"); @@ -1143,8 +711,7 @@ bool Master_info_index::init_all_master_info() thd->store_globals(); reinit_io_cache(&index_file, READ_CACHE, 0L,0,0); - while (!init_strvar_from_file(sign, sizeof(sign), - &index_file, NULL)) + while (!sign.load_from(&index_file)) { LEX_CSTRING connection_name; Master_info *mi; @@ -1888,7 +1455,7 @@ bool Domain_id_filter::update_ids(DYNAMIC_ARRAY *do_ids, return true; } - if (using_gtid == Master_info::USE_GTID_NO && + if (!using_gtid && (!do_list_empty || !ignore_list_empty)) { sql_print_error("DO_DOMAIN_IDS or IGNORE_DOMAIN_IDS lists can't be " @@ -1907,22 +1474,6 @@ bool Domain_id_filter::update_ids(DYNAMIC_ARRAY *do_ids, return false; } -/** - Serialize and store the ids from domain id lists into the thd's protocol - buffer. - - @param thd [IN] thread handler - - @retval void -*/ -void Domain_id_filter::store_ids(THD *thd) -{ - for (int i= DO_DOMAIN_IDS; i <= IGNORE_DOMAIN_IDS; i ++) - { - prot_store_ids(thd, &m_domain_ids[i]); - } -} - void Domain_id_filter::store_ids(Field ***field) { for (int i= DO_DOMAIN_IDS; i <= IGNORE_DOMAIN_IDS; i ++) @@ -1931,59 +1482,6 @@ void Domain_id_filter::store_ids(Field ***field) } } -/** - Initialize the given domain_id list (DYNAMIC_ARRAY) with the - space-separated list of numbers from the specified IO_CACHE where - the first number represents the total number of entries to follows. - - @param f [IN] IO_CACHE file - @param type [IN] domain id list type - - @retval false Success - true Error -*/ -bool Domain_id_filter::init_ids(IO_CACHE *f, enum_list_type type) -{ - return init_dynarray_intvar_from_file(&m_domain_ids[type], f); -} - -/** - Return the elements of the give domain id list type as string. - - @param type [IN] domain id list type - - @retval a string buffer storing the total number - of elements followed by the individual - elements (space-separated) in the - specified list. - - Note: Its caller's responsibility to free the returned string buffer. -*/ -char *Domain_id_filter::as_string(enum_list_type type) -{ - char *buf; - size_t sz; - DYNAMIC_ARRAY *ids= &m_domain_ids[type]; - - sz= (sizeof(ulong) * 3 + 1) * (1 + ids->elements); - - if (!(buf= (char *) my_malloc(PSI_INSTRUMENT_ME, sz, MYF(MY_WME)))) - return NULL; - - // Store the total number of elements followed by the individual elements. - size_t cur_len= sprintf(buf, "%zu", ids->elements); - sz-= cur_len; - - for (uint i= 0; i < ids->elements; i++) - { - ulong domain_id; - get_dynamic(ids, (void *) &domain_id, i); - cur_len+= my_snprintf(buf + cur_len, sz, " %lu", domain_id); - sz-= cur_len; - } - return buf; -} - void update_change_master_ids(DYNAMIC_ARRAY *new_ids, DYNAMIC_ARRAY *old_ids) { reset_dynamic(old_ids); @@ -2031,24 +1529,6 @@ static size_t store_ids(DYNAMIC_ARRAY *ids, char *buff, size_t buff_len) } -/** - Serialize and store the ids from the given ids DYNAMIC_ARRAY into the thd's - protocol buffer. - - @param thd [IN] thread handler - @param ids [IN] ids list - - @retval void -*/ - -void prot_store_ids(THD *thd, DYNAMIC_ARRAY *ids) -{ - char buff[FN_REFLEN]; - size_t cur_len= store_ids(ids, buff, sizeof(buff)); - thd->protocol->store(buff, cur_len, &my_charset_bin); -} - - void field_store_ids(Field *field, DYNAMIC_ARRAY *ids) { char buff[FN_REFLEN]; @@ -2115,14 +1595,15 @@ void setup_mysql_connection_for_master(MYSQL *mysql, Master_info *mi, mysql_options(mysql, MYSQL_OPT_READ_TIMEOUT, (char *) &timeout); #ifdef HAVE_OPENSSL - if (mi->ssl) + if (mi->master_ssl) { - mysql_ssl_set(mysql, mi->ssl_key, mi->ssl_cert, mi->ssl_ca, mi->ssl_capath, - mi->ssl_cipher); - mysql_options(mysql, MYSQL_OPT_SSL_CRL, mi->ssl_crl); - mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH, mi->ssl_crlpath); + mysql_ssl_set(mysql, + mi->master_ssl_key, mi->master_ssl_cert, mi->master_ssl_ca, + mi->master_ssl_capath, mi->master_ssl_cipher); + mysql_options(mysql, MYSQL_OPT_SSL_CRL, mi->master_ssl_crl); + mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH, mi->master_ssl_crlpath); mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, - &mi->ssl_verify_server_cert); + &mi->master_ssl_verify_server_cert); } else #endif diff --git a/sql/rpl_mi.h b/sql/rpl_mi.h index c7288353009be..128088d80a626 100644 --- a/sql/rpl_mi.h +++ b/sql/rpl_mi.h @@ -18,6 +18,7 @@ #ifdef HAVE_REPLICATION +#include "rpl_master_info_file.h" #include "rpl_rli.h" #include "rpl_reporting.h" #include @@ -96,44 +97,8 @@ class Domain_id_filter bool update_ids(DYNAMIC_ARRAY *do_ids, DYNAMIC_ARRAY *ignore_ids, bool using_gtid); - /* - Serialize and store the ids from domain id lists into the thd's protocol - buffer. - - @param thd [IN] thread handler - - @retval void - */ - void store_ids(THD *thd); - /* Same as above, but store the id's into a group of fields */ + /// Serialize and store the ids from domain id lists into a group of fields. void store_ids(Field ***field); - /* - Initialize the given domain id list (DYNAMIC_ARRAY) with the - space-separated list of numbers from the specified IO_CACHE where - the first number is the total number of entries to follows. - - @param f [IN] IO_CACHE file - @param type [IN] domain id list type - - @retval false Success - true Error - */ - bool init_ids(IO_CACHE *f, enum_list_type type); - - /* - Return the elements of the give domain id list type as string. - - @param type [IN] domain id list type - - @retval a string buffer storing the total number - of elements followed by the individual - elements (space-separated) in the - specified list. - - Note: Its caller's responsibility to free the returned string buffer. - */ - char *as_string(enum_list_type type); - }; @@ -184,12 +149,14 @@ typedef struct st_rows_event_tracker *****************************************************************************/ -class Master_info : public Slave_reporting_capability +class Master_info: public Master_info_file, public Slave_reporting_capability { public: - enum enum_using_gtid { - USE_GTID_NO= 0, USE_GTID_CURRENT_POS= 1, USE_GTID_SLAVE_POS= 2 - }; + /// @deprecated use the new namespace instead + inline static constexpr enum_master_use_gtid + USE_GTID_NO = enum_master_use_gtid::NO, + USE_GTID_CURRENT_POS= enum_master_use_gtid::CURRENT_POS, + USE_GTID_SLAVE_POS = enum_master_use_gtid::SLAVE_POS; Master_info(LEX_CSTRING *connection_name, bool is_slave_recovery); ~Master_info(); @@ -200,7 +167,11 @@ class Master_info : public Slave_reporting_capability /* If malloc() in initialization failed */ return connection_name.str == 0; } - static const char *using_gtid_astext(enum enum_using_gtid arg); + inline const char *using_gtid_astext(enum_master_use_gtid arg) + { + DBUG_ASSERT(arg <= enum_master_use_gtid::DEFAULT); + return master_use_gtid_names[static_cast(arg)]; + } bool using_parallel() { return opt_slave_parallel_threads > 0 && @@ -222,21 +193,16 @@ class Master_info : public Slave_reporting_capability } /* the variables below are needed because we can change masters on the fly */ - char master_log_name[FN_REFLEN+6]; /* Room for multi-*/ - char host[HOSTNAME_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1]; - char user[USERNAME_LENGTH+1]; - char password[MAX_PASSWORD_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1]; + char (&master_log_name)[FN_REFLEN]= master_log_file.buf; + char (&host)[HOSTNAME_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1]= master_host.buf; + char (&user)[USERNAME_LENGTH+1]= master_user.buf; + char (&password)[MAX_PASSWORD_LENGTH*SYSTEM_CHARSET_MBMAXLEN+1]= + master_password.buf; LEX_CSTRING connection_name; /* User supplied connection name */ LEX_CSTRING cmp_connection_name; /* Connection name in lower case */ - bool ssl; // enables use of SSL connection if true - char ssl_ca[FN_REFLEN], ssl_capath[FN_REFLEN], ssl_cert[FN_REFLEN]; - char ssl_cipher[FN_REFLEN], ssl_key[FN_REFLEN]; - char ssl_crl[FN_REFLEN], ssl_crlpath[FN_REFLEN]; - my_bool ssl_verify_server_cert; /* MUST be my_bool, see mysql_option() */ - my_off_t master_log_pos; + my_off_t &master_log_pos= Master_info_file::master_log_pos.value; File fd; // we keep the file open, so we need to remember the file pointer - IO_CACHE file; mysql_mutex_t data_lock, run_lock, sleep_lock, start_stop_lock, start_alter_lock, start_alter_list_lock; mysql_cond_t data_cond, start_cond, stop_cond, sleep_cond; @@ -245,7 +211,7 @@ class Master_info : public Slave_reporting_capability uint32 file_id; /* for 3.23 load data infile */ uint mysql_version; Relay_log_info rli; - uint port; + uint32_t &port= master_port.value; Rpl_filter* rpl_filter; /* Each replication can set its filter rule*/ /* to hold checksum alg in use until IO thread has received FD. @@ -254,9 +220,9 @@ class Master_info : public Slave_reporting_capability */ enum_binlog_checksum_alg checksum_alg_before_fd; /** pause duration between each connection retry */ - uint connect_retry; + decltype(master_connect_retry) &connect_retry= master_connect_retry; /** per-slave @ref master_retry_count */ - ulong retry_count; + decltype(master_retry_count) &retry_count= master_retry_count; /** count of connects the most-recent (or the current) connection has tried */ ulong connects_tried; #ifndef DBUG_OFF @@ -289,7 +255,6 @@ class Master_info : public Slave_reporting_capability events should happen before fsyncing. */ uint sync_counter; - float heartbeat_period; // interface with CHANGE MASTER or master.info ulonglong received_heartbeats; // counter of received heartbeat events DYNAMIC_ARRAY ignore_server_ids; ulong master_id; @@ -305,7 +270,7 @@ class Master_info : public Slave_reporting_capability Note that you can not change the numeric values of these, they are used in master.info. */ - enum enum_using_gtid using_gtid; + decltype(master_use_gtid) &using_gtid= master_use_gtid; /* This GTID position records how far we have fetched into the relay logs. @@ -404,7 +369,7 @@ class Master_info : public Slave_reporting_capability first need to test if the master supports GTIDs. If not, fall back to 'No'. Cache the value so future RESET SLAVE commands don't revert to Slave_Pos. */ - bool master_supports_gtid= true; + bool &master_supports_gtid= master_use_gtid.gtid_supported; /* When TRUE, transition this server from being an active master to a slave. @@ -435,7 +400,6 @@ int flush_master_info(Master_info* mi, bool need_lock_relay_log); void copy_filter_setting(Rpl_filter* dst_filter, Rpl_filter* src_filter); void update_change_master_ids(DYNAMIC_ARRAY *new_ids, DYNAMIC_ARRAY *old_ids); -void prot_store_ids(THD *thd, DYNAMIC_ARRAY *ids); void field_store_ids(Field *field, DYNAMIC_ARRAY *ids); /* Multi master are handled trough this struct. diff --git a/sql/rpl_relay_log_info_file.h b/sql/rpl_relay_log_info_file.h new file mode 100644 index 0000000000000..fd2f530cc608f --- /dev/null +++ b/sql/rpl_relay_log_info_file.h @@ -0,0 +1,58 @@ +/* + Copyright (c) 2025 MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA. +*/ + +#ifndef RPL_RELAY_LOG_INFO_FILE_H +#define RPL_RELAY_LOG_INFO_FILE_H + +#include "rpl_info_file.h" + +struct Relay_log_info_file: Info_file +{ + /** + `@@relay_log_info_file` values in SHOW SLAVE STATUS order + @{ + */ + String_value<> relay_log_file; + Int_value relay_log_pos; + /// Relay_Master_Log_File (of the event *group*) + String_value<> read_master_log_file; + /// Exec_Master_Log_Pos (of the event *group*) + Int_value read_master_log_pos; + /// SQL_Delay + Int_value sql_delay; + /// }@ + + inline static const Mem_fn::List VALUE_LIST= { + &Relay_log_info_file::relay_log_file, + &Relay_log_info_file::relay_log_pos, + &Relay_log_info_file::read_master_log_file, + &Relay_log_info_file::read_master_log_pos, + &Relay_log_info_file::sql_delay + }; + + bool load_from_file() override + { + return Info_file::load_from_file(VALUE_LIST, /* Exec_Master_Log_Pos */ 4); + } + void save_to_file() override + { + return Info_file::save_to_file(VALUE_LIST, + VALUE_LIST.size() + /* line count line */ 1); + } +}; + +#endif diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 2709988d263f6..ec57839ed777a 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -45,15 +45,14 @@ rpl_slave_state *rpl_global_gtid_slave_state; gtid_waiting rpl_global_gtid_waiting; Relay_log_info::Relay_log_info(bool is_slave_recovery, const char* thread_name) - :Slave_reporting_capability(thread_name), + :Relay_log_info_file(), Slave_reporting_capability(thread_name), replicate_same_server_id(::replicate_same_server_id), info_fd(-1), cur_log_fd(-1), relay_log(&sync_relaylog_period), sync_counter(0), is_relay_log_recovery(is_slave_recovery), save_temporary_tables(0), mi(0), inuse_relaylog_list(0), last_inuse_relaylog(0), cur_log_old_open_count(0), error_on_rli_init_info(false), - group_relay_log_pos(0), event_relay_log_pos(0), - group_master_log_pos(0), log_space_total(0), ignore_log_space_limit(0), + event_relay_log_pos(0), log_space_total(0), ignore_log_space_limit(0), sql_thread_caught_up(true), last_master_timestamp(0), newest_master_timestamp(0), slave_timestamp(0), slave_skip_counter(0), @@ -62,12 +61,12 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery, const char* thread_name) slave_running(MYSQL_SLAVE_NOT_RUN), until_condition(UNTIL_NONE), until_log_pos(0), is_until_before_gtids(false), retried_trans(0), executed_entries(0), - last_trans_retry_count(0), sql_delay(0), sql_delay_end(0), + last_trans_retry_count(0), sql_delay_end(0), until_relay_log_names_defer(false), m_flags(0) { DBUG_ENTER("Relay_log_info::Relay_log_info"); - + group_relay_log_pos= group_master_log_pos= sql_delay= 0; relay_log.is_relay_log= TRUE; relay_log_state.init(); #ifdef HAVE_PSI_INTERFACE @@ -324,75 +323,10 @@ Failed to open the existing relay log info file '%s' (errno %d)", } } - int relay_log_pos, master_log_pos, lines; - char *first_non_digit; - - /* - Starting from MySQL 5.6.x, relay-log.info has a new format. - Now, its first line contains the number of lines in the file. - By reading this number we can determine which version our master.info - comes from. We can't simply count the lines in the file, since - versions before 5.6.x could generate files with more lines than - needed. If first line doesn't contain a number, or if it - contains a number less than LINES_IN_RELAY_LOG_INFO_WITH_DELAY, - then the file is treated like a file from pre-5.6.x version. - There is no ambiguity when reading an old master.info: before - 5.6.x, the first line contained the binlog's name, which is - either empty or has an extension (contains a '.'), so can't be - confused with an integer. - - So we're just reading first line and trying to figure which - version is this. - */ - - /* - The first row is temporarily stored in mi->master_log_name, if - it is line count and not binlog name (new format) it will be - overwritten by the second row later. - */ - if (init_strvar_from_file(group_relay_log_name, - sizeof(group_relay_log_name), - &info_file, "")) - { - msg="Error reading slave log configuration"; - goto err; - } - - lines= strtoul(group_relay_log_name, &first_non_digit, 10); - - if (group_relay_log_name[0] != '\0' && - *first_non_digit == '\0' && - lines >= LINES_IN_RELAY_LOG_INFO_WITH_DELAY) - { - DBUG_PRINT("info", ("relay_log_info file is in new format.")); - /* Seems to be new format => read relay log name from next line */ - if (init_strvar_from_file(group_relay_log_name, - sizeof(group_relay_log_name), - &info_file, "")) - { - msg="Error reading slave log configuration"; - goto err; - } - } - else - DBUG_PRINT("info", ("relay_log_info file is in old format.")); - - if (init_intvar_from_file(&relay_log_pos, - &info_file, BIN_LOG_HEADER_SIZE) || - init_strvar_from_file(group_master_log_name, - sizeof(group_master_log_name), - &info_file, "") || - init_intvar_from_file(&master_log_pos, &info_file, 0) || - (lines >= LINES_IN_RELAY_LOG_INFO_WITH_DELAY && - init_intvar_from_file(&sql_delay, &info_file, 0))) - { - msg="Error reading slave log configuration"; + if (load_from_file()) goto err; - } - strmake_buf(event_relay_log_name,group_relay_log_name); - group_relay_log_pos= event_relay_log_pos= relay_log_pos; - group_master_log_pos= master_log_pos; + event_relay_log_pos= group_relay_log_pos; if (is_relay_log_recovery && init_recovery(mi, &msg)) goto err; @@ -2619,11 +2553,6 @@ bool rpl_sql_thread_info::cached_charset_compare(char *charset) const - Error can happen if writing to file fails or if flushing the file fails. - @param rli The object representing the Relay_log_info. - - @todo Change the log file information to a binary format to avoid - calling longlong2str. - @return 0 on success, 1 on error. */ bool Relay_log_info::flush() @@ -2633,23 +2562,7 @@ bool Relay_log_info::flush() DBUG_ENTER("Relay_log_info::flush()"); IO_CACHE *file = &info_file; - // 2*file name, 2*long long, 2*unsigned long, 6*'\n' - char buff[FN_REFLEN * 2 + 22 * 2 + 10 * 2 + 6], *pos; - my_b_seek(file, 0L); - pos= longlong10_to_str(LINES_IN_RELAY_LOG_INFO_WITH_DELAY, buff, 10); - *pos++='\n'; - pos=strmov(pos, group_relay_log_name); - *pos++='\n'; - pos=longlong10_to_str(group_relay_log_pos, pos, 10); - *pos++='\n'; - pos=strmov(pos, group_master_log_name); - *pos++='\n'; - pos=longlong10_to_str(group_master_log_pos, pos, 10); - *pos++='\n'; - pos= longlong10_to_str(sql_delay, pos, 10); - *pos++= '\n'; - if (my_b_write(file, (uchar*) buff, (size_t) (pos-buff))) - error=1; + save_to_file(); if (flush_io_cache(file)) error=1; if (sync_relayloginfo_period && diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index 9b8ff5b9746f4..1090b90d5cb67 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -17,6 +17,7 @@ #ifndef RPL_RLI_H #define RPL_RLI_H +#include "rpl_relay_log_info_file.h" #include "rpl_tblmap.h" #include "rpl_reporting.h" #include "rpl_utility.h" @@ -59,7 +60,8 @@ class Rpl_filter; struct rpl_group_info; struct inuse_relaylog; -class Relay_log_info : public Slave_reporting_capability +class Relay_log_info: + public Relay_log_info_file, public Slave_reporting_capability { public: /** @@ -138,7 +140,7 @@ class Relay_log_info : public Slave_reporting_capability /* The following variables are safe to read any time */ /* IO_CACHE of the info file - set only during init or end */ - IO_CACHE info_file; + IO_CACHE &info_file= Info_file::file; /* List of temporary tables used by this connection. @@ -209,8 +211,8 @@ class Relay_log_info : public Slave_reporting_capability !belongs_to_client(); client thread executing BINLOG statement if belongs_to_client()). */ - char group_relay_log_name[FN_REFLEN]; - ulonglong group_relay_log_pos; + char (&group_relay_log_name)[FN_REFLEN]= relay_log_file.buf; + my_off_t &group_relay_log_pos= relay_log_pos.value; char event_relay_log_name[FN_REFLEN]; ulonglong event_relay_log_pos; ulonglong future_event_relay_log_pos; @@ -231,8 +233,8 @@ class Relay_log_info : public Slave_reporting_capability !belongs_to_client(); client thread executing BINLOG statement if belongs_to_client()). */ - char group_master_log_name[FN_REFLEN]; - volatile my_off_t group_master_log_pos; + char (&group_master_log_name)[FN_REFLEN]= read_master_log_file.buf; + my_off_t &group_master_log_pos= read_master_log_pos.value; /* Handling of the relay_log_space_limit optional constraint. @@ -587,7 +589,7 @@ class Relay_log_info : public Slave_reporting_capability slave SQL thread is running, since the SQL thread reads it without a lock when executing Relay_log_info::flush(). */ - int sql_delay; + uint32_t &sql_delay= Relay_log_info_file::sql_delay.value; /** During a delay, specifies the point in time when the delay ends. diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 28cefd3dbf299..09bd1c1bd8676 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -8482,11 +8482,11 @@ ER_SLAVE_HEARTBEAT_FAILURE spa "Datos inesperados de latido (heartbeat) de maestro (master): %s" sw "Data isiyotarajiwa ya mapigo ya moyo ya bwana: %s" ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE - chi "心跳周期的请求值是负的或超过允许的最大值(%u秒)" - eng "The requested value for the heartbeat period is either negative or exceeds the maximum allowed (%u seconds)" - geo "გულისცემის პერიოდის მოთოხვნილი მნიშვნელობა ან უარყოფითა, ან მაქსიმალურ დაშვებულ მნიშვნელობას (%u წამი) აჭარბებს" - spa "El valor requerido para el período de latido o es negativo o excede al máximo permitido (%u segundos)" - sw "Thamani iliyoombwa kwa kipindi cha mpigo wa moyo ni hasi au inazidi kiwango cha juu kinachoruhusiwa (sekunde %u)" + chi "心跳周期的请求值是负的或超过允许的最大值(%s秒)" + eng "The requested value for the heartbeat period is either negative or exceeds the maximum allowed (%s seconds)" + geo "გულისცემის პერიოდის მოთოხვნილი მნიშვნელობა ან უარყოფითა, ან მაქსიმალურ დაშვებულ მნიშვნელობას (%s წამი) აჭარბებს" + spa "El valor requerido para el período de latido o es negativo o excede al máximo permitido (%s segundos)" + sw "Thamani iliyoombwa kwa kipindi cha mpigo wa moyo ni hasi au inazidi kiwango cha juu kinachoruhusiwa (sekunde %s)" ER_UNUSED_14 eng "You should never see it" geo "ამას ვერ უნდა ხედავდეთ" diff --git a/sql/slave.cc b/sql/slave.cc index 3771dee99f020..cf4d7a9e8c41a 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1466,173 +1466,6 @@ bool net_request_file(NET* net, const char* fname) (uchar*) "", 0)); } -#endif /* HAVE_REPLICATION */ - -int init_strvar_from_file(char *var, int max_size, IO_CACHE *f, - const char *default_val) -{ - size_t length; - DBUG_ENTER("init_strvar_from_file"); - - if ((length=my_b_gets(f,var, max_size))) - { - char* last_p = var + length -1; - if (*last_p == '\n') - *last_p = 0; // if we stopped on newline, kill it - else - { - /* - If we truncated a line or stopped on last char, remove all chars - up to and including newline. - */ - int c; - while (((c=my_b_get(f)) != '\n' && c != my_b_EOF)) ; - } - DBUG_RETURN(0); - } - else if (default_val) - { - strmake(var, default_val, max_size-1); - DBUG_RETURN(0); - } - DBUG_RETURN(1); -} - -/* - when moving these functions to mysys, don't forget to - remove slave.cc from libmysqld/CMakeLists.txt -*/ -int init_intvar_from_file(int* var, IO_CACHE* f, int default_val) -{ - char buf[32]; - DBUG_ENTER("init_intvar_from_file"); - - - if (my_b_gets(f, buf, sizeof(buf))) - { - *var = atoi(buf); - DBUG_RETURN(0); - } - else if (default_val) - { - *var = default_val; - DBUG_RETURN(0); - } - DBUG_RETURN(1); -} - -int init_floatvar_from_file(float* var, IO_CACHE* f, float default_val) -{ - char buf[16]; - DBUG_ENTER("init_floatvar_from_file"); - - - if (my_b_gets(f, buf, sizeof(buf))) - { - if (sscanf(buf, "%f", var) != 1) - DBUG_RETURN(1); - else - DBUG_RETURN(0); - } - else if (default_val != 0.0) - { - *var = default_val; - DBUG_RETURN(0); - } - DBUG_RETURN(1); -} - - -/** - A master info read method - - This function is called from @c init_master_info() along with - relatives to restore some of @c active_mi members. - Particularly, this function is responsible for restoring - IGNORE_SERVER_IDS list of servers whose events the slave is - going to ignore (to not log them in the relay log). - Items being read are supposed to be decimal output of values of a - type shorter or equal of @c long and separated by the single space. - It also used to restore DO_DOMAIN_IDS & IGNORE_DOMAIN_IDS lists. - - @param arr @c DYNAMIC_ARRAY pointer to storage for servers id - @param f @c IO_CACHE pointer to the source file - - @retval 0 All OK - @retval non-zero An error -*/ - -int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f) -{ - int ret= 0; - char buf[16 * (sizeof(long)*4 + 1)]; // static buffer to use most of times - char *buf_act= buf; // actual buffer can be dynamic if static is short - char *token, *last; - uint num_items; // number of items of `arr' - size_t read_size; - DBUG_ENTER("init_dynarray_intvar_from_file"); - - if ((read_size= my_b_gets(f, buf_act, sizeof(buf))) == 0) - { - DBUG_RETURN(0); // no line in master.info - } - if (read_size + 1 == sizeof(buf) && buf[sizeof(buf) - 2] != '\n') - { - /* - short read happend; allocate sufficient memory and make the 2nd read - */ - char buf_work[(sizeof(long)*3 + 1)*16]; - memcpy(buf_work, buf, sizeof(buf_work)); - num_items= atoi(strtok_r(buf_work, " ", &last)); - size_t snd_size; - /* - max size lower bound approximate estimation bases on the formula: - (the items number + items themselves) * - (decimal size + space) - 1 + `\n' + '\0' - */ - size_t max_size= (1 + num_items) * (sizeof(long)*3 + 1) + 1; - buf_act= (char*) my_malloc(key_memory_Rpl_info_file_buffer, max_size, - MYF(MY_WME)); - memcpy(buf_act, buf, read_size); - snd_size= my_b_gets(f, buf_act + read_size, max_size - read_size); - if (snd_size == 0 || - ((snd_size + 1 == max_size - read_size) && buf_act[max_size - 2] != '\n')) - { - /* - failure to make the 2nd read or short read again - */ - ret= 1; - goto err; - } - } - token= strtok_r(buf_act, " ", &last); - if (token == NULL) - { - ret= 1; - goto err; - } - num_items= atoi(token); - for (uint i=0; i < num_items; i++) - { - token= strtok_r(NULL, " ", &last); - if (token == NULL) - { - ret= 1; - goto err; - } - else - { - ulong val= atol(token); - insert_dynamic(arr, (uchar *) &val); - } - } -err: - if (buf_act != buf) - my_free(buf_act); - DBUG_RETURN(ret); -} - -#ifdef HAVE_REPLICATION /* Check if the error is caused by network. @@ -1687,6 +1520,7 @@ static int get_master_version_and_clock(MYSQL* mysql, Master_info* mi) MYSQL_ROW master_row; uint full_version= mysql_get_server_version(mysql); uint version= full_version/ 10000; + uint32_t heartbeat_period= mi->master_heartbeat_period; DBUG_ENTER("get_master_version_and_clock"); /* @@ -2069,15 +1903,13 @@ when it try to get the value of TIME_ZONE global variable from master."; } } - if (mi->heartbeat_period != 0.0) + if (heartbeat_period) { - const char query_format[]= "SET @master_heartbeat_period= %llu"; - char query[sizeof(query_format) + 32]; - /* - the period is an ulonglong of nano-secs. - */ - my_snprintf(query, sizeof(query), query_format, - (ulonglong) (mi->heartbeat_period*1000000000UL)); + // The user variable is in nanoseconds + static const char query_format[]= + "SET @master_heartbeat_period= %" PRIu32 "000""000"; + char query[sizeof(query_format) + Int_IO_CACHE::BUF_SIZE]; + my_snprintf(query, sizeof(query), query_format, heartbeat_period); DBUG_EXECUTE_IF("simulate_slave_heartbeat_network_error", { static ulong dbug_count= 0; @@ -2988,16 +2820,18 @@ void store_master_info(THD *thd, Master_info *mi, TABLE *table, store_string_or_null(field++, mi->rli.until_log_name); (*field++)->store((ulonglong) mi->rli.until_log_pos, true); + (*field++)->store(mi->master_ssl ? #ifdef HAVE_OPENSSL - (*field++)->store(mi->ssl ? &msg_yes : &msg_no, &my_charset_bin); + &msg_yes #else - (*field++)->store(mi->ssl ? &msg_ignored: &msg_no, &my_charset_bin); + &msg_ignored #endif - store_string_or_null(field++, mi->ssl_ca); - store_string_or_null(field++, mi->ssl_capath); - store_string_or_null(field++, mi->ssl_cert); - store_string_or_null(field++, mi->ssl_cipher); - store_string_or_null(field++, mi->ssl_key); + : &msg_no, &my_charset_bin); + store_string_or_null(field++, mi->master_ssl_ca); + store_string_or_null(field++, mi->master_ssl_capath); + store_string_or_null(field++, mi->master_ssl_cert); + store_string_or_null(field++, mi->master_ssl_cipher); + store_string_or_null(field++, mi->master_ssl_key); /* Seconds_Behind_Master: if SQL thread is running and I/O thread is @@ -3049,7 +2883,7 @@ void store_master_info(THD *thd, Master_info *mi, TABLE *table, else (*field++)->set_null(); - (*field++)->store(mi->ssl_verify_server_cert? &msg_yes : &msg_no, + (*field++)->store(mi->master_ssl_verify_server_cert? &msg_yes : &msg_no, &my_charset_bin); // Last_IO_Errno @@ -3066,9 +2900,9 @@ void store_master_info(THD *thd, Master_info *mi, TABLE *table, (*field++)->store((uint32) mi->master_id, true); // SQL_Delay // Master_Ssl_Crl - store_string_or_null(field++, mi->ssl_crl); + store_string_or_null(field++, mi->master_ssl_crl); // Master_Ssl_Crlpath - store_string_or_null(field++, mi->ssl_crlpath); + store_string_or_null(field++, mi->master_ssl_crlpath); // Using_Gtid store_string_or_null(field++, mi->using_gtid_astext(mi->using_gtid)); // Gtid_IO_Pos @@ -3114,7 +2948,7 @@ void store_master_info(THD *thd, Master_info *mi, TABLE *table, (*field++)->store((ulonglong) mi->rli.max_relay_log_size, true); (*field++)->store(mi->rli.executed_entries, true); (*field++)->store((uint) mi->received_heartbeats, true); - (*field++)->store((double) mi->heartbeat_period); + (*field++)->store(mi->master_heartbeat_period / 1000.0); (*field++)->store(gtid_pos->ptr(), gtid_pos->length(), &my_charset_bin); /* @@ -3330,7 +3164,7 @@ static int request_dump(THD *thd, MYSQL* mysql, Master_info* mi, else sql_print_error("Error on COM_BINLOG_DUMP: %d %s, will retry in %d secs", mysql_errno(mysql), mysql_error(mysql), - mi->connect_retry); + static_cast(mi->master_connect_retry)); DBUG_RETURN(1); } @@ -6982,10 +6816,11 @@ static int connect_to_master(THD* thd, MYSQL* mysql, Master_info* mi, suppress_warnings= 0; mi->report(ERROR_LEVEL, last_errno, NULL, "error %s to master '%s@%s:%d'" - " - retry-time: %d maximum-retries: %lu message: %s", + " - retry-time: %d maximum-retries: %" PRIu64 " message: %s", (reconnect ? "reconnecting" : "connecting"), mi->user, mi->host, mi->port, - mi->connect_retry, mi->retry_count, + static_cast(mi->master_connect_retry), + static_cast(mi->master_retry_count), mysql_error(mysql)); } if ((++(mi->connects_tried) == mi->retry_count) && mi->retry_count) diff --git a/sql/slave.h b/sql/slave.h index 0e57b467cb7a5..01193632b06f2 100644 --- a/sql/slave.h +++ b/sql/slave.h @@ -32,16 +32,6 @@ @file */ -/** - Some of defines are need in parser even though replication is not - compiled in (embedded). -*/ - -/** - The maximum is defined as (ULONG_MAX/1000) with 4 bytes ulong -*/ -#define SLAVE_MAX_HEARTBEAT_PERIOD 4294967 - #ifdef HAVE_REPLICATION #include "log.h" @@ -64,12 +54,6 @@ class Master_info_index; struct rpl_group_info; struct rpl_parallel_thread; -int init_intvar_from_file(int* var, IO_CACHE* f, int default_val); -int init_strvar_from_file(char *var, int max_size, IO_CACHE *f, - const char *default_val); -int init_floatvar_from_file(float* var, IO_CACHE* f, float default_val); -int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f); - /***************************************************************************** MySQL Replication @@ -129,7 +113,7 @@ int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f); (so that you have to update the .index file). */ -extern ulong master_retry_count; +extern uint64_t master_retry_count; extern MY_BITMAP slave_error_mask; extern char slave_skip_error_names[]; extern bool use_slave_mask; @@ -265,12 +249,6 @@ int apply_event_and_update_pos(Log_event* ev, THD* thd, int apply_event_and_update_pos_for_parallel(Log_event* ev, THD* thd, struct rpl_group_info *rgi); -int init_intvar_from_file(int* var, IO_CACHE* f, int default_val); -int init_floatvar_from_file(float* var, IO_CACHE* f, float default_val); -int init_strvar_from_file(char *var, int max_size, IO_CACHE *f, - const char *default_val); -int init_dynarray_intvar_from_file(DYNAMIC_ARRAY* arr, IO_CACHE* f); - pthread_handler_t handle_slave_io(void *arg); void slave_output_error_info(rpl_group_info *rgi, THD *thd); pthread_handler_t handle_slave_sql(void *arg); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index bbf4ac009db12..707da16ff1951 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -21,6 +21,7 @@ #ifndef SQL_LEX_INCLUDED #define SQL_LEX_INCLUDED +#include #include "lex_ident_sys.h" #include "violite.h" /* SSL_type */ #include "sql_trigger.h" @@ -40,6 +41,7 @@ #include "table.h" #include "sql_class.h" // enum enum_column_usage #include "select_handler.h" +#include "rpl_master_info_file.h" // Master_info_file /* Used for flags of nesting constructs */ #define SELECT_NESTING_MAP_SIZE 64 @@ -358,8 +360,6 @@ struct LEX_MASTER_INFO DYNAMIC_ARRAY repl_do_domain_ids; DYNAMIC_ARRAY repl_ignore_domain_ids; const char *host, *user, *password, *log_file_name; - const char *ssl_key, *ssl_cert, *ssl_ca, *ssl_capath, *ssl_cipher; - const char *ssl_crl, *ssl_crlpath; const char *relay_log_name; LEX_CSTRING connection_name; /* Value in START SLAVE UNTIL master_gtid_pos=xxx */ @@ -367,9 +367,7 @@ struct LEX_MASTER_INFO ulonglong pos; ulong relay_log_pos; ulong server_id; - uint port, connect_retry; - ulong retry_count; - float heartbeat_period; + uint port; int sql_delay; bool is_demotion_opt; bool is_until_before_gtids; @@ -380,22 +378,35 @@ struct LEX_MASTER_INFO changed variable or if it should be left at old value */ enum {LEX_MI_UNCHANGED= 0, LEX_MI_DISABLE, LEX_MI_ENABLE} - ssl, ssl_verify_server_cert, heartbeat_opt, repl_ignore_server_ids_opt, + repl_ignore_server_ids_opt, repl_do_domain_ids_opt, repl_ignore_domain_ids_opt; - enum { - LEX_GTID_UNCHANGED, LEX_GTID_NO, LEX_GTID_CURRENT_POS, LEX_GTID_SLAVE_POS - } use_gtid_opt; + + /**TODO + Going through this struct means it must contain a repeated set of CHANGE + MASTER and START SLAVE variables that additionally knows which values are + not changing, not to mention support for `CHANGE MASTER ...= DEFAULT`. + This creates complexity and leads to inconsistency. + Instead, it is possible to track and apply CHANGE MASTER configs during + parsing (in `sql_yacc.yy`) without stashing them in a @ref LEX_MASTER_INFO. + But for now, lambdas in `sql_yacc.yy` demonstrates this concept while + keeping them deferred to the "post-processing" in change_master(). + */ + using mi_functor= std::function; + mi_functor connect_retry, heartbeat_period, ssl, + ssl_key, ssl_cert, ssl_ca, ssl_capath, ssl_cipher, ssl_crl, ssl_crlpath, + ssl_verify_server_cert, retry_count, use_gtid; void init() { - bzero(this, sizeof(*this)); + reset(false); + connection_name= null_clex_str; + show_all_slaves= false; my_init_dynamic_array(PSI_INSTRUMENT_ME, &repl_ignore_server_ids, sizeof(::server_id), 0, 16, MYF(0)); my_init_dynamic_array(PSI_INSTRUMENT_ME, &repl_do_domain_ids, sizeof(ulong), 0, 16, MYF(0)); my_init_dynamic_array(PSI_INSTRUMENT_ME, &repl_ignore_domain_ids, sizeof(ulong), 0, 16, MYF(0)); - sql_delay= -1; } void reset(bool is_change_master) { @@ -407,15 +418,24 @@ struct LEX_MASTER_INFO delete_dynamic(&repl_ignore_domain_ids); } - host= user= password= log_file_name= ssl_key= ssl_cert= ssl_ca= - ssl_capath= ssl_cipher= ssl_crl= ssl_crlpath= relay_log_name= NULL; - pos= relay_log_pos= server_id= retry_count= port= connect_retry= 0; - heartbeat_period= 0; - ssl= ssl_verify_server_cert= heartbeat_opt= - repl_ignore_server_ids_opt= repl_do_domain_ids_opt= - repl_ignore_domain_ids_opt= LEX_MI_UNCHANGED; + host= user= password= log_file_name= relay_log_name= NULL; + ssl_key= nullptr; + ssl_cert= nullptr; + ssl_ca= nullptr; + ssl_capath= nullptr; + ssl_cipher= nullptr; + ssl_crl= nullptr; + ssl_crlpath= nullptr; + pos= relay_log_pos= server_id= port= 0; + retry_count= nullptr; + connect_retry= nullptr; + heartbeat_period= nullptr; + ssl= nullptr; + ssl_verify_server_cert= nullptr; + repl_ignore_server_ids_opt= + repl_do_domain_ids_opt= repl_ignore_domain_ids_opt= LEX_MI_UNCHANGED; gtid_pos_str= null_clex_str; - use_gtid_opt= LEX_GTID_UNCHANGED; + use_gtid= nullptr; sql_delay= -1; is_demotion_opt= 0; is_until_before_gtids= false; diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index e027571cc90f1..1a7cfb249ff49 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -3917,7 +3917,7 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) char saved_host[HOSTNAME_LENGTH + 1]; uint saved_port; char saved_log_name[FN_REFLEN]; - Master_info::enum_using_gtid saved_using_gtid; + enum_master_use_gtid saved_using_gtid; char master_info_file_tmp[FN_REFLEN]; char relay_log_info_file_tmp[FN_REFLEN]; my_off_t saved_log_pos; @@ -4058,17 +4058,15 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) if (lex_mi->port) mi->port = lex_mi->port; if (lex_mi->connect_retry) - mi->connect_retry = lex_mi->connect_retry; + lex_mi->connect_retry(mi); if (lex_mi->retry_count) { - mi->retry_count= lex_mi->retry_count; + lex_mi->retry_count(mi); + // also reset the counter in case `connects_tried > master_retry_count` mi->connects_tried= 0; } - if (lex_mi->heartbeat_opt != LEX_MASTER_INFO::LEX_MI_UNCHANGED) - mi->heartbeat_period = lex_mi->heartbeat_period; - else - mi->heartbeat_period= (float) MY_MIN(SLAVE_MAX_HEARTBEAT_PERIOD, - (slave_net_timeout/2.0)); + if (lex_mi->heartbeat_period) + lex_mi->heartbeat_period(mi); mi->received_heartbeats= 0; // counter lives until master is CHANGEd /* @@ -4095,30 +4093,28 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) &mi->ignore_server_ids); } - if (lex_mi->ssl != LEX_MASTER_INFO::LEX_MI_UNCHANGED) - mi->ssl= (lex_mi->ssl == LEX_MASTER_INFO::LEX_MI_ENABLE); + if (lex_mi->ssl) + lex_mi->ssl(mi); if (lex_mi->sql_delay != -1) mi->rli.set_sql_delay(lex_mi->sql_delay); - if (lex_mi->ssl_verify_server_cert != LEX_MASTER_INFO::LEX_MI_UNCHANGED) - mi->ssl_verify_server_cert= - (lex_mi->ssl_verify_server_cert == LEX_MASTER_INFO::LEX_MI_ENABLE); - + if (lex_mi->ssl_verify_server_cert) + lex_mi->ssl_verify_server_cert(mi); if (lex_mi->ssl_ca) - strmake_buf(mi->ssl_ca, lex_mi->ssl_ca); + lex_mi->ssl_ca(mi); if (lex_mi->ssl_capath) - strmake_buf(mi->ssl_capath, lex_mi->ssl_capath); + lex_mi->ssl_capath(mi); if (lex_mi->ssl_cert) - strmake_buf(mi->ssl_cert, lex_mi->ssl_cert); + lex_mi->ssl_cert(mi); if (lex_mi->ssl_cipher) - strmake_buf(mi->ssl_cipher, lex_mi->ssl_cipher); + lex_mi->ssl_cipher(mi); if (lex_mi->ssl_key) - strmake_buf(mi->ssl_key, lex_mi->ssl_key); + lex_mi->ssl_key(mi); if (lex_mi->ssl_crl) - strmake_buf(mi->ssl_crl, lex_mi->ssl_crl); + lex_mi->ssl_crl(mi); if (lex_mi->ssl_crlpath) - strmake_buf(mi->ssl_crlpath, lex_mi->ssl_crlpath); + lex_mi->ssl_crlpath(mi); #ifndef HAVE_OPENSSL if (lex_mi->ssl || lex_mi->ssl_ca || lex_mi->ssl_capath || @@ -4144,22 +4140,17 @@ bool change_master(THD* thd, Master_info* mi, bool *master_info_added) mi->rli.group_relay_log_pos= mi->rli.event_relay_log_pos= lex_mi->relay_log_pos; } - if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_SLAVE_POS) - mi->using_gtid= Master_info::USE_GTID_SLAVE_POS; - else if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_CURRENT_POS) - mi->using_gtid= Master_info::USE_GTID_CURRENT_POS; - else if (lex_mi->use_gtid_opt == LEX_MASTER_INFO::LEX_GTID_NO || + if (lex_mi->use_gtid) + lex_mi->use_gtid(mi); + else if ( lex_mi->log_file_name || lex_mi->pos || lex_mi->relay_log_name || lex_mi->relay_log_pos) { - if (lex_mi->use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_NO) - { push_warning_printf( thd, Sql_condition::WARN_LEVEL_NOTE, WARN_OPTION_CHANGING, ER_THD(thd, WARN_OPTION_CHANGING), "CHANGE MASTER TO", "Using_Gtid", mi->using_gtid_astext(mi->using_gtid), mi->using_gtid_astext(Master_info::USE_GTID_NO)); - } mi->using_gtid= Master_info::USE_GTID_NO; } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 5aed54f243c7d..f654630589d04 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -193,6 +193,13 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() %} + +%code requires +{ +// Master_info_file, enum_master_use_gtid, std::optional +#include "rpl_master_info_file.h" +} + %union { int num; ulong ulong_num; @@ -206,6 +213,18 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() Longlong_hybrid longlong_hybrid_number= Longlong_hybrid(0, false); /* structs */ + /** + This is a stand-in for @ref std::optional, + which is not trivially `union`-safe. + */ + struct + { + bool has_value; uint64_t value; +#if __cplusplus >= 201703L || _MSVC_LANG >= 201703L + template operator std::optional() + { return has_value ? std::optional(static_cast(value)) : std::nullopt; } +#endif + } optional_uint; LEX_CSTRING lex_str; Lex_comment_st lex_comment; Lex_ident_cli_st kwd; @@ -308,6 +327,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() st_trg_execution_order trg_execution_order; /* enums */ + trilean tril; enum enum_sp_suid_behaviour sp_suid; enum enum_sp_aggregate_type sp_aggregate_type; enum enum_view_suid view_suid; @@ -340,6 +360,7 @@ void _CONCAT_UNDERSCORED(turn_parser_debug_on,yyparse)() enum Column_definition::enum_column_versioning vers_column_versioning; enum plsql_cursor_attr_t plsql_cursor_attr; enum Alter_info::enum_alter_table_algorithm alter_table_algo_val; + enum_master_use_gtid master_use_gtid; privilege_t privilege; struct { @@ -1425,6 +1446,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type field_length_str opt_compression_method + path_or_default %type text_string hex_or_bin_String opt_gconcat_separator @@ -1589,6 +1611,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %type NUM_literal + num_or_default %type text_literal @@ -2012,6 +2035,11 @@ rule: sp_package_function_body sp_package_procedure_body +%type uint32_or_default +%type uint64_or_default +%type bool_or_default +%type master_use_gtid_enum + %ifdef MARIADB %type sp_tail_standalone @@ -2316,13 +2344,17 @@ master_def: { Lex->mi.port = $3; } - | MASTER_CONNECT_RETRY_SYM '=' ulong_num + | MASTER_CONNECT_RETRY_SYM '=' uint32_or_default { - Lex->mi.connect_retry = $3; + Lex->mi.connect_retry= [optional= + static_cast>($3)](Master_info_file *mi) + { mi->master_connect_retry= std::move(optional); }; } - | MASTER_RETRY_COUNT_SYM '=' ulong_num + | MASTER_RETRY_COUNT_SYM '=' uint64_or_default { - Lex->mi.retry_count = $3; + Lex->mi.retry_count= [optional= + static_cast>($3)](Master_info_file *mi) + { mi->master_retry_count= std::move(optional); }; } | MASTER_DELAY_SYM '=' ulong_num { @@ -2334,72 +2366,80 @@ master_def: else Lex->mi.sql_delay = $3; } - | MASTER_SSL_SYM '=' ulong_num + | MASTER_SSL_SYM '=' bool_or_default { - Lex->mi.ssl= $3 ? - LEX_MASTER_INFO::LEX_MI_ENABLE : LEX_MASTER_INFO::LEX_MI_DISABLE; + Lex->mi.ssl= [trilean= $3](Master_info_file *mi) + { mi->master_ssl= trilean; }; } - | MASTER_SSL_CA_SYM '=' TEXT_STRING_sys + | MASTER_SSL_CA_SYM '=' path_or_default { - Lex->mi.ssl_ca= $3.str; + Lex->mi.ssl_ca= [path= $3](Master_info_file *mi) + { mi->master_ssl_ca= path; }; } - | MASTER_SSL_CAPATH_SYM '=' TEXT_STRING_sys + | MASTER_SSL_CAPATH_SYM '=' path_or_default { - Lex->mi.ssl_capath= $3.str; + Lex->mi.ssl_capath= [path= $3](Master_info_file *mi) + { mi->master_ssl_capath= path; }; } - | MASTER_SSL_CERT_SYM '=' TEXT_STRING_sys + | MASTER_SSL_CERT_SYM '=' path_or_default { - Lex->mi.ssl_cert= $3.str; + Lex->mi.ssl_cert= [path= $3](Master_info_file *mi) + { mi->master_ssl_cert= path; }; } - | MASTER_SSL_CIPHER_SYM '=' TEXT_STRING_sys + | MASTER_SSL_CIPHER_SYM '=' path_or_default { - Lex->mi.ssl_cipher= $3.str; + Lex->mi.ssl_cipher= [path= $3](Master_info_file *mi) + { mi->master_ssl_cipher= path; }; } - | MASTER_SSL_KEY_SYM '=' TEXT_STRING_sys + | MASTER_SSL_KEY_SYM '=' path_or_default { - Lex->mi.ssl_key= $3.str; + Lex->mi.ssl_key= [path= $3](Master_info_file *mi) + { mi->master_ssl_key= path; }; } - | MASTER_SSL_VERIFY_SERVER_CERT_SYM '=' ulong_num + | MASTER_SSL_VERIFY_SERVER_CERT_SYM '=' bool_or_default { - Lex->mi.ssl_verify_server_cert= $3 ? - LEX_MASTER_INFO::LEX_MI_ENABLE : LEX_MASTER_INFO::LEX_MI_DISABLE; + Lex->mi.ssl_verify_server_cert= [trilean= $3](Master_info_file *mi) + { mi->master_ssl_verify_server_cert= trilean; }; } - | MASTER_SSL_CRL_SYM '=' TEXT_STRING_sys + | MASTER_SSL_CRL_SYM '=' path_or_default { - Lex->mi.ssl_crl= $3.str; + Lex->mi.ssl_crl= [path= $3](Master_info_file *mi) + { mi->master_ssl_crl= path; }; } - | MASTER_SSL_CRLPATH_SYM '=' TEXT_STRING_sys + | MASTER_SSL_CRLPATH_SYM '=' path_or_default { - Lex->mi.ssl_crlpath= $3.str; + Lex->mi.ssl_crlpath= [path= $3](Master_info_file *mi) + { mi->master_ssl_crlpath= path; }; } - | MASTER_HEARTBEAT_PERIOD_SYM '=' NUM_literal + | MASTER_HEARTBEAT_PERIOD_SYM '=' num_or_default { - Lex->mi.heartbeat_period= (float) $3->val_real(); - if (unlikely(Lex->mi.heartbeat_period > - SLAVE_MAX_HEARTBEAT_PERIOD) || - unlikely(Lex->mi.heartbeat_period < 0.0)) - my_yyabort_error((ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE, MYF(0), - SLAVE_MAX_HEARTBEAT_PERIOD)); - - if (unlikely(Lex->mi.heartbeat_period > slave_net_timeout)) - { - push_warning(thd, Sql_condition::WARN_LEVEL_WARN, - ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX, - ER_THD(thd, ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX)); - } - if (unlikely(Lex->mi.heartbeat_period < 0.001)) + if ($3) { - if (unlikely(Lex->mi.heartbeat_period != 0.0)) - { + uint32_t milliseconds; + bool overprecise; + auto decimal_buf= my_decimal(), + *decimal= $3->val_decimal(&decimal_buf); + DBUG_ASSERT(decimal); + if (Master_info_file::Heartbeat_period_value::from_decimal( + milliseconds, *decimal, overprecise + )) + my_yyabort_error((ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE, MYF(0), + Master_info_file::Heartbeat_period_value::MAX)); + if (unlikely(milliseconds > slave_net_timeout*1000ULL)) push_warning(thd, Sql_condition::WARN_LEVEL_WARN, - ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN, - ER_THD(thd, ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN)); - Lex->mi.heartbeat_period= 0.0; - } - Lex->mi.heartbeat_opt= LEX_MASTER_INFO::LEX_MI_DISABLE; + ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX, + ER_THD(thd, ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX)); + else if (unlikely(!milliseconds && overprecise)) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN, + ER_THD(thd, ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN)); + Lex->mi.heartbeat_period= [milliseconds](Master_info_file *mi) + { mi->master_heartbeat_period= milliseconds; }; } - Lex->mi.heartbeat_opt= LEX_MASTER_INFO::LEX_MI_ENABLE; + else + Lex->mi.heartbeat_period= [](Master_info_file *mi) + { mi->master_heartbeat_period.set_default(); }; } | IGNORE_SERVER_IDS_SYM '=' '(' ignore_server_id_list ')' { @@ -2417,6 +2457,32 @@ master_def: master_file_def ; +uint32_or_default: + ulong_num { $$= {true, $1}; } + | DEFAULT { $$= {false, 0}; } + ; +uint64_or_default: + ulonglong_num { $$= {true, $1}; } + | DEFAULT { $$= {false, 0}; } + ; +path_or_default: + TEXT_STRING_sys { $$= $1.str; DBUG_ASSERT($$); } + | DEFAULT { $$= nullptr; } + ; +bool_or_default: + bool { $$= $1 ? trilean::YES : trilean::NO; } + | DEFAULT { $$= trilean::DEFAULT; }; +master_use_gtid_enum: + NO_SYM { $$= enum_master_use_gtid::NO; } + | CURRENT_POS_SYM { $$= enum_master_use_gtid::CURRENT_POS; } + | SLAVE_POS_SYM { $$= enum_master_use_gtid::SLAVE_POS; } + | DEFAULT { $$= enum_master_use_gtid::DEFAULT; } + ; +num_or_default: + NUM_literal { DBUG_ASSERT($$); } + | DEFAULT { $$= nullptr; } + ; + ignore_server_id_list: /* Empty */ | ignore_server_id @@ -2486,23 +2552,12 @@ master_file_def: /* Adjust if < BIN_LOG_HEADER_SIZE (same comment as Lex->mi.pos) */ Lex->mi.relay_log_pos= MY_MAX(BIN_LOG_HEADER_SIZE, Lex->mi.relay_log_pos); } - | MASTER_USE_GTID_SYM '=' CURRENT_POS_SYM - { - if (unlikely(Lex->mi.use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_UNCHANGED)) - my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "MASTER_use_gtid")); - Lex->mi.use_gtid_opt= LEX_MASTER_INFO::LEX_GTID_CURRENT_POS; - } - | MASTER_USE_GTID_SYM '=' SLAVE_POS_SYM - { - if (unlikely(Lex->mi.use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_UNCHANGED)) - my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "MASTER_use_gtid")); - Lex->mi.use_gtid_opt= LEX_MASTER_INFO::LEX_GTID_SLAVE_POS; - } - | MASTER_USE_GTID_SYM '=' NO_SYM + | MASTER_USE_GTID_SYM '=' master_use_gtid_enum { - if (unlikely(Lex->mi.use_gtid_opt != LEX_MASTER_INFO::LEX_GTID_UNCHANGED)) + if (Lex->mi.use_gtid) my_yyabort_error((ER_DUP_ARGUMENT, MYF(0), "MASTER_use_gtid")); - Lex->mi.use_gtid_opt= LEX_MASTER_INFO::LEX_GTID_NO; + Lex->mi.use_gtid= [use_gtid= $3](Master_info_file *mi) + { mi->master_use_gtid= use_gtid; }; } | MASTER_DEMOTE_TO_SLAVE_SYM '=' bool { diff --git a/storage/perfschema/table_replication_connection_configuration.cc b/storage/perfschema/table_replication_connection_configuration.cc index 9d49de76c4279..b7e8a3ea18ea9 100644 --- a/storage/perfschema/table_replication_connection_configuration.cc +++ b/storage/perfschema/table_replication_connection_configuration.cc @@ -184,6 +184,7 @@ void table_replication_connection_configuration::make_row(Master_info *mi) { DBUG_ENTER("table_replication_connection_configuration::make_row"); char * temp_store; + const char *const_temp_store; bool error= false; m_row_exists= false; @@ -214,50 +215,52 @@ void table_replication_connection_configuration::make_row(Master_info *mi) else m_row.using_gtid= PS_USE_GTID_SLAVE_POS; -#ifdef HAVE_OPENSSL - m_row.ssl_allowed= mi->ssl? PS_SSL_ALLOWED_YES:PS_SSL_ALLOWED_NO; -#else - m_row.ssl_allowed= mi->ssl? PS_SSL_ALLOWED_IGNORED:PS_SSL_ALLOWED_NO; -#endif + m_row.ssl_allowed= mi->master_ssl ? + #ifdef HAVE_OPENSSL + PS_SSL_ALLOWED_YES + #else + PS_SSL_ALLOWED_IGNORED + #endif + : PS_SSL_ALLOWED_NO; - temp_store= (char*)mi->ssl_ca; - m_row.ssl_ca_file_length= static_cast(strlen(temp_store)); - memcpy(m_row.ssl_ca_file, temp_store, m_row.ssl_ca_file_length); + const_temp_store= mi->master_ssl_ca; + m_row.ssl_ca_file_length= static_cast(strlen(const_temp_store)); + memcpy(m_row.ssl_ca_file, const_temp_store, m_row.ssl_ca_file_length); - temp_store= (char*)mi->ssl_capath; - m_row.ssl_ca_path_length= static_cast(strlen(temp_store)); - memcpy(m_row.ssl_ca_path, temp_store, m_row.ssl_ca_path_length); + const_temp_store= mi->master_ssl_capath; + m_row.ssl_ca_path_length= static_cast(strlen(const_temp_store)); + memcpy(m_row.ssl_ca_path, const_temp_store, m_row.ssl_ca_path_length); - temp_store= (char*)mi->ssl_cert; - m_row.ssl_certificate_length= static_cast(strlen(temp_store)); - memcpy(m_row.ssl_certificate, temp_store, m_row.ssl_certificate_length); + const_temp_store= mi->master_ssl_cert; + m_row.ssl_certificate_length= static_cast(strlen(const_temp_store)); + memcpy(m_row.ssl_certificate, const_temp_store, m_row.ssl_certificate_length); - temp_store= (char*)mi->ssl_cipher; - m_row.ssl_cipher_length= static_cast(strlen(temp_store)); - memcpy(m_row.ssl_cipher, temp_store, m_row.ssl_cipher_length); + const_temp_store= mi->master_ssl_cipher; + m_row.ssl_cipher_length= static_cast(strlen(const_temp_store)); + memcpy(m_row.ssl_cipher, const_temp_store, m_row.ssl_cipher_length); - temp_store= (char*)mi->ssl_key; - m_row.ssl_key_length= static_cast(strlen(temp_store)); - memcpy(m_row.ssl_key, temp_store, m_row.ssl_key_length); + const_temp_store= mi->master_ssl_key; + m_row.ssl_key_length= static_cast(strlen(const_temp_store)); + memcpy(m_row.ssl_key, const_temp_store, m_row.ssl_key_length); - if (mi->ssl_verify_server_cert) + if (mi->master_ssl_verify_server_cert) m_row.ssl_verify_server_certificate= PS_RPL_YES; else m_row.ssl_verify_server_certificate= PS_RPL_NO; - temp_store= (char*)mi->ssl_crl; - m_row.ssl_crl_file_length= static_cast(strlen(temp_store)); - memcpy(m_row.ssl_crl_file, temp_store, m_row.ssl_crl_file_length); + const_temp_store= mi->master_ssl_crl; + m_row.ssl_crl_file_length= static_cast(strlen(const_temp_store)); + memcpy(m_row.ssl_crl_file, const_temp_store, m_row.ssl_crl_file_length); - temp_store= (char*)mi->ssl_crlpath; - m_row.ssl_crl_path_length= static_cast(strlen(temp_store)); - memcpy(m_row.ssl_crl_path, temp_store, m_row.ssl_crl_path_length); + const_temp_store= mi->master_ssl_crlpath; + m_row.ssl_crl_path_length= static_cast(strlen(const_temp_store)); + memcpy(m_row.ssl_crl_path, const_temp_store, m_row.ssl_crl_path_length); m_row.connection_retry_interval= (unsigned int) mi->connect_retry; m_row.connection_retry_count= mi->retry_count; - m_row.heartbeat_interval= (double)mi->heartbeat_period; + m_row.heartbeat_interval= mi->master_heartbeat_period / 1000.0; m_row.ignore_server_ids= convert_array_to_str(&mi->ignore_server_ids); if (m_row.ignore_server_ids == NULL) diff --git a/storage/perfschema/table_replication_connection_configuration.h b/storage/perfschema/table_replication_connection_configuration.h index c9ff121be2bf7..f33f37ddeaaae 100644 --- a/storage/perfschema/table_replication_connection_configuration.h +++ b/storage/perfschema/table_replication_connection_configuration.h @@ -94,7 +94,7 @@ struct st_row_connect_config { char ssl_crl_path[FN_REFLEN]; uint ssl_crl_path_length; uint connection_retry_interval; - ulong connection_retry_count; + ulonglong connection_retry_count; double heartbeat_interval; char *ignore_server_ids; uint ignore_server_ids_length;