diff --git a/include/MySQL_HostGroups_Manager.h b/include/MySQL_HostGroups_Manager.h index 9bdf7b5e89..8370159e77 100644 --- a/include/MySQL_HostGroups_Manager.h +++ b/include/MySQL_HostGroups_Manager.h @@ -227,6 +227,15 @@ class MySrvC { // MySQL Server Container bool shunned_and_kill_all_connections; // if a serious failure is detected, this will cause all connections to die even if the server is just shunned int32_t use_ssl; char *comment; + + // 'server_backoff_time' stores a timestamp that prevents the server from being + // considered for random selection ('MyHGC::get_random_MySrvC') until that time passes. + // + // This is primarily used when `session_track_variables::ENFORCED` mode is active. + // If a server lacks the required capabilities in this mode, it is temporarily + // excluded from selection for a specified duration. + unsigned long long server_backoff_time; + MySrvConnList *ConnectionsUsed; MySrvConnList *ConnectionsFree; /** diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index 45c6231f4d..59e310a61e 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -241,6 +241,36 @@ 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 c2d726910e..9b19b97be0 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -311,6 +311,8 @@ enum session_status { SETTING_NEXT_TRANSACTION_READ, PROCESSING_EXTENDED_QUERY_SYNC, RESYNCHRONIZING_CONNECTION, + SETTING_SESSION_TRACK_VARIABLES, + SETTING_SESSION_TRACK_STATE, session_status___NONE // special marker }; @@ -1305,6 +1307,7 @@ __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; +__thread int mysql_thread___session_track_variables; /* variables used for Query Cache */ __thread int mysql_thread___query_cache_size_MB; @@ -1609,6 +1612,7 @@ 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; +extern __thread int mysql_thread___session_track_variables; /* variables used for Query Cache */ extern __thread int mysql_thread___query_cache_size_MB; diff --git a/lib/MyHGC.cpp b/lib/MyHGC.cpp index 8af6956d17..8208f77073 100644 --- a/lib/MyHGC.cpp +++ b/lib/MyHGC.cpp @@ -38,6 +38,10 @@ MySrvC *MyHGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_ for (j=0; jidx(j); if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { // consider this server only if ONLINE + // skip servers that are in backoff period + if (mysrvc->server_backoff_time > sess->thread->curtime) + continue; + if (mysrvc->myhgc->num_online_servers.load(std::memory_order_relaxed) <= mysrvc->myhgc->attributes.max_num_online_servers) { // number of online servers in HG is within configured range if (mysrvc->ConnectionsUsed->conns_length() < mysrvc->max_connections) { // consider this server only if didn't reach max_connections if (mysrvc->current_latency_us < (mysrvc->max_latency_us ? mysrvc->max_latency_us : mysql_thread___default_max_latency_ms*1000)) { // consider the host only if not too far diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index f83ff07005..0d5ab220eb 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -1111,9 +1111,13 @@ bool MySQL_Protocol::generate_pkt_initial_handshake(bool send, void **ptr, unsig // variable. This is the first step of ensuring that client connections doesn't // enable 'CLIENT_DEPRECATE_EOF' unless explicitly stated by 'mysql-enable_client_deprecate_eof'. // Second step occurs during client handshake response (process_pkt_handshake_response). - if (deprecate_eof_active && mysql_thread___enable_client_deprecate_eof) { - extended_capabilities |= CLIENT_DEPRECATE_EOF; + if (deprecate_eof_active) { + if (mysql_thread___enable_client_deprecate_eof + || mysql_thread___session_track_variables == session_track_variables::ENFORCED) { + extended_capabilities |= CLIENT_DEPRECATE_EOF; + } } + // Copy the 'capability_flags_2' uint16_t upper_word = static_cast(extended_capabilities >> 16); memcpy(_ptr+l, static_cast(&upper_word), sizeof(upper_word)); l += sizeof(upper_word); diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 6e4bc4cd5c..ebad1ccc04 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -1915,10 +1915,12 @@ bool MySQL_Session::handler_again___verify_backend_session_track_gtids() { proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session %p , client: %s , backend: %s\n", this, client_myds->myconn->options.session_track_gtids, mybe->server_myds->myconn->options.session_track_gtids); // we first verify that the backend supports it // if backend is old (or if it is not mysql) ignore this setting - if ((mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_SESSION_TRACKING) == 0) { - // the backend doesn't support CLIENT_SESSION_TRACKING - return ret; // exit immediately + if ((mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_SESSION_TRACKING) == 0 + || (mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_DEPRECATE_EOF) == 0 + || mysql_thread___enable_server_deprecate_eof == false) { + return ret; } + uint32_t b_int = mybe->server_myds->myconn->options.session_track_gtids_int; uint32_t f_int = client_myds->myconn->options.session_track_gtids_int; @@ -1964,6 +1966,35 @@ bool MySQL_Session::handler_again___verify_backend_session_track_gtids() { return ret; } +bool MySQL_Session::handler_again___verify_backend_session_track_variables() { + int mode = mysql_thread___session_track_variables; + + // skip enabling session variable tracking in the following cases + if (mode == session_track_variables::DISABLED) { + return false; + } + if ((mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_SESSION_TRACKING) == 0 + || (mybe->server_myds->myconn->mysql->server_capabilities & CLIENT_DEPRECATE_EOF) == 0) { + return false; + } + if (!mysql_thread___enable_server_deprecate_eof && mode != session_track_variables::ENFORCED) { + return false; + } + + // enable session tracking + 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); + } + if (mybe->server_myds->myconn->options.session_track_state_sent == false) { + mybe->server_myds->myconn->options.session_track_state_sent = true; + set_previous_status_mode3(); + NEXT_IMMEDIATE_NEW(SETTING_SESSION_TRACK_STATE); + } + + return false; +} bool MySQL_Session::handler_again___verify_multiple_variables(MySQL_Connection* myconn) { for (auto i = 0; i < SQL_NAME_LAST_LOW_WM; i++) { @@ -2764,6 +2795,20 @@ 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_SETTING_SESSION_TRACK_STATE(int *_rc) { + bool ret=false; + assert(mybe->server_myds->myconn); + ret = handler_again___status_SETTING_GENERIC_VARIABLE(_rc, (char *)"session_track_state_change", "ON", false); + return ret; +} + bool MySQL_Session::handler_again___status_CHANGING_SCHEMA(int *_rc) { bool ret=false; //fprintf(stderr,"CHANGING_SCHEMA\n"); @@ -2911,6 +2956,11 @@ bool MySQL_Session::handler_again___status_CONNECTING_SERVER(int *_rc) { } enum session_status st=status; if (mybe->server_myds->myconn->async_state_machine==ASYNC_IDLE) { + if (handle_session_track_capabilities() == false) { + pause_until = thread->curtime + mysql_thread___connect_retries_delay*1000; + return false; + } + st=previous_status.top(); previous_status.pop(); NEXT_IMMEDIATE_NEW(st); @@ -2940,6 +2990,13 @@ bool MySQL_Session::handler_again___status_CONNECTING_SERVER(int *_rc) { previous_status.pop(); myds->wait_until=0; + if (handle_session_track_capabilities() == false) { + previous_status.push(st); + pause_until = thread->curtime + mysql_thread___connect_retries_delay * 1000; + set_status(CONNECTING_SERVER); + return false; + } + // NOTE: Even if a connection has correctly been created, since the CLIENT_DEPRECATE_EOF // capability isn't always enforced to match for backend conns (no direct propagation), a // mismatch can take place after the creation. Right now this is only true for @@ -4905,6 +4962,41 @@ 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)) { + std::string variable; + std::string value; + + 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; + 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.c_str(), value.c_str()); + + const MARIADB_CHARSET_INFO *ci = NULL; + if (variable == "character_set_results" || variable == "character_set_connection" || + variable == "character_set_client" || variable == "character_set_database") { + ci = proxysql_find_charset_name(value.c_str()); + } + else if (variable == "collation_connection") { + ci = proxysql_find_charset_collate(value.c_str()); + } + + if (ci) { + value = std::to_string(ci->nr); + } + + mysql_variables.client_set_value(this, idx, value); + mysql_variables.server_set_value(this, idx, value.c_str()); + } + } + } +} + 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 @@ -5102,6 +5194,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; @@ -5182,6 +5278,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) { @@ -5478,6 +5576,12 @@ 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_SESSION_TRACK_STATE: + ret = handler_again___status_SETTING_SESSION_TRACK_STATE(rc); + break; case SETTING_SET_NAMES: ret = handler_again___status_CHANGING_CHARSET(rc); break; @@ -8445,3 +8549,57 @@ char* MySQL_Session::get_current_query(int max_length) { return res; } + +/** + * @brief Handle session track capabilities validation. + * + * This function validates whether the backend connection has capabilities such as 'CLIENT_DEPRECATE_EOF' + * and 'CLIENT_SESSION_TRACKING' which are required to enable 'session_track_system_variables' in a MySQL session. + * + * If the connection lacks the capabilities and ProxySQL configuration is set in 'ENFORCED' mode, it returns the + * connection to the pool and set a backoff time for the backend server. This backoff time prevents the server from + * being selected again during connection pooling. + * + * @return 'true' if backend connection has required capabilities, otherwise returns 'false'. + */ +bool MySQL_Session::handle_session_track_capabilities() { + if (mysql_thread___session_track_variables != session_track_variables::ENFORCED) { + return true; + } + + // this function should not be called in these states + if (client_myds == NULL + || client_myds->myconn == NULL + || mybe == NULL + || mybe->server_myds == NULL + || mybe->server_myds->myconn == NULL + || mybe->server_myds->myconn->mysql == NULL) { + return true; + } + + MySQL_Connection *be_conn = mybe->server_myds->myconn; + unsigned long srv_cap = be_conn->mysql->server_capabilities; + uint32_t client_flag = client_myds->myconn->options.client_flag; + + bool client_support_session_track = ((client_flag & CLIENT_SESSION_TRACKING) != 0 && (client_flag & CLIENT_DEPRECATE_EOF) != 0); + bool server_support_session_track = ((srv_cap & CLIENT_SESSION_TRACKING) != 0 && (srv_cap & CLIENT_DEPRECATE_EOF) != 0); + + // In fast forward, if client does not support session tracking, + // then ProxySQL do not have to enforce session track on backend connections + if ((session_fast_forward == SESSION_FORWARD_TYPE_PERMANENT) && !client_support_session_track) { + return true; + } + + if (!server_support_session_track) { + // be_conn->parent->server_backoff_time = thread->curtime + (600 * 1000000); // 10 minutes + be_conn->parent->server_backoff_time = thread->curtime + (30 * 1000000); // 30 seconds + if (session_fast_forward) { + mybe->server_myds->destroy_MySQL_Connection_From_Pool(false); + } else { + mybe->server_myds->return_MySQL_Connection_To_Pool(); + } + return false; + } + + return true; +} diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index d9e721b436..6480f34c6d 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -517,6 +517,7 @@ static char * mysql_thread_variables_names[]= { (char *)"protocol_compression_level", (char *)"ignore_min_gtid_annotations", (char *)"fast_forward_grace_close_ms", + (char *)"session_track_variables", NULL }; @@ -1180,6 +1181,8 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() { variables.data_packets_history_size=0; variables.protocol_compression_level=3; variables.ignore_min_gtid_annotations=false; + variables.session_track_variables=session_track_variables::DISABLED; + // status variables status_variables.mirror_sessions_current=0; __global_MySQL_Thread_Variables_version=1; @@ -2361,7 +2364,7 @@ char ** MySQL_Threads_Handler::get_variables_list() { VariablesPointers_int["wait_timeout"] = make_tuple(&variables.wait_timeout, 0, 0, true); VariablesPointers_int["select_version_forwarding"] = make_tuple(&variables.select_version_forwarding, 0, 3, false); VariablesPointers_int["data_packets_history_size"] = make_tuple(&variables.data_packets_history_size, 0, 0, true); - + VariablesPointers_int["session_track_variables"] = make_tuple(&variables.session_track_variables, 0, 2, false); } @@ -4399,6 +4402,7 @@ void MySQL_Thread::refresh_variables() { REFRESH_VARIABLE_INT(handle_warnings); REFRESH_VARIABLE_INT(evaluate_replication_lag_on_servers_load); REFRESH_VARIABLE_BOOL(ignore_min_gtid_annotations); + REFRESH_VARIABLE_INT(session_track_variables); #ifdef DEBUG REFRESH_VARIABLE_BOOL(session_debug); #endif /* DEBUG */ @@ -5737,7 +5741,11 @@ MySQL_Connection * MySQL_Thread::get_MyConn_local(unsigned int _hid, MySQL_Sessi std::vector parents; // this is a vector of srvers that needs to be excluded in case gtid_uuid is used MySQL_Connection *c=NULL; for (i=0; ilen; i++) { - c=(MySQL_Connection *)cached_connections->index(i); + c = (MySQL_Connection *) cached_connections->index(i); + // skip servers that are in backoff period + if (c->parent->server_backoff_time > curtime) + continue; + if (c->parent->myhgc->hid==_hid && sess->client_myds->myconn->match_tracked_options(c)) { // options are all identical if ( (gtid_uuid == NULL) || // gtid_uuid is not used diff --git a/lib/MySrvC.cpp b/lib/MySrvC.cpp index 860ae56efe..1e94b41aac 100644 --- a/lib/MySrvC.cpp +++ b/lib/MySrvC.cpp @@ -31,6 +31,7 @@ MySrvC::MySrvC( bytes_recv=0; max_connections_used=0; queries_gtid_sync=0; + server_backoff_time = 0; time_last_detected_error=0; connect_ERR_at_time_last_detected_error=0; shunned_automatic=false; diff --git a/lib/mysql_connection.cpp b/lib/mysql_connection.cpp index 6c85315753..daf517bfe7 100644 --- a/lib/mysql_connection.cpp +++ b/lib/mysql_connection.cpp @@ -452,6 +452,8 @@ 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.session_track_state_sent = false; options.ldap_user_variable=NULL; options.ldap_user_variable_value=NULL; options.ldap_user_variable_sent=false; @@ -887,13 +889,18 @@ void MySQL_Connection::connect_start_SetClientFlag(unsigned long& client_flags) } } - // set 'CLIENT_DEPRECATE_EOF' flag if explicitly stated by 'mysql-enable_server_deprecate_eof'. - // Capability is disabled by default in 'mariadb_client', so setting this option is not optional - // for having 'CLIENT_DEPRECATE_EOF' in the connection to be stablished. + // 'CLIENT_DEPRECATE_EOF' capability is disabled by default in mariadb_client. + // Based on the value of 'mysql-enable_server_deprecate_eof', enable this + // capability in a new connection. if (mysql_thread___enable_server_deprecate_eof) { mysql->options.client_flag |= CLIENT_DEPRECATE_EOF; } + // override 'mysql-enable_server_deprecate_eof' behavior if 'session_track_variables' is set to 'ENFORCED' + if (mysql_thread___session_track_variables == session_track_variables::ENFORCED) { + mysql->options.client_flag |= CLIENT_DEPRECATE_EOF; + } + if (myds != NULL) { if (myds->sess != NULL) { if (myds->sess->session_fast_forward) { // this is a fast_forward connection @@ -3103,6 +3110,8 @@ void MySQL_Connection::reset() { options.session_track_gtids = NULL; options.session_track_gtids_sent = false; } + options.session_track_variables_sent = false; + options.session_track_state_sent = false; } bool MySQL_Connection::get_gtid(char *buff, uint64_t *trx_id) { @@ -3137,6 +3146,46 @@ 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; + } + } + + 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 8924971415..a9a92c5bf8 100644 --- a/test/tap/groups/groups.json +++ b/test/tap/groups/groups.json @@ -243,5 +243,9 @@ "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" ], "pgsql-query_digests_stages_test-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" ], "pgsql-monitor_ssl_connections_test-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-session_track_variables_optional-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", "mysql56-single-g4" ], + "mysql-session_track_variables_enforced-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", "mysql56-single-g4" ], + "mysql-session_track_variables_ff_optional-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", "mysql56-single-g4" ], + "mysql-session_track_variables_ff_enforced-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", "mysql56-single-g4" ] "unit-strip_schema_from_query-t": [ "unit-tests-g1" ] } diff --git a/test/tap/groups/mysql56-single/pre-proxysql.bash b/test/tap/groups/mysql56-single/pre-proxysql.bash new file mode 100755 index 0000000000..4bea061044 --- /dev/null +++ b/test/tap/groups/mysql56-single/pre-proxysql.bash @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# +# change infra config +# inherits env from tester script +# + +[[ $(mysql --skip-ssl-verify-server-cert -h 2>&1) =~ skip-ssl-verify-server-cert ]] || export SSLOPT=--skip-ssl-verify-server-cert + +INFRA=infra-mysql56 + +# destroy running infras +$JENKINS_SCRIPTS_PATH/infra-default/docker-compose-destroy.bash +# cleanup +mysql ${SSLOPT} -h127.0.0.1 -P6032 -uadmin -padmin -e " \ +DELETE FROM mysql_users; \ +LOAD MYSQL USERS TO RUNTIME; \ +SAVE MYSQL USERS TO DISK; \ +DELETE FROM mysql_servers; \ +DELETE FROM mysql_replication_hostgroups; \ +DELETE FROM mysql_group_replication_hostgroups; \ +DELETE FROM mysql_galera_hostgroups; \ +LOAD MYSQL SERVERS TO RUNTIME; \ +SAVE MYSQL SERVERS TO DISK; \ +DELETE FROM mysql_query_rules; \ +LOAD MYSQL QUERY RULES TO RUNTIME; \ +SAVE MYSQL QUERY RULES TO DISK; \ +DELETE FROM pgsql_users; \ +LOAD PGSQL USERS TO RUNTIME; \ +SAVE PGSQL USERS TO DISK; \ +DELETE FROM pgsql_servers; \ +LOAD PGSQL SERVERS TO RUNTIME; \ +SAVE PGSQL SERVERS TO DISK; \ +#DELETE FROM pgsql_query_rules; \ +#LOAD PGSQL QUERY RULES TO RUNTIME; \ +#SAVE PGSQL QUERY RULES TO DISK; \ +" 2>&1 | grep -vP 'mysql: .?Warning' + +# load environment for infra +source $JENKINS_SCRIPTS_PATH/${INFRA}/.env + +# Start infra +$JENKINS_SCRIPTS_PATH/infra-docker-hoster/docker-compose-init.bash +$JENKINS_SCRIPTS_PATH/${INFRA}/docker-compose-init.bash + +# create default users +for MYUSER in root user testuser sbtest1 sbtest2 sbtest3 sbtest4 ssluser ; do + # FIXME: using 0 as default hostgroup + mysql ${SSLOPT} -h127.0.0.1 -P6032 -uadmin -padmin -e " \ + INSERT OR IGNORE INTO mysql_users (username,password,active,default_hostgroup,comment) values ('${MYUSER}','${MYUSER}',1,${WHG},'${INFRA}'); \ + UPDATE mysql_users SET default_hostgroup=0,comment='${INFRA}' WHERE username='${MYUSER}'; \ + " 2>&1 | grep -vP 'mysql: .?Warning' +done +mysql ${SSLOPT} -h127.0.0.1 -P6032 -uadmin -padmin -e " \ +LOAD MYSQL USERS TO RUNTIME; \ +SAVE MYSQL USERS TO DISK; \ +" 2>&1 | grep -vP 'mysql: .?Warning' + +# create default hostgroups +mysql ${SSLOPT} -h127.0.0.1 -P6032 -uadmin -padmin -e " \ +DELETE FROM mysql_servers WHERE hostgroup_id IN (0,1); \ +INSERT INTO mysql_servers (hostgroup_id,hostname,port,max_replication_lag,comment) VALUES (0,'mysql1.${INFRA}',3306,1,'mysql1.${INFRA}'); \ +INSERT INTO mysql_servers (hostgroup_id,hostname,port,max_replication_lag,comment) VALUES (1,'mysql1.${INFRA}',3306,1,'mysql1.${INFRA}'); \ +LOAD MYSQL SERVERS TO RUNTIME; \ +SAVE MYSQL SERVERS TO DISK; \ +" 2>&1 | grep -vP 'mysql: .?Warning' + +# wait for infra to stabilize +sleep 10 diff --git a/test/tap/tests/mysql-session_track_variables_enforced-t.cpp b/test/tap/tests/mysql-session_track_variables_enforced-t.cpp new file mode 100644 index 0000000000..d374290e53 --- /dev/null +++ b/test/tap/tests/mysql-session_track_variables_enforced-t.cpp @@ -0,0 +1,178 @@ +/** + * @file mysql-session_track_variables_enforced-t.cpp + * @brief This test verifies that ProxySQL properly handles session variable tracking + * in ENFORCED mode based on MySQL server version. Session tracking should work + * on MySQL 5.7+ and backends without support should be rejected (queries should timeout). + */ + +#include +#include +#include "json.hpp" +#include "mysql.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using nlohmann::json; + +bool get_server_version(MYSQL* proxy, int& major, int& minor) { + MYSQL_QUERY_T(proxy, "SELECT @@version"); + MYSQL_RES* result = mysql_store_result(proxy); + if (!result) { + return false; + } + + MYSQL_ROW row = mysql_fetch_row(result); + if (!row) { + mysql_free_result(result); + return false; + } + + // Parse version string + if (sscanf(row[0], "%d.%d", &major, &minor) != 2) { + mysql_free_result(result); + return false; + } + + mysql_free_result(result); + return true; +} + +bool test_session_variables(MYSQL* proxy, int& set_value, int& backend_value, int& client_value) { + set_value = -1; + backend_value = -1; + client_value = -1; + + MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); + MYSQL_QUERY_T(proxy, "SELECT 1"); + mysql_free_result(mysql_store_result(proxy)); + + MYSQL_QUERY_T(proxy, "DROP PROCEDURE IF EXISTS test.set_innodb_lock_wait_timeout"); + const char* create_proc = + "CREATE PROCEDURE test.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 test.set_innodb_lock_wait_timeout()"); + + 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); + if (!result) { + return false; + } + + MYSQL_ROW row = mysql_fetch_row(result); + if (!row) { + mysql_free_result(result); + return false; + } + + auto j_session = nlohmann::json::parse(row[0]); + mysql_free_result(result); + + 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()); + } + } + + return true; +} + +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* admin = init_mysql_conn(cl.admin_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(); + } + + // Set session_track_variables to ENFORCED mode (value 2) + MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=2"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + 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(); + } + + int major = 0, minor = 0; + if (!get_server_version(proxy, major, minor)) { + diag("Failed to get server version"); + return exit_status(); + } + + diag("Detected MySQL version: %d.%d", major, minor); + + int set_value = -1; + int backend_value = -1; + int client_value = -1; + bool test_result = test_session_variables(proxy, set_value, backend_value, client_value); + + // Verify results based on server version + bool mysql57_plus = (major > 5) || (major == 5 && minor >= 7); + + if (mysql57_plus) { + if (!test_result) { + diag("Failed to run test"); + return exit_status(); + } + + ok(set_value == backend_value && set_value == client_value, + "MySQL %d.%d supports session tracking. Match innodb_lock_wait_timeout value with backend & client variable list. Expected: %d, Backend: %d, Client: %d", + major, minor, set_value, backend_value, client_value); + } else { + // MySQL 5.6 or below: enforced mode should reject connections with error 9001 + if (test_result) { + diag("Test unexpectedly passed on MySQL %d.%d without session tracking support", major, minor); + return exit_status(); + } + + int error_code = mysql_errno(proxy); + const char* error_msg = mysql_error(proxy); + + ok(error_code == 9001, + "MySQL %d.%d lacks session tracking. ENFORCED mode. Connection should timeout with 9001. Error: %d, Message: %s", + major, minor, error_code, error_msg); + } + + // Cleanup + MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + mysql_close(proxy); + mysql_close(admin); + return exit_status(); +} diff --git a/test/tap/tests/mysql-session_track_variables_ff_enforced-t.cpp b/test/tap/tests/mysql-session_track_variables_ff_enforced-t.cpp new file mode 100644 index 0000000000..98cea22196 --- /dev/null +++ b/test/tap/tests/mysql-session_track_variables_ff_enforced-t.cpp @@ -0,0 +1,195 @@ +/** + * @file mysql-session_track_variables_ff_enforced-t.cpp + * @brief This test verifies that ProxySQL properly handles session variable tracking + * in ENFORCED mode with fast_forward enabled. Session tracking should work + * on MySQL 5.7+ and backends without support should be rejected (queries fail). + */ + +#include +#include +#include "mysql.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +bool get_server_version(MYSQL* proxy, int& major, int& minor) { + MYSQL_QUERY_T(proxy, "SELECT @@version"); + MYSQL_RES* result = mysql_store_result(proxy); + if (!result) { + return false; + } + + MYSQL_ROW row = mysql_fetch_row(result); + if (!row) { + mysql_free_result(result); + return false; + } + + // Parse version string like "5.7.33" or "8.0.25" + if (sscanf(row[0], "%d.%d", &major, &minor) != 2) { + mysql_free_result(result); + return false; + } + + mysql_free_result(result); + return true; +} + +bool extract_session_variable(MYSQL* proxy, const char* var_name, int& tracked_value) { + tracked_value = -1; + + if ((proxy != nullptr) + && (proxy->net.last_errno == 0) + && (proxy->server_status & SERVER_SESSION_STATE_CHANGED)) { + const char *data; + size_t length; + + if (mysql_session_track_get_first(proxy, SESSION_TRACK_SYSTEM_VARIABLES, &data, &length) == 0) { + std::string current_var_name(data, length); + // get_first() returns a variable_name + // get_next() will return the value + bool expect_value = true; + + while (mysql_session_track_get_next(proxy, SESSION_TRACK_SYSTEM_VARIABLES, &data, &length) == 0) { + if (expect_value) { + // This is the value for current_var_name + if (current_var_name == var_name) { + std::string value_str(data, length); + tracked_value = atoi(value_str.c_str()); + return true; + } + // got a value in this iteration + // in the next iteration, we have to expect a variable_name + expect_value = false; + } else { + current_var_name = std::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; + } + } + } + } + + return false; +} + +bool test_session_variables(MYSQL* proxy, int& set_value, int& tracked_value) { + set_value = -1; + tracked_value = -1; + + MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); + MYSQL_QUERY_T(proxy, "SELECT 1"); + mysql_free_result(mysql_store_result(proxy)); + + MYSQL_QUERY_T(proxy, "DROP PROCEDURE IF EXISTS test.set_innodb_lock_wait_timeout"); + const char* create_proc = + "CREATE PROCEDURE test.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 test.set_innodb_lock_wait_timeout()"); + + extract_session_variable(proxy, "innodb_lock_wait_timeout", tracked_value); + + 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); + } + + return true; +} + +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* admin = init_mysql_conn(cl.admin_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(); + } + + // Set session_track_variables to ENFORCED mode (value 2) + MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=2"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + // Enable fast_forward for test user + MYSQL_QUERY_T(admin, "DELETE FROM mysql_users WHERE username='ff_test_user'"); + MYSQL_QUERY_T(admin, "INSERT INTO mysql_users (username,password,fast_forward,default_hostgroup) VALUES ('ff_test_user','ff_test_pass',1,0)"); + MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME"); + + MYSQL* proxy = mysql_init(NULL); + // Enable CLIENT_DEPRECATE_EOF. This is required for session tracking + proxy->options.client_flag |= CLIENT_DEPRECATE_EOF; + if (!mysql_real_connect(proxy, cl.host, "ff_test_user", "ff_test_pass", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy)); + return exit_status(); + } + + // Enable session tracking on client for fast_forward mode + MYSQL_QUERY_T(proxy, "SET session_track_system_variables='*'"); + MYSQL_QUERY_T(proxy, "SET session_track_state_change=ON"); + + int major = 0, minor = 0; + if (!get_server_version(proxy, major, minor)) { + diag("Failed to get server version"); + return exit_status(); + } + + diag("Detected MySQL version: %d.%d", major, minor); + + int set_value = -1; + int tracked_value = -1; + bool test_result = test_session_variables(proxy, set_value, tracked_value); + + // Verify results based on server version + bool mysql57_plus = (major > 5) || (major == 5 && minor >= 7); + + if (mysql57_plus) { + if (!test_result) { + diag("Failed to run test"); + return exit_status(); + } + + ok(set_value == tracked_value, + "MySQL %d.%d supports session tracking. Fast forward with ENFORCED mode: Expected: %d, Tracked: %d", + major, minor, set_value, tracked_value); + } else { + // MySQL 5.6 or below: enforced mode should reject connections with error 9001 + if (test_result) { + diag("Test unexpectedly passed on MySQL %d.%d without session tracking support", major, minor); + return exit_status(); + } + + int error_code = mysql_errno(proxy); + const char* error_msg = mysql_error(proxy); + + ok(error_code == 9001, + "MySQL %d.%d lacks session tracking. Fast forward with ENFORCED mode. Connection should timeout with 9001. Error: %d, Message: %s", + major, minor, error_code, error_msg); + } + + // Cleanup + MYSQL_QUERY_T(admin, "DELETE FROM mysql_users WHERE username='ff_test_user'"); + MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME"); + MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + mysql_close(proxy); + mysql_close(admin); + return exit_status(); +} diff --git a/test/tap/tests/mysql-session_track_variables_ff_optional-t.cpp b/test/tap/tests/mysql-session_track_variables_ff_optional-t.cpp new file mode 100644 index 0000000000..67859e2fa4 --- /dev/null +++ b/test/tap/tests/mysql-session_track_variables_ff_optional-t.cpp @@ -0,0 +1,186 @@ +/** + * @file mysql-session_track_variables_ff_optional-t.cpp + * @brief This test verifies that ProxySQL properly handles session variable tracking + * in OPTIONAL mode with fast_forward enabled. Session tracking should work + * on MySQL 5.7+ and gracefully degrade on 5.6 and below. + */ + +#include +#include +#include "mysql.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +bool get_server_version(MYSQL* proxy, int& major, int& minor) { + MYSQL_QUERY_T(proxy, "SELECT @@version"); + MYSQL_RES* result = mysql_store_result(proxy); + if (!result) { + return false; + } + + MYSQL_ROW row = mysql_fetch_row(result); + if (!row) { + mysql_free_result(result); + return false; + } + + // Parse version string + if (sscanf(row[0], "%d.%d", &major, &minor) != 2) { + mysql_free_result(result); + return false; + } + + mysql_free_result(result); + return true; +} + +bool extract_session_variable(MYSQL* proxy, const char* var_name, int& tracked_value) { + tracked_value = -1; + + if ((proxy != nullptr) + && (proxy->net.last_errno == 0) + && (proxy->server_status & SERVER_SESSION_STATE_CHANGED)) { + const char *data; + size_t length; + + if (mysql_session_track_get_first(proxy, SESSION_TRACK_SYSTEM_VARIABLES, &data, &length) == 0) { + std::string current_var_name(data, length); + // get_first() returns a variable_name + // get_next() will return the value + bool expect_value = true; + + while (mysql_session_track_get_next(proxy, SESSION_TRACK_SYSTEM_VARIABLES, &data, &length) == 0) { + if (expect_value) { + // This is the value for current_var_name + if (current_var_name == var_name) { + std::string value_str(data, length); + tracked_value = atoi(value_str.c_str()); + return true; + } + // got a value in this iteration + // in the next iteration, we have to expect a variable_name + expect_value = false; + } else { + current_var_name = std::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; + } + } + } + } + + return false; +} + +bool test_session_variables(MYSQL* proxy, int& set_value, int& tracked_value) { + set_value = -1; + tracked_value = -1; + + MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); + MYSQL_QUERY_T(proxy, "SELECT 1"); + mysql_free_result(mysql_store_result(proxy)); + + MYSQL_QUERY_T(proxy, "DROP PROCEDURE IF EXISTS test.set_innodb_lock_wait_timeout"); + const char* create_proc = + "CREATE PROCEDURE test.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 test.set_innodb_lock_wait_timeout()"); + + extract_session_variable(proxy, "innodb_lock_wait_timeout", tracked_value); + + 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); + } + + return true; +} + +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* admin = init_mysql_conn(cl.admin_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(); + } + + // Set session_track_variables to OPTIONAL mode (value 1) + MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=1"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + // Enable fast_forward for test user + MYSQL_QUERY_T(admin, "DELETE FROM mysql_users WHERE username='ff_test_user'"); + MYSQL_QUERY_T(admin, "INSERT INTO mysql_users (username,password,fast_forward,default_hostgroup) VALUES ('ff_test_user','ff_test_pass',1,0)"); + MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME"); + + MYSQL* proxy = mysql_init(NULL); + // Enable CLIENT_DEPRECATE_EOF. This is required for session tracking + proxy->options.client_flag |= CLIENT_DEPRECATE_EOF; + if (!mysql_real_connect(proxy, cl.host, "ff_test_user", "ff_test_pass", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy)); + return exit_status(); + } + + // Enable session tracking on client for fast_forward mode + MYSQL_QUERY_T(proxy, "SET session_track_system_variables='*'"); + MYSQL_QUERY_T(proxy, "SET session_track_state_change=ON"); + + int major = 0, minor = 0; + if (!get_server_version(proxy, major, minor)) { + diag("Failed to get server version"); + return exit_status(); + } + + diag("Detected MySQL version: %d.%d", major, minor); + + int set_value = -1; + int tracked_value = -1; + + if (!test_session_variables(proxy, set_value, tracked_value)) { + diag("Failed to run test"); + return exit_status(); + } + + // Verify results based on server version + bool mysql57_plus = (major > 5) || (major == 5 && minor >= 7); + + if (mysql57_plus) { + ok(set_value == tracked_value, + "MySQL %d.%d supports session tracking. Fast forward with OPTIONAL mode: Expected: %d, Tracked: %d", + major, minor, set_value, tracked_value); + } else { + // MySQL 5.6 or below: session tracking should not be enabled + ok(tracked_value == -1, + "MySQL %d.%d lacks session tracking. Fast forward with OPTIONAL mode: Tracking disabled. Tracked: %d", + major, minor, tracked_value); + } + + // Cleanup + MYSQL_QUERY_T(admin, "DELETE FROM mysql_users WHERE username='ff_test_user'"); + MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME"); + MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + mysql_close(proxy); + mysql_close(admin); + return exit_status(); +} diff --git a/test/tap/tests/mysql-session_track_variables_optional-t.cpp b/test/tap/tests/mysql-session_track_variables_optional-t.cpp new file mode 100644 index 0000000000..7589ce90a0 --- /dev/null +++ b/test/tap/tests/mysql-session_track_variables_optional-t.cpp @@ -0,0 +1,170 @@ +/** + * @file mysql-track_system_variables_optional-t.cpp + * @brief This test verifies that ProxySQL properly handles session variable tracking + * in OPTIONAL mode based on MySQL server version. Session tracking should work + * on MySQL 5.7+ and gracefully degrade on 5.6 and below. + */ + +#include +#include +#include "json.hpp" +#include "mysql.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using nlohmann::json; + +bool get_server_version(MYSQL* proxy, int& major, int& minor) { + MYSQL_QUERY_T(proxy, "SELECT @@version"); + MYSQL_RES* result = mysql_store_result(proxy); + if (!result) { + return false; + } + + MYSQL_ROW row = mysql_fetch_row(result); + if (!row) { + mysql_free_result(result); + return false; + } + + // Parse version string + if (sscanf(row[0], "%d.%d", &major, &minor) != 2) { + mysql_free_result(result); + return false; + } + + mysql_free_result(result); + return true; +} + +bool test_session_variables(MYSQL* proxy, int& set_value, int& backend_value, int& client_value) { + set_value = -1; + backend_value = -1; + client_value = -1; + + MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); + MYSQL_QUERY_T(proxy, "SELECT 1"); + mysql_free_result(mysql_store_result(proxy)); + + MYSQL_QUERY_T(proxy, "DROP PROCEDURE IF EXISTS test.set_innodb_lock_wait_timeout"); + const char* create_proc = + "CREATE PROCEDURE test.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 test.set_innodb_lock_wait_timeout()"); + + 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); + if (!result) { + return false; + } + + MYSQL_ROW row = mysql_fetch_row(result); + if (!row) { + mysql_free_result(result); + return false; + } + + auto j_session = nlohmann::json::parse(row[0]); + mysql_free_result(result); + + 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()); + } + } + + return true; +} + +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* admin = init_mysql_conn(cl.admin_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(); + } + + // Set session_track_variables to OPTIONAL mode (value 1) + MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=1"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + 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(); + } + + int major = 0, minor = 0; + if (!get_server_version(proxy, major, minor)) { + diag("Failed to get server version"); + return exit_status(); + } + + diag("Detected MySQL version: %d.%d", major, minor); + + int set_value = -1; + int backend_value = -1; + int client_value = -1; + + if (!test_session_variables(proxy, set_value, backend_value, client_value)) { + diag("Failed to run test"); + return exit_status(); + } + + // Verify results based on server version + bool mysql57_plus = (major > 5) || (major == 5 && minor >= 7); + + if (mysql57_plus) { + ok(set_value == backend_value && set_value == client_value, + "MySQL %d.%d supports session tracking. Match innodb_lock_wait_timeout value with backend & client variable list. Expected: %d, Backend: %d, Client: %d", + major, minor, set_value, backend_value, client_value); + } else { + // MySQL 5.6 or below: session tracking should not be enabled + // The backend and client values should be -1 + ok(backend_value == -1 && client_value == -1, + "MySQL %d.%d lacks session tracking. Verify tracking disabled. Backend: %d, Client: %d", + major, minor, backend_value, client_value); + } + + // Cleanup + MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + mysql_close(proxy); + mysql_close(admin); + return exit_status(); +} diff --git a/test/tap/tests/reg_test_4264-commit_rollback-t.cpp b/test/tap/tests/reg_test_4264-commit_rollback-t.cpp index 1c5c6585ab..11533dc1d3 100644 --- a/test/tap/tests/reg_test_4264-commit_rollback-t.cpp +++ b/test/tap/tests/reg_test_4264-commit_rollback-t.cpp @@ -1129,6 +1129,9 @@ int prepare_tables_and_config(MYSQL* admin, MYSQL* proxy) { MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); MYSQL_QUERY_T(admin, "SET mysql-auto_increment_delay_multiplex=0"); + MYSQL_QUERY_T(admin, "SET mysql-default_session_track_gtids='OFF'"); + MYSQL_QUERY_T(admin, "SET mysql-session_track_variables=0"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES FROM DISK"); diff --git a/test/tap/tests/test_binlog_reader-t.cpp b/test/tap/tests/test_binlog_reader-t.cpp index f4cfaca5d8..17d4b5499b 100644 --- a/test/tap/tests/test_binlog_reader-t.cpp +++ b/test/tap/tests/test_binlog_reader-t.cpp @@ -259,6 +259,9 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } + MYSQL_QUERY_T(proxysql_admin, "SET mysql-session_track_variables=0"); + MYSQL_QUERY_T(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + vector> failed_rows {}; vector reader_1_read {}; vector reader_2_read {}; @@ -279,10 +282,6 @@ int main(int argc, char** argv) { rc = perform_update(proxysql_mysql, NUM_ROWS); if (rc != EXIT_SUCCESS) { goto cleanup; } - MYSQL_RES* my_res = mysql_store_result(proxysql_admin); - vector pre_select_rows = extract_mysql_rows(my_res); - mysql_free_result(my_res); - int r_row = rand() % NUM_ROWS; if (r_row == 0) { r_row = 1; }