#include #include #include #include #include #include "mysql.h" #include "tap.h" #include "command_line.h" #include "utils.h" int main(int argc, char** argv) { CommandLine cl; if (cl.getEnv()) { diag("Failed to get the required environmental variables."); return -1; } plan(15); diag("=== FFTO Bypass Test ==="); diag("This test validates that queries larger than the FFTO max buffer size"); diag("bypass the FFTO (Fast Forward To Optimization) mechanism entirely."); diag("========================"); MYSQL* admin = mysql_init(NULL); if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { diag("Admin connection failed"); return -1; } ok(admin != NULL, "Connected to ProxySQL Admin"); // Set a very small threshold: 100 bytes ok(mysql_query(admin, "UPDATE global_variables SET variable_value='true' WHERE variable_name='mysql-ffto_enabled'") == 0, "Enable FFTO"); ok(mysql_query(admin, "UPDATE global_variables SET variable_value='100' WHERE variable_name='mysql-ffto_max_buffer_size'") == 0, "Set FFTO max buffer size to 100"); ok(mysql_query(admin, "LOAD MYSQL VARIABLES TO RUNTIME") == 0, "Load variables to runtime"); // Ensure user exists char user_query[1024]; snprintf(user_query, sizeof(user_query), "INSERT OR REPLACE INTO mysql_users (username, password, default_hostgroup, fast_forward, default_schema) VALUES ('%s', '%s', 0, 1, 'information_schema')", cl.username, cl.password); ok(mysql_query(admin, user_query) == 0, "Configure user with fast_forward=1 and default_schema"); ok(mysql_query(admin, "LOAD MYSQL USERS TO RUNTIME") == 0, "Load users to runtime"); // Ensure backend server exists char server_query[1024]; snprintf(server_query, sizeof(server_query), "INSERT OR REPLACE INTO mysql_servers (hostgroup_id, hostname, port) VALUES (0, '%s', %d)", cl.mysql_host, cl.mysql_port); mysql_query(admin, server_query); mysql_query(admin, "LOAD MYSQL SERVERS TO RUNTIME"); ok(mysql_query(admin, "TRUNCATE TABLE stats_mysql_query_digest") == 0, "Clear stats_mysql_query_digest using TRUNCATE"); MYSQL* conn = mysql_init(NULL); if (!mysql_real_connect(conn, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { diag("Client connection failed: %s", mysql_error(conn)); return -1; } ok(conn != NULL, "Connected to ProxySQL Data Port"); ok(mysql_query(conn, "USE information_schema") == 0, "Execute USE information_schema"); // 1. Send a SMALL query (should be recorded) const char* small_query = "SELECT 1111"; ok(mysql_query(conn, small_query) == 0, "Send small query (should be recorded)"); { MYSQL_RES* query_res = mysql_store_result(conn); if (query_res) mysql_free_result(query_res); } // Verify small query recorded - wait up to 2 seconds int count = 0; for (int i=0; i<20; i++) { mysql_query(admin, "SELECT count(*) FROM stats_mysql_query_digest WHERE digest_text = 'SELECT ?'"); MYSQL_RES* res = mysql_store_result(admin); MYSQL_ROW row = mysql_fetch_row(res); count = row ? atoi(row[0]) : -1; if (res) mysql_free_result(res); if (count > 0) break; usleep(100000); // 100ms } if (count == 0) { diag("Small query NOT found. Dumping ALL digests in stats_mysql_query_digest:"); mysql_query(admin, "SELECT digest_text, count_star FROM stats_mysql_query_digest"); MYSQL_RES* dr = mysql_store_result(admin); MYSQL_ROW drw; while(dr && (drw = mysql_fetch_row(dr))) { diag(" Digest: %s, Count: %s", drw[0], drw[1]); } if (dr) mysql_free_result(dr); } ok(count > 0, "Small query was recorded by FFTO (count: %d)", count); // 2. Clear and verify it is empty ok(mysql_query(admin, "TRUNCATE TABLE stats_mysql_query_digest") == 0, "Clear stats_mysql_query_digest again using TRUNCATE"); mysql_query(admin, "SELECT count(*) FROM stats_mysql_query_digest"); MYSQL_RES* res = mysql_store_result(admin); MYSQL_ROW row = mysql_fetch_row(res); count = row ? atoi(row[0]) : -1; ok(count == 0, "Verified stats_mysql_query_digest is empty after TRUNCATE (count: %d)", count); if (res) mysql_free_result(res); // 3. Send LARGE query (should NOT be recorded) std::string large_query = "SELECT '"; for(int i=0; i<200; i++) large_query += "x"; large_query += "', 2222"; ok(mysql_query(conn, large_query.c_str()) == 0, "Send large query (exceeding threshold, should NOT be recorded)"); { MYSQL_RES* query_res = mysql_store_result(conn); if (query_res) mysql_free_result(query_res); } // Wait a bit to ensure it would have appeared if it was recorded usleep(500000); // Verify that NO digest was recorded for the large query mysql_query(admin, "SELECT count(*), digest_text FROM stats_mysql_query_digest"); res = mysql_store_result(admin); if (!res) { ok(0, "Failed to store result from stats query"); } else { row = mysql_fetch_row(res); count = row ? atoi(row[0]) : -1; if (count != 0) { diag("Unexpected digests found in stats_mysql_query_digest:"); mysql_data_seek(res, 0); while((row = mysql_fetch_row(res))) { diag(" Count: %s, Digest: %s", row[0], row[1]); } } ok(count == 0, "No digests recorded for queries exceeding threshold (count: %d)", count); } if (res) mysql_free_result(res); mysql_close(conn); mysql_close(admin); return exit_status(); }