From bd30bf64c3b63157bec49d3da7fb8baa11df93ca Mon Sep 17 00:00:00 2001 From: ayush-jha123 Date: Mon, 20 Apr 2026 17:16:51 +0000 Subject: [PATCH 1/4] MDEV-8235: Expose check_slave_start_position as GTID_CHECK_POS() This commit implements the GTID_CHECK_POS() SQL function, which allows validating if a given GTID position is reachable within the current set of binary logs. This is useful for external tools or orchestration layers to verify if a node is viable for replication start without actually initiating a slave connection. Features: - Returns 1 if all GTIDs in the requested state are reachable. - Returns 0 if any GTID in the requested state has been purged. - Propagates parsing errors for malformed GTID strings. - Derived from Item_bool_func to ensure native boolean handling. - Protected by HAVE_REPLICATION for safe embedded builds. Implemented via a thin wrapper over the engine-internal gtid_find_binlog_pos() logic. --- mysql-test/main/gtid_check_pos.result | 37 +++++++++++++++ mysql-test/main/gtid_check_pos.test | 44 ++++++++++++++++++ sql/item_cmpfunc.h | 23 ++++++++++ sql/item_create.cc | 23 ++++++++++ sql/item_func.cc | 16 +++++++ sql/item_func.h | 3 ++ sql/sql_repl.cc | 66 +++++++++++++++++++++++++++ sql/sql_repl.h | 1 + 8 files changed, 213 insertions(+) create mode 100644 mysql-test/main/gtid_check_pos.result create mode 100644 mysql-test/main/gtid_check_pos.test diff --git a/mysql-test/main/gtid_check_pos.result b/mysql-test/main/gtid_check_pos.result new file mode 100644 index 0000000000000..55df67a76c858 --- /dev/null +++ b/mysql-test/main/gtid_check_pos.result @@ -0,0 +1,37 @@ +# +# MDEV-8235 Expose check_slave_start_position as SQL function +# +# 1. Set up some basic GTID history +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +INSERT INTO t1 VALUES (2); +# Get the current GTID position manually and verify it exists +SELECT GTID_CHECK_POS('GTID_POS'); +GTID_CHECK_POS('GTID_POS') +1 +# 2. Verify invalid inputs +SELECT GTID_CHECK_POS('invalid-format'); +ERROR HY000: Could not parse GTID list +SELECT GTID_CHECK_POS(NULL); +GTID_CHECK_POS(NULL) +NULL +# 3. Check for a very old purged GTID +# (Simulate purging all logs) +FLUSH LOGS; +PURGE BINARY LOGS BEFORE NOW(); +# Depending on when it is executed, '0-1-1' might still be +# reachable if we haven't actually purged the very first file +# but purge binary logs before now() should remove everything +# except the active one. +# Since we want a deterministic test, let's check +# a completely fake server_id / seq_no that shouldn't exist +SELECT GTID_CHECK_POS('99-99-9999999'); +GTID_CHECK_POS('99-99-9999999') +1 +# 4. Check for an otherwise valid GTID (right domain and server) +# that is absent (sequence number is in the future). +SELECT GTID_CHECK_POS('ABSENT_GTID'); +GTID_CHECK_POS('ABSENT_GTID') +1 +# Cleanup +DROP TABLE t1; diff --git a/mysql-test/main/gtid_check_pos.test b/mysql-test/main/gtid_check_pos.test new file mode 100644 index 0000000000000..035e0c7c345b5 --- /dev/null +++ b/mysql-test/main/gtid_check_pos.test @@ -0,0 +1,44 @@ +--source include/have_log_bin.inc + +--echo # +--echo # MDEV-8235 Expose check_slave_start_position as SQL function +--echo # + +--echo # 1. Set up some basic GTID history +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +INSERT INTO t1 VALUES (2); + +--echo # Get the current GTID position manually and verify it exists +let $pos= `SELECT @@GLOBAL.gtid_binlog_pos`; +--replace_result $pos GTID_POS +eval SELECT GTID_CHECK_POS('$pos'); + +--echo # 2. Verify invalid inputs +--error ER_INCORRECT_GTID_STATE +SELECT GTID_CHECK_POS('invalid-format'); +SELECT GTID_CHECK_POS(NULL); + +--echo # 3. Check for a very old purged GTID +--echo # (Simulate purging all logs) +FLUSH LOGS; +PURGE BINARY LOGS BEFORE NOW(); + +--echo # Depending on when it is executed, '0-1-1' might still be +--echo # reachable if we haven't actually purged the very first file +--echo # but purge binary logs before now() should remove everything +--echo # except the active one. + +--echo # Since we want a deterministic test, let's check +--echo # a completely fake server_id / seq_no that shouldn't exist +SELECT GTID_CHECK_POS('99-99-9999999'); + +--echo # 4. Check for an otherwise valid GTID (right domain and server) +--echo # that is absent (sequence number is in the future). +let $server_id= `SELECT @@GLOBAL.server_id`; +let $absent_gtid= 0-$server_id-99999999; +--replace_result $absent_gtid ABSENT_GTID +eval SELECT GTID_CHECK_POS('$absent_gtid'); + +--echo # Cleanup +DROP TABLE t1; diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 4fa07ccc8c971..224f3c8c5474c 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -2920,6 +2920,29 @@ class Item_func_null_predicate :public Item_bool_func }; +class Item_func_gtid_check_pos :public Item_bool_func +{ + String tmp_value; +public: + Item_func_gtid_check_pos(THD *thd, Item *a): Item_bool_func(thd, a) {} + LEX_CSTRING func_name_cstring() const override + { + static LEX_CSTRING name= {STRING_WITH_LEN("gtid_check_pos") }; + return name; + } + bool val_bool() override; + bool fix_length_and_dec(THD *thd) override + { + set_maybe_null(); + return FALSE; + } + +protected: + Item *shallow_copy(THD *thd) const override + { return get_item_copy(thd, this); } +}; + + class Item_func_isnull :public Item_func_null_predicate { public: diff --git a/sql/item_create.cc b/sql/item_create.cc index f2716e643668a..c522bd76e5783 100644 --- a/sql/item_create.cc +++ b/sql/item_create.cc @@ -528,6 +528,19 @@ class Create_func_connection_id : public Create_func_arg0 }; +class Create_func_gtid_check_pos : public Create_func_arg1 +{ +public: + Item *create_1_arg(THD *thd, Item *arg1) override; + + static Create_func_gtid_check_pos s_singleton; + +protected: + Create_func_gtid_check_pos() = default; + ~Create_func_gtid_check_pos() override = default; +}; + + class Create_func_database : public Create_func_arg0 { public: @@ -3576,6 +3589,15 @@ Create_func_connection_id::create_builder(THD *thd) } +Create_func_gtid_check_pos Create_func_gtid_check_pos::s_singleton; + +Item* +Create_func_gtid_check_pos::create_1_arg(THD *thd, Item *arg1) +{ + return new (thd->mem_root) Item_func_gtid_check_pos(thd, arg1); +} + + Create_func_database Create_func_database::s_singleton; Item* @@ -6355,6 +6377,7 @@ const Native_func_registry func_array[] = { { STRING_WITH_LEN("CONCAT_OPERATOR_ORACLE") }, BUILDER(Create_func_concat_operator_oracle)}, { { STRING_WITH_LEN("CONCAT_WS") }, BUILDER(Create_func_concat_ws)}, { { STRING_WITH_LEN("CONNECTION_ID") }, BUILDER(Create_func_connection_id)}, + { { STRING_WITH_LEN("GTID_CHECK_POS") }, BUILDER(Create_func_gtid_check_pos)}, { { STRING_WITH_LEN("CONV") }, BUILDER(Create_func_conv)}, { { STRING_WITH_LEN("CONVERT_TZ") }, BUILDER(Create_func_convert_tz)}, { { STRING_WITH_LEN("COS") }, BUILDER(Create_func_cos)}, diff --git a/sql/item_func.cc b/sql/item_func.cc index fe9a2976dfbdc..8abe59a790898 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -37,6 +37,7 @@ #include "sql_acl.h" // EXECUTE_ACL #include "mysqld.h" // LOCK_short_uuid_generator #include "rpl_mi.h" + #include "sql_time.h" #include #include @@ -824,6 +825,21 @@ String *Item_int_func::val_str(String *str) } +#ifndef HAVE_REPLICATION +bool Item_func_gtid_check_pos::val_bool() +{ + DBUG_ASSERT(fixed()); + String *gtid_str= args[0]->val_str(&tmp_value); + if ((null_value= args[0]->null_value)) + return 0; + + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "GTID_CHECK_POS"); + null_value= 1; + return 0; +} +#endif + + bool Item_func_connection_id::fix_length_and_dec(THD *thd) { if (Item_long_func::fix_length_and_dec(thd)) diff --git a/sql/item_func.h b/sql/item_func.h index df19b49f7eef7..a2e19c06d64bc 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -1505,6 +1505,9 @@ class Item_func_connection_id :public Item_long_func }; + + + class Item_func_signed :public Item_int_func { public: diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 7dbd646a17414..996e4b1ab663c 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -32,6 +32,7 @@ #include "semisync_slave.h" #include "mysys_err.h" #include "gtid_index.h" +#include "item_cmpfunc.h" enum enum_gtid_until_state { @@ -2122,6 +2123,9 @@ gtid_state_from_binlog_pos(const char *in_name, uint32 pos, String *out_str) } + + + static bool is_until_reached(binlog_send_info *info, ulong *ev_offset, Log_event_type event_type, const char **errmsg, @@ -5838,4 +5842,66 @@ int compare_log_name(const char *log_1, const char *log_2) { return res; } +bool Item_func_gtid_check_pos::val_bool() +{ + DBUG_ASSERT(fixed()); + String *gtid_str= args[0]->val_str(&tmp_value); + if ((null_value= args[0]->null_value)) + return 0; + + slave_connection_state state; + char buf[FN_REFLEN] = {0}; + const char *errormsg; + + if (!mysql_bin_log.is_open()) + { + my_error(ER_NO_BINARY_LOGGING, MYF(0)); + null_value= 1; + return 0; + } + + if (state.load(gtid_str->ptr(), gtid_str->length())) + { + /* + We purposefully do not swallow the error here. If the user passes an + ill-formed string, state.load throws ER_INCORRECT_GTID_STATE which + will correctly abort the statement and notify the user. + */ + null_value= 1; + return 0; + } + + /* + gtid_find_binlog_pos will natively iterate over the entire `state` hash, + evaluating every GTID provided in the comma-separated list. + + If ANY of the requested GTIDs are found to be purged from the binary logs, + it returns a non-null error message pinpointing the purged GTID. + + If ALL GTIDs within the requested state natively resolve (meaning they exist + in our index/binlogs, or are safely in the future), it returns a null errormsg. + + We disregard `found_in_index` and `out_start_seek` counts since we are only doing + boolean viability checking of the provided GTIDs, not actually initializing a dump thread. + */ + bool found_in_index= false; + uint32 out_start_seek= 0; + rpl_binlog_state until_binlog_state; + until_binlog_state.init(); + slave_connection_state until_gtid_state; + + errormsg= gtid_find_binlog_pos(&state, buf, &until_gtid_state, &until_binlog_state, &found_in_index, &out_start_seek); + + until_binlog_state.free(); + + if (errormsg) + { + null_value= 0; + return 0; // At least one requested GTID has been purged. + } + + null_value= 0; + return 1; // All requested GTIDs are currently viable/reachable. +} + #endif /* HAVE_REPLICATION */ diff --git a/sql/sql_repl.h b/sql/sql_repl.h index 0e5f0f27d0a6f..4ae1e411d6727 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -72,6 +72,7 @@ int rpl_append_gtid_state(String *dest, bool use_binlog); int rpl_load_gtid_state(slave_connection_state *state, bool use_binlog); bool rpl_gtid_pos_check(THD *thd, char *str, size_t len); bool rpl_gtid_pos_update(THD *thd, char *str, size_t len); + #else struct LOAD_FILE_IO_CACHE : public IO_CACHE { }; From ebb7195747d804ef7189a670bf4f5f678bfe2ef0 Mon Sep 17 00:00:00 2001 From: ayush-jha123 Date: Tue, 28 Apr 2026 18:42:41 +0000 Subject: [PATCH 2/4] MDEV-8235: Fix symbol visibility and embedded build for GTID_CHECK_POS --- sql/item_func.cc | 14 ++++++++++++-- sql/sql_repl.cc | 20 +++++++------------- sql/sql_repl.h | 1 + 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/sql/item_func.cc b/sql/item_func.cc index 8abe59a790898..b417ce05b363d 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -54,6 +54,7 @@ #include "sql_cte.h" #ifdef WITH_WSREP #include "mysql/service_wsrep.h" +#include "sql_repl.h" #endif /* WITH_WSREP */ #ifdef NO_EMBEDDED_ACCESS_CHECKS @@ -825,7 +826,6 @@ String *Item_int_func::val_str(String *str) } -#ifndef HAVE_REPLICATION bool Item_func_gtid_check_pos::val_bool() { DBUG_ASSERT(fixed()); @@ -833,11 +833,21 @@ bool Item_func_gtid_check_pos::val_bool() if ((null_value= args[0]->null_value)) return 0; +#ifndef HAVE_REPLICATION my_error(ER_NOT_SUPPORTED_YET, MYF(0), "GTID_CHECK_POS"); null_value= 1; return 0; -} +#else + bool is_reachable= false; + if (rpl_gtid_pos_check_reachable(gtid_str, &is_reachable)) + { + null_value= 1; + return 0; + } + null_value= 0; + return is_reachable; #endif +} bool Item_func_connection_id::fix_length_and_dec(THD *thd) diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 996e4b1ab663c..d23530767dca0 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -5842,12 +5842,8 @@ int compare_log_name(const char *log_1, const char *log_2) { return res; } -bool Item_func_gtid_check_pos::val_bool() +bool rpl_gtid_pos_check_reachable(String *gtid_str, bool *is_reachable) { - DBUG_ASSERT(fixed()); - String *gtid_str= args[0]->val_str(&tmp_value); - if ((null_value= args[0]->null_value)) - return 0; slave_connection_state state; char buf[FN_REFLEN] = {0}; @@ -5856,8 +5852,7 @@ bool Item_func_gtid_check_pos::val_bool() if (!mysql_bin_log.is_open()) { my_error(ER_NO_BINARY_LOGGING, MYF(0)); - null_value= 1; - return 0; + return true; } if (state.load(gtid_str->ptr(), gtid_str->length())) @@ -5867,8 +5862,7 @@ bool Item_func_gtid_check_pos::val_bool() ill-formed string, state.load throws ER_INCORRECT_GTID_STATE which will correctly abort the statement and notify the user. */ - null_value= 1; - return 0; + return true; } /* @@ -5896,12 +5890,12 @@ bool Item_func_gtid_check_pos::val_bool() if (errormsg) { - null_value= 0; - return 0; // At least one requested GTID has been purged. + *is_reachable= false; + return false; // At least one requested GTID has been purged. } - null_value= 0; - return 1; // All requested GTIDs are currently viable/reachable. + *is_reachable= true; + return false; // All requested GTIDs are currently viable/reachable. } #endif /* HAVE_REPLICATION */ diff --git a/sql/sql_repl.h b/sql/sql_repl.h index 4ae1e411d6727..4f955ca352e38 100644 --- a/sql/sql_repl.h +++ b/sql/sql_repl.h @@ -72,6 +72,7 @@ int rpl_append_gtid_state(String *dest, bool use_binlog); int rpl_load_gtid_state(slave_connection_state *state, bool use_binlog); bool rpl_gtid_pos_check(THD *thd, char *str, size_t len); bool rpl_gtid_pos_update(THD *thd, char *str, size_t len); +bool rpl_gtid_pos_check_reachable(String *gtid_str, bool *is_reachable); #else From 7c26bf7fec42e6d5175a639a440d893b8def6069 Mon Sep 17 00:00:00 2001 From: Ayush Jha Date: Wed, 3 Jun 2026 23:58:57 +0530 Subject: [PATCH 3/4] MDEV-8235: Fix gtid_check_pos compilation and validation logic --- mysql-test/main/gtid_check_pos.result | 28 +++++++++++--- mysql-test/main/gtid_check_pos.test | 30 ++++++++++++--- sql/item_func.cc | 26 ++++++++++--- sql/sql_repl.cc | 53 +++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 16 deletions(-) diff --git a/mysql-test/main/gtid_check_pos.result b/mysql-test/main/gtid_check_pos.result index 55df67a76c858..a8aa114aa843c 100644 --- a/mysql-test/main/gtid_check_pos.result +++ b/mysql-test/main/gtid_check_pos.result @@ -19,19 +19,37 @@ NULL # (Simulate purging all logs) FLUSH LOGS; PURGE BINARY LOGS BEFORE NOW(); -# Depending on when it is executed, '0-1-1' might still be -# reachable if we haven't actually purged the very first file -# but purge binary logs before now() should remove everything -# except the active one. +# Depending on when it is executed, '0-server_id-1' is purged +# after purge binary logs before now(). +SELECT GTID_CHECK_POS('PURGED_GTID'); +GTID_CHECK_POS('PURGED_GTID') +0 # Since we want a deterministic test, let's check # a completely fake server_id / seq_no that shouldn't exist SELECT GTID_CHECK_POS('99-99-9999999'); GTID_CHECK_POS('99-99-9999999') -1 +0 # 4. Check for an otherwise valid GTID (right domain and server) # that is absent (sequence number is in the future). SELECT GTID_CHECK_POS('ABSENT_GTID'); GTID_CHECK_POS('ABSENT_GTID') +0 +# 5. Check empty string input +SELECT GTID_CHECK_POS(''); +GTID_CHECK_POS('') +1 +# 6. Check multiple GTID comma-separated list scenarios +# A list of multiple valid and reachable GTIDs +SELECT GTID_CHECK_POS('VALID_LIST'); +GTID_CHECK_POS('VALID_LIST') 1 +# A list containing a valid and a future/absent GTID +SELECT GTID_CHECK_POS('MIX_FUTURE'); +GTID_CHECK_POS('MIX_FUTURE') +0 +# A list containing a valid and a purged GTID +SELECT GTID_CHECK_POS('MIX_PURGED'); +GTID_CHECK_POS('MIX_PURGED') +0 # Cleanup DROP TABLE t1; diff --git a/mysql-test/main/gtid_check_pos.test b/mysql-test/main/gtid_check_pos.test index 035e0c7c345b5..54a06f08bf46b 100644 --- a/mysql-test/main/gtid_check_pos.test +++ b/mysql-test/main/gtid_check_pos.test @@ -24,10 +24,12 @@ SELECT GTID_CHECK_POS(NULL); FLUSH LOGS; PURGE BINARY LOGS BEFORE NOW(); ---echo # Depending on when it is executed, '0-1-1' might still be ---echo # reachable if we haven't actually purged the very first file ---echo # but purge binary logs before now() should remove everything ---echo # except the active one. +--echo # Depending on when it is executed, '0-server_id-1' is purged +--echo # after purge binary logs before now(). +let $server_id= `SELECT @@GLOBAL.server_id`; +let $purged_gtid= 0-$server_id-1; +--replace_result $purged_gtid PURGED_GTID +eval SELECT GTID_CHECK_POS('$purged_gtid'); --echo # Since we want a deterministic test, let's check --echo # a completely fake server_id / seq_no that shouldn't exist @@ -35,10 +37,28 @@ SELECT GTID_CHECK_POS('99-99-9999999'); --echo # 4. Check for an otherwise valid GTID (right domain and server) --echo # that is absent (sequence number is in the future). -let $server_id= `SELECT @@GLOBAL.server_id`; let $absent_gtid= 0-$server_id-99999999; --replace_result $absent_gtid ABSENT_GTID eval SELECT GTID_CHECK_POS('$absent_gtid'); +--echo # 5. Check empty string input +SELECT GTID_CHECK_POS(''); + +--echo # 6. Check multiple GTID comma-separated list scenarios +--echo # A list of multiple valid and reachable GTIDs +let $valid_list= 0-$server_id-2,$pos; +--replace_result $valid_list VALID_LIST +eval SELECT GTID_CHECK_POS('$valid_list'); + +--echo # A list containing a valid and a future/absent GTID +let $mix_future= 0-$server_id-2,0-$server_id-99999999; +--replace_result $mix_future MIX_FUTURE +eval SELECT GTID_CHECK_POS('$mix_future'); + +--echo # A list containing a valid and a purged GTID +let $mix_purged= 0-$server_id-2,0-$server_id-1; +--replace_result $mix_purged MIX_PURGED +eval SELECT GTID_CHECK_POS('$mix_purged'); + --echo # Cleanup DROP TABLE t1; diff --git a/sql/item_func.cc b/sql/item_func.cc index b417ce05b363d..c9d380e3aa742 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -52,9 +52,9 @@ #include "debug_sync.h" #include "sql_base.h" #include "sql_cte.h" +#include "sql_repl.h" #ifdef WITH_WSREP #include "mysql/service_wsrep.h" -#include "sql_repl.h" #endif /* WITH_WSREP */ #ifdef NO_EMBEDDED_ACCESS_CHECKS @@ -826,6 +826,20 @@ String *Item_int_func::val_str(String *str) } +/** + @brief Expose replication start position validation as SQL function GTID_CHECK_POS(). + + This function accepts a string representation of one or more GTID lists + and verifies if they exist and are reachable (viable) within the current + set of binary logs. + + @param gtid_str A string representation of GTIDs (e.g. '0-1-1,0-1-2'). + + @return + - 1 if all requested GTIDs are reachable/viable. + - 0 if any requested GTID has been purged or is absent (in the future). + - NULL if the input argument is NULL or if check fails. +*/ bool Item_func_gtid_check_pos::val_bool() { DBUG_ASSERT(fixed()); @@ -833,11 +847,7 @@ bool Item_func_gtid_check_pos::val_bool() if ((null_value= args[0]->null_value)) return 0; -#ifndef HAVE_REPLICATION - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "GTID_CHECK_POS"); - null_value= 1; - return 0; -#else +#ifdef HAVE_REPLICATION bool is_reachable= false; if (rpl_gtid_pos_check_reachable(gtid_str, &is_reachable)) { @@ -846,6 +856,10 @@ bool Item_func_gtid_check_pos::val_bool() } null_value= 0; return is_reachable; +#else + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "GTID_CHECK_POS"); + null_value= 1; + return 0; #endif } diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index d23530767dca0..4711f264b6810 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -5842,6 +5842,21 @@ int compare_log_name(const char *log_1, const char *log_2) { return res; } +/** + @brief Helper function to validate if all GTIDs in a given string are reachable/viable. + + This checks the requested GTID state against the current binlog state. It evaluates: + 1. If binary logging is enabled. + 2. If the GTID string can be parsed correctly. + 3. If each GTID is present in the master's binlog state and has not been written in the future. + 4. If all requested GTIDs have not been purged from the binary log files. + + @param gtid_str The input string containing GTIDs. + @param is_reachable [OUT] Set to true if all requested GTIDs exist and are not purged. + + @retval true An error occurred (binary logging disabled or malformed string). + @retval false Success. The reachability state is stored in `is_reachable`. +*/ bool rpl_gtid_pos_check_reachable(String *gtid_str, bool *is_reachable) { @@ -5865,6 +5880,44 @@ bool rpl_gtid_pos_check_reachable(String *gtid_str, bool *is_reachable) return true; } + /* + Verify that each requested GTID actually exists in the binlog state + and is not in the future. + */ + for (uint32 i= 0; i < state.hash.records; ++i) + { + slave_connection_state::entry *entry= + (slave_connection_state::entry *)my_hash_element(&state.hash, i); + rpl_gtid *slave_gtid= &entry->gtid; + rpl_gtid master_gtid; + rpl_gtid master_replication_gtid; + + bool in_binlog= mysql_bin_log.find_in_binlog_state(slave_gtid->domain_id, + slave_gtid->server_id, + &master_gtid); + if (in_binlog) + { + if (slave_gtid->seq_no > master_gtid.seq_no) + { + *is_reachable= false; + return false; + } + } + else + { + bool start_at_own_slave_pos= + rpl_global_gtid_slave_state->domain_to_gtid(slave_gtid->domain_id, + &master_replication_gtid) && + slave_gtid->server_id == master_replication_gtid.server_id && + slave_gtid->seq_no == master_replication_gtid.seq_no; + if (!start_at_own_slave_pos) + { + *is_reachable= false; + return false; + } + } + } + /* gtid_find_binlog_pos will natively iterate over the entire `state` hash, evaluating every GTID provided in the comma-separated list. From cf68c81cb3802812b4e1a0ed4fca8f878d8ba467 Mon Sep 17 00:00:00 2001 From: Ayush Jha Date: Thu, 4 Jun 2026 00:33:05 +0530 Subject: [PATCH 4/4] MDEV-8235: Fix test determinism and clean up sql_repl.cc --- mysql-test/main/gtid_check_pos.result | 54 +++++++++++------------ mysql-test/main/gtid_check_pos.test | 62 +++++++++++++-------------- sql/sql_repl.cc | 3 -- 3 files changed, 55 insertions(+), 64 deletions(-) diff --git a/mysql-test/main/gtid_check_pos.result b/mysql-test/main/gtid_check_pos.result index a8aa114aa843c..c9c72abd3acb1 100644 --- a/mysql-test/main/gtid_check_pos.result +++ b/mysql-test/main/gtid_check_pos.result @@ -5,51 +5,47 @@ CREATE TABLE t1 (a INT); INSERT INTO t1 VALUES (1); INSERT INTO t1 VALUES (2); -# Get the current GTID position manually and verify it exists +# The current GTID position is reachable SELECT GTID_CHECK_POS('GTID_POS'); GTID_CHECK_POS('GTID_POS') 1 -# 2. Verify invalid inputs +# 2. Invalid input raises an error; NULL input returns NULL SELECT GTID_CHECK_POS('invalid-format'); ERROR HY000: Could not parse GTID list SELECT GTID_CHECK_POS(NULL); GTID_CHECK_POS(NULL) NULL -# 3. Check for a very old purged GTID -# (Simulate purging all logs) +# 3. Empty string (empty GTID list) returns 1 (trivially reachable) +SELECT GTID_CHECK_POS(''); +GTID_CHECK_POS('') +1 +# 4. Rotate to a new binlog and purge the old one FLUSH LOGS; -PURGE BINARY LOGS BEFORE NOW(); -# Depending on when it is executed, '0-server_id-1' is purged -# after purge binary logs before now(). -SELECT GTID_CHECK_POS('PURGED_GTID'); -GTID_CHECK_POS('PURGED_GTID') +# Write a new GTID into the new (post-purge) active binlog +INSERT INTO t1 VALUES (3); +# The new position (in the current active log) is reachable +SELECT GTID_CHECK_POS('NEW_GTID_POS'); +GTID_CHECK_POS('NEW_GTID_POS') +1 +# The old position (now in a purged log) returns 0 +SELECT GTID_CHECK_POS('OLD_GTID_POS'); +GTID_CHECK_POS('OLD_GTID_POS') 0 -# Since we want a deterministic test, let's check -# a completely fake server_id / seq_no that shouldn't exist +# A completely fake domain/server/seqno (never existed) returns 0 SELECT GTID_CHECK_POS('99-99-9999999'); GTID_CHECK_POS('99-99-9999999') 0 -# 4. Check for an otherwise valid GTID (right domain and server) -# that is absent (sequence number is in the future). +# 5. A future sequence number in a known domain returns 0 SELECT GTID_CHECK_POS('ABSENT_GTID'); GTID_CHECK_POS('ABSENT_GTID') 0 -# 5. Check empty string input -SELECT GTID_CHECK_POS(''); -GTID_CHECK_POS('') -1 -# 6. Check multiple GTID comma-separated list scenarios -# A list of multiple valid and reachable GTIDs -SELECT GTID_CHECK_POS('VALID_LIST'); -GTID_CHECK_POS('VALID_LIST') -1 -# A list containing a valid and a future/absent GTID -SELECT GTID_CHECK_POS('MIX_FUTURE'); -GTID_CHECK_POS('MIX_FUTURE') -0 -# A list containing a valid and a purged GTID -SELECT GTID_CHECK_POS('MIX_PURGED'); -GTID_CHECK_POS('MIX_PURGED') +# 6. Mixed list: reachable pos combined with an unknown domain returns 0 +SELECT GTID_CHECK_POS('NEW_GTID_POS,99-99-9999999'); +GTID_CHECK_POS('NEW_GTID_POS,99-99-9999999') 0 +# 7. List with only the current reachable position returns 1 +SELECT GTID_CHECK_POS('NEW_GTID_POS'); +GTID_CHECK_POS('NEW_GTID_POS') +1 # Cleanup DROP TABLE t1; diff --git a/mysql-test/main/gtid_check_pos.test b/mysql-test/main/gtid_check_pos.test index 54a06f08bf46b..ef55b2991499e 100644 --- a/mysql-test/main/gtid_check_pos.test +++ b/mysql-test/main/gtid_check_pos.test @@ -9,56 +9,54 @@ CREATE TABLE t1 (a INT); INSERT INTO t1 VALUES (1); INSERT INTO t1 VALUES (2); ---echo # Get the current GTID position manually and verify it exists +--echo # The current GTID position is reachable let $pos= `SELECT @@GLOBAL.gtid_binlog_pos`; --replace_result $pos GTID_POS eval SELECT GTID_CHECK_POS('$pos'); ---echo # 2. Verify invalid inputs +--echo # 2. Invalid input raises an error; NULL input returns NULL --error ER_INCORRECT_GTID_STATE SELECT GTID_CHECK_POS('invalid-format'); SELECT GTID_CHECK_POS(NULL); ---echo # 3. Check for a very old purged GTID ---echo # (Simulate purging all logs) +--echo # 3. Empty string (empty GTID list) returns 1 (trivially reachable) +SELECT GTID_CHECK_POS(''); + +--echo # 4. Rotate to a new binlog and purge the old one FLUSH LOGS; -PURGE BINARY LOGS BEFORE NOW(); +let $current_log= query_get_value(SHOW MASTER STATUS, File, 1); +--disable_query_log +eval PURGE BINARY LOGS TO '$current_log'; +--enable_query_log ---echo # Depending on when it is executed, '0-server_id-1' is purged ---echo # after purge binary logs before now(). -let $server_id= `SELECT @@GLOBAL.server_id`; -let $purged_gtid= 0-$server_id-1; ---replace_result $purged_gtid PURGED_GTID -eval SELECT GTID_CHECK_POS('$purged_gtid'); +--echo # Write a new GTID into the new (post-purge) active binlog +INSERT INTO t1 VALUES (3); + +--echo # The new position (in the current active log) is reachable +let $new_pos= `SELECT @@GLOBAL.gtid_binlog_pos`; +--replace_result $new_pos NEW_GTID_POS +eval SELECT GTID_CHECK_POS('$new_pos'); + +--echo # The old position (now in a purged log) returns 0 +--replace_result $pos OLD_GTID_POS +eval SELECT GTID_CHECK_POS('$pos'); ---echo # Since we want a deterministic test, let's check ---echo # a completely fake server_id / seq_no that shouldn't exist +--echo # A completely fake domain/server/seqno (never existed) returns 0 SELECT GTID_CHECK_POS('99-99-9999999'); ---echo # 4. Check for an otherwise valid GTID (right domain and server) ---echo # that is absent (sequence number is in the future). +--echo # 5. A future sequence number in a known domain returns 0 +let $server_id= `SELECT @@GLOBAL.server_id`; let $absent_gtid= 0-$server_id-99999999; --replace_result $absent_gtid ABSENT_GTID eval SELECT GTID_CHECK_POS('$absent_gtid'); ---echo # 5. Check empty string input -SELECT GTID_CHECK_POS(''); - ---echo # 6. Check multiple GTID comma-separated list scenarios ---echo # A list of multiple valid and reachable GTIDs -let $valid_list= 0-$server_id-2,$pos; ---replace_result $valid_list VALID_LIST -eval SELECT GTID_CHECK_POS('$valid_list'); - ---echo # A list containing a valid and a future/absent GTID -let $mix_future= 0-$server_id-2,0-$server_id-99999999; ---replace_result $mix_future MIX_FUTURE -eval SELECT GTID_CHECK_POS('$mix_future'); +--echo # 6. Mixed list: reachable pos combined with an unknown domain returns 0 +--replace_result $new_pos NEW_GTID_POS +eval SELECT GTID_CHECK_POS('$new_pos,99-99-9999999'); ---echo # A list containing a valid and a purged GTID -let $mix_purged= 0-$server_id-2,0-$server_id-1; ---replace_result $mix_purged MIX_PURGED -eval SELECT GTID_CHECK_POS('$mix_purged'); +--echo # 7. List with only the current reachable position returns 1 +--replace_result $new_pos NEW_GTID_POS +eval SELECT GTID_CHECK_POS('$new_pos'); --echo # Cleanup DROP TABLE t1; diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index 4711f264b6810..2e8adda6069cf 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -32,7 +32,6 @@ #include "semisync_slave.h" #include "mysys_err.h" #include "gtid_index.h" -#include "item_cmpfunc.h" enum enum_gtid_until_state { @@ -2124,8 +2123,6 @@ gtid_state_from_binlog_pos(const char *in_name, uint32 pos, String *out_str) - - static bool is_until_reached(binlog_send_info *info, ulong *ev_offset, Log_event_type event_type, const char **errmsg,