diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index 25826dbb15..12e191dcf2 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -225,6 +225,7 @@ class MySQL_Session: public Base_Session&); void reduce_auto_increment_delay_token() { if (auto_increment_delay_token) auto_increment_delay_token--; }; bool match_ff_req_options(const MySQL_Connection *c); diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index fdcfa4a307..7f65aa9a37 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -311,6 +311,7 @@ enum session_status { SETTING_NEXT_TRANSACTION_READ, PROCESSING_EXTENDED_QUERY_SYNC, RESYNCHRONIZING_CONNECTION, + SETTING_SESSION_TRACK_VARIABLES, session_status___NONE // special marker }; diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index c6c97b5dc9..21f2870db3 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -1953,6 +1953,16 @@ bool MySQL_Session::handler_again___verify_backend_session_track_gtids() { return ret; } +bool MySQL_Session::handler_again___verify_backend_session_track_variables() { + if (mybe->server_myds->myconn->options.session_track_variables_sent == false) { + mybe->server_myds->myconn->options.session_track_variables_sent = true; + set_previous_status_mode3(); + NEXT_IMMEDIATE_NEW(SETTING_SESSION_TRACK_VARIABLES); + } + + return false; +} + bool MySQL_Session::handler_again___verify_multiple_variables(MySQL_Connection* myconn) { for (auto i = 0; i < SQL_NAME_LAST_LOW_WM; i++) { @@ -2565,6 +2575,12 @@ bool MySQL_Session::handler_again___status_SETTING_GENERIC_VARIABLE(int *_rc, co free(query); query=NULL; } + + if (rc == 0 && strncasecmp(var_name, "session_track_system_variables", strlen("session_track_system_variables")) == 0) { + char *q = (char *)"SET session_track_state_change=ON"; + rc = myconn->async_send_simple_command(myds->revents, q, strlen(q)); + } + if (rc==0) { myds->revents|=POLLOUT; // we also set again POLLOUT to send a query immediately! myds->DSS = STATE_MARIADB_GENERIC; @@ -2753,6 +2769,13 @@ bool MySQL_Session::handler_again___status_SETTING_SESSION_TRACK_GTIDS(int *_rc) return ret; } +bool MySQL_Session::handler_again___status_SETTING_SESSION_TRACK_VARIABLES(int *_rc) { + bool ret=false; + assert(mybe->server_myds->myconn); + ret = handler_again___status_SETTING_GENERIC_VARIABLE(_rc, (char *)"session_track_system_variables", "*", false); + return ret; +} + bool MySQL_Session::handler_again___status_CHANGING_SCHEMA(int *_rc) { bool ret=false; //fprintf(stderr,"CHANGING_SCHEMA\n"); @@ -4862,6 +4885,28 @@ void MySQL_Session::handler_rc0_Process_GTID(MySQL_Connection *myconn) { } } +void MySQL_Session::handler_rc0_Process_Variables(MySQL_Connection *myconn) { + std::unordered_map var_map; + + if(myconn->get_variables(var_map)) { + const char *variable = nullptr; + const char *value = nullptr; + + for (int idx = 0 ; idx < SQL_NAME_LAST_HIGH_WM ; idx++) { + variable = mysql_tracked_variables[idx].set_variable_name; + + auto itr = var_map.find(variable); + if(itr != var_map.end()) { + value = itr->second.c_str(); + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session=%p, backend=%p. Notification for session_track_system_variables: variable=%s, value=%s\n", this, this->mybe, variable, value); + + mysql_variables.client_set_value(this, idx, value); + mysql_variables.server_set_value(this, idx, value); + } + } + } +} + void MySQL_Session::handler_KillConnectionIfNeeded() { if ( // two conditions // If the server connection is in a non-idle state (ASYNC_IDLE), and the current time is greater than or equal to mybe->server_myds->wait_until @@ -5059,6 +5104,10 @@ int MySQL_Session::handler() { goto handler_again; } + if (handler_again___verify_backend_session_track_variables()) { + goto handler_again; + } + // Optimize network traffic when we can use 'SET NAMES' if (verify_set_names(this)) { goto handler_again; @@ -5139,6 +5188,8 @@ int MySQL_Session::handler() { handler_rc0_Process_GTID(myconn); + handler_rc0_Process_Variables(myconn); + // if we are locked on hostgroup, the value of autocommit is copied from the backend connection // see bug #3549 if (locked_on_hostgroup >= 0) { @@ -5435,6 +5486,9 @@ bool MySQL_Session::handler_again___multiple_statuses(int *rc) { case SETTING_SESSION_TRACK_GTIDS: ret = handler_again___status_SETTING_SESSION_TRACK_GTIDS(rc); break; + case SETTING_SESSION_TRACK_VARIABLES: + ret = handler_again___status_SETTING_SESSION_TRACK_VARIABLES(rc); + break; case SETTING_SET_NAMES: ret = handler_again___status_CHANGING_CHARSET(rc); break; diff --git a/lib/mysql_connection.cpp b/lib/mysql_connection.cpp index 0e5367edfc..6c2c7947de 100644 --- a/lib/mysql_connection.cpp +++ b/lib/mysql_connection.cpp @@ -452,6 +452,7 @@ MySQL_Connection::MySQL_Connection() { options.init_connect_sent=false; options.session_track_gtids = NULL; options.session_track_gtids_sent = false; + options.session_track_variables_sent = false; options.ldap_user_variable=NULL; options.ldap_user_variable_value=NULL; options.ldap_user_variable_sent=false; @@ -3082,6 +3083,7 @@ void MySQL_Connection::reset() { options.session_track_gtids = NULL; options.session_track_gtids_sent = false; } + options.session_track_variables_sent = false; } bool MySQL_Connection::get_gtid(char *buff, uint64_t *trx_id) { @@ -3116,6 +3118,48 @@ bool MySQL_Connection::get_gtid(char *buff, uint64_t *trx_id) { return ret; } +bool MySQL_Connection::get_variables(std::unordered_map& variables) { + bool ret = false; + + if ((mysql != nullptr) + && (mysql->net.last_errno == 0) + && (mysql->server_status & SERVER_SESSION_STATE_CHANGED)) { + // when there is no error and status changed + const char *data; + size_t length; + + if (mysql_session_track_get_first(mysql, SESSION_TRACK_SYSTEM_VARIABLES, &data, &length) == 0) { + string var_name(data, length); + string val; + + // get_first() returns a variable_name + // get_next() will return the value + bool expect_value = true; + + while (mysql_session_track_get_next(mysql, SESSION_TRACK_SYSTEM_VARIABLES, &data, &length) == 0) { + if (expect_value) { + val = string(data, length); + variables[var_name] = val; + // got a value in this iteration + // in the next iteration, we have to expect a variable_name + expect_value = false; + } else { + var_name = string(data, length); + // got a variable_name in this iteration + // in the next iteration, we have to expect the value of this variable + expect_value = true; + } + } + + // update counters + // __sync_fetch_and_add(&myds->sess->thread->status_variables.stvar[st_var_gtid_session_collected],1); + ret = true; + } + } + + return ret; +} + void MySQL_Connection::set_ssl_params(MYSQL *mysql, MySQLServers_SslParams *ssl_params) { if (ssl_params == NULL) { mysql_ssl_set(mysql, diff --git a/test/tap/groups/groups.json b/test/tap/groups/groups.json index 7ecfd9e215..bfe4fbbe51 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -205,5 +205,7 @@ "eof_mixed_flags_queries-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" ], "eof_packet_mixed_queries-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" ], "ok_packet_mixed_queries-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_load_from_config_validation-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_load_from_config_validation-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" ], + "mysql-show_ssl_version-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" ], + "mysql-track_system_variables-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/mysql-track_system_variables-t.cpp b/test/tap/tests/mysql-track_system_variables-t.cpp new file mode 100644 index 0000000000..523b67797e --- /dev/null +++ b/test/tap/tests/mysql-track_system_variables-t.cpp @@ -0,0 +1,87 @@ +/** + * @file test_track_system_variables-t.cpp + * @brief This test verifies that ProxySQL properly tracks session-specific + * system variables across the backend connections. + */ + +#include +#include +#include "json.hpp" +#include "mysql.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using nlohmann::json; + +int main(int argc, char** argv) { + CommandLine cl; + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return exit_status(); + } + + plan(1); + + MYSQL* proxy = init_mysql_conn(cl.host, cl.port, cl.username, cl.password, true); + if (!proxy) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy)); + return exit_status(); + } + + MYSQL_QUERY_T(proxy, "USE test"); + MYSQL_QUERY_T(proxy, "SELECT 1"); + MYSQL_RES* reset_result = mysql_store_result(proxy); + mysql_free_result(reset_result); + + MYSQL_QUERY_T(proxy, "DROP PROCEDURE set_innodb_lock_wait_timeout"); + const char* create_proc = + "CREATE PROCEDURE set_innodb_lock_wait_timeout() " + "BEGIN " + " SET innodb_lock_wait_timeout = CAST(FLOOR(50 + (RAND() * 100)) AS UNSIGNED); " + "END"; + + MYSQL_QUERY_T(proxy, create_proc); + MYSQL_QUERY_T(proxy, "CALL set_innodb_lock_wait_timeout()"); + + int set_value = -1; + MYSQL_QUERY_T(proxy, "SELECT @@innodb_lock_wait_timeout"); + MYSQL_RES* result = mysql_store_result(proxy); + if (result) { + MYSQL_ROW row = mysql_fetch_row(result); + if (row) { + set_value = atoi(row[0]); + } + mysql_free_result(result); + } + + MYSQL_QUERY(proxy, "PROXYSQL INTERNAL SESSION"); + result = mysql_store_result(proxy); + MYSQL_ROW row = mysql_fetch_row(result); + auto j_session = nlohmann::json::parse(row[0]); + mysql_free_result(result); + + int backend_value = -1; + int client_value = -1; + if (j_session.contains("backends")) { + for (auto& backend : j_session["backends"]) { + if (backend != nullptr && backend.contains("conn")) { + if (backend["conn"].contains("innodb_lock_wait_timeout")) { + backend_value = std::stoi(backend["conn"]["innodb_lock_wait_timeout"].get()); + break; + } + } + } + } + if (j_session.contains("conn")) { + if (j_session["conn"].contains("innodb_lock_wait_timeout")) { + client_value = std::stoi(j_session["conn"]["innodb_lock_wait_timeout"].get()); + } + } + + ok(set_value == backend_value && set_value == client_value, + "Match session innodb_lock_wait_timeout value with backend & client variable list. Expected: %d, Backend: %d, Client: %d", set_value, backend_value, client_value); + + mysql_close(proxy); + return exit_status(); +}