From 0e5fd66d467bbb672a321df0e74fd08425c09ef7 Mon Sep 17 00:00:00 2001 From: Wazir Ahmed Date: Fri, 3 Apr 2026 14:01:43 +0530 Subject: [PATCH 1/6] Preserve prepared statement `min_gtid` in `first_comment_parsing` mode Issue --- When `first_comment` parsing runs before query rule processing, query rules can strip the `min_gtid` annotation during `STMT_PREPARE`. As a result, `STMT_EXECUTE` can run without the original `min_gtid` constraint. Refs --- - Commit - 1c25787b886aecba4530babf0a46a1dd248df7e6 - https://github.com/sysown/proxysql/issues/5384#issuecomment-4137207612 Fix --- - Store `min_gtid` by frontend `client_stmt_id` in `MySQL_STMTs_local_v14` and restore it during `STMT_EXECUTE` using the client statement handle. - Add TAP test to cover the case of `[first_comment_parsing=1 + prepared_stmt + min_gtid + query_rewrite]` Signed-off-by: Wazir Ahmed --- include/MySQL_PreparedStatement.h | 6 + lib/MySQL_PreparedStatement.cpp | 21 ++ lib/MySQL_Session.cpp | 34 ++- test/tap/groups/groups.json | 3 +- test/tap/tap/utils.cpp | 147 +++++++++++++ test/tap/tap/utils.h | 19 ++ test/tap/tests/Makefile | 3 + test/tap/tests/test_ps_min_gtid_fc-t.cpp | 257 +++++++++++++++++++++++ 8 files changed, 488 insertions(+), 2 deletions(-) create mode 100644 test/tap/tests/test_ps_min_gtid_fc-t.cpp diff --git a/include/MySQL_PreparedStatement.h b/include/MySQL_PreparedStatement.h index af3776fcb..bd1628452 100644 --- a/include/MySQL_PreparedStatement.h +++ b/include/MySQL_PreparedStatement.h @@ -189,6 +189,8 @@ class MySQL_STMTs_local_v14 { public: // this map associate client_stmt_id to global_stmt_id : this is used only for client connections std::map client_stmt_to_global_ids; + // this map associates client_stmt_id to prepare-time min_gtid annotations + std::map client_stmt_to_min_gtid; // this multimap associate global_stmt_id to client_stmt_id : this is used only for client connections std::multimap global_stmt_to_client_ids; @@ -205,6 +207,7 @@ class MySQL_STMTs_local_v14 { sess = NULL; is_client_ = _ic; client_stmt_to_global_ids = std::map(); + client_stmt_to_min_gtid = std::map(); global_stmt_to_client_ids = std::multimap(); backend_stmt_to_global_ids = std::map(); global_stmt_to_backend_ids = std::map(); @@ -224,6 +227,9 @@ class MySQL_STMTs_local_v14 { unsigned int get_num_backend_stmts() { return backend_stmt_to_global_ids.size(); } uint32_t generate_new_client_stmt_id(uint64_t global_statement_id); uint64_t find_global_stmt_id_from_client(uint32_t client_stmt_id); + void set_client_min_gtid(uint32_t client_stmt_id, const char* min_gtid); + const char* find_client_min_gtid(uint32_t client_stmt_id) const; + void erase_client_min_gtid(uint32_t client_stmt_id); bool client_close(uint32_t client_statement_id); MYSQL_STMT * find_backend_stmt_by_global_id(uint32_t global_statement_id) { auto s=global_stmt_to_backend_stmt.find(global_statement_id); diff --git a/lib/MySQL_PreparedStatement.cpp b/lib/MySQL_PreparedStatement.cpp index 1b27c6a6e..e729ae351 100644 --- a/lib/MySQL_PreparedStatement.cpp +++ b/lib/MySQL_PreparedStatement.cpp @@ -794,10 +794,31 @@ uint64_t MySQL_STMTs_local_v14::find_global_stmt_id_from_client(uint32_t client_ return ret; } +void MySQL_STMTs_local_v14::set_client_min_gtid(uint32_t client_stmt_id, const char* min_gtid) { + if (min_gtid && min_gtid[0] != '\0') { + client_stmt_to_min_gtid[client_stmt_id] = min_gtid; + } else { + client_stmt_to_min_gtid.erase(client_stmt_id); + } +} + +const char* MySQL_STMTs_local_v14::find_client_min_gtid(uint32_t client_stmt_id) const { + auto s = client_stmt_to_min_gtid.find(client_stmt_id); + if (s != client_stmt_to_min_gtid.end()) { + return s->second.c_str(); + } + return NULL; +} + +void MySQL_STMTs_local_v14::erase_client_min_gtid(uint32_t client_stmt_id) { + client_stmt_to_min_gtid.erase(client_stmt_id); +} + bool MySQL_STMTs_local_v14::client_close(uint32_t client_statement_id) { auto s = client_stmt_to_global_ids.find(client_statement_id); if (s != client_stmt_to_global_ids.end()) { // found uint64_t global_stmt_id = s->second; + erase_client_min_gtid(client_statement_id); client_stmt_to_global_ids.erase(s); GloMyStmt->ref_count_client(global_stmt_id, -1); //auto s2 = global_stmt_to_client_ids.find(global_stmt_id); diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 5ef07a0a5..f50b07869 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -3377,6 +3377,16 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C // we will now generate a unique stmt and send it to the client uint32_t new_stmt_id=client_myds->myconn->local_stmts->generate_new_client_stmt_id(stmt_info->statement_id); CurrentQuery.stmt_client_id=new_stmt_id; + + // When first_comment_parsing is set to 1 (before query rules) or 3 (before_and_after query rules), + // query rules may strip the min_gtid annotation during STMT_PREPARE. Persist it per client + // statement ID so that STMT_EXECUTE can restore the original routing constraint. + // For more context, refer to https://github.com/sysown/proxysql/issues/5384 + int first_comment_parsing = mysql_thread___query_processor_first_comment_parsing; + if (first_comment_parsing == 1 || first_comment_parsing == 3) { + client_myds->myconn->local_stmts->set_client_min_gtid(new_stmt_id, qpo->min_gtid); + } + client_myds->setDSS_STATE_QUERY_SENT_NET(); client_myds->myprot.generate_STMT_PREPARE_RESPONSE(client_myds->pkt_sid+1,stmt_info,new_stmt_id); LogQuery(NULL); @@ -3492,10 +3502,22 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C // but as we reached here, stmt_meta is not null and we save the metadata sess_STMTs_meta->insert(stmt_global_id,stmt_meta); } - // else CurrentQuery.stmt_meta=stmt_meta; //current_hostgroup=qpo->destination_hostgroup; + + // When first_comment_parsing is set to 1 (before query rules) or 3 (before_and_after query rules), + // query rules may strip the min_gtid annotation during STMT_PREPARE. So we persist it per client + // statement ID and restore it during STMT_EXECUTE. + // For more context, refer to https://github.com/sysown/proxysql/issues/5384 + int first_comment_parsing = mysql_thread___query_processor_first_comment_parsing; + if (!qpo->min_gtid && (first_comment_parsing == 1 || first_comment_parsing == 3)) { + const char *saved_min_gtid = client_myds->myconn->local_stmts->find_client_min_gtid(client_stmt_id); + if (saved_min_gtid) { + qpo->min_gtid = strdup(saved_min_gtid); + } + } + rc_break=handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(&pkt, &lock_hostgroup, ps_type_execute_stmt); if (rc_break==true) { return; @@ -5619,6 +5641,16 @@ bool MySQL_Session::handler_rc0_PROCESSING_STMT_PREPARE(enum session_status& st, CurrentQuery.stmt_client_id=client_stmtid; } CurrentQuery.mysql_stmt=NULL; + + // When first_comment_parsing is set to 1 (before query rules) or 3 (before_and_after query rules), + // query rules may strip the min_gtid annotation during STMT_PREPARE. Persist it per client + // statement ID so that STMT_EXECUTE can restore the original routing constraint. + // For more context, refer to https://github.com/sysown/proxysql/issues/5384 + int first_comment_parsing = mysql_thread___query_processor_first_comment_parsing; + if (previous_status.size() == 0 && (first_comment_parsing == 1 || first_comment_parsing == 3)) { + client_myds->myconn->local_stmts->set_client_min_gtid(client_stmtid, qpo->min_gtid); + } + st=status; size_t sts=previous_status.size(); if (sts) { diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index eb986f730..ef03c6ae9 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -379,5 +379,6 @@ "transaction_state_unit-t" : [ "unit-tests-g1" ], "unit-strip_schema_from_query-t" : [ "unit-tests-g1" ], "vector_db_performance-t" : [ "ai-g1" ], - "vector_features-t" : [ "ai-g1" ] + "vector_features-t" : [ "ai-g1" ], + "test_ps_min_gtid_fc-t" : [ "legacy-binlog-g1" ] } diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index a4d494432..d9490982f 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -2565,3 +2565,150 @@ void spawn_noise(const CommandLine& cl, const std::string& tool_path, const std: fprintf(stderr, "Failed to fork for noise tool: %s\n", tool_path.c_str()); } } + +int get_backend_gtid_position( + MYSQL* admin, + MYSQL* proxy, + const std::string& backend_host, + uint32_t backend_port, + std::string& server_uuid, + uint64_t& max_trxid +) { + auto trim = [](const std::string& s) -> std::string { + size_t start = s.find_first_not_of(" \t\n\r"); + if (start == std::string::npos) return ""; + size_t end = s.find_last_not_of(" \t\n\r"); + return s.substr(start, end - start + 1); + }; + + auto strip_dashes = [](const std::string& uuid) -> std::string { + std::string result; + result.reserve(uuid.size()); + for (char c : uuid) { + if (c != '-') result.push_back(c); + } + return result; + }; + + auto parse_interval_end = [](const std::string& token, uint64_t& interval_end) -> bool { + size_t dash_pos = token.find('-'); + if (dash_pos == std::string::npos) { + interval_end = std::stoull(token); + return true; + } + std::string to_str = token.substr(dash_pos + 1); + if (to_str.empty()) return false; + interval_end = std::stoull(to_str); + return true; + }; + + auto parse_gtid_for_uuid = [&](const std::string& gtid_executed_raw, const std::string& target_uuid) -> bool { + std::string gtid_executed = trim(gtid_executed_raw); + if (gtid_executed.empty() || target_uuid.empty()) return false; + + max_trxid = 0; + bool found = false; + + size_t pos = 0; + while (pos < gtid_executed.size()) { + size_t comma_pos = gtid_executed.find(',', pos); + std::string group = (comma_pos == std::string::npos) + ? gtid_executed.substr(pos) + : gtid_executed.substr(pos, comma_pos - pos); + + group = trim(group); + size_t colon_pos = group.find(':'); + if (colon_pos == std::string::npos || colon_pos == 0) { + pos = (comma_pos == std::string::npos) ? std::string::npos : comma_pos + 1; + continue; + } + + std::string uuid = strip_dashes(group.substr(0, colon_pos)); + if (uuid != target_uuid) { + pos = (comma_pos == std::string::npos) ? std::string::npos : comma_pos + 1; + continue; + } + + std::string intervals_str = group.substr(colon_pos + 1); + size_t ipos = 0; + while (ipos < intervals_str.size()) { + size_t next_colon = intervals_str.find(':', ipos); + std::string token = (next_colon == std::string::npos) + ? intervals_str.substr(ipos) + : intervals_str.substr(ipos, next_colon - ipos); + + if (!token.empty()) { + uint64_t interval_end = 0; + if (parse_interval_end(token, interval_end)) { + if (interval_end > max_trxid) { + max_trxid = interval_end; + } + found = true; + } + } + + ipos = (next_colon == std::string::npos) ? std::string::npos : next_colon + 1; + } + + pos = (comma_pos == std::string::npos) ? std::string::npos : comma_pos + 1; + } + + return found; + }; + + std::string query_uuid = "SELECT @@server_uuid"; + int rc = mysql_query(proxy, query_uuid.c_str()); + if (rc != 0) { + diag("Failed to query @@server_uuid from backend: %s", mysql_error(proxy)); + return -1; + } + + MYSQL_RES* res = mysql_store_result(proxy); + if (!res) { + diag("Failed to store result for @@server_uuid query"); + return -1; + } + + MYSQL_ROW row = mysql_fetch_row(res); + if (!row || !row[0]) { + mysql_free_result(res); + diag("No rows returned for @@server_uuid query"); + return -1; + } + + server_uuid = strip_dashes(row[0]); + mysql_free_result(res); + + std::string gtid_query = "SELECT gtid_executed FROM stats.stats_mysql_gtid_executed" + " WHERE hostname='" + backend_host + "' AND port=" + std::to_string(backend_port) + + " AND gtid_executed IS NOT NULL AND gtid_executed != ''"; + + rc = mysql_query(admin, gtid_query.c_str()); + if (rc != 0) { + diag("Failed to query gtid_executed from stats: %s", mysql_error(admin)); + return -1; + } + + res = mysql_store_result(admin); + if (!res) { + diag("Failed to store result for gtid_executed query"); + return -1; + } + + row = mysql_fetch_row(res); + if (!row || !row[0]) { + mysql_free_result(res); + diag("No GTID info for backend %s:%d", backend_host.c_str(), backend_port); + return -1; + } + + std::string gtid_executed = row[0]; + mysql_free_result(res); + + if (!parse_gtid_for_uuid(gtid_executed, server_uuid)) { + diag("Failed to find UUID %s in gtid_executed: %s", server_uuid.c_str(), gtid_executed.c_str()); + return -1; + } + + return 0; +} diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index 064a74ad4..417651f64 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -981,6 +981,25 @@ using pool_state_t = std::map; */ std::pair fetch_conn_stats(MYSQL* admin, const std::vector hgs); +/** + * @brief Fetches the backend's server_uuid and max executed GTID transaction ID. + * @param admin An already opened connection to ProxySQL admin interface. + * @param proxy An already opened connection to a backend MySQL server. + * @param backend_host The hostname of the backend server. + * @param backend_port The port of the backend server. + * @param server_uuid Output: the backend's server_uuid with dashes stripped. + * @param max_trxid Output: the maximum transaction ID for that UUID from gtid_executed. + * @return 0 on success, -1 on failure (query error, missing UUID, parse error). + */ +int get_backend_gtid_position( + MYSQL* admin, + MYSQL* proxy, + const std::string& backend_host, + uint32_t backend_port, + std::string& server_uuid, + uint64_t& max_trxid +); + /** * @brief Waits for a generic condition. * @details Wait finishes by a non-zero return code by the condition or by timeout. diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index f9d294743..e34c00f87 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -219,6 +219,9 @@ test_gtid_forwarding-t: test_gtid_forwarding-t.cpp $(TAP_LDIR)/libtap.so test_ignore_min_gtid-t: test_ignore_min_gtid-t.cpp $(TAP_LDIR)/libtap.so $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) -o $@ +test_ps_min_gtid_fc-t: test_ps_min_gtid_fc-t.cpp $(TAP_LDIR)/libtap.so + $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) -o $@ + test_admin_prometheus_metrics_dump-t: test_admin_prometheus_metrics_dump-t.cpp $(TAP_LDIR)/libtap.so $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) -o $@ diff --git a/test/tap/tests/test_ps_min_gtid_fc-t.cpp b/test/tap/tests/test_ps_min_gtid_fc-t.cpp new file mode 100644 index 000000000..7409a4371 --- /dev/null +++ b/test/tap/tests/test_ps_min_gtid_fc-t.cpp @@ -0,0 +1,257 @@ +/** + * @file test_ps_min_gtid_fc-t.cpp + * @brief Test for min_gtid preservation in prepared statements with first_comment_parsing mode 1 + * + * This test verifies that min_gtid annotations are preserved across STMT_PREPARE and STMT_EXECUTE + * when using mysql-query_processor_first_comment_parsing=1 (parse before rules). + * + * Issue: https://github.com/sysown/proxysql/issues/5384 + * + * Test flow: + * 1. Set first_comment_parsing=1 and install a rewrite rule to strip min_gtid + * 2. Test 1: Prepare/execute with a reachable GTID - should succeed + * 3. Test 2: Prepare/execute with a future GTID - should fail with timeout + * 4. Both tests use the same normalized SQL (rewrite rule strips min_gtid) + */ + +#include +#include +#include +#include +#include +#include "mysql.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + + +static const int DEDICATED_HG = 59999; +static const char* EXPECTED_PS_QUERY = "/*+ */ SELECT id FROM test.ps_min_gtid_fc WHERE id=?"; + +static int setup(MYSQL* admin, MYSQL* proxy, const CommandLine& cl) { + diag("========== Setup =========="); + + MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); + MYSQL_QUERY_T(proxy, "DROP TABLE IF EXISTS test.ps_min_gtid_fc"); + MYSQL_QUERY_T(proxy, "CREATE TABLE test.ps_min_gtid_fc (id INT PRIMARY KEY)"); + MYSQL_QUERY_T(proxy, "INSERT INTO test.ps_min_gtid_fc VALUES (1)"); + + char server_query[512]; + snprintf(server_query, sizeof(server_query), + "INSERT OR REPLACE INTO mysql_servers (hostgroup_id, hostname, port) VALUES (%d, '%s', %d)", + DEDICATED_HG, cl.mysql_host, cl.mysql_port); + MYSQL_QUERY_T(admin, server_query); + MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + char rule_query[1024]; + MYSQL_QUERY_T(admin, "DELETE FROM mysql_query_rules WHERE rule_id=42"); + snprintf(rule_query, sizeof(rule_query), + "INSERT INTO mysql_query_rules (rule_id, active, match_pattern, replace_pattern, apply, destination_hostgroup, comment)" + " VALUES (42, 1, ';min_gtid=[\\\\:\\\\-\\\\w]+', '', 1, %d, 'Remove min_gtid annotation and route to dedicated HG')", + DEDICATED_HG); + MYSQL_QUERY_T(admin, rule_query); + MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); + + MYSQL_QUERY_T(admin, "SET mysql-query_processor_first_comment_parsing = 1"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + return 0; +} + +static int cleanup(MYSQL* admin, MYSQL* proxy) { + diag("========== Teardown =========="); + + MYSQL_QUERY_T(admin, "DELETE FROM mysql_query_rules WHERE rule_id=42"); + MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); + MYSQL_QUERY_T(admin, "SET mysql-query_processor_first_comment_parsing = 2"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + char del_query[256]; + snprintf(del_query, sizeof(del_query), + "DELETE FROM mysql_servers WHERE hostgroup_id=%d", DEDICATED_HG); + MYSQL_QUERY_T(admin, del_query); + MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + MYSQL_QUERY_T(proxy, "DROP TABLE IF EXISTS test.ps_min_gtid_fc"); + + return 0; +} + +static int get_ps_cache_count(MYSQL* admin) { + std::string query = std::string("SELECT COUNT(*) FROM stats.stats_mysql_prepared_statements_info") + + " WHERE schemaname='test' AND query='" + EXPECTED_PS_QUERY + "'"; + + ext_val_t result = mysql_query_ext_val(admin, query, int32_t(0)); + if (result.err != 0) { + diag("Failed to query PS cache count: err=%d, val=%d", result.err, result.val); + return -1; + } + return result.val; +} + +static int run_prepared_stmt( + MYSQL* proxy, + const std::string& gtid, + unsigned int& stmt_errno, + std::string& stmt_errmsg +) { + std::string query = "/*+ ;min_gtid=" + gtid + " */ SELECT id FROM test.ps_min_gtid_fc WHERE id=?"; + + MYSQL_STMT* stmt = mysql_stmt_init(proxy); + if (!stmt) { + stmt_errno = mysql_errno(proxy); + stmt_errmsg = mysql_error(proxy) ? mysql_error(proxy) : "(null)"; + diag("mysql_stmt_init() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + return -1; + } + + if (mysql_stmt_prepare(stmt, query.c_str(), query.length()) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_prepare() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_close(stmt); + return -1; + } + + int id_val = 1; + unsigned long len = 0; + my_bool is_null = 0; + + MYSQL_BIND bind_param; + memset(&bind_param, 0, sizeof(bind_param)); + bind_param.buffer_type = MYSQL_TYPE_LONG; + bind_param.buffer = &id_val; + bind_param.buffer_length = sizeof(id_val); + bind_param.length = &len; + bind_param.is_null = &is_null; + + if (mysql_stmt_bind_param(stmt, &bind_param) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_bind_param() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_close(stmt); + return -1; + } + + if (mysql_stmt_execute(stmt) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_execute() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_close(stmt); + return -1; + } + + int result_id = 0; + MYSQL_BIND bind_result; + memset(&bind_result, 0, sizeof(bind_result)); + bind_result.buffer_type = MYSQL_TYPE_LONG; + bind_result.buffer = &result_id; + bind_result.buffer_length = sizeof(result_id); + + if (mysql_stmt_bind_result(stmt, &bind_result) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_bind_result() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_close(stmt); + return -1; + } + + if (mysql_stmt_fetch(stmt) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_fetch() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_free_result(stmt); + mysql_stmt_close(stmt); + return -1; + } + + mysql_stmt_free_result(stmt); + mysql_stmt_close(stmt); + + return result_id; +} + +static void test_prepare_stmt_valid_gtid(MYSQL* admin, MYSQL* proxy, const std::string& current_gtid) { + diag("========== Test 1: Valid GTID =========="); + + unsigned int stmt_errno = 0; + std::string stmt_errmsg; + int result_id = run_prepared_stmt(proxy, current_gtid, stmt_errno, stmt_errmsg); + + ok(result_id == 1, "test_valid_gtid: Execute with valid GTID succeeded, result=%d", result_id); + + int ps_count = get_ps_cache_count(admin); + if (ps_count < 0) { + BAIL_OUT("test_valid_gtid: Failed to query PS cache count"); + } + ok(ps_count == 1, "test_valid_gtid: PS cache should have exactly 1 entry for query, got %d", ps_count); +} + +static void test_prepare_stmt_future_gtid(MYSQL* admin, MYSQL* proxy, const std::string& future_gtid) { + diag("========== Test 2: Future GTID =========="); + + unsigned int stmt_errno = 0; + std::string stmt_errmsg; + int result_id = run_prepared_stmt(proxy, future_gtid, stmt_errno, stmt_errmsg); + + ok(result_id == -1, "test_future_gtid: Execute with future GTID should fail"); + ok(stmt_errno == 9001, "test_future_gtid: Error code should be 9001 (timeout), got %u", stmt_errno); + + bool has_timeout_msg = (stmt_errmsg.find("Max connect timeout") != std::string::npos); + ok(has_timeout_msg, "test_future_gtid: Error message should contain 'Max connect timeout': %s", stmt_errmsg.c_str()); + + int ps_count = get_ps_cache_count(admin); + if (ps_count < 0) { + BAIL_OUT("test_future_gtid: Failed to query PS cache count"); + } + ok(ps_count == 1, "test_future_gtid: PS cache should still have exactly 1 entry for query, got %d", ps_count); +} + +int main(int, char**) { + CommandLine cl; + if (cl.getEnv()) { + diag("Failed to get required environmental variables"); + return -1; + } + + plan(6); + + MYSQL* admin = init_mysql_conn(cl.host, cl.admin_port, cl.admin_username, cl.admin_password); + if (!admin) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); + return exit_status(); + } + + MYSQL* proxy = init_mysql_conn(cl.host, cl.port, cl.username, cl.password); + if (!proxy) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy)); + mysql_close(admin); + return exit_status(); + } + + setup(admin, proxy, cl); + + std::string server_uuid; + uint64_t max_trxid = 0; + if (get_backend_gtid_position(admin, proxy, cl.mysql_host, cl.mysql_port, server_uuid, max_trxid) != 0) { + mysql_close(proxy); + mysql_close(admin); + BAIL_OUT("No GTID info available from stats.stats_mysql_gtid_executed for backend %s:%d", + cl.mysql_host, cl.mysql_port); + } + + std::string current_gtid = server_uuid + ":" + std::to_string(max_trxid); + std::string future_gtid = server_uuid + ":" + std::to_string(max_trxid + 100000); + + diag("Current GTID: %s", current_gtid.c_str()); + diag("Future GTID: %s", future_gtid.c_str()); + test_prepare_stmt_valid_gtid(admin, proxy, current_gtid); + test_prepare_stmt_future_gtid(admin, proxy, future_gtid); + + cleanup(admin, proxy); + + mysql_close(proxy); + mysql_close(admin); + + return exit_status(); +} \ No newline at end of file From 17e5e289b9be85be3b558360066ee2ac1a220ed7 Mon Sep 17 00:00:00 2001 From: Wazir Ahmed Date: Thu, 9 Apr 2026 20:49:19 +0530 Subject: [PATCH 2/6] TAP: Fix regex pattern in `test_ps_min_gtid_fc-t` --- test/tap/tests/Makefile | 3 --- test/tap/tests/test_ps_min_gtid_fc-t.cpp | 8 +++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index e34c00f87..f9d294743 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -219,9 +219,6 @@ test_gtid_forwarding-t: test_gtid_forwarding-t.cpp $(TAP_LDIR)/libtap.so test_ignore_min_gtid-t: test_ignore_min_gtid-t.cpp $(TAP_LDIR)/libtap.so $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) -o $@ -test_ps_min_gtid_fc-t: test_ps_min_gtid_fc-t.cpp $(TAP_LDIR)/libtap.so - $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) -o $@ - test_admin_prometheus_metrics_dump-t: test_admin_prometheus_metrics_dump-t.cpp $(TAP_LDIR)/libtap.so $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) -o $@ diff --git a/test/tap/tests/test_ps_min_gtid_fc-t.cpp b/test/tap/tests/test_ps_min_gtid_fc-t.cpp index 7409a4371..3fd47a564 100644 --- a/test/tap/tests/test_ps_min_gtid_fc-t.cpp +++ b/test/tap/tests/test_ps_min_gtid_fc-t.cpp @@ -47,7 +47,7 @@ static int setup(MYSQL* admin, MYSQL* proxy, const CommandLine& cl) { MYSQL_QUERY_T(admin, "DELETE FROM mysql_query_rules WHERE rule_id=42"); snprintf(rule_query, sizeof(rule_query), "INSERT INTO mysql_query_rules (rule_id, active, match_pattern, replace_pattern, apply, destination_hostgroup, comment)" - " VALUES (42, 1, ';min_gtid=[\\\\:\\\\-\\\\w]+', '', 1, %d, 'Remove min_gtid annotation and route to dedicated HG')", + " VALUES (42, 1, ';min_gtid=[:\\-\\w]+', '', 1, %d, 'Remove min_gtid annotation and route to dedicated HG')", DEDICATED_HG); MYSQL_QUERY_T(admin, rule_query); MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); @@ -79,7 +79,7 @@ static int cleanup(MYSQL* admin, MYSQL* proxy) { static int get_ps_cache_count(MYSQL* admin) { std::string query = std::string("SELECT COUNT(*) FROM stats.stats_mysql_prepared_statements_info") - + " WHERE schemaname='test' AND query='" + EXPECTED_PS_QUERY + "'"; + + " WHERE query='" + EXPECTED_PS_QUERY + "'"; ext_val_t result = mysql_query_ext_val(admin, query, int32_t(0)); if (result.err != 0) { @@ -113,6 +113,8 @@ static int run_prepared_stmt( return -1; } + diag("Prepared statement_id: %lu", stmt->stmt_id); + int id_val = 1; unsigned long len = 0; my_bool is_null = 0; @@ -254,4 +256,4 @@ int main(int, char**) { mysql_close(admin); return exit_status(); -} \ No newline at end of file +} From 0f1aa30f4c6975663c49e6d91dd9c15290c2a264 Mon Sep 17 00:00:00 2001 From: Wazir Ahmed Date: Fri, 10 Apr 2026 12:25:32 +0530 Subject: [PATCH 3/6] TAP: Test `min_gtid` annotation in prepare statements Signed-off-by: Wazir Ahmed --- test/tap/groups/groups.json | 3 +- test/tap/tests/test_ps_min_gtid-t.cpp | 233 ++++++++++++++++++++++++++ 2 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 test/tap/tests/test_ps_min_gtid-t.cpp diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index ef03c6ae9..197c88093 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -380,5 +380,6 @@ "unit-strip_schema_from_query-t" : [ "unit-tests-g1" ], "vector_db_performance-t" : [ "ai-g1" ], "vector_features-t" : [ "ai-g1" ], - "test_ps_min_gtid_fc-t" : [ "legacy-binlog-g1" ] + "test_ps_min_gtid_fc-t" : [ "legacy-binlog-g1" ], + "test_ps_min_gtid-t" : [ "legacy-binlog-g1" ] } diff --git a/test/tap/tests/test_ps_min_gtid-t.cpp b/test/tap/tests/test_ps_min_gtid-t.cpp new file mode 100644 index 000000000..142df42da --- /dev/null +++ b/test/tap/tests/test_ps_min_gtid-t.cpp @@ -0,0 +1,233 @@ +/** + * @file test_ps_min_gtid-t.cpp + * @brief Test min_gtid based routing for prepared statements + * + * This test verifies that min_gtid annotation is handled properly + * during STMT_PREPARE and STMT_EXECUTE + * + * Test flow: + * 1. Test 1: Prepare/execute with a reachable GTID - should succeed + * 2. Test 2: Prepare/execute with a future GTID - should fail with timeout + */ + +#include +#include +#include +#include +#include +#include "mysql.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +static const char* EXPECTED_PS_QUERY = "SELECT id FROM test.ps_min_gtid_fc WHERE id=?"; + +static int setup(MYSQL* admin, MYSQL* proxy, const CommandLine& cl) { + diag("========== Setup =========="); + + MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); + MYSQL_QUERY_T(proxy, "DROP TABLE IF EXISTS test.ps_min_gtid_fc"); + MYSQL_QUERY_T(proxy, "CREATE TABLE test.ps_min_gtid_fc (id INT PRIMARY KEY)"); + MYSQL_QUERY_T(proxy, "INSERT INTO test.ps_min_gtid_fc VALUES (1)"); + + return 0; +} + +static int cleanup(MYSQL* admin, MYSQL* proxy) { + diag("========== Teardown =========="); + + MYSQL_QUERY_T(admin, "DELETE FROM mysql_query_rules WHERE rule_id=42"); + MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); + MYSQL_QUERY_T(admin, "SET mysql-query_processor_first_comment_parsing = 2"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + MYSQL_QUERY_T(proxy, "DROP TABLE IF EXISTS test.ps_min_gtid_fc"); + + return 0; +} + +static int get_ps_cache_count(MYSQL* admin) { + std::string query = std::string("SELECT COUNT(*) FROM stats.stats_mysql_prepared_statements_info") + + " WHERE query like '%" + EXPECTED_PS_QUERY + "'"; + + ext_val_t result = mysql_query_ext_val(admin, query, int32_t(0)); + if (result.err != 0) { + diag("Failed to query PS cache count: err=%d, val=%d", result.err, result.val); + return -1; + } + return result.val; +} + +static int run_prepared_stmt( + MYSQL* proxy, + const std::string& gtid, + unsigned int& stmt_errno, + std::string& stmt_errmsg +) { + std::string query = "/*+ ;min_gtid=" + gtid + " */ SELECT id FROM test.ps_min_gtid_fc WHERE id=?"; + + MYSQL_STMT* stmt = mysql_stmt_init(proxy); + if (!stmt) { + stmt_errno = mysql_errno(proxy); + stmt_errmsg = mysql_error(proxy) ? mysql_error(proxy) : "(null)"; + diag("mysql_stmt_init() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + return -1; + } + + + if (mysql_stmt_prepare(stmt, query.c_str(), query.length()) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_prepare() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_close(stmt); + return -1; + } + + diag("Prepared statement_id: %lu", stmt->stmt_id); + + int id_val = 1; + unsigned long len = 0; + my_bool is_null = 0; + + MYSQL_BIND bind_param; + memset(&bind_param, 0, sizeof(bind_param)); + bind_param.buffer_type = MYSQL_TYPE_LONG; + bind_param.buffer = &id_val; + bind_param.buffer_length = sizeof(id_val); + bind_param.length = &len; + bind_param.is_null = &is_null; + + if (mysql_stmt_bind_param(stmt, &bind_param) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_bind_param() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_close(stmt); + return -1; + } + + if (mysql_stmt_execute(stmt) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_execute() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_close(stmt); + return -1; + } + + int result_id = 0; + MYSQL_BIND bind_result; + memset(&bind_result, 0, sizeof(bind_result)); + bind_result.buffer_type = MYSQL_TYPE_LONG; + bind_result.buffer = &result_id; + bind_result.buffer_length = sizeof(result_id); + + if (mysql_stmt_bind_result(stmt, &bind_result) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_bind_result() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_close(stmt); + return -1; + } + + if (mysql_stmt_fetch(stmt) != 0) { + stmt_errno = mysql_stmt_errno(stmt); + stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; + diag("mysql_stmt_fetch() failed: errno=%u, error=%s", stmt_errno, stmt_errmsg.c_str()); + mysql_stmt_free_result(stmt); + mysql_stmt_close(stmt); + return -1; + } + + mysql_stmt_free_result(stmt); + mysql_stmt_close(stmt); + + return result_id; +} + +static void test_prepare_stmt_valid_gtid(MYSQL* admin, MYSQL* proxy, const std::string& current_gtid, int exp_ps_cache_count) { + diag("========== Test 1: Valid GTID =========="); + + unsigned int stmt_errno = 0; + std::string stmt_errmsg; + int result_id = run_prepared_stmt(proxy, current_gtid, stmt_errno, stmt_errmsg); + + ok(result_id == 1, "test_valid_gtid: Execute with valid GTID succeeded, result=%d", result_id); + + int ps_count = get_ps_cache_count(admin); + if (ps_count < 0) { + BAIL_OUT("test_valid_gtid: Failed to query PS cache count"); + } + ok(ps_count == exp_ps_cache_count, "test_valid_gtid: PS cache should have exactly %d entry for query, got %d", exp_ps_cache_count, ps_count); +} + +static void test_prepare_stmt_future_gtid(MYSQL* admin, MYSQL* proxy, const std::string& future_gtid, int exp_ps_cache_count) { + diag("========== Test 2: Future GTID =========="); + + unsigned int stmt_errno = 0; + std::string stmt_errmsg; + int result_id = run_prepared_stmt(proxy, future_gtid, stmt_errno, stmt_errmsg); + + ok(result_id == -1, "test_future_gtid: Execute with future GTID should fail"); + ok(stmt_errno == 9001, "test_future_gtid: Error code should be 9001 (timeout), got %u", stmt_errno); + + bool has_timeout_msg = (stmt_errmsg.find("Max connect timeout") != std::string::npos); + ok(has_timeout_msg, "test_future_gtid: Error message should contain 'Max connect timeout': %s", stmt_errmsg.c_str()); + + int ps_count = get_ps_cache_count(admin); + if (ps_count < 0) { + BAIL_OUT("test_future_gtid: Failed to query PS cache count"); + } + + ok(ps_count == exp_ps_cache_count, "test_valid_gtid: PS cache should have exactly %d entry for query, got %d", exp_ps_cache_count, ps_count); +} + +int main(int, char**) { + CommandLine cl; + if (cl.getEnv()) { + diag("Failed to get required environmental variables"); + return -1; + } + + plan(6); + + MYSQL* admin = init_mysql_conn(cl.host, cl.admin_port, cl.admin_username, cl.admin_password); + if (!admin) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); + return exit_status(); + } + + MYSQL* proxy = init_mysql_conn(cl.host, cl.port, cl.username, cl.password); + if (!proxy) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy)); + mysql_close(admin); + return exit_status(); + } + + cleanup(admin, proxy); + + setup(admin, proxy, cl); + + std::string server_uuid; + uint64_t max_trxid = 0; + if (get_backend_gtid_position(admin, proxy, cl.mysql_host, cl.mysql_port, server_uuid, max_trxid) != 0) { + mysql_close(proxy); + mysql_close(admin); + BAIL_OUT("No GTID info available from stats.stats_mysql_gtid_executed for backend %s:%d", + cl.mysql_host, cl.mysql_port); + } + + std::string current_gtid = server_uuid + ":" + std::to_string(max_trxid); + std::string future_gtid = server_uuid + ":" + std::to_string(max_trxid + 100000); + + diag("Current GTID: %s", current_gtid.c_str()); + diag("Future GTID: %s", future_gtid.c_str()); + + int ps_cache_count = get_ps_cache_count(admin); + + test_prepare_stmt_valid_gtid(admin, proxy, current_gtid, (ps_cache_count + 1)); + test_prepare_stmt_future_gtid(admin, proxy, future_gtid, (ps_cache_count + 1)); + + mysql_close(proxy); + mysql_close(admin); + + return exit_status(); +} From db0d58ee50c45d4b65937e5e43814918049cf73d Mon Sep 17 00:00:00 2001 From: Wazir Ahmed Date: Sat, 11 Apr 2026 12:57:12 +0530 Subject: [PATCH 4/6] TAP: Remove `@@server_uuid` dependency from `test_ps_min_gtid-t` and `test_ps_min_gtid_fc-t` Signed-off-by: Wazir Ahmed --- test/tap/tap/utils.cpp | 111 +++++++++-------------- test/tap/tap/utils.h | 8 +- test/tap/tests/test_ps_min_gtid-t.cpp | 41 ++------- test/tap/tests/test_ps_min_gtid_fc-t.cpp | 17 ++-- 4 files changed, 64 insertions(+), 113 deletions(-) diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index d9490982f..84d9d6271 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -2568,7 +2568,6 @@ void spawn_noise(const CommandLine& cl, const std::string& tool_path, const std: int get_backend_gtid_position( MYSQL* admin, - MYSQL* proxy, const std::string& backend_host, uint32_t backend_port, std::string& server_uuid, @@ -2602,94 +2601,70 @@ int get_backend_gtid_position( return true; }; - auto parse_gtid_for_uuid = [&](const std::string& gtid_executed_raw, const std::string& target_uuid) -> bool { + // A GTID executed set may contain multiple comma-separated GTID entries, + // we parse only the first GTID entry from the set. + auto parse_first_gtid = [&](const std::string& gtid_executed_raw) -> bool { std::string gtid_executed = trim(gtid_executed_raw); - if (gtid_executed.empty() || target_uuid.empty()) return false; + if (gtid_executed.empty()) return false; + size_t comma_pos = gtid_executed.find(','); + std::string group = trim( + (comma_pos == std::string::npos) ? gtid_executed : gtid_executed.substr(0, comma_pos) + ); + if (group.empty()) return false; + + size_t colon_pos = group.find(':'); + if (colon_pos == std::string::npos || colon_pos == 0 || colon_pos == group.size() - 1) { + return false; + } + + server_uuid = strip_dashes(group.substr(0, colon_pos)); + if (server_uuid.empty()) return false; + + std::string intervals_str = group.substr(colon_pos + 1); max_trxid = 0; bool found = false; - size_t pos = 0; - while (pos < gtid_executed.size()) { - size_t comma_pos = gtid_executed.find(',', pos); - std::string group = (comma_pos == std::string::npos) - ? gtid_executed.substr(pos) - : gtid_executed.substr(pos, comma_pos - pos); - - group = trim(group); - size_t colon_pos = group.find(':'); - if (colon_pos == std::string::npos || colon_pos == 0) { - pos = (comma_pos == std::string::npos) ? std::string::npos : comma_pos + 1; - continue; - } - - std::string uuid = strip_dashes(group.substr(0, colon_pos)); - if (uuid != target_uuid) { - pos = (comma_pos == std::string::npos) ? std::string::npos : comma_pos + 1; - continue; - } + size_t ipos = 0; + while (ipos < intervals_str.size()) { + size_t next_colon = intervals_str.find(':', ipos); + std::string token = trim( + (next_colon == std::string::npos) ? intervals_str.substr(ipos) : intervals_str.substr(ipos, next_colon - ipos) + ); - std::string intervals_str = group.substr(colon_pos + 1); - size_t ipos = 0; - while (ipos < intervals_str.size()) { - size_t next_colon = intervals_str.find(':', ipos); - std::string token = (next_colon == std::string::npos) - ? intervals_str.substr(ipos) - : intervals_str.substr(ipos, next_colon - ipos); - - if (!token.empty()) { - uint64_t interval_end = 0; - if (parse_interval_end(token, interval_end)) { - if (interval_end > max_trxid) { - max_trxid = interval_end; - } - found = true; - } + if (!token.empty()) { + uint64_t interval_end = 0; + if (!parse_interval_end(token, interval_end)) { + return false; } - - ipos = (next_colon == std::string::npos) ? std::string::npos : next_colon + 1; + if (interval_end > max_trxid) { + max_trxid = interval_end; + } + found = true; } - pos = (comma_pos == std::string::npos) ? std::string::npos : comma_pos + 1; + if (next_colon == std::string::npos) { + break; + } + ipos = next_colon + 1; } return found; }; - std::string query_uuid = "SELECT @@server_uuid"; - int rc = mysql_query(proxy, query_uuid.c_str()); - if (rc != 0) { - diag("Failed to query @@server_uuid from backend: %s", mysql_error(proxy)); - return -1; - } - - MYSQL_RES* res = mysql_store_result(proxy); - if (!res) { - diag("Failed to store result for @@server_uuid query"); - return -1; - } - - MYSQL_ROW row = mysql_fetch_row(res); - if (!row || !row[0]) { - mysql_free_result(res); - diag("No rows returned for @@server_uuid query"); - return -1; - } - - server_uuid = strip_dashes(row[0]); - mysql_free_result(res); - std::string gtid_query = "SELECT gtid_executed FROM stats.stats_mysql_gtid_executed" " WHERE hostname='" + backend_host + "' AND port=" + std::to_string(backend_port) + " AND gtid_executed IS NOT NULL AND gtid_executed != ''"; - rc = mysql_query(admin, gtid_query.c_str()); + int rc = mysql_query(admin, gtid_query.c_str()); if (rc != 0) { diag("Failed to query gtid_executed from stats: %s", mysql_error(admin)); return -1; } - res = mysql_store_result(admin); + MYSQL_RES* res = mysql_store_result(admin); + MYSQL_ROW row = nullptr; + if (!res) { diag("Failed to store result for gtid_executed query"); return -1; @@ -2705,8 +2680,8 @@ int get_backend_gtid_position( std::string gtid_executed = row[0]; mysql_free_result(res); - if (!parse_gtid_for_uuid(gtid_executed, server_uuid)) { - diag("Failed to find UUID %s in gtid_executed: %s", server_uuid.c_str(), gtid_executed.c_str()); + if (!parse_first_gtid(gtid_executed)) { + diag("Failed to parse GTID entry from gtid_executed: %s", gtid_executed.c_str()); return -1; } diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index 417651f64..5c8382f92 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -982,18 +982,16 @@ using pool_state_t = std::map; std::pair fetch_conn_stats(MYSQL* admin, const std::vector hgs); /** - * @brief Fetches the backend's server_uuid and max executed GTID transaction ID. + * @brief Fetches GTID info for a backend from the gtid_executed set. * @param admin An already opened connection to ProxySQL admin interface. - * @param proxy An already opened connection to a backend MySQL server. * @param backend_host The hostname of the backend server. * @param backend_port The port of the backend server. - * @param server_uuid Output: the backend's server_uuid with dashes stripped. - * @param max_trxid Output: the maximum transaction ID for that UUID from gtid_executed. + * @param server_uuid Output: the UUID of the first GTID entry in gtid_executed set, with dashes stripped. + * @param max_trxid Output: the maximum transaction ID found in the first GTID entry. * @return 0 on success, -1 on failure (query error, missing UUID, parse error). */ int get_backend_gtid_position( MYSQL* admin, - MYSQL* proxy, const std::string& backend_host, uint32_t backend_port, std::string& server_uuid, diff --git a/test/tap/tests/test_ps_min_gtid-t.cpp b/test/tap/tests/test_ps_min_gtid-t.cpp index 142df42da..0699dc33c 100644 --- a/test/tap/tests/test_ps_min_gtid-t.cpp +++ b/test/tap/tests/test_ps_min_gtid-t.cpp @@ -20,31 +20,7 @@ #include "command_line.h" #include "utils.h" -static const char* EXPECTED_PS_QUERY = "SELECT id FROM test.ps_min_gtid_fc WHERE id=?"; - -static int setup(MYSQL* admin, MYSQL* proxy, const CommandLine& cl) { - diag("========== Setup =========="); - - MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); - MYSQL_QUERY_T(proxy, "DROP TABLE IF EXISTS test.ps_min_gtid_fc"); - MYSQL_QUERY_T(proxy, "CREATE TABLE test.ps_min_gtid_fc (id INT PRIMARY KEY)"); - MYSQL_QUERY_T(proxy, "INSERT INTO test.ps_min_gtid_fc VALUES (1)"); - - return 0; -} - -static int cleanup(MYSQL* admin, MYSQL* proxy) { - diag("========== Teardown =========="); - - MYSQL_QUERY_T(admin, "DELETE FROM mysql_query_rules WHERE rule_id=42"); - MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); - MYSQL_QUERY_T(admin, "SET mysql-query_processor_first_comment_parsing = 2"); - MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); - - MYSQL_QUERY_T(proxy, "DROP TABLE IF EXISTS test.ps_min_gtid_fc"); - - return 0; -} +static const char* EXPECTED_PS_QUERY = "SELECT id FROM test.ps_min_gtid WHERE id=?"; static int get_ps_cache_count(MYSQL* admin) { std::string query = std::string("SELECT COUNT(*) FROM stats.stats_mysql_prepared_statements_info") @@ -64,7 +40,7 @@ static int run_prepared_stmt( unsigned int& stmt_errno, std::string& stmt_errmsg ) { - std::string query = "/*+ ;min_gtid=" + gtid + " */ SELECT id FROM test.ps_min_gtid_fc WHERE id=?"; + std::string query = "/*+ ;min_gtid=" + gtid + " */ SELECT id FROM test.ps_min_gtid WHERE id=?"; MYSQL_STMT* stmt = mysql_stmt_init(proxy); if (!stmt) { @@ -74,7 +50,6 @@ static int run_prepared_stmt( return -1; } - if (mysql_stmt_prepare(stmt, query.c_str(), query.length()) != 0) { stmt_errno = mysql_stmt_errno(stmt); stmt_errmsg = mysql_stmt_error(stmt) ? mysql_stmt_error(stmt) : "(null)"; @@ -202,13 +177,15 @@ int main(int, char**) { return exit_status(); } - cleanup(admin, proxy); - - setup(admin, proxy, cl); + diag("========== Setup =========="); + MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); + MYSQL_QUERY_T(proxy, "DROP TABLE IF EXISTS test.ps_min_gtid"); + MYSQL_QUERY_T(proxy, "CREATE TABLE test.ps_min_gtid (id INT PRIMARY KEY)"); + MYSQL_QUERY_T(proxy, "INSERT INTO test.ps_min_gtid VALUES (1)"); std::string server_uuid; uint64_t max_trxid = 0; - if (get_backend_gtid_position(admin, proxy, cl.mysql_host, cl.mysql_port, server_uuid, max_trxid) != 0) { + if (get_backend_gtid_position(admin, cl.mysql_host, cl.mysql_port, server_uuid, max_trxid) != 0) { mysql_close(proxy); mysql_close(admin); BAIL_OUT("No GTID info available from stats.stats_mysql_gtid_executed for backend %s:%d", @@ -226,6 +203,8 @@ int main(int, char**) { test_prepare_stmt_valid_gtid(admin, proxy, current_gtid, (ps_cache_count + 1)); test_prepare_stmt_future_gtid(admin, proxy, future_gtid, (ps_cache_count + 1)); + diag("========== Teardown =========="); + MYSQL_QUERY_T(proxy, "DROP TABLE IF EXISTS test.ps_min_gtid"); mysql_close(proxy); mysql_close(admin); diff --git a/test/tap/tests/test_ps_min_gtid_fc-t.cpp b/test/tap/tests/test_ps_min_gtid_fc-t.cpp index 3fd47a564..e71df8960 100644 --- a/test/tap/tests/test_ps_min_gtid_fc-t.cpp +++ b/test/tap/tests/test_ps_min_gtid_fc-t.cpp @@ -24,9 +24,8 @@ #include "command_line.h" #include "utils.h" - -static const int DEDICATED_HG = 59999; -static const char* EXPECTED_PS_QUERY = "/*+ */ SELECT id FROM test.ps_min_gtid_fc WHERE id=?"; +static const int TEST_HG = 59999; +static const char* EXP_PS_CACHE_QUERY = "/*+ */ SELECT id FROM test.ps_min_gtid_fc WHERE id=?"; static int setup(MYSQL* admin, MYSQL* proxy, const CommandLine& cl) { diag("========== Setup =========="); @@ -39,7 +38,7 @@ static int setup(MYSQL* admin, MYSQL* proxy, const CommandLine& cl) { char server_query[512]; snprintf(server_query, sizeof(server_query), "INSERT OR REPLACE INTO mysql_servers (hostgroup_id, hostname, port) VALUES (%d, '%s', %d)", - DEDICATED_HG, cl.mysql_host, cl.mysql_port); + TEST_HG, cl.mysql_host, cl.mysql_port); MYSQL_QUERY_T(admin, server_query); MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); @@ -47,8 +46,8 @@ static int setup(MYSQL* admin, MYSQL* proxy, const CommandLine& cl) { MYSQL_QUERY_T(admin, "DELETE FROM mysql_query_rules WHERE rule_id=42"); snprintf(rule_query, sizeof(rule_query), "INSERT INTO mysql_query_rules (rule_id, active, match_pattern, replace_pattern, apply, destination_hostgroup, comment)" - " VALUES (42, 1, ';min_gtid=[:\\-\\w]+', '', 1, %d, 'Remove min_gtid annotation and route to dedicated HG')", - DEDICATED_HG); + " VALUES (42, 1, ';min_gtid=[:\\-\\w]+', '', 1, %d, 'Strip min_gtid annotation and route to HG %d')", + TEST_HG, TEST_HG); MYSQL_QUERY_T(admin, rule_query); MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); @@ -68,7 +67,7 @@ static int cleanup(MYSQL* admin, MYSQL* proxy) { char del_query[256]; snprintf(del_query, sizeof(del_query), - "DELETE FROM mysql_servers WHERE hostgroup_id=%d", DEDICATED_HG); + "DELETE FROM mysql_servers WHERE hostgroup_id=%d", TEST_HG); MYSQL_QUERY_T(admin, del_query); MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); @@ -79,7 +78,7 @@ static int cleanup(MYSQL* admin, MYSQL* proxy) { static int get_ps_cache_count(MYSQL* admin) { std::string query = std::string("SELECT COUNT(*) FROM stats.stats_mysql_prepared_statements_info") - + " WHERE query='" + EXPECTED_PS_QUERY + "'"; + + " WHERE query='" + EXP_PS_CACHE_QUERY + "'"; ext_val_t result = mysql_query_ext_val(admin, query, int32_t(0)); if (result.err != 0) { @@ -235,7 +234,7 @@ int main(int, char**) { std::string server_uuid; uint64_t max_trxid = 0; - if (get_backend_gtid_position(admin, proxy, cl.mysql_host, cl.mysql_port, server_uuid, max_trxid) != 0) { + if (get_backend_gtid_position(admin, cl.mysql_host, cl.mysql_port, server_uuid, max_trxid) != 0) { mysql_close(proxy); mysql_close(admin); BAIL_OUT("No GTID info available from stats.stats_mysql_gtid_executed for backend %s:%d", From 6a0ea94d08b311b840f6f903b9c92df6e2f516ec Mon Sep 17 00:00:00 2001 From: Wazir Ahmed Date: Sun, 12 Apr 2026 18:52:33 +0530 Subject: [PATCH 5/6] chore(gitignore): Add `compile_commands.json` Signed-off-by: Wazir Ahmed --- .gitignore | 3 +++ compile_commands.events.json | Bin 130712 -> 0 bytes 2 files changed, 3 insertions(+) delete mode 100644 compile_commands.events.json diff --git a/.gitignore b/.gitignore index 4ea675f0c..b7820870c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ lint/ # direnv .envrc.local +# bear/clangd +compile_commands.json +compile_commands.events.json # Intelij .idea/ diff --git a/compile_commands.events.json b/compile_commands.events.json deleted file mode 100644 index 260facad0e95f3304942c98c517ec2bd7c3db06f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130712 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 NsNJsm&)TQI{2x#1D)s;X From a12b66a9821e440360ea96072d3468df6f89ac7e Mon Sep 17 00:00:00 2001 From: Wazir Ahmed Date: Mon, 13 Apr 2026 00:29:36 +0530 Subject: [PATCH 6/6] TAP: fix typo in `test_ps_min_gtid-t` --- test/tap/tests/test_ps_min_gtid-t.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tap/tests/test_ps_min_gtid-t.cpp b/test/tap/tests/test_ps_min_gtid-t.cpp index 0699dc33c..bbce04100 100644 --- a/test/tap/tests/test_ps_min_gtid-t.cpp +++ b/test/tap/tests/test_ps_min_gtid-t.cpp @@ -152,7 +152,7 @@ static void test_prepare_stmt_future_gtid(MYSQL* admin, MYSQL* proxy, const std: BAIL_OUT("test_future_gtid: Failed to query PS cache count"); } - ok(ps_count == exp_ps_cache_count, "test_valid_gtid: PS cache should have exactly %d entry for query, got %d", exp_ps_cache_count, ps_count); + ok(ps_count == exp_ps_cache_count, "test_future_gtid: PS cache should have exactly %d entry for query, got %d", exp_ps_cache_count, ps_count); } int main(int, char**) {