diff --git a/lib/MySQL_Logger.cpp b/lib/MySQL_Logger.cpp index 9a520885a9..dfe63da342 100644 --- a/lib/MySQL_Logger.cpp +++ b/lib/MySQL_Logger.cpp @@ -626,6 +626,9 @@ void MySQL_Logger::audit_set_datadir(char *s) { void MySQL_Logger::log_request(MySQL_Session *sess, MySQL_Data_Stream *myds) { if (events.enabled==false) return; if (events.logfile==NULL) return; + // 'MySQL_Session::client_myds' could be NULL in case of 'RequestEnd' being called over a freshly created session + // due to a failed 'CONNECTION_RESET'. Because this scenario isn't a client request, we just return. + if (sess->client_myds==NULL || sess->client_myds->myconn== NULL) return; MySQL_Connection_userinfo *ui=sess->client_myds->myconn->userinfo; diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 582e025546..e731bee70d 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -7181,7 +7181,14 @@ void MySQL_Session::LogQuery(MySQL_Data_Stream *myds) { // this should become the place to hook other functions void MySQL_Session::RequestEnd(MySQL_Data_Stream *myds) { // check if multiplexing needs to be disabled - char *qdt=CurrentQuery.get_digest_text(); + char *qdt = NULL; + + if (status != PROCESSING_STMT_EXECUTE) { + qdt = CurrentQuery.get_digest_text(); + } else { + qdt = CurrentQuery.stmt_info->digest_text; + } + if (qdt && myds && myds->myconn) { myds->myconn->ProcessQueryAndSetStatusFlags(qdt); } diff --git a/test/tap/tests/reg_test_sql_calc_found_rows-t.cpp b/test/tap/tests/reg_test_sql_calc_found_rows-t.cpp new file mode 100644 index 0000000000..10aef61c33 --- /dev/null +++ b/test/tap/tests/reg_test_sql_calc_found_rows-t.cpp @@ -0,0 +1,268 @@ +/** + * @file reg_test_stmt_dis_multiplex-t.cpp + * @brief This is a simple regression test checking that 'STMT_EXECUTE' for queries holding + * 'SQL_CALC_FOUND_ROWS' disable multiplexing and return the expected results when used in combination with + * 'FOUND_ROWS()'. Unlike other tests, this test doesn't rely on 'PROXYSQL INTERNAL SESSION' info. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "json.hpp" + +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using std::vector; +using std::string; +using std::pair; + +using nlohmann::json; + +int get_stmt_result(MYSQL_STMT* stmt, int64_t& out_data) { + MYSQL_BIND bind[1]; + int64_t data_c; + + char is_null[1]; + long unsigned int length[1]; + char error[1]; + + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type = MYSQL_TYPE_LONG; + bind[0].buffer = (char *)&data_c; + bind[0].buffer_length = sizeof(int); + bind[0].is_null = &is_null[0]; + bind[0].length = &length[0]; + + if (mysql_stmt_bind_result(stmt, bind)) { + diag("'mysql_stmt_bind_result' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt)); + return EXIT_FAILURE; + } + + while (!mysql_stmt_fetch(stmt)) {} + + out_data = data_c; + + return EXIT_SUCCESS; +} + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + plan(6); + + diag("Checking that 'SQL_CALC_FOUND_ROWS' and 'FOUND_ROWS()' returns expected results for STMT"); + + const char* Q_CALC_FOUND_ROWS_1 { "SELECT SQL_CALC_FOUND_ROWS 1" }; + const char* Q_CALC_FOUND_ROWS_2 { "SELECT SQL_CALC_FOUND_ROWS 3 UNION SELECT 4" }; + const char* Q_FOUND_ROWS { "SELECT FOUND_ROWS()" }; + + // 1. Prepare the 'SQL_CALC_FOUND_ROWS' stmt in a connection + MYSQL* proxy_mysql = mysql_init(NULL); + + diag("%s: Openning initial connection...", tap_curtime().c_str()); + if (!mysql_real_connect(proxy_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(proxy_mysql)); + return EXIT_FAILURE; + } + + MYSQL_STMT* stmt_1 = mysql_stmt_init(proxy_mysql); + MYSQL_STMT* stmt_2 = mysql_stmt_init(proxy_mysql); + MYSQL_STMT* stmt_3 = nullptr; + + diag("%s: Issuing the prepare for `%s` in init conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_1); + int my_err = mysql_stmt_prepare(stmt_1, Q_CALC_FOUND_ROWS_1, strlen(Q_CALC_FOUND_ROWS_1)); + if (my_err) { + diag( + "'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'", + Q_CALC_FOUND_ROWS_1, mysql_errno(proxy_mysql), mysql_error(proxy_mysql) + ); + goto cleanup; + } + + diag("%s: Issuing the prepare for `%s` in init conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_2); + my_err = mysql_stmt_prepare(stmt_2, Q_CALC_FOUND_ROWS_2, strlen(Q_CALC_FOUND_ROWS_2)); + if (my_err) { + diag( + "'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'", + Q_CALC_FOUND_ROWS_1, mysql_errno(proxy_mysql), mysql_error(proxy_mysql) + ); + goto cleanup; + } + + mysql_stmt_close(stmt_1); + mysql_stmt_close(stmt_2); + + diag("%s: Closing initial connection...", tap_curtime().c_str()); + mysql_close(proxy_mysql); + + // 2. Open a new connection and prepare the stmts it again in a new connection + proxy_mysql = mysql_init(NULL); + + diag("%s: Openning new connection for testing 'Multiplex' disabling", tap_curtime().c_str()); + if (!mysql_real_connect(proxy_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(proxy_mysql)); + return EXIT_FAILURE; + } + + stmt_1 = mysql_stmt_init(proxy_mysql); + stmt_2 = mysql_stmt_init(proxy_mysql); + stmt_3 = mysql_stmt_init(proxy_mysql); + + diag("%s: Issuing the prepare for `%s` in new conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_1); + my_err = mysql_stmt_prepare(stmt_1, Q_CALC_FOUND_ROWS_1, strlen(Q_CALC_FOUND_ROWS_1)); + if (my_err) { + diag( + "'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'", + Q_CALC_FOUND_ROWS_1, mysql_errno(proxy_mysql), mysql_error(proxy_mysql) + ); + goto cleanup; + } + + { + diag("%s: Issuing execute for `%s` in new conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_1); + my_err = mysql_stmt_execute(stmt_1); + if (my_err) { + diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_1)); + goto cleanup; + } + + int64_t f_query_res = 0; + my_err = get_stmt_result(stmt_1, f_query_res); + if (my_err) { + diag("'get_stmt_result' at line %d failed", __LINE__); + goto cleanup; + } + } + { + diag("%s: Issuing the prepare for `%s` in new conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_2); + my_err = mysql_stmt_prepare(stmt_2, Q_CALC_FOUND_ROWS_2, strlen(Q_CALC_FOUND_ROWS_2)); + if (my_err) { + diag( + "'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'", + Q_CALC_FOUND_ROWS_2, mysql_errno(proxy_mysql), mysql_error(proxy_mysql) + ); + goto cleanup; + } + + { + diag("%s: Issuing execute for `%s` in new conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_2); + my_err = mysql_stmt_execute(stmt_2); + if (my_err) { + diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_2)); + goto cleanup; + } + + int64_t s_query_res = 0; + my_err = get_stmt_result(stmt_2, s_query_res); + if (my_err) { + diag("'get_stmt_result' at line %d failed", __LINE__); + goto cleanup; + } + } + + diag("%s: Issuing the prepare for `%s` in new conn", tap_curtime().c_str(), Q_FOUND_ROWS); + my_err = mysql_stmt_prepare(stmt_3, Q_FOUND_ROWS, strlen(Q_FOUND_ROWS)); + if (my_err) { + diag( + "'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'", + Q_FOUND_ROWS, mysql_errno(proxy_mysql), mysql_error(proxy_mysql) + ); + goto cleanup; + } + + // 4. Perform execs of both stmt followed by 'found_rows()' stmt and check results + bool exp_results = false; + const int RETRIES = 3; + const int ITERATIONS = 5; + + for (int i = 0; i < RETRIES; i++) { + my_err = mysql_stmt_execute(stmt_1); + if (my_err) { + diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_1)); + goto cleanup; + } + + int64_t f_query_res = 0; + my_err = get_stmt_result(stmt_1, f_query_res); + if (my_err) { + diag("'get_stmt_result' at line %d failed", __LINE__); + goto cleanup; + } + + my_err = mysql_stmt_execute(stmt_2); + if (my_err) { + diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_2)); + goto cleanup; + } + + int64_t s_query_res = 0; + my_err = get_stmt_result(stmt_2, s_query_res); + if (my_err) { + diag("'get_stmt_result' at line %d failed", __LINE__); + goto cleanup; + } + + pair results {f_query_res, s_query_res}; + diag("Results from 'mysql_stmt_execute' were: %s", json {results}.dump().c_str()); + + ok( + results.first == 1 && results.second == 4, + "'mysql_stmt_execute' returned the expected values - first: %d, second: %d", + results.first, results.second + ); + + diag("Perform multiple execute for 'FOUND_ROWS()' and check result"); + + vector found_rows_results {}; + + for (int i = 0; i < ITERATIONS; i++) { + my_err = mysql_stmt_execute(stmt_3); + if (my_err) { + diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_1)); + goto cleanup; + } + + int64_t found_rows_res = 0; + my_err = get_stmt_result(stmt_3, found_rows_res); + found_rows_results.push_back(found_rows_res); + } + + diag("Results from 'FOUND_ROWS' executions - '%s'", json { found_rows_results }.dump().c_str()); + + bool correct_found_rows = false; + bool exp_f_val = found_rows_results.front() == 2; + bool exp_tail_val = true; + + for (int i = 1; i < ITERATIONS; i++) { + exp_tail_val &= exp_tail_val && found_rows_results[i] == 1; + } + + ok(exp_f_val && exp_tail_val, "'FOUND_ROWS' execution returned expected values"); + } + } + +cleanup: + + mysql_stmt_close(stmt_1); + mysql_stmt_close(stmt_2); + mysql_stmt_close(stmt_3); + + mysql_close(proxy_mysql); + + return exit_status(); +} diff --git a/test/tap/tests/test_session_status_flags-t.cpp b/test/tap/tests/test_session_status_flags-t.cpp index a752ceb968..77bb861e9e 100644 --- a/test/tap/tests/test_session_status_flags-t.cpp +++ b/test/tap/tests/test_session_status_flags-t.cpp @@ -1,20 +1,39 @@ /** * @file test_session_status_flags-t.cpp * @brief Test file for testing the different operations that modify the 'status_flags' in a MySQL_Session. + * @details The test performs the queries for both TEXT and BINARY protocols (when supported). For this + * purpose, the test exposes a generic payload format for performing the queries and inspecting + * 'PROXYSQL INTERNAL SESSION'. + * + * NOTE-TODO: The test needs to deal with potential replication issues for several queries. Due to current + * limitations in how ProxySQL handles errors for prepared statements, replications checks are performed in + * freshly created connections using TEXT protocol, prior to the statements executions. */ +#include #include -#include -#include #include #include +#include +#include +#include #include + +#include "mysql.h" +#include "mysqld_error.h" + #include "json.hpp" #include "tap.h" #include "utils.h" #include "command_line.h" +using std::pair; +using std::tuple; +using std::vector; +using std::string; +using std::function; + using nlohmann::json; void parse_result_json_column(MYSQL_RES *result, json& j) { @@ -31,759 +50,1021 @@ void parse_result_json_column(MYSQL_RES *result, json& j) { // create operations are performed. See #3282 for context. constexpr const int replication_timeout = 10; -int main(int argc, char *argv[]) { - CommandLine cl; +json get_nested_json_elem(const json& j, const std::vector& path) { + json cur_j = j; - if(cl.getEnv()) { - return exit_status(); + for (const auto& step : path) { + if (cur_j.contains(step)) { + cur_j = cur_j.at(step); + + if (&step == &path.back()) { + return cur_j; + } + } else { + cur_j = {}; + break; + } } - plan(23); + return cur_j; +} + +int execute_queries(MYSQL* proxy_mysql, const vector& queries, vector& out_j_sts) { + vector j_sts {}; + + for (const auto& query : queries) { + MYSQL_QUERY(proxy_mysql, query.c_str()); + MYSQL_RES* tr_res = mysql_store_result(proxy_mysql); + + if (query == "PROXYSQL INTERNAL SESSION") { + json j_st {}; + parse_result_json_column(tr_res, j_st); + + j_sts.push_back(j_st); + } - MYSQL* proxysql_admin = mysql_init(NULL); - if (!mysql_real_connect(proxysql_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); - return -1; + mysql_free_result(tr_res); } - // Set a replication lag inferior to default one (60). This is to prevent reads - // from a replica in which replication is currently disabled. - MYSQL_QUERY(proxysql_admin, "UPDATE mysql_servers SET max_replication_lag=20"); - MYSQL_QUERY(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME"); + out_j_sts = j_sts; - // Wait for ProxySQL to detect replication issues - //sleep(10); + return EXIT_SUCCESS; +} - { - MYSQL* proxysql_mysql = mysql_init(NULL); +json get_backend_elem(const json& j_status, const vector& elem_path) { + json j_tg_elem {}; - 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 -1; + if (elem_path.empty()) { + if (j_status.contains("backends")) { + return j_status["backends"]; + } else { + return {}; } + } + + if (j_status.contains("backends")) { + for (auto& backend : j_status["backends"]) { + j_tg_elem = get_nested_json_elem(backend, elem_path); - diag("Preparing table test.test_savepoint"); - MYSQL_QUERY(proxysql_mysql, "CREATE DATABASE IF NOT EXISTS test"); - MYSQL_QUERY(proxysql_mysql, "CREATE TABLE IF NOT EXISTS test.test_savepoint(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY) ENGINE=INNODB"); - MYSQL_QUERY(proxysql_mysql, "DELETE FROM test.test_savepoint"); - MYSQL_QUERY(proxysql_mysql, "INSERT INTO test.test_savepoint VALUES (1), (2)"); - sleep(1); - - // Check that transaction state is reflected when actively in a transaction - const std::vector transaction_queries { "START TRANSACTION", "SELECT 1", "PROXYSQL INTERNAL SESSION", "COMMIT" }; - json j_status; - - for (const auto& query : transaction_queries) { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); + if (!j_tg_elem.empty()) { + break; } - mysql_free_result(tr_res); } + } - if (j_status.contains("backends")) { - bool found_backend = false; - for (auto& backend : j_status["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - int32_t server_status = backend["conn"]["mysql"]["server_status"]; - ok(server_status & 0x01, "Connection status should reflect being in a transaction"); - } - } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); + return j_tg_elem; +} + +using rep_errno_t = int; +using rep_timeout = int; + +using rep_check_t = pair; +using sess_check_t = tuple, function, string>; +using query_t = tuple>; +struct QUERY { enum idx { QUERY_STR, REP_CHECK, SESS_CHECKS }; }; + +int exec_with_retry(MYSQL* proxy, const query_t& query_def) { + const string& query = std::get(query_def); + const rep_check_t& rep_check = std::get(query_def); + + int timeout = 0; + int query_err = 0; + + diag("Executing query '%s' with retrying due to replication lag.", query.c_str()); + while (timeout < replication_timeout) { + query_err = mysql_query(proxy, query.c_str()); + if (query_err) { + int query_errno = mysql_errno(proxy); + if (query_errno != rep_check.first) { + break; + } else { + sleep(1); + diag("Retrying query '%s' due to replication lag.", query.c_str()); } } else { - ok(false, "No backends detected for the current connection."); + mysql_free_result(mysql_store_result(proxy)); + break; } - - mysql_close(proxysql_mysql); + timeout++; } - { - MYSQL* proxysql_mysql = mysql_init(NULL); + if (query_err) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy)); + return EXIT_FAILURE; + } else { + diag("Replication lag took '%d.'", timeout); + return EXIT_SUCCESS; + } +} - if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, CLIENT_COMPRESS)) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql)); - return -1; +function capture_exception(const function sess_check_t) { + return [=](const json& j_tg_elem) -> bool { + try { + return sess_check_t(j_tg_elem); + } catch (const std::exception& ex) { + diag("Invalid target elem conversion:\n -Elem: %s\n -Except: %s", j_tg_elem.dump().c_str(), ex.what()); + return false; } + }; +} - // Check that state reflects when in a compressed connection - const std::string internal_session_query { "PROXYSQL INTERNAL SESSION" }; - json j_status; +int prepare_stmt_queries(const CommandLine& cl, const vector& p_queries) { + // 1. Prepare the stmt in a connection + MYSQL* proxy_mysql = mysql_init(NULL); - MYSQL_QUERY(proxysql_mysql, internal_session_query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - parse_result_json_column(tr_res, j_status); - mysql_free_result(tr_res); - - bool compression_enabled = j_status["conn"]["status"]["compression"]; - ok(compression_enabled == true, "Connection status should reflect being in a compressed connection"); + diag("%s: Openning INITIAL connection...", tap_curtime().c_str()); + if (!mysql_real_connect(proxy_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(proxy_mysql)); + return EXIT_FAILURE; + } - mysql_close(proxysql_mysql); + vector queries {}; + vector str_queries {}; + std::accumulate( + queries.begin(), queries.end(), vector {}, + [] (vector& elems, const query_t& query_def) { + const string& query = std::get(query_def); + elems.push_back(query); + return elems; + } + ); + + if (p_queries.empty() == false) { + std::copy_if( + p_queries.begin(), p_queries.end(), std::back_inserter(queries), + [] (const query_t& query_def) { + const string& query = std::get(query_def); + return strcasecmp(query.c_str(), "PROXYSQL INTERNAL SESSION"); + } + ); } - // USER VARIABLE - { - MYSQL* proxysql_mysql = mysql_init(NULL); + diag( + "%s: PREPARING multiplexing disabling queries - `%s`", tap_curtime().c_str(), + json(str_queries).dump().c_str() + ); + for (const query_t& query_def : queries) { + const string& query = std::get(query_def); + const rep_check_t& rep_check = std::get(query_def); - 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 -1; + if (rep_check.first == 0 || rep_check.second == 0) { } - // Check that state reflects when in a compressed connection - const std::vector user_variable_queries { "SET @test_variable = 43", "PROXYSQL INTERNAL SESSION" }; - json j_status; - - for (const auto& query : user_variable_queries) { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); - } - mysql_free_result(tr_res); + MYSQL_STMT* stmt = mysql_stmt_init(proxy_mysql); + if (!stmt) { + fprintf(stderr, " mysql_stmt_init(), out of memory\n"); + return EXIT_FAILURE; } - if (j_status.contains("backends")) { - bool found_backend = false; - for (auto& backend : j_status["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - int32_t user_variable_status = backend["conn"]["status"]["user_variable"]; - ok(user_variable_status == true, "Connection status should reflect that a 'user_variable' have been set."); - } - } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); - } - } else { - ok(false, "No backends detected for the current connection."); + diag("%s: Issuing PREPARE for `%s` in INIT conn", tap_curtime().c_str(), query.c_str()); + int my_err = mysql_stmt_prepare(stmt, query.c_str(), strlen(query.c_str())); + if (my_err) { + diag( + "'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'", + query.c_str(), mysql_stmt_errno(stmt), mysql_stmt_error(stmt) + ); + return EXIT_FAILURE; } - mysql_close(proxysql_mysql); + mysql_stmt_close(stmt); } - // PREPARED STATEMENT + diag("%s: Closing PREPARING connection...", tap_curtime().c_str()); + mysql_close(proxy_mysql); + + return EXIT_SUCCESS; +} + +int store_and_discard_stmt_res(MYSQL_STMT* stmt) { + /* Fetch result set meta information */ + MYSQL_RES* prepare_meta_result = mysql_stmt_result_metadata(stmt); + if (!prepare_meta_result) { - MYSQL* proxysql_mysql = mysql_init(NULL); + fprintf(stderr, " mysql_stmt_result_metadata(), returned no meta information\n"); + fprintf(stderr, " %s\n", mysql_stmt_error(stmt)); + return EXIT_FAILURE; + } - 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 -1; - } + /* Get total columns in the query */ + uint32_t column_count = mysql_num_fields(prepare_meta_result); + MYSQL_FIELD* fields = mysql_fetch_fields(prepare_meta_result); + + vector res_binds(column_count, MYSQL_BIND {}); + size_t res_idx = 0; + + vector is_null(column_count); + vector length(column_count); + vector res_bin_data {}; + + vector> data_buffs {}; + + for (uint32_t column = 0; column < column_count; column++) { + data_buffs.push_back(vector(fields[column].length)); + } + + for (uint32_t column = 0; column < column_count; column++) { + res_binds[column].buffer_type = fields[column].type; + res_binds[column].buffer = static_cast(&data_buffs[column][0]); + res_binds[column].buffer_length = sizeof(int); + res_binds[column].is_null = &is_null[column]; + res_binds[column].length = &length[column]; + } + + if (mysql_stmt_bind_result(stmt, &res_binds[0])) { + diag("'mysql_stmt_bind_result' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt)); + return EXIT_FAILURE; + } - const std::vector prepared_stmt_queries { "PREPARE stmt_test FROM 'SELECT 1'", "PROXYSQL INTERNAL SESSION" }; - json j_status; + mysql_free_result(prepare_meta_result); - for (const auto& query : prepared_stmt_queries) { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); + while (!mysql_stmt_fetch(stmt)) {} + + return EXIT_SUCCESS; +} + +int exec_stmt_queries(MYSQL* proxy_mysql, const vector& test_queries) { + for (const query_t& test_query : test_queries) { + const string& query = std::get(test_query); + const rep_check_t& rep_check = std::get(test_query); + const vector& sess_checks = std::get(test_query); + + if (query == "PROXYSQL INTERNAL SESSION") { + for (const sess_check_t sess_check : sess_checks) { + if (std::get<0>(sess_check).empty() || static_cast(std::get<1>(sess_check)) == false) { + diag("ABORT: Empty 'sess_check_t' defined for query '%s'", query.c_str()); + return EXIT_FAILURE; + } } + + json j_st {}; + + MYSQL_QUERY(proxy_mysql, query.c_str()); + MYSQL_RES* tr_res = mysql_store_result(proxy_mysql); + parse_result_json_column(tr_res, j_st); mysql_free_result(tr_res); - } - if (j_status.contains("backends")) { - bool found_backend = false; - for (auto& backend : j_status["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool prepared_stmt = backend["conn"]["status"]["prepared_statement"]; - ok(prepared_stmt == true, "Connection status should reflect that a 'prepared statement' have been prepared."); - - bool multiplex_disabled = backend["conn"]["MultiplexDisabled"]; - ok(multiplex_disabled == true, "Connection status should reflect that 'MultiplexDisabled' is enabled due to the 'prepared statement'."); + for (const sess_check_t sess_check : sess_checks) { + const vector& tg_elem_path = std::get<0>(sess_check); + const function& status_check = std::get<1>(sess_check); + const string& check_msg = std::get<2>(sess_check); + const function& no_except_status_check = capture_exception(status_check); + + json j_tg_elem {}; + if (tg_elem_path.front() == "backends") { + j_tg_elem = get_backend_elem(j_st, vector {tg_elem_path.begin() + 1, tg_elem_path.end()}); + } else { + j_tg_elem = get_nested_json_elem(j_st, tg_elem_path); } - } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); + + ok( + j_tg_elem.empty() == false, "Backend 'conn' objects should be found holding sess info - `%s`", + json { tg_elem_path }.dump().c_str() + ); + + ok(no_except_status_check(j_tg_elem), "Connection status should reflect - %s", check_msg.c_str()); } } else { - ok(false, "No backends detected for the current connection."); - } + MYSQL_STMT* stmt = mysql_stmt_init(proxy_mysql); + + diag("%s: Issuing PREPARE for `%s` in new conn", tap_curtime().c_str(), query.c_str()); + int my_err = mysql_stmt_prepare(stmt, query.c_str(), strlen(query.c_str())); + if (my_err) { + diag( + "LINE %d: 'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'", + __LINE__, query.c_str(), mysql_stmt_errno(stmt), mysql_stmt_error(stmt) + ); + return EXIT_FAILURE; + } - mysql_close(proxysql_mysql); - } + // TODO: Remember to DOC requiring to execute + { + if (rep_check.first == 0 || rep_check.second == 0) { + diag("%s: Issuing EXECUTE for `%s` in new conn", tap_curtime().c_str(), query.c_str()); + my_err = mysql_stmt_execute(stmt); + if (my_err) { + diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt)); + return EXIT_FAILURE; + } + } else { + int timeout = 0; + int query_err = 0; + + diag("Executing query '%s' with retrying due to replication lag.", query.c_str()); + while (timeout < replication_timeout) { + query_err = mysql_stmt_execute(stmt); + if (query_err) { + int query_errno = mysql_stmt_errno(stmt); + if (query_errno != rep_check.first) { + break; + } else { + sleep(1); + diag("Retrying EXECUTE for '%s' due to replication lag. Errno: %d", query.c_str(), query_errno); + } + } else { + break; + } + timeout++; + } - // STATUS_MYSQL_CONNECTION_LOCK_TABLES - { - MYSQL* proxysql_mysql = mysql_init(NULL); + if (query_err) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_stmt_error(stmt)); + return EXIT_FAILURE; + } else { + diag("Replication lag took '%d.'", timeout); + } + } + + if (query.find("SELECT") != string::npos) { + my_err = store_and_discard_stmt_res(stmt); + if (my_err) { + diag("'get_stmt_result' at line %d failed", __LINE__); + return EXIT_FAILURE; + } + } + } - 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 -1; + mysql_stmt_close(stmt); } + } + + return EXIT_SUCCESS; +} - const char* create_test_table = - "CREATE TABLE IF NOT EXISTS sysbench.test_session_var (" - " c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY," - " c2 VARCHAR(100)," - " c3 VARCHAR(100)" - ")"; - const std::vector prepared_stmt_queries { - create_test_table, - "LOCK TABLES sysbench.test_session_var READ", - "PROXYSQL INTERNAL SESSION", - // Set a variable so we make sure connection is not dropped after "UNLOCK TABLES" - "SET @test_variable = 43", - "UNLOCK TABLES", - "PROXYSQL INTERNAL SESSION", - "DROP TABLE sysbench.test_session_var" - }; - - std::vector vj_status; - - for (const auto& query : prepared_stmt_queries) { - json j_status; - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); - vj_status.push_back(j_status); +int exec_test_queries(MYSQL* proxy_mysql, const vector& test_queries) { + for (const query_t& test_query : test_queries) { + const string& query = std::get(test_query); + const rep_check_t& rep_check = std::get(test_query); + const vector& sess_checks = std::get(test_query); + + if (query == "PROXYSQL INTERNAL SESSION") { + for (const sess_check_t sess_check : sess_checks) { + if (std::get<0>(sess_check).empty() || static_cast(std::get<1>(sess_check)) == false) { + diag("ABORT: Empty 'sess_check_t' defined for query '%s'", query.c_str()); + return EXIT_FAILURE; + } } - mysql_free_result(tr_res); } - if (vj_status[0].contains("backends")) { - bool found_backend = false; - for (const auto& backend : vj_status[0]["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool lock_tables = backend["conn"]["status"]["lock_tables"]; - ok(lock_tables == true, "Connection status should reflect that 'LOCK TABLE' have been executed."); + diag("Issuing test query - `%s`", query.c_str()); + MYSQL_RES* tr_res = nullptr; + + if (rep_check.first == 0 || rep_check.second == 0) { + MYSQL_QUERY(proxy_mysql, query.c_str()); + tr_res = mysql_store_result(proxy_mysql); + } else { + int timeout = 0; + int query_err = 0; + + diag("Executing query '%s' with retrying due to replication lag.", query.c_str()); + while (timeout < replication_timeout) { + query_err = mysql_query(proxy_mysql, query.c_str()); + if (query_err) { + int query_errno = mysql_errno(proxy_mysql); + if (query_errno != rep_check.first) { + break; + } else { + sleep(1); + diag("Retrying query '%s' due to replication lag.", query.c_str()); + } + } else { + tr_res = mysql_store_result(proxy_mysql); + break; } + timeout++; } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); + + if (query_err) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_mysql)); + return -1; + } else { + diag("Replication lag took '%d.'", timeout); } - } else { - ok(false, "No backends detected for the current connection."); } - if (vj_status[1].contains("backends")) { - bool found_backend = false; - for (const auto& backend : vj_status[1]["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool unlock_tables = backend["conn"]["status"]["lock_tables"]; - ok(unlock_tables == false, "Connection status should reflect that 'UNLOCK TABLE' have been executed."); + // Perform the status checks for this query + if (query == "PROXYSQL INTERNAL SESSION") { + json j_st {}; + parse_result_json_column(tr_res, j_st); + + for (const sess_check_t sess_check : sess_checks) { + const vector& tg_elem_path = std::get<0>(sess_check); + const function& status_check = std::get<1>(sess_check); + const string& check_msg = std::get<2>(sess_check); + const function& no_except_status_check = capture_exception(status_check); + + json j_tg_elem {}; + if (tg_elem_path.front() == "backends") { + j_tg_elem = get_backend_elem(j_st, vector {tg_elem_path.begin() + 1, tg_elem_path.end()}); + } else { + j_tg_elem = get_nested_json_elem(j_st, tg_elem_path); } + + ok( + j_tg_elem.empty() == false, "Backend 'conn' objects should be found holding sess info - `%s`", + json { tg_elem_path }.dump().c_str() + ); + + ok(no_except_status_check(j_tg_elem), "Connection status should reflect - %s", check_msg.c_str()); } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); - } - } else { - ok(false, "No backends detected for the current connection."); } - mysql_close(proxysql_mysql); + mysql_free_result(tr_res); } - // STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE - { - MYSQL* proxysql_mysql = mysql_init(NULL); + return EXIT_SUCCESS; +} - 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 -1; - } +bool check_server_status_in_trx(const json& j_tg_elem) { + int32_t server_status = 0; - const char* create_test_table = - "CREATE TEMPORARY TABLE IF NOT EXISTS sysbench.test_temp_table_session_var (" - " c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY," - " c2 VARCHAR(100)," - " c3 VARCHAR(100)" - ")"; - const std::vector prepared_stmt_queries { - create_test_table, - "PROXYSQL INTERNAL SESSION", - "DROP TABLE sysbench.test_temp_table_session_var" - }; - json j_status; - - for (const auto& query : prepared_stmt_queries) { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); - } - mysql_free_result(tr_res); - } + if (j_tg_elem.empty() == false) { + server_status = j_tg_elem.get(); + } - if (j_status.contains("backends")) { - bool found_backend = false; - for (const auto& backend : j_status["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool temp_table = backend["conn"]["status"]["temporary_table"]; - ok(temp_table == true, "Connection status should reflect that a 'CREATE TEMPORARY TABLE' have been executed."); - - bool multiplex_disabled = backend["conn"]["MultiplexDisabled"]; - ok(multiplex_disabled == true, "Connection status should reflect that 'MultiplexDisabled' is enabled due to 'CREATE TEMPORARY TABLE'."); - } - } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); - } - } else { - ok(false, "No backends detected for the current connection."); - } + return server_status & 0x01; +} - mysql_close(proxysql_mysql); +using setup_teardown_t = pair,vector>; +using test_def_t = tuple>; +struct SCONN_TEST_DEF { enum idx { NAME, SETUP_TEARDOWN, TEST_QUERIES }; }; + +int exec_and_discard(MYSQL* proxy_mysql, const vector& queries) { + for (const auto& query : queries) { + MYSQL_QUERY(proxy_mysql, query.c_str()); + mysql_free_result(mysql_store_result(proxy_mysql)); } - // STATUS_MYSQL_CONNECTION_GET_LOCK - // TODO: Check why when GET_LOCK is executed the first backend is "NULL", and not filled like in the rest - { - MYSQL* proxysql_mysql = mysql_init(NULL); + return EXIT_SUCCESS; +} - 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 -1; - } +using queries_exec_t = function&)>; - const std::vector prepared_stmt_queries { - "SELECT 1", - "SELECT GET_LOCK('test_session_vars_lock', 2)", - "PROXYSQL INTERNAL SESSION", - "SELECT RELEASE_LOCK('test_session_vars_lock')" - }; - json j_status; - - for (const auto& query : prepared_stmt_queries) { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); - } - mysql_free_result(tr_res); - } +int exec_simple_conn_tests( + const CommandLine& cl, const vector& tests_def, const queries_exec_t& queries_exec +) { + for (const auto& test_def : tests_def) { + const string& test_name { std::get(test_def) }; + const setup_teardown_t& setup_teardown = std::get(test_def); + const vector& test_queries = std::get(test_def); - if (j_status.contains("backends")) { - bool found_backend = false; - for (auto& backend : j_status["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool lock_tables = backend["conn"]["status"]["get_lock"]; - ok(lock_tables == true, "Connection status should reflect that a 'GET_LOCK' have been executed."); - - bool multiplex_disabled = backend["conn"]["MultiplexDisabled"]; - ok(multiplex_disabled == true, "Connection status should reflect that 'MultiplexDisabled' is enabled due to 'GET_LOCK'."); - } - } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); - } - } else { - ok(false, "No backends detected for the current connection."); + diag("Starting test '%s'", test_name.c_str()); + + MYSQL* proxy_mysql = mysql_init(NULL); + + if (!mysql_real_connect(proxy_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(proxy_mysql)); + return EXIT_FAILURE; } - mysql_close(proxysql_mysql); + // TEST SETUP queries + int setup_err = exec_and_discard(proxy_mysql, setup_teardown.first); + if (setup_err) { return EXIT_FAILURE; } + + int test_err = queries_exec(proxy_mysql, test_queries); + if (test_err) { return EXIT_FAILURE; } + + // TEST TEARDOWN queries + int tear_err = exec_and_discard(proxy_mysql, setup_teardown.second); + if (tear_err) { return EXIT_FAILURE; } + + mysql_close(proxy_mysql); } - // STATUS_MYSQL_CONNECTION_NO_MULTIPLEX - SET VARIABLE - { - MYSQL* proxysql_mysql = mysql_init(NULL); + return EXIT_SUCCESS; +} +int text_exec_simple_conn_tests(const CommandLine& cl, const vector& tests_def) { + for (const auto& test_def : tests_def) { + const string& test_name { std::get(test_def) }; + const setup_teardown_t& setup_teardown = std::get(test_def); + const vector& test_queries = std::get(test_def); - 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 -1; - } + diag("Starting test '%s'", test_name.c_str()); - const std::vector prepared_stmt_queries { - "SET @test_variable = 44", - "PROXYSQL INTERNAL SESSION", - }; - json j_status; - - for (const auto& query : prepared_stmt_queries) { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); - } - mysql_free_result(tr_res); - } + MYSQL* proxy_mysql = mysql_init(NULL); - if (j_status.contains("backends")) { - bool found_backend = false; - for (const auto& backend : j_status["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool user_variable = backend["conn"]["status"]["user_variable"]; - ok(user_variable == true, "Connection status should have 'status.user_variable' set due to 'SET @variable'."); - - bool no_multiplex = backend["conn"]["status"]["no_multiplex"]; - ok(no_multiplex == true, "Connection status should have 'no_multiplex' set due to 'SET @variable'."); - } - } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); - } - } else { - ok(false, "No backends detected for the current connection."); + if (!mysql_real_connect(proxy_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(proxy_mysql)); + return EXIT_FAILURE; } - mysql_close(proxysql_mysql); + // TEST SETUP queries + int setup_err = exec_and_discard(proxy_mysql, setup_teardown.first); + if (setup_err) { return EXIT_FAILURE; } + + exec_test_queries(proxy_mysql, test_queries); + + // TEST TEARDOWN queries + int tear_err = exec_and_discard(proxy_mysql, setup_teardown.second); + if (tear_err) { return EXIT_FAILURE; } + + mysql_close(proxy_mysql); } - // STATUS_MYSQL_CONNECTION_NO_MULTIPLEX - TRANSACTION SHOULD NOT REPORT DISABLED MULTIPLEXING + return EXIT_SUCCESS; +} - // Transaction detection is done through server status, while the MULTIPLEXING will be disabled for the connection and - // the connection wont be returned to the connection pool, both of the metrics 'MultiplexDisabled' and 'status.no_multiplex' - // will report 'false'. +const double COLISSION_PROB = 1e-8; + +int _wait_for_replication( + const CommandLine& cl, MYSQL* proxy_admin, const std::string& check, uint32_t timeout, uint32_t read_hg +) { + const std::string t_count_reader_hg_servers { + "SELECT COUNT(*) FROM mysql_servers WHERE hostgroup_id=%d" + }; + std::string count_reader_hg_servers {}; + size_t size = + snprintf( nullptr, 0, t_count_reader_hg_servers.c_str(), read_hg) + 1; { - MYSQL* proxysql_mysql = mysql_init(NULL); + std::unique_ptr buf(new char[size]); + snprintf(buf.get(), size, t_count_reader_hg_servers.c_str(), read_hg); + count_reader_hg_servers = std::string(buf.get(), buf.get() + size - 1); + } - 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 -1; - } + MYSQL_QUERY(proxy_admin, count_reader_hg_servers.c_str()); + MYSQL_RES* hg_count_res = mysql_store_result(proxy_admin); + MYSQL_ROW row = mysql_fetch_row(hg_count_res); + uint32_t srv_count = strtoul(row[0], NULL, 10); + mysql_free_result(hg_count_res); + + if (srv_count > UINT_MAX) { + return EXIT_FAILURE; + } - const std::vector transaction_queries { "START TRANSACTION", "SELECT 1", "PROXYSQL INTERNAL SESSION", "COMMIT" }; - json j_status; + int waited = 0; + int queries = 0; + int result = EXIT_FAILURE; - for (const auto& query : transaction_queries) { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); + if (srv_count != 0) { + int retries = ceil(log10(COLISSION_PROB) / log10(static_cast(1)/srv_count)); + auto start = std::chrono::system_clock::now(); + std::chrono::duration elapsed {}; + + while (elapsed.count() < timeout && queries < retries) { + MYSQL* proxy = mysql_init(NULL); + if (!mysql_real_connect(proxy, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy)); + return EXIT_FAILURE; } - mysql_free_result(tr_res); - } - if (j_status.contains("backends")) { - bool found_backend = false; - for (const auto& backend : j_status["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool MultiplexDisabled = backend["conn"]["MultiplexDisabled"]; - ok(MultiplexDisabled == false, "Connection status should have 'MultiplexDisabled' set to false even with 'START TRANSACTION'."); - - bool no_multiplex = backend["conn"]["status"]["no_multiplex"]; - ok(no_multiplex == false, "Connection status should have 'no_multiplex' set to false even with 'START TRANSACTION'."); - } + int rc = mysql_query(proxy, check.c_str()); + bool correct_result = false; + + if (rc == EXIT_SUCCESS) { + MYSQL_RES* st_res = mysql_store_result(proxy); + correct_result = true; + queries += 1; + mysql_free_result(st_res); + } else { + diag("Replication check failed with error: ('%d','%s')", mysql_errno(proxy), mysql_error(proxy)); } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); + + if (correct_result == false) { + diag("Replication check failed with error: ('%d','%s')", mysql_errno(proxy), mysql_error(proxy)); + queries = 0; + waited += 1; + sleep(1); + } else { + mysql_close(proxy); + continue; } - } else { - ok(false, "No backends detected for the current connection."); + + auto it_end = std::chrono::system_clock::now(); + elapsed = it_end - start; + + mysql_close(proxy); } - mysql_close(proxysql_mysql); + if (queries == retries) { + result = EXIT_SUCCESS; + } + } else { + result = EXIT_SUCCESS; } - // STATUS_MYSQL_CONNECTION_NO_MULTIPLEX - Multiplex disabled due to STATUS_MYSQL_CONNECTION_LOCK_TABLES - { - MYSQL* proxysql_mysql = mysql_init(NULL); + return result; +} - 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 -1; - } +int stmt_exec_simple_conn_tests(const CommandLine& cl, const vector& tests_def) { + for (const auto& test_def : tests_def) { + const string& test_name { std::get(test_def) }; + const setup_teardown_t& setup_teardown = std::get(test_def); + const vector& test_queries = std::get(test_def); - const char* create_test_table = - "CREATE TABLE IF NOT EXISTS sysbench.test_session_var (" - " c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY," - " c2 VARCHAR(100)," - " c3 VARCHAR(100)" - ")"; - - const std::vector lock_tables_queries { - create_test_table, - "LOCK TABLES sysbench.test_session_var READ", - "PROXYSQL INTERNAL SESSION", - "UNLOCK TABLES", - "PROXYSQL INTERNAL SESSION", - "DROP TABLE sysbench.test_session_var" - }; - - std::vector vj_status; - - for (const auto& query : lock_tables_queries) { - json j_status; - - // we are trying to lock new created table 'sysbench.test_session_var' - if (query == lock_tables_queries[1]) { - int timeout = 0; - int query_err = 0; - int query_errno = 0; - - diag("Executing the locking table query with retrying due to replication lag."); - - while (timeout < replication_timeout) { - query_err = mysql_query(proxysql_mysql, query.c_str()); - if (query_err) { - query_errno = mysql_errno(proxysql_mysql); - if (query_errno != ER_NO_SUCH_TABLE) { - break; - } else { - sleep(1); - } - } else { - break; - } - timeout++; - } + diag("Starting test '%s'", test_name.c_str()); - if (query_err) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql)); - return -1; - } else { - diag("Replication lag took '%d.'", timeout); - } - } else { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); - vj_status.push_back(j_status); - } - mysql_free_result(tr_res); - } + MYSQL* proxy_mysql = mysql_init(NULL); + + if (!mysql_real_connect(proxy_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(proxy_mysql)); + return EXIT_FAILURE; } - if (vj_status[0].contains("backends")) { - bool found_backend = false; - for (const auto& backend : vj_status[0]["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool MultiplexDisabled = backend["conn"]["MultiplexDisabled"]; - ok(MultiplexDisabled == true, "Connection status should have 'MultiplexDisabled' set to 'true' 'DUE TO 'LOCK TABLES'."); + // TEST SETUP queries + int setup_err = exec_and_discard(proxy_mysql, setup_teardown.first); + if (setup_err) { return EXIT_FAILURE; } + + // NOTE-TODO: Due to current limitations in prepared statements handling, replication check needs to + // be handled by TEXT PROTOCOL queries. Once we are sure replication is OK, we proceed with 'prepared + // statements' checks. + for (const query_t& query_def : test_queries) { + const string& query = std::get(query_def); + + if (strcasecmp(query.c_str(), "PROXYSQL INTERNAL SESSION")) { + diag("Executing query '%s' with REPLICATION WAIT", query.c_str()); + + MYSQL* admin = mysql_init(NULL); + if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); + return EXIT_FAILURE; } - } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); - } - } else { - ok(false, "No backends detected for the current connection."); - } - if (vj_status[1].contains("backends")) { - bool found_backend = false; - for (const auto& backend : vj_status[1]["backends"]) { - if (backend != nullptr) { - found_backend = true; - ok(backend.contains("conn") == false, "Connection should be returned to the connection pool due to 'UNLOCK TABLES'."); + int wait_res = _wait_for_replication(cl, admin, query, 10, 1); + if (wait_res != EXIT_SUCCESS) { + diag("Waiting for replication FAILED. EXITING"); + return EXIT_FAILURE; } + + mysql_close(admin); } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); - } - } else { - ok(false, "No backends detected for the current connection."); } - mysql_close(proxysql_mysql); - } + prepare_stmt_queries(cl, test_queries); - // STATUS_MYSQL_CONNECTION_SQL_LOG_BIN0 - Multiplex disabled due to STATUS_MYSQL_CONNECTION_SQL_LOG_BIN0 - { - MYSQL* proxysql_mysql = mysql_init(NULL); + exec_stmt_queries(proxy_mysql, test_queries); - 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 -1; - } + // TEST TEARDOWN queries + int tear_err = exec_and_discard(proxy_mysql, setup_teardown.second); + if (tear_err) { return EXIT_FAILURE; } - const std::vector sql_log_bin_queries { - "SET SQL_LOG_BIN=0", - "SELECT 1", - "PROXYSQL INTERNAL SESSION" - }; + mysql_close(proxy_mysql); + } - json j_status; + return EXIT_SUCCESS; +} - for (const auto& query : sql_log_bin_queries) { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); - } - mysql_free_result(tr_res); +bool check_if_tg_elem_is_true(const json& j_tg_elem) { + return j_tg_elem.get() == true; +} + +bool check_if_tg_elem_is_false(const json& j_tg_elem) { + return j_tg_elem.get() == false; +} + +bool check_if_multiplex_enabled(const json& j_backends) { + for (const json& j_backend : j_backends) { + if (j_backend.contains("conn")) { + return false; } + } - if (j_status.contains("backends")) { - bool found_backend = false; - for (const auto& backend : j_status["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool MultiplexDisabled = backend["conn"]["MultiplexDisabled"]; - ok(MultiplexDisabled == true, "Connection status should have 'MultiplexDisabled' set to 'true' 'DUE TO 'SET SQL_LOG_BIN'."); + return true; +} + +const vector text_tests_defs { + { + "TRX_SERVER_STATUS", + {}, + { + { "START TRANSACTION", {}, {} }, + { "SELECT 1", {}, {} }, + { "PROXYSQL INTERNAL SESSION", {}, { {{"backends","conn","mysql","server_status"}, check_server_status_in_trx, "IN_TRANSACTION"} } }, + { "COMMIT", {}, {} } + }, + }, + { + "STATUS_MYSQL_CONNECTION_USER_VARIABLE", + setup_teardown_t { {}, {} }, + vector { + { "SET @test_variable = 44", rep_check_t {}, vector {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","user_variable"}, check_if_tg_elem_is_true, "USER_VARIABLE" }, + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_true, "MultiplexDisabled due to 'USER_VARIABLE'" } } } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); + } + }, + { + "TEXT_PROTOCOL_PREPARE_STMT", + {}, + { + { "PREPARE stmt_test FROM 'SELECT 1'", {}, {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","prepared_statement"}, check_if_tg_elem_is_true, "PREPARED_STATEMENT" }, + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_true, "MultiplexDisabled" } + } } - } else { - ok(false, "No backends detected for the current connection."); } - - mysql_close(proxysql_mysql); - } - - // STATUS_MYSQL_CONNECTION_FOUND_ROWS - Multiplex disabled due to STATUS_MYSQL_CONNECTION_FOUND_ROWS + }, { - 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 -1; + "STATUS_MYSQL_CONNECTION_LOCK_TABLES", + { + { + "CREATE TABLE IF NOT EXISTS test.sess_st_lock_tables (" + " c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY," + " c2 VARCHAR(100)," + " c3 VARCHAR(100)" + ")" + }, + { + "DROP TABLE test.sess_st_lock_tables" + } + }, + { + { "LOCK TABLES test.sess_st_lock_tables READ", {ER_NO_SUCH_TABLE, 10}, {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","lock_tables"}, check_if_tg_elem_is_true, "LOCK_TABLES" }, + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_true, "MultiplexDisabled" } + } + }, + { "UNLOCK TABLES", {}, {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends"}, check_if_multiplex_enabled, "UNLOCK_TABLES re-enable multiplex" }, + } + }, + { "LOCK TABLES test.sess_st_lock_tables READ", {}, {} }, + { "SET @test_variable = 43", {}, {} }, + { "UNLOCK TABLES", {}, {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","lock_tables"}, check_if_tg_elem_is_false, "UNLOCK_TABLES unset 'LOCK_TABLES' in status_flags" }, + } + } } - - const char* create_test_table = - "CREATE TABLE IF NOT EXISTS sysbench.test_session_var (" - " c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY," - " c2 VARCHAR(100)," - " c3 VARCHAR(100)" - ")"; - - const std::vector found_rows { - create_test_table, - "SELECT SQL_CALC_FOUND_ROWS * from sysbench.test_session_var", - "SELECT FOUND_ROWS()", - "PROXYSQL INTERNAL SESSION" - }; - - json j_status; - - for (const auto& query : found_rows) { - // we are trying to 'SELECT' over new created table 'sysbench.test_session_var' - if (query == found_rows[1]) { - int timeout = 0; - int query_err = 0; - int query_errno = 0; - - diag("Executing 'SELECT' with retrying due to replication lag."); - - while (timeout < replication_timeout) { - query_err = mysql_query(proxysql_mysql, query.c_str()); - if (query_err) { - query_errno = mysql_errno(proxysql_mysql); - if (query_errno != ER_NO_SUCH_TABLE) { - break; - } else { - sleep(1); - } - } else { - // free select results - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - mysql_free_result(tr_res); - break; - } - timeout++; + }, + { + "STATUS_MYSQL_CONNECTION_FOUND_ROWS", + { + { + "CREATE TABLE IF NOT EXISTS test.sess_st_sql_cacl_rows (" + " c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY," + " c2 VARCHAR(100)," + " c3 VARCHAR(100)" + ")" + }, + { + "DROP TABLE test.sess_st_sql_cacl_rows" + } + }, + { + { "SELECT SQL_CALC_FOUND_ROWS * FROM test.sess_st_sql_cacl_rows", {ER_NO_SUCH_TABLE, 10}, {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","found_rows"}, check_if_tg_elem_is_true, "SQL_CALC_FOUND_ROWS" }, + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_true, "MultiplexDisabled" } } - - if (query_err) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql)); - return -1; - } else { - diag("Replication lag took '%d.'", timeout); + }, + { "SELECT FOUND_ROWS()", {}, {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","found_rows"}, check_if_tg_elem_is_true, "SQL_CALC_FOUND_ROWS" }, + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_true, "MultiplexDisabled" } } - } else { - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); + } + } + }, + { + "STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE", + { + { + "CREATE TEMPORARY TABLE IF NOT EXISTS test.conn_st_temp_table (" + " c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY," + " c2 VARCHAR(100)," + " c3 VARCHAR(100)" + ")" + }, + { + "DROP TABLE test.conn_st_temp_table" + } + }, + { + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","temporary_table"}, check_if_tg_elem_is_true, "TEMPORARY_TABLE" }, + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_true, "MultiplexDisabled due to 'CREATE TEMPORARY TABLE'" } } - mysql_free_result(tr_res); } } - - if (j_status.contains("backends")) { - bool found_backend = false; - for (const auto& backend : j_status["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool found_rows = backend["conn"]["status"]["found_rows"]; - ok(found_rows == true, "Connection status should have 'status.found_rows' set to 'true' 'DUE TO 'SQL_CALC_FOUND_ROWS'."); - - bool MultiplexDisabled = backend["conn"]["MultiplexDisabled"]; - ok(MultiplexDisabled == true, "Connection status should have 'MultiplexDisabled' set to 'true' 'DUE TO 'SQL_CALC_FOUND_ROWS'."); + }, + { + // TODO: Check why when GET_LOCK is executed the first backend is "NULL", and not filled like in the rest + "STATUS_MYSQL_CONNECTION_GET_LOCK", + { {}, {} }, + { + { "SELECT 1", {}, {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { { {"backends"}, check_if_multiplex_enabled, "MultiplexEnabled after simple 'SELECT'" } } + }, + { "SELECT GET_LOCK('test_session_vars_lock', 2)", {}, {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","get_lock"}, check_if_tg_elem_is_true, "GET_LOCK" }, + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_true, "MultiplexDisabled due to 'CREATE TEMPORARY TABLE'" } + } + }, + { "SELECT RELEASE_LOCK('test_session_vars_lock')", {}, {} }, + // NOTE: Enable when supported + // { + // "PROXYSQL INTERNAL SESSION", {}, + // { { {"backends"}, check_if_multiplex_enabled, "MultiplexEnabled after 'RELEASE_LOCK()'" } } + // }, + } + }, + { + // Transaction detection is done through server status, while the MULTIPLEXING will be disabled for the connection and + // the connection wont be returned to the connection pool, both of the metrics 'MultiplexDisabled' and 'status.no_multiplex' + // will report 'false'. + "STATUS_MYSQL_CONNECTION_NO_MULTIPLEX - TRANSACTION", + setup_teardown_t { {}, {} }, + vector { + { "START TRANSACTION", rep_check_t {}, vector {} }, + { "SELECT 1", rep_check_t {}, vector {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","no_multiplex"}, check_if_tg_elem_is_false, "NO_MULTIPLEX status is 'False'" }, + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_false, "MultiplexDisabled reports 'false' during 'TRANSACTION'" } + } + }, + { "COMMIT", rep_check_t {}, vector {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends"}, check_if_multiplex_enabled, "COMMIT re-enables MULTIPLEXING" }, + } + }, + } + }, + { + "STATUS_MYSQL_CONNECTION_SQL_LOG_BIN0", + setup_teardown_t { {}, {} }, + vector { + { "SET SQL_LOG_BIN=0", rep_check_t {}, vector {} }, + { "SELECT 1", rep_check_t {}, vector {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_true, "MultiplexDisabled due to 'SET SQL_LOG_BIN'" } } } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); + } + }, + { + "STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT", + setup_teardown_t { + { "CREATE TABLE IF NOT EXISTS test.test_conn_has_savepoint(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY) ENGINE=INNODB" }, {} + }, + vector { + { "SET AUTOCOMMIT=0", rep_check_t {}, vector {} }, + { "SELECT * FROM test.test_conn_has_savepoint LIMIT 1 FOR UPDATE", rep_check_t {}, vector {} }, + { "SAVEPOINT test_conn_has_savepoint", rep_check_t {}, vector {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends","conn","status","has_savepoint"}, check_if_tg_elem_is_true, "HAS_SAVEPOINT status is 'True'" }, + { {"backends","conn","MultiplexDisabled"}, check_if_tg_elem_is_true, "MultiplexDisabled reports 'True' due to 'SAVEPOINT'" } + } + }, + { "COMMIT", rep_check_t {}, vector {} }, + { + "PROXYSQL INTERNAL SESSION", {}, + { + { {"backends"}, check_if_multiplex_enabled, "COMMIT re-enables MULTIPLEXING" }, + } } - } else { - ok(false, "No backends detected for the current connection."); } + } +}; + +const vector stmt_compatible_tests { + "STATUS_MYSQL_CONNECTION_USER_VARIABLE", + "STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE", + "STATUS_MYSQL_CONNECTION_FOUND_ROWS", + "STATUS_MYSQL_CONNECTION_GET_LOCK" +}; + +const vector test_compression_queries { + { "PROXYSQL INTERNAL SESSION", {}, {{{"conn","status","compression"}, check_if_tg_elem_is_true, "COMPRESSED_CONNECTION"}} }, +}; - mysql_close(proxysql_mysql); +int test_client_conn_compression_st(const CommandLine& cl) { + MYSQL* proxysql_mysql = mysql_init(NULL); + + if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, CLIENT_COMPRESS)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql)); + return EXIT_FAILURE; } - // STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT - Multiplex disabled due to STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT - { - MYSQL* proxysql_mysql = mysql_init(NULL); + exec_test_queries(proxysql_mysql, test_compression_queries); - 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 -1; - } + mysql_close(proxysql_mysql); - const std::vector savepoint_queries { - "SET AUTOCOMMIT=0", - "SELECT * FROM test.test_savepoint LIMIT 1 FOR UPDATE", - "SAVEPOINT test_session_variables_savepoint", - "PROXYSQL INTERNAL SESSION", - "COMMIT", - "PROXYSQL INTERNAL SESSION" - }; - - std::vector vj_status; - - for (const auto& query : savepoint_queries) { - json j_status; - MYSQL_QUERY(proxysql_mysql, query.c_str()); - MYSQL_RES* tr_res = mysql_store_result(proxysql_mysql); - if (query == "PROXYSQL INTERNAL SESSION") { - parse_result_json_column(tr_res, j_status); - vj_status.push_back(j_status); - } - mysql_free_result(tr_res); - } + return EXIT_SUCCESS; +} - if (vj_status[0].contains("backends")) { - bool found_backend = false; - for (const auto& backend : vj_status[0]["backends"]) { - if (backend != nullptr && backend.contains("conn") && backend["conn"].contains("status")) { - found_backend = true; - bool found_rows = backend["conn"]["status"]["has_savepoint"]; - ok(found_rows == true, "Connection status should have 'status.has_savepoint' set to 'true' 'DUE TO 'SAVEPOINT'."); +uint32_t compute_planned_tests(const vector text_tests_def, const vector& stmt_compatible_tests) { + size_t test_count = 0; - bool MultiplexDisabled = backend["conn"]["MultiplexDisabled"]; - ok(MultiplexDisabled == true, "Connection status should have 'MultiplexDisabled' set to 'true' 'DUE TO 'SAVEPOINT'."); - } + for (const test_def_t& test_def : text_tests_defs) { + const string& test_name = std::get(test_def); + + for (const query_t& test_query : std::get(test_def)) { + uint32_t test_checks = 0; + + for (const sess_check_t& check : std::get(test_query)) { + test_checks += 2; } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); + + const vector& c_tests = stmt_compatible_tests; + bool is_also_stmt_test = std::find(c_tests.begin(), c_tests.end(), test_name) != c_tests.end(); + + if (is_also_stmt_test) { + test_checks *= 2; } - } else { - ok(false, "No backends detected for the current connection."); + + test_count += test_checks; } + } + + return test_count; +} + +int main(int argc, char *argv[]) { + CommandLine cl; - if (vj_status[1].contains("backends")) { - bool found_backend = false; - for (const auto& backend : vj_status[1]["backends"]) { - if (backend != nullptr) { - found_backend = true; - ok(backend.contains("conn") == false, "Connection should be returned to the connection pool due to 'COMMIT'."); + if(cl.getEnv()) { + return exit_status(); + } + + uint32_t computed_exp_tests = compute_planned_tests(text_tests_defs, stmt_compatible_tests); + uint32_t compression_exp_tests = 2; + + diag("Computed simple connection 'TEXT' and 'STMT' tests where: '%d'", computed_exp_tests); + diag("Special connections tests where: '%d'", compression_exp_tests); + + plan(compression_exp_tests + computed_exp_tests); + + MYSQL* proxy_admin = mysql_init(NULL); + if (!mysql_real_connect(proxy_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_admin)); + return EXIT_FAILURE; + } + + // Set a replication lag inferior to default one (60). This is to prevent reads + // from a replica in which replication is currently disabled. + MYSQL_QUERY(proxy_admin, "UPDATE mysql_servers SET max_replication_lag=20"); + MYSQL_QUERY(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + const vector& c_tests = stmt_compatible_tests; + vector stmt_supp_tests { + std::accumulate( + text_tests_defs.begin(), text_tests_defs.end(), vector {}, + [](vector& elems, const test_def_t& t_def) -> vector& { + const auto same_name = [&t_def] (const string& test_name) -> bool { + return std::get(t_def) == test_name; + }; + auto f_test = std::find_if(stmt_compatible_tests.begin(), stmt_compatible_tests.end(), same_name); + + if (f_test != stmt_compatible_tests.end()) { + elems.push_back(t_def); } + + return elems; } - if (found_backend == false) { - ok(false, "'backends' doens't contains 'conn' objects with the relevant session information"); - } - } else { - ok(false, "No backends detected for the current connection."); - } - mysql_close(proxysql_mysql); - } + ) + }; + + diag("####### START SPECIAL CONNECTIONS TESTS #######"); + int t_res = test_client_conn_compression_st(cl); + if (t_res) { goto cleanup; } + diag("####### END SPECIAL PROTOCOL TESTS #######\n"); + + diag("####### START TEXT PROTOCOL TESTS #######"); + t_res = text_exec_simple_conn_tests(cl, text_tests_defs); + if (t_res) { goto cleanup; } + diag("####### END TEXT PROTOCOL TESTS #######\n"); + + diag("####### START STMT PROTOCOL TESTS #######"); + t_res = stmt_exec_simple_conn_tests(cl, stmt_supp_tests); + if (t_res) { goto cleanup; } + diag("####### END STMT PROTOCOL TESTS #######\n"); + +cleanup: - mysql_close(proxysql_admin); + mysql_close(proxy_admin); return exit_status(); }