@ -1,6 +1,76 @@
/**
* @ file mysql - select_version_without_backend - t . cpp
* @ brief This TAP test validates if SELECT VERSION ( ) works without making calls to the backend .
* @ brief TAP test for validating SELECT VERSION ( ) behavior with mysql - select_version_forwarding
*
* # # Overview
*
* This test validates the behavior of the ` mysql - select_version_forwarding ` variable
* when ProxySQL has NO backend servers configured . This scenario tests how ProxySQL
* handles SELECT VERSION ( ) and SELECT @ @ VERSION queries when there are no available
* backend connections to peek at .
*
* # # Background
*
* Since ProxySQL 3.0 .4 , SELECT VERSION ( ) queries are intercepted by ProxySQL .
* The ` mysql - select_version_forwarding ` variable controls this behavior with 4 modes :
*
* - Mode 0 ( NEVER ) : Always return ProxySQL ' s own mysql - server_version
* - Mode 1 ( ALWAYS ) : Always proxy the query to a backend server
* - Mode 2 ( SMART_FALLBACK_INTERNAL ) : Try to get version from backend connection ,
* fallback to ProxySQL ' s mysql - server_version if no connection available
* - Mode 3 ( SMART_FALLBACK_PROXY , default ) : Try to get version from backend connection ,
* fallback to proxying the query if no connection available
*
* # # Test Scenarios
*
* This test runs TWO scenarios with NO backend servers configured :
*
* # # # Scenario 1 : Mode 3 ( smart with fallback to proxying ) - EXPECTED TO FAIL
*
* When ` mysql - select_version_forwarding = 3 ` and there are NO backend servers :
* - ProxySQL tries to peek at backend connections to get the version
* - No connections exist ( no backends configured )
* - Fallback behavior : ProxySQL attempts to proxy the query to a backend
* - Result : Query FAILS because there is no backend to proxy to
*
* This test explicitly verifies that mode 3 behaves correctly when there are no
* backends - it should attempt to proxy and fail , rather than returning an internal
* version incorrectly .
*
* # # # Scenario 2 : Mode 2 ( smart with fallback to internal ) - EXPECTED TO SUCCEED
*
* When ` mysql - select_version_forwarding = 2 ` and there are NO backend servers :
* - ProxySQL tries to peek at backend connections to get the version
* - No connections exist ( no backends configured )
* - Fallback behavior : Return ProxySQL ' s mysql - server_version ( internal version )
* - Result : Query SUCCEEDS and returns the configured mysql - server_version
*
* This test verifies that mode 2 provides a safe fallback that allows queries
* to succeed even when no backends are available .
*
* # # Test Execution Flow
*
* For each scenario :
* 1. Set ` mysql - select_version_forwarding ` to the desired mode ( 3 or 2 )
* 2. Set ` mysql - server_version ` to a known test value ( e . g . , " 8.4.6 " )
* 3. Delete all backend servers ( ensure no backends exist )
* 4. Execute two queries :
* - " SELECT @@VERSION " - Returns ProxySQL ' s mysql - server_version
* - " SELECT VERSION() " - Behavior depends on mode
* 5. Verify the results match the expected behavior for the mode
*
* # # Why Both Scenarios Matter
*
* - Mode 3 is the DEFAULT and provides the most accurate version information
* by ensuring clients get the real backend version or nothing . This is
* important for clients like SQLAlchemy that need to detect MariaDB vs MySQL .
*
* - Mode 2 provides a safer fallback that maintains availability at the cost
* of potentially returning less accurate version information when no backends
* are available .
*
* Testing both ensures the feature works correctly in all configurations and
* prevents regressions in future versions .
*/
# include <unistd.h>
@ -37,32 +107,30 @@ int run_q(MYSQL *mysql, const char *q) {
return 0 ;
}
int main ( int argc , char * * argv ) {
plan ( 2 ) ;
CommandLine cl ;
if ( cl . getEnv ( ) ) {
diag ( " Failed to get the required environmental variables. " ) ;
return exit_status ( ) ;
}
MYSQL * admin = init_mysql_conn ( cl . host , cl . admin_username , cl . admin_password , cl . admin_port ) ;
if ( ! admin ) {
fprintf ( stderr , " File %s, line %d, Error: %s \n " , __FILE__ , __LINE__ , mysql_error ( admin ) ) ;
return exit_status ( ) ;
}
MYSQL_QUERY_T ( admin , MYSQL_SET_SERVER_VERSION_QUERY ) ;
/**
* @ brief Test SELECT VERSION ( ) behavior with a specific mode
*
* @ param admin Admin connection for configuration
* @ param proxy Proxy connection for queries
* @ param mode The mysql - select_version_forwarding mode to test
* @ param expect_success Whether queries are expected to succeed
* @ return int 0 on success , EXIT_FAILURE on error
*/
int test_mode ( MYSQL * admin , MYSQL * proxy , int mode , bool expect_success ) {
// Set the mode explicitly
char set_mode_query [ 128 ] ;
snprintf ( set_mode_query , sizeof ( set_mode_query ) ,
" SET mysql-select_version_forwarding=%d " , mode ) ;
MYSQL_QUERY_T ( admin , set_mode_query ) ;
MYSQL_QUERY_T ( admin , " LOAD MYSQL VARIABLES TO RUNTIME " ) ;
// Ensure no backends exist
MYSQL_QUERY_T ( admin , " DELETE FROM mysql_servers " ) ;
MYSQL_QUERY_T ( admin , " LOAD MYSQL SERVERS TO RUNTIME " ) ;
MYSQL * proxy = init_mysql_conn ( cl . host , cl . username , cl . password , cl . port ) ;
if ( ! proxy ) {
fprintf ( stderr , " File %s, line %d, Error: %s \n " , __FILE__ , __LINE__ , mysql_error ( proxy ) ) ;
return exit_status ( ) ;
}
diag ( " === Testing mode %d (expect %s) === " ,
mode ,
expect_success ? " SUCCESS " : " FAILURE " ) ;
const char * version_get_queries [ 2 ] = {
" SELECT @@VERSION " ,
@ -73,8 +141,11 @@ int main(int argc, char** argv) {
MYSQL_ROW row = nullptr ;
string res_server_version ;
// Attempt to run the query
int rc = run_q ( proxy , version_get_queries [ i ] ) ;
if ( rc = = 0 ) {
// Query succeeded - fetch result
MYSQL_RES * proxy_res = mysql_store_result ( proxy ) ;
row = mysql_fetch_row ( proxy_res ) ;
@ -85,9 +156,65 @@ int main(int argc, char** argv) {
mysql_free_result ( proxy_res ) ;
}
ok ( row & & ( res_server_version = = MYSQL_TEST_SERVER_VERSION ) , " Server version: %s " , res_server_version . c_str ( ) ) ;
// Verify result matches expected behavior
if ( expect_success ) {
// Mode 2: Query should succeed and return ProxySQL's version
bool test_passed = row & & ( res_server_version = = MYSQL_TEST_SERVER_VERSION ) ;
ok ( test_passed ,
" Mode %d: %s should return '%s' - got '%s' " ,
mode ,
version_get_queries [ i ] ,
MYSQL_TEST_SERVER_VERSION ,
res_server_version . c_str ( ) ) ;
} else {
// Mode 3: Query should FAIL (no backend to proxy to)
bool test_passed = ( row = = nullptr ) ;
ok ( test_passed ,
" Mode %d: %s should FAIL (no backend) - %s " ,
mode ,
version_get_queries [ i ] ,
row ? " UNEXPECTED SUCCESS " : " correctly failed " ) ;
}
}
return 0 ;
}
int main ( int argc , char * * argv ) {
// We have 2 queries × 2 modes = 4 tests total
plan ( 4 ) ;
CommandLine cl ;
if ( cl . getEnv ( ) ) {
diag ( " Failed to get the required environmental variables. " ) ;
return exit_status ( ) ;
}
MYSQL * admin = init_mysql_conn ( cl . host , cl . admin_username , cl . admin_password , cl . admin_port ) ;
if ( ! admin ) {
fprintf ( stderr , " File %s, line %d, Error: %s \n " , __FILE__ , __LINE__ , mysql_error ( admin ) ) ;
return exit_status ( ) ;
}
// Set the ProxySQL version that will be returned for internal fallback
MYSQL_QUERY_T ( admin , MYSQL_SET_SERVER_VERSION_QUERY ) ;
MYSQL_QUERY_T ( admin , " LOAD MYSQL VARIABLES TO RUNTIME " ) ;
MYSQL * proxy = init_mysql_conn ( cl . host , cl . username , cl . password , cl . port ) ;
if ( ! proxy ) {
fprintf ( stderr , " File %s, line %d, Error: %s \n " , __FILE__ , __LINE__ , mysql_error ( proxy ) ) ;
mysql_close ( admin ) ;
return exit_status ( ) ;
}
// Scenario 1: Mode 3 (smart with fallback to proxying) - EXPECTED TO FAIL
// With no backends, mode 3 will try to proxy the query and fail
test_mode ( admin , proxy , 3 , false ) ;
// Scenario 2: Mode 2 (smart with fallback to internal) - EXPECTED TO SUCCEED
// With no backends, mode 2 will fall back to returning mysql-server_version
test_mode ( admin , proxy , 2 , true ) ;
mysql_close ( admin ) ;
mysql_close ( proxy ) ;