/** * @file reg_test_3549-autocommit_tracking-t.cpp * @brief This test verifies that ProxySQL is properly tracking autocommit being * set, being properly forwarded to the client after changing its status. * * TODO: This test should serve as the template from which construct a more * complete test for 'autocommit' tracking, for client and backend sides. */ #include #include #include #include #include "mysql.h" #include "proxysql_utils.h" #include "tap.h" #include "command_line.h" #include "utils.h" #include "json.hpp" using nlohmann::json; using query_spec = std::tuple; void fetch_and_discard_results(MYSQL_RES* result, bool verbose=false) { MYSQL_ROW row = nullptr; unsigned int num_fields = 0; unsigned int i = 0; unsigned int j = 0; num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { unsigned long *lengths = mysql_fetch_lengths(result); if (verbose) { printf("# RowNum_%d: ", j); } for(i = 0; i < num_fields; i++) { if (verbose) { printf("[%.*s] ", (int) lengths[i], row[i] ? row[i] : "NULL"); } } if (verbose) { printf("\n"); } j++; } } /** * @brief Execute the supplied queries and fetch and ignore the data in case of * being necessary. * * @param proxysql An already opened MYSQL connection to ProxySQL. * * @param queries The query to be executed. */ int execute_and_fetch_query(MYSQL* proxysql, const std::string& query) { int query_err = mysql_query(proxysql, query.c_str()); if (query_err == EXIT_SUCCESS) { MYSQL_RES* result = mysql_store_result(proxysql); if (result) { fetch_and_discard_results(result, false); mysql_free_result(result); } } return query_err; } void parse_result_json_column(MYSQL_RES *result, json& j) { if(!result) return; MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { j = json::parse(row[0]); } } bool check_client_autocommit(MYSQL* proxysql) { if (proxysql == NULL) return false; return proxysql->server_status & SERVER_STATUS_AUTOCOMMIT; } using test_spec = std::vector>; /** * @brief Executes the provided queries specs, and logs if an error is found with * the query execution, or the value expected after the query doesn't match. * * @param proxysql An already opened MYSQL connection to ProxySQL. * @param queries_specs The queries specs to execute and check. * * @return 'EXIT_SUCCESS' if all the queries were properly executed, * 'EXIT_FAILURE' otherwise. */ int execute_queries_specs(MYSQL* proxysql, const test_spec& queries_specs) { for (const auto& query_spec : queries_specs) { std::string query = std::get<0>(query_spec); int exp_autocommit = std::get<1>(query_spec); int myerr = execute_and_fetch_query(proxysql, query); if (myerr != EXIT_SUCCESS) { diag( "Query failed to be executed:" " (query: '%s', exp_err: '%d', act_err: '%d', err_msg: '%s', line: '%d')", query.c_str(), EXIT_SUCCESS, mysql_errno(proxysql), mysql_error(proxysql), __LINE__ ); return EXIT_FAILURE; } else { bool autocommit = check_client_autocommit(proxysql); if (autocommit != exp_autocommit) { diag( "Unexpected autocommit value for:" " (query: '%s', exp_autocommit: '%d', autocommit: '%d', line: '%d')", query.c_str(), exp_autocommit, autocommit, __LINE__ ); return EXIT_FAILURE; } } } return EXIT_SUCCESS; } /** * @brief The tests definition. */ std::vector> test_definitions { { // Check if autocommit is properly set by using simple queries, // target to be handled by 'handler_special_queries' in ProxySQL side. "simple_set_autocommit_no_lock", { { "SET autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 } } }, { "simple_set_autocommit_no_lock_2", { { "SET autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 } } }, { "simple_set_autocommit_lock_1", { { "SET @session_var=1", 1 }, { "SET autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 } } }, { "simple_set_autocommit_lock_2", { { "SET @session_var=1", 1 }, { "SET autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 } } }, { // Check if autocommit is properly set by using complex queries, // target to be handled by 'handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo' // in ProxySQL side. "complex_set_autocommit_no_lock_1", { { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 } } }, { "complex_set_autocommit_no_lock_2", { { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 } } }, { "complex_set_autocommit_lock_1", { { "SET @session_var=1", 1 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 } } }, { "complex_set_autocommit_lock_2", { { "SET @session_var=1", 1 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 } } }, { "mix_set_autocommit_1", { { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET autocommit=0", 0 }, { "COMMIT", 0 }, { "SET autocommit=1", 1 }, { "BEGIN", 1 }, { "SET @session_var=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "COMMIT", 1 }, } }, { "mix_set_autocommit_2", { { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "SET time_zone='+04:00', character_set_client='latin1', max_join_size=10000, autocommit=1", 1 }, { "SELECT /* ;hostgroup=0 */ 1", 1 }, { "SET autocommit=0", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, { "BEGIN", 0 }, { "SET @session_var=1", 0 }, { "SELECT /* ;hostgroup=0 */ 1", 0 }, { "COMMIT", 0 }, } }, }; /** * @brief Execute the supplied test definition on the provided, already oppened * MySQL connection to ProxySQL. * * @param proxysql An already oppened connection to ProxySQL. * @param test_def The test definition to be verified. */ void execute_test_definition(MYSQL* proxysql, std::pair test_def) { int queries_res = execute_queries_specs(proxysql, test_def.second); std::string t_ok_msg { "Autocommit should match expected values for '%s' queries" }; std::string ok_msg {}; string_format(t_ok_msg, ok_msg, test_def.first.c_str()); ok( queries_res == EXIT_SUCCESS, "%s", ok_msg.c_str() ); } int main(int argc, char** argv) { CommandLine cl; if (cl.getEnv()) { diag("Failed to get the required environmental variables."); return -1; } plan(test_definitions.size()); for (const auto& test_def : test_definitions) { MYSQL* proxysql_mysql = mysql_init(NULL); if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { fprintf( stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql) ); return EXIT_FAILURE; } // perform the next test execute_test_definition(proxysql_mysql, test_def); mysql_close(proxysql_mysql); } return exit_status(); }