From 6cb522283dea756bdd86febca187889a7f4f4b06 Mon Sep 17 00:00:00 2001 From: Lisandro Pin Date: Wed, 8 Oct 2025 14:36:32 +0200 Subject: [PATCH 1/2] Implement a ignore_min_gtid_annotation global variable for ProxySQL When true, all `min_gtid` query annotations are ignored; see https://proxysql.com/documentation/query-annotations/ for details. This is useful on ProxySQL setups with multiple layers, where some layers mandate GTID-based routing while others don't. --- include/MySQL_Query_Processor.h | 24 ++++++++++++++++-------- include/MySQL_Thread.h | 2 ++ include/proxysql_structs.h | 2 ++ lib/MySQL_Session.cpp | 2 +- lib/MySQL_Thread.cpp | 25 +++++++++++++++---------- 5 files changed, 36 insertions(+), 19 deletions(-) diff --git a/include/MySQL_Query_Processor.h b/include/MySQL_Query_Processor.h index 5aaf471b0..ae9873300 100644 --- a/include/MySQL_Query_Processor.h +++ b/include/MySQL_Query_Processor.h @@ -6,7 +6,7 @@ #include "query_processor.h" class Command_Counter; -typedef struct _MySQL_Query_processor_Rule_t : public QP_rule_t { +typedef struct _MySQL_Query_processor_Rule_t : public QP_rule_t { int gtid_from_hostgroup; } MySQL_Query_Processor_Rule_t; @@ -75,14 +75,22 @@ private: inline void query_parser_first_comment_extended(const char* key, const char* value, MySQL_Query_Processor_Output* qpo) { if (!strcasecmp(key, "min_gtid")) { - size_t l = strlen(value); - if (_is_valid_gtid((char*)value, l)) { - char* buf = (char*)malloc(l + 1); - strncpy(buf, value, l); - buf[l + 1] = '\0'; - qpo->min_gtid = buf; + if (mysql_thread___ignore_min_gtid_annotations) { + proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 5, "Ignoring min_gtid=%s\n", value); } else { - proxy_warning("Invalid gtid value=%s\n", value); + size_t l = strlen(value); + if (_is_valid_gtid((char*)value, l)) { + char* buf = (char*)malloc(l + 1); + strncpy(buf, value, l); + buf[l] = '\0'; + + if (qpo->min_gtid) { + free(qpo->min_gtid); + } + qpo->min_gtid = buf; + } else { + proxy_warning("Invalid min_gtid value=%s\n", value); + } } } } diff --git a/include/MySQL_Thread.h b/include/MySQL_Thread.h index 296aebbe6..896324405 100644 --- a/include/MySQL_Thread.h +++ b/include/MySQL_Thread.h @@ -577,6 +577,8 @@ class MySQL_Threads_Handler #endif int show_processlist_extended; int processlist_max_query_length; + + bool ignore_min_gtid_annotations; } variables; struct { unsigned int mirror_sessions_current; diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index c6bef82f2..893a2bfda 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -1303,6 +1303,7 @@ __thread int mysql_thread___client_host_cache_size; __thread int mysql_thread___client_host_error_counts; __thread int mysql_thread___handle_warnings; __thread int mysql_thread___evaluate_replication_lag_on_servers_load; +__thread bool mysql_thread___ignore_min_gtid_annotations; /* variables used for Query Cache */ __thread int mysql_thread___query_cache_size_MB; @@ -1605,6 +1606,7 @@ extern __thread int mysql_thread___client_host_cache_size; extern __thread int mysql_thread___client_host_error_counts; extern __thread int mysql_thread___handle_warnings; extern __thread int mysql_thread___evaluate_replication_lag_on_servers_load; +extern __thread bool mysql_thread___ignore_min_gtid_annotations; /* variables used for Query Cache */ extern __thread int mysql_thread___query_cache_size_MB; diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 861279fa9..22b8f19dc 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -7162,7 +7162,7 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C } void MySQL_Session::handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED__get_connection() { - // Get a MySQL Connection + // Get a MySQL Connection MySQL_Connection *mc=NULL; MySQL_Backend * _gtid_from_backend = NULL; diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index 5b2d467c9..3a2380e8a 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -510,6 +510,7 @@ static char * mysql_thread_variables_names[]= { (char *)"evaluate_replication_lag_on_servers_load", (char *)"proxy_protocol_networks", (char *)"protocol_compression_level", + (char *)"ignore_min_gtid_annotations", NULL }; @@ -1149,6 +1150,7 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() { variables.log_mysql_warnings_enabled=false; variables.data_packets_history_size=0; variables.protocol_compression_level=3; + variables.ignore_min_gtid_annotations=false; // status variables status_variables.mirror_sessions_current=0; __global_MySQL_Thread_Variables_version=1; @@ -1373,6 +1375,7 @@ char * MySQL_Threads_Handler::get_variable_string(char *name) { if (!strcmp(name,"keep_multiplexing_variables")) return strdup(variables.keep_multiplexing_variables); if (!strcmp(name,"default_authentication_plugin")) return strdup(variables.default_authentication_plugin); if (!strcmp(name,"proxy_protocol_networks")) return strdup(variables.proxy_protocol_networks); + // LCOV_EXCL_START proxy_error("Not existing variable: %s\n", name); assert(0); return NULL; @@ -2171,6 +2174,7 @@ char ** MySQL_Threads_Handler::get_variables_list() { VariablesPointers_bool["stats_time_query_processor"] = make_tuple(&variables.stats_time_query_processor, false); VariablesPointers_bool["use_tcp_keepalive"] = make_tuple(&variables.use_tcp_keepalive, false); VariablesPointers_bool["verbose_query_error"] = make_tuple(&variables.verbose_query_error, false); + VariablesPointers_bool["ignore_min_gtid_annotations"] = make_tuple(&variables.ignore_min_gtid_annotations, false); #ifdef IDLE_THREADS VariablesPointers_bool["session_idle_show_processlist"] = make_tuple(&variables.session_idle_show_processlist, false); #endif // IDLE_THREADS @@ -2291,16 +2295,16 @@ char ** MySQL_Threads_Handler::get_variables_list() { VariablesPointers_int["eventslog_default_log"] = make_tuple(&variables.eventslog_default_log, 0, 1, false); VariablesPointers_int["eventslog_stmt_parameters"] = make_tuple(&variables.eventslog_stmt_parameters, 0, 1, false); // various - VariablesPointers_int["long_query_time"] = make_tuple(&variables.long_query_time, 0, 20*24*3600*1000, false); - VariablesPointers_int["max_allowed_packet"] = make_tuple(&variables.max_allowed_packet, 8192, 1024*1024*1024, false); - VariablesPointers_int["max_connections"] = make_tuple(&variables.max_connections, 1, 1000*1000, false); - VariablesPointers_int["max_stmts_per_connection"] = make_tuple(&variables.max_stmts_per_connection, 1, 1024, false); - VariablesPointers_int["max_stmts_cache"] = make_tuple(&variables.max_stmts_cache, 128, 1024*1024, false); - VariablesPointers_int["max_transaction_idle_time"] = make_tuple(&variables.max_transaction_idle_time, 1000, 20*24*3600*1000, false); - VariablesPointers_int["max_transaction_time"] = make_tuple(&variables.max_transaction_time, 1000, 20*24*3600*1000, false); - VariablesPointers_int["query_cache_size_mb"] = make_tuple(&variables.query_cache_size_MB, 0, 1024*10240, false); - VariablesPointers_int["query_cache_soft_ttl_pct"] = make_tuple(&variables.query_cache_soft_ttl_pct, 0, 100, false); - VariablesPointers_int["query_cache_handle_warnings"] = make_tuple(&variables.query_cache_handle_warnings, 0, 1, false); + VariablesPointers_int["long_query_time"] = make_tuple(&variables.long_query_time, 0, 20*24*3600*1000, false); + VariablesPointers_int["max_allowed_packet"] = make_tuple(&variables.max_allowed_packet, 8192, 1024*1024*1024, false); + VariablesPointers_int["max_connections"] = make_tuple(&variables.max_connections, 1, 1000*1000, false); + VariablesPointers_int["max_stmts_per_connection"] = make_tuple(&variables.max_stmts_per_connection, 1, 1024, false); + VariablesPointers_int["max_stmts_cache"] = make_tuple(&variables.max_stmts_cache, 128, 1024*1024, false); + VariablesPointers_int["max_transaction_idle_time"] = make_tuple(&variables.max_transaction_idle_time, 1000, 20*24*3600*1000, false); + VariablesPointers_int["max_transaction_time"] = make_tuple(&variables.max_transaction_time, 1000, 20*24*3600*1000, false); + VariablesPointers_int["query_cache_size_mb"] = make_tuple(&variables.query_cache_size_MB, 0, 1024*10240, false); + VariablesPointers_int["query_cache_soft_ttl_pct"] = make_tuple(&variables.query_cache_soft_ttl_pct, 0, 100, false); + VariablesPointers_int["query_cache_handle_warnings"] = make_tuple(&variables.query_cache_handle_warnings, 0, 1, false); #ifdef IDLE_THREADS VariablesPointers_int["session_idle_ms"] = make_tuple(&variables.session_idle_ms, 1, 3600*1000, false); @@ -4268,6 +4272,7 @@ void MySQL_Thread::refresh_variables() { REFRESH_VARIABLE_INT(client_host_error_counts); REFRESH_VARIABLE_INT(handle_warnings); REFRESH_VARIABLE_INT(evaluate_replication_lag_on_servers_load); + REFRESH_VARIABLE_BOOL(ignore_min_gtid_annotations); #ifdef DEBUG REFRESH_VARIABLE_BOOL(session_debug); #endif /* DEBUG */ From c23930b2eebe7ee59e0baac6f8ebdd79a96e8beb Mon Sep 17 00:00:00 2001 From: Lisandro Pin Date: Thu, 30 Oct 2025 11:54:15 +0100 Subject: [PATCH 2/2] Add TAP test for `mysql-ignore_min_gtid_annotations` Co-authored-by: Wazir Ahmed --- test/tap/groups/groups.json | 3 +- test/tap/tests/Makefile | 3 ++ test/tap/tests/test_ignore_min_gtid-t.cpp | 66 +++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 test/tap/tests/test_ignore_min_gtid-t.cpp diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index c49515924..97b43321b 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -226,5 +226,6 @@ "test_ssl_fast_forward-2_libmariadb-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ], "test_ssl_fast_forward-2_libmysql-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ], "test_ssl_fast_forward-3_libmariadb-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ], - "test_ssl_fast_forward-3_libmysql-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ] + "test_ssl_fast_forward-3_libmysql-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ], + "test_ignore_min_gtid-t": [ "default-g4", "mysql-auto_increment_delay_multiplex=0-g4", "mysql-multiplexing=false-g4", "mysql-query_digests=0-g4", "mysql-query_digests_keep_comment=1-g4" ] } diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index 082923fed..ae8833715 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -193,6 +193,9 @@ sqlite3-t: sqlite3-t.cpp $(TAP_LDIR)/libtap.so test_gtid_forwarding-t: test_gtid_forwarding-t.cpp $(TAP_LDIR)/libtap.so $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) -o $@ +test_ignore_min_gtid-t: test_ignore_min_gtid-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_ignore_min_gtid-t.cpp b/test/tap/tests/test_ignore_min_gtid-t.cpp new file mode 100644 index 000000000..fdbb8a701 --- /dev/null +++ b/test/tap/tests/test_ignore_min_gtid-t.cpp @@ -0,0 +1,66 @@ +/** + * @file test_ignore_min_gtid-t.cpp + * @brief This test file verifies the functionality of the mysql-ignore_min_gtid_annotations variable. + * - Sets mysql-ignore_min_gtid_annotations=true, so queries annotated with min_gtid succeed. + * - Sets mysql-ignore_min_gtid_annotations=false, so queries annotated with min_gtid fail. + */ + +#include + +#include "command_line.h" +#include "mysql.h" +#include "tap.h" +#include "utils.h" + +int main(int, char**) { + CommandLine cl; + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return -1; + } + + plan(2); + + 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)); + return exit_status(); + } + + const char *query = "SELECT /*+ ;min_gtid=01010101-0101-0101-0101-010101010101:101010101010 */ 1"; + + diag(" ========== Test 1 =========="); + + MYSQL_QUERY_T(admin, "SET mysql-ignore_min_gtid_annotations = true"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + int rc = run_q(proxy, query); + if (rc == 0) { + MYSQL_RES* result = mysql_store_result(proxy); + mysql_free_result(result); + } + ok(rc == 0, "Query execution should be successful. mysql-ignore_min_gtid_annotations=%s, rc=%d", "true", rc); + + diag(" ========== Test 2 =========="); + + MYSQL_QUERY_T(admin, "SET mysql-ignore_min_gtid_annotations = false"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + rc = run_q(proxy, query); + if (rc == 0) { + MYSQL_RES* result = mysql_store_result(proxy); + mysql_free_result(result); + } + ok(rc != 0, "Query execution should fail. mysql-ignore_min_gtid_annotations=%s, rc=%d", "false", rc); + + mysql_close(admin); + mysql_close(proxy); + + return exit_status(); +}