diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 9a9f583ea..0086c3a74 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -4436,6 +4436,14 @@ handler_again: handler_rc0_Process_GTID(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) { + assert(myconn != NULL); + assert(myconn->mysql != NULL); + autocommit = myconn->mysql->server_status & SERVER_STATUS_AUTOCOMMIT; + } + if (mirror == false) { // Support for LAST_INSERT_ID() if (myconn->mysql->insert_id) { diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index 19f859669..558608455 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -17,6 +17,14 @@ } \ } while(0) +#define MYSQL_QUERY_err(mysql, query) \ + do { \ + if (mysql_query(mysql, query)) { \ + fprintf(stderr, "File %s, line %d, Error: %s\n", \ + __FILE__, __LINE__, mysql_error(mysql)); \ + } \ + } while(0) + #ifdef __cplusplus extern "C" { #endif diff --git a/test/tap/tests/reg_test_3549-autocommit_tracking-t.cpp b/test/tap/tests/reg_test_3549-autocommit_tracking-t.cpp new file mode 100644 index 000000000..5243c7841 --- /dev/null +++ b/test/tap/tests/reg_test_3549-autocommit_tracking-t.cpp @@ -0,0 +1,301 @@ +/** + * @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 + +#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; + } + + 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(); +}