From 4b5fe5888ee0aa8b599672b7addb2c7522f8f388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 20 Jul 2023 09:27:31 +0200 Subject: [PATCH 1/9] Fix race condition when reloading 'Query Rules Fast Routing' --- include/query_processor.h | 2 +- lib/Query_Processor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/query_processor.h b/include/query_processor.h index 741830589..5fd659a9a 100644 --- a/include/query_processor.h +++ b/include/query_processor.h @@ -388,7 +388,7 @@ class Query_Processor { */ SQLite3_result* load_fast_routing(const fast_routing_hashmap_t& fast_routing_hashmap); int search_rules_fast_routing_dest_hg( - khash_t(khStrInt)* _rules_fast_routing, const char* u, const char* s, int flagIN, bool lock + khash_t(khStrInt)*& _rules_fast_routing, const char* u, const char* s, int flagIN, bool lock ); SQLite3_result * get_current_query_rules_fast_routing(); SQLite3_result * get_current_query_rules_fast_routing_inner(); diff --git a/lib/Query_Processor.cpp b/lib/Query_Processor.cpp index c09b8edbc..998947659 100644 --- a/lib/Query_Processor.cpp +++ b/lib/Query_Processor.cpp @@ -981,7 +981,7 @@ SQLite3_result * Query_Processor::get_current_query_rules_fast_routing() { } int Query_Processor::search_rules_fast_routing_dest_hg( - khash_t(khStrInt)* _rules_fast_routing, const char* u, const char* s, int flagIN, bool lock + khash_t(khStrInt)*& _rules_fast_routing, const char* u, const char* s, int flagIN, bool lock ) { int dest_hg = -1; const size_t u_len = strlen(u); From d3a34712dfdf689948763bd5f012c728aced2c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 20 Jul 2023 14:04:44 +0200 Subject: [PATCH 2/9] Improve function resource acquisition readability - Improved readability for the locked map acquisition for function 'search_rules_fast_routing_dest_hg'. - Improved function documentation and added note about usage. --- include/query_processor.h | 15 ++++++++++++++- lib/Query_Processor.cpp | 11 +++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/include/query_processor.h b/include/query_processor.h index 5fd659a9a..c9dd826a3 100644 --- a/include/query_processor.h +++ b/include/query_processor.h @@ -387,8 +387,21 @@ class Query_Processor { * @return Old 'fast_routing_resultset' that has been replaced. Required to be freed by caller. */ SQLite3_result* load_fast_routing(const fast_routing_hashmap_t& fast_routing_hashmap); + /** + * @brief Searches for a matching rule in the supplied map, returning the destination hostgroup. + * @details This functions takes a pointer to the hashmap pointer. This is because it performs a + * conditional internal locking of member 'rwlock'. Since the original pointer value could be modified + * after the function call, we must perform the resource acquisition (dereference) after we have + * acquired the internal locking. + * @param khStrInt The map to be used for performing the search. See @details. + * @param u Username, used for the search as part of the map key. + * @param s Schemaname, used for the search as part of the map key. + * @param flagIN FlagIn, used for the search as part of the map key. + * @param lock Whether or not the member lock 'rwlock' should be taken for the search. + * @return If a matching rule is found, the target destination hostgroup, -1 otherwise. + */ int search_rules_fast_routing_dest_hg( - khash_t(khStrInt)*& _rules_fast_routing, const char* u, const char* s, int flagIN, bool lock + khash_t(khStrInt)** __rules_fast_routing, const char* u, const char* s, int flagIN, bool lock ); SQLite3_result * get_current_query_rules_fast_routing(); SQLite3_result * get_current_query_rules_fast_routing_inner(); diff --git a/lib/Query_Processor.cpp b/lib/Query_Processor.cpp index 998947659..fbe289da2 100644 --- a/lib/Query_Processor.cpp +++ b/lib/Query_Processor.cpp @@ -981,7 +981,7 @@ SQLite3_result * Query_Processor::get_current_query_rules_fast_routing() { } int Query_Processor::search_rules_fast_routing_dest_hg( - khash_t(khStrInt)*& _rules_fast_routing, const char* u, const char* s, int flagIN, bool lock + khash_t(khStrInt)** __rules_fast_routing, const char* u, const char* s, int flagIN, bool lock ) { int dest_hg = -1; const size_t u_len = strlen(u); @@ -998,6 +998,7 @@ int Query_Processor::search_rules_fast_routing_dest_hg( if (lock) { pthread_rwlock_rdlock(&this->rwlock); } + khash_t(khStrInt)* _rules_fast_routing = *__rules_fast_routing; khiter_t k = kh_get(khStrInt, _rules_fast_routing, keybuf_ptr); if (k == kh_end(_rules_fast_routing)) { khiter_t k2 = kh_get(khStrInt, _rules_fast_routing, keybuf_ptr + u_len); @@ -2133,10 +2134,12 @@ __exit_process_mysql_query: if (_thr_SQP_rules_fast_routing != nullptr) { proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 7, "Searching thread-local 'rules_fast_routing' hashmap with: user='%s', schema='%s', and flagIN='%d'\n", u, s, flagIN); - dst_hg = search_rules_fast_routing_dest_hg(_thr_SQP_rules_fast_routing, u, s, flagIN, false); + dst_hg = search_rules_fast_routing_dest_hg(&_thr_SQP_rules_fast_routing, u, s, flagIN, false); } else if (rules_fast_routing != nullptr) { proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 7, "Searching global 'rules_fast_routing' hashmap with: user='%s', schema='%s', and flagIN='%d'\n", u, s, flagIN); - dst_hg = search_rules_fast_routing_dest_hg(rules_fast_routing, u, s, flagIN, true); + // NOTE: A pointer to the member 'this->rules_fast_routing' is required, since the value of the + // member could have changed before the function acquires the internal lock. See function doc. + dst_hg = search_rules_fast_routing_dest_hg(&this->rules_fast_routing, u, s, flagIN, true); } if (dst_hg != -1) { @@ -3263,7 +3266,7 @@ int Query_Processor::testing___find_HG_in_mysql_query_rules_fast_routing_dual( khash_t(khStrInt)* rules_fast_routing = _rules_fast_routing ? _rules_fast_routing : this->rules_fast_routing; if (rules_fast_routing) { - ret = search_rules_fast_routing_dest_hg(rules_fast_routing, username, schemaname, flagIN, lock); + ret = search_rules_fast_routing_dest_hg(&rules_fast_routing, username, schemaname, flagIN, lock); } return ret; From 786b29e9a7ca19698d92747c4bd3b8698b5c5958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 20 Jul 2023 15:51:08 +0200 Subject: [PATCH 3/9] Minor fixes for test 'test_digest_umap_aux-t' - Fixed check interval taking into account rounding error (1). - Fixed some compilation warnings - Added some notes on test assumptions --- test/tap/tests/test_digest_umap_aux-t.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/test/tap/tests/test_digest_umap_aux-t.cpp b/test/tap/tests/test_digest_umap_aux-t.cpp index 609640cec..c75a06fa6 100644 --- a/test/tap/tests/test_digest_umap_aux-t.cpp +++ b/test/tap/tests/test_digest_umap_aux-t.cpp @@ -6,6 +6,9 @@ * execution time of the dummy queries has no been afected by the execution * time of the queries that read from table stats_mysql_query_digest. Finally, * check that the data stored in stats_mysql_query_digest is correct. + * + * NOTE: This test assumes that the queries being executed in sequence ('DUMMY_QUERIES') are completed within + * the same second. Failures are expected if this is not the case. */ #include @@ -83,7 +86,7 @@ vector get_digest_stats(MYSQL* proxy_admin) { MYSQL_RES *res = NULL; res = mysql_store_result(proxy_admin); MYSQL_ROW row; - while (row = mysql_fetch_row(res)) { + while ((row = mysql_fetch_row(res))) { digest_stats ds = {}; ds.hostgroup = atoi(row[0]); ds.schemaname = row[1]; @@ -267,7 +270,7 @@ int main(int argc, char** argv) { " Client_address -> before:`%s` - after:`%s`.\n" " Digests -> before:`%s` - after:`%s`.\n" " Digests_text -> before:`%s` - after:`%s`.\n" - " First_seen -> before:`%lld` - after:`%lldd`.", + " First_seen -> before:`%lld` - after:`%lld`.", ds_vector_before[i].hostgroup, ds_vector_after[i].hostgroup, ds_vector_before[i].schemaname.c_str(), ds_vector_after[i].schemaname.c_str(), ds_vector_before[i].username.c_str(), ds_vector_after[i].username.c_str(), @@ -278,12 +281,14 @@ int main(int argc, char** argv) { ); ok( ds_vector_after[i].count_star - ds_vector_before[i].count_star == num_dummy_queries_executed, - "Query `%s` should be executed %lld times. Act:'%lld'", + "Query `%s` should be executed %d times. Act:'%lld'", ds_vector_after[i].digest_text.c_str(), num_dummy_queries_executed, ds_vector_after[i].count_star - ds_vector_before[i].count_star ); + + // NOTE: Equality is included for 'before' and 'after' just in case query execution was very fast. ok( - ds_vector_before[i].last_seen < ds_vector_after[i].last_seen && + ds_vector_before[i].last_seen <= ds_vector_after[i].last_seen && ds_vector_before[i].sum_time < ds_vector_after[i].sum_time, "Last_seen and sum_time must have increased.\n" " Last_seen -> before:`%lld` - after:`%lld`.\n" @@ -301,9 +306,9 @@ int main(int argc, char** argv) { uint64_t bf_last_seen = ds_vector_before[i].last_seen; ok( - init_time <= bf_last_seen && final_time >= bf_last_seen, + init_time - 1 <= bf_last_seen && final_time + 1 >= bf_last_seen, "'last_seen' within required time range - min: %ld, max: %ld, last_seen: %ld", - init_time, final_time, bf_last_seen + init_time - 1, final_time + 1, bf_last_seen ); } From bb40e9e4319b8b96705a3a00536d2b6ce8730020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 28 Jul 2023 17:19:23 +0200 Subject: [PATCH 4/9] Fix 'COMMIT|ROLLBACK' forwarding to backend connections - Closes #4264 These commands are now forwarded to the backend connections from a session with potential transaction statuses, using the following priority order: 1. Connections flagged with 'SERVER_STATUS_IN_TRANS', or 'autocommit=0' when 'autocommit_false_is_transaction' is set. 2. Connections with 'autocommit=0' holding a 'SAVEPOINT'. 3. Connections with 'unknown transaction status', e.g: connections with errors. --- include/MySQL_Session.h | 22 ++++++++++++++++++++++ include/mysql_connection.h | 14 ++++++++++++++ lib/MySQL_Session.cpp | 15 ++++++++++++--- lib/mysql_connection.cpp | 10 ++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index dda1304e3..c5a802340 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -127,6 +127,16 @@ class MySQL_Session void return_proxysql_internal(PtrSize_t *); bool handler_special_queries(PtrSize_t *); + /** + * @brief Handles 'COMMIT|ROLLBACK' commands. + * @details Forwarding the packet is required when there are active transactions. Since we are limited to + * forwarding just one 'COMMIT|ROLLBACK', we work under the assumption that we only have one active + * transaction. If more transactions are simultaneously open for the session, more 'COMMIT|ROLLBACK'. + * commands are required to be issued by the client, so they could be forwarded to the corresponding + * backend connections. + * @param The received packet to be handled. + * @return 'true' if the packet is intercepted and never forwarded to the client, 'false' otherwise. + */ bool handler_CommitRollback(PtrSize_t *); bool handler_SetAutocommit(PtrSize_t *); /** @@ -329,6 +339,18 @@ class MySQL_Session unsigned int NumActiveTransactions(bool check_savpoint=false); bool HasOfflineBackends(); bool SetEventInOfflineBackends(); + /** + * @brief Finds one active transaction in the current backend connections. + * @details Since only one connection is returned, if the session holds multiple backend connections with + * potential transactions, the priority is: + * 1. Connections flagged with 'SERVER_STATUS_IN_TRANS', or 'autocommit=0' in combination with + * 'autocommit_false_is_transaction'. + * 2. Connections with 'autocommit=0' holding a 'SAVEPOINT'. + * 3. Connections with 'unknown transaction status', e.g: connections with errors. + * @param check_savepoint Used to also check for connections holding savepoints. See MySQL bug + * https://bugs.mysql.com/bug.php?id=107875. + * @returns The hostgroup in which the connection was found, -1 in case no connection is found. + */ int FindOneActiveTransaction(bool check_savepoint=false); unsigned long long IdleTime(); diff --git a/include/mysql_connection.h b/include/mysql_connection.h index dda947f2d..472ba2118 100644 --- a/include/mysql_connection.h +++ b/include/mysql_connection.h @@ -209,6 +209,20 @@ class MySQL_Connection { void process_rows_in_ASYNC_STMT_EXECUTE_STORE_RESULT_CONT(unsigned long long& processed_bytes); void async_free_result(); + /** + * @brief Returns if the connection is **for sure**, known to be in an active transaction. + * @details The function considers two things: + * 1. If 'server_status' is flagged with 'SERVER_STATUS_IN_TRANS'. + * 2. If the connection has 'autcommit=0' and 'autocommit_false_is_transaction' is set. + * @return True if the connection is known to be in a transaction, or equivalent state. + */ + bool IsKnownActiveTransaction(); + /** + * @brief Returns if the connection is in a **potential transaction**. + * @details This function is a more strict version of 'IsKnownActiveTransaction', which also considers + * connections which holds 'unknown_transaction_status' as potentially active transactions. + * @return True if the connection is in potentially in an active transaction. + */ bool IsActiveTransaction(); /* { bool ret=false; if (mysql) { diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 215cff61c..86d2cba5c 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -917,9 +917,16 @@ bool MySQL_Session::handler_CommitRollback(PtrSize_t *pkt) { // in this part of the code (as at release 2.4.3) where we call // NumActiveTransactions() with the check_savepoint flag . // This to try to handle MySQL bug https://bugs.mysql.com/bug.php?id=107875 - unsigned int nTrx=NumActiveTransactions(true); - if (nTrx) { + // + // Since we are limited to forwarding just one 'COMMIT|ROLLBACK', we work under the assumption that we + // only have one active transaction. Under this premise, we should execute this command under that + // specific connection, for that, we update 'current_hostgroup' with the first active transaction we are + // able to find. If more transactions are simultaneously open for the session, more 'COMMIT|ROLLBACK' + // commands are required to be issued by the client to continue ending transactions. + unsigned int hg = FindOneActiveTransaction(true); + if (hg != -1) { // there is an active transaction, we must forward the request + current_hostgroup = hg; return false; } else { // there is no active transaction, we will just reply OK @@ -7497,8 +7504,10 @@ int MySQL_Session::FindOneActiveTransaction(bool check_savepoint) { _mybe=(MySQL_Backend *)mybes->index(i); if (_mybe->server_myds) { if (_mybe->server_myds->myconn) { - if (_mybe->server_myds->myconn->IsActiveTransaction()) { + if (_mybe->server_myds->myconn->IsKnownActiveTransaction()) { return (int)_mybe->server_myds->myconn->parent->myhgc->hid; + } else if (_mybe->server_myds->myconn->IsActiveTransaction()) { + ret = (int)_mybe->server_myds->myconn->parent->myhgc->hid; } else { // we use check_savepoint to check if we shouldn't ignore COMMIT or ROLLBACK due // to MySQL bug https://bugs.mysql.com/bug.php?id=107875 related to diff --git a/lib/mysql_connection.cpp b/lib/mysql_connection.cpp index d3300bb2e..d1aa96368 100644 --- a/lib/mysql_connection.cpp +++ b/lib/mysql_connection.cpp @@ -2385,6 +2385,16 @@ bool MySQL_Connection::AutocommitFalse_AndSavepoint() { return ret; } +bool MySQL_Connection::IsKnownActiveTransaction() { + bool in_trx = mysql ? mysql->server_status & SERVER_STATUS_IN_TRANS : false; + + if (in_trx == false) { + in_trx = mysql_thread___autocommit_false_is_transaction && (IsAutoCommit() == false); + } + + return in_trx; +} + bool MySQL_Connection::IsActiveTransaction() { bool ret=false; if (mysql) { From f35cebfb3ae183b3da16abe08b071b6823e21c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 28 Jul 2023 17:52:26 +0200 Subject: [PATCH 5/9] Add regression test for issue #4264 - General testing for 'COMMIT|ROLLBACK' routing. - Add multiple testing utility functions. --- test/tap/tap/utils.cpp | 317 +++++ test/tap/tap/utils.h | 74 + .../tests/reg_test_4264-commit_rollback-t.cpp | 1195 +++++++++++++++++ 3 files changed, 1586 insertions(+) create mode 100644 test/tap/tests/reg_test_4264-commit_rollback-t.cpp diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index 9a9afedb6..2875f9a87 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -16,15 +16,21 @@ #include "utils.h" #include +#include #include #include #include #include "proxysql_utils.h" +using std::pair; +using std::map; using std::string; using std::vector; +using std::to_string; +using nlohmann::json; + std::size_t count_matches(const string& str, const string& substr) { std::size_t result = 0; std::size_t pos = 0; @@ -1256,3 +1262,314 @@ cleanup: return res; } + +json fetch_internal_session(MYSQL* proxy) { + int rc = mysql_query_t(proxy, "PROXYSQL INTERNAL SESSION"); + + if (rc ) { + return json {}; + } else { + MYSQL_RES* myres = mysql_store_result(proxy); + MYSQL_ROW row = mysql_fetch_row(myres); + json j_session = json::parse(row[0]); + mysql_free_result(myres); + + return j_session; + } +} + +struct cols_table_info_t { + vector names; + vector widths; +}; + +std::string dump_as_table(MYSQL_RES* result, const cols_table_info_t& cols_info) { + if (!result) { return {}; } + + const vector& cols_names { cols_info.names }; + const vector& cols_widths { cols_info.widths }; + + uint32_t num_fields = mysql_num_fields(result); + std::string table_str { "+" }; + + for (size_t width : cols_widths) { + table_str += std::string(width + 2, '-') + "+"; + } + table_str += "\n"; + + table_str += "|"; + for (size_t col = 0; col < num_fields; col++) { + table_str += " " + cols_names[col] + std::string(cols_widths[col] - cols_names[col].size(), ' ') + " |"; + } + table_str += "\n"; + + table_str += "+"; + for (size_t width : cols_widths) { + table_str += std::string(width + 2, '-') + "+"; + } + table_str += "\n"; + + while (MYSQL_ROW row = mysql_fetch_row(result)) { + table_str += "|"; + for (size_t col = 0; col < num_fields; col++) { + std::string value = row[col] ? row[col] : ""; + table_str += " " + value + std::string(cols_widths[col] - value.size(), ' ') + " |"; + } + table_str += "\n"; + } + + table_str += "+"; + for (size_t width : cols_widths) { + table_str += std::string(width + 2, '-') + "+"; + } + table_str += "\n"; + + mysql_data_seek(result, 0); + + return table_str; +} + +std::string dump_as_table(MYSQL_RES* result) { + if (!result) { return {}; } + + uint32_t num_fields = mysql_num_fields(result); + MYSQL_FIELD* fields = mysql_fetch_fields(result); + + vector columns {}; + for (uint32_t i = 0; i < num_fields; ++i) { + columns.push_back(fields[i].name); + } + + vector cols_widths(num_fields, 0); + + for (int col = 0; col < num_fields; ++col) { + cols_widths[col] = std::max(cols_widths[col], columns[col].size()); + } + + while (MYSQL_ROW row = mysql_fetch_row(result)) { + for (uint32_t col = 0; col < num_fields; col++) { + if (row[col]) { + cols_widths[col] = std::max(cols_widths[col], strlen(row[col])); + } + } + } + + mysql_data_seek(result, 0); + std::string res { dump_as_table(result, {columns, cols_widths}) }; + + return res; +} + +pair> exec_dql_query(MYSQL* conn, const string& query, bool dump_res) { + if (mysql_query(conn, query.c_str())) { + diag("Failed to executed query `%s`", query.c_str()); + return { EXIT_FAILURE, {} }; + } + + MYSQL_RES* my_stats_res = mysql_store_result(conn); + if (my_stats_res == nullptr) { + diag("Failed to retrieve a resultset, expected DQL query"); + + return { EXIT_FAILURE, {} }; + } else { + if (dump_res) { + fprintf(stderr, "%s", dump_as_table(my_stats_res).c_str()); + } + + vector my_rows { extract_mysql_rows(my_stats_res) }; + mysql_free_result(my_stats_res); + + return { EXIT_SUCCESS, my_rows }; + } +} + +string join(string delim, const vector& words) { + return std::accumulate( + words.begin(), words.end(), string {}, + [&delim] (const string& s1, const string& s2) { + if (s1.empty()) { + return s2; + } else { + return s1 + delim + s2; + } + } + ); +} + +string gen_conn_stats_query(const vector& hgs) { + const auto _to_string = [] (uint32_t n) -> string { return to_string(n); }; + + vector hgs_str {}; + std::transform(hgs.begin(), hgs.end(), std::back_inserter(hgs_str), _to_string); + + const string CONN_STATS_HGS { join(",", hgs_str) }; + const string CONN_STATS_QUERY_T { + "SELECT hostgroup,ConnUsed,ConnFree,ConnOk,ConnERR,MaxConnUsed,Queries" + " FROM stats.stats_mysql_connection_pool" + }; + + if (hgs.empty()) { + return CONN_STATS_QUERY_T; + } else { + return CONN_STATS_QUERY_T + " WHERE hostgroup IN (" + CONN_STATS_HGS + ")"; + } +} + +int dump_conn_stats(MYSQL* admin, const vector hgs) { + const string query { gen_conn_stats_query(hgs) }; + MYSQL_QUERY(admin, query.c_str()); + + MYSQL_RES* myres = mysql_store_result(admin); + const string table { dump_as_table(myres) }; + mysql_free_result(myres); + fprintf(stderr, "%s", table.c_str()); + + return EXIT_SUCCESS; +} + +pair fetch_conn_stats(MYSQL* admin, const vector hgs) { + const string stats_query { gen_conn_stats_query(hgs) }; + const pair> conn_pool_stats { exec_dql_query(admin, stats_query, true) }; + + if (conn_pool_stats.first || conn_pool_stats.second.size() != hgs.size()) { + if (conn_pool_stats.first) { + diag("Failed to extract stats from 'CONNPOOL'"); + } + if (conn_pool_stats.second.size() != hgs.size()) { + diag("Expected '%ld' row in 'CONNPOOL' stats resultset", hgs.size()); + } + return { EXIT_FAILURE, {} }; + } + + if (conn_pool_stats.first) { + return { conn_pool_stats.first, {} }; + } else { + map res_map {}; + + for (const mysql_row_t& row : conn_pool_stats.second) { + const string& column = row[POOL_STATS_IDX::HOSTGROUP]; + const uint32_t hg = std::stol(row[POOL_STATS_IDX::HOSTGROUP]); + + res_map.insert({ hg, row }); + } + + return { EXIT_SUCCESS, res_map }; + } +} + +int wait_for_cond(MYSQL* mysql, const std::string& query, uint32_t timeout) { + int result = EXIT_FAILURE; + + auto start = std::chrono::system_clock::now(); + std::chrono::duration elapsed {}; + + while (elapsed.count() < timeout && result == EXIT_FAILURE) { + int rc = mysql_query(mysql, query.c_str()); + fprintf( + stderr, "# %s: Waiting for condition '%s' in ('%s':%d)\n", + get_formatted_time().c_str(), query.c_str(), mysql->host, mysql->port + ); + + if (rc == EXIT_SUCCESS) { + MYSQL_RES* myres = mysql_store_result(mysql); + if (myres) { + uint32_t field_num = mysql_num_fields(myres); + uint32_t row_num = mysql_num_rows(myres); + + if (field_num == 1 && row_num == 1) { + MYSQL_ROW row = mysql_fetch_row(myres); + + if (row && strcasecmp("TRUE", row[0]) == 0) { + result = EXIT_SUCCESS; + } + } + + mysql_free_result(myres); + + if (result == EXIT_SUCCESS) { + break; + } + } + } else { + diag("Condition query failed with error: ('%d','%s')", mysql_errno(mysql), mysql_error(mysql)); + result = EXIT_FAILURE; + break; + } + + usleep(500 * 1000); + + auto it_end = std::chrono::system_clock::now(); + elapsed = it_end - start; + } + + return result; +} + +void check_conn_count(MYSQL* admin, const string& conn_type, uint32_t conn_num, int32_t hg) { + const string hg_s { to_string(hg) }; + const string conn_num_s { to_string(conn_num) }; + string select_conns_in_hg {}; + + if (hg == -1) { + select_conns_in_hg = "SELECT SUM(" + conn_type + ") FROM stats_mysql_connection_pool"; + } else { + select_conns_in_hg = "SELECT " + conn_type + " FROM stats_mysql_connection_pool WHERE hostgroup=" + hg_s; + } + + const string check_used_conns { + "SELECT IIF((" + select_conns_in_hg + ")=" + conn_num_s + ",'TRUE','FALSE')" + }; + + int to = wait_for_cond(admin, check_used_conns, 3); + ok(to == EXIT_SUCCESS, "Conns should met the required condition"); + + if (to != EXIT_SUCCESS) { + dump_conn_stats(admin, {}); + } +}; + +void check_query_count(MYSQL* admin, uint32_t queries, uint32_t hg) { + const string queries_s { to_string(queries) }; + const string hg_s { to_string(hg) }; + + const string select_hg_queries { + "SELECT Queries FROM stats_mysql_connection_pool WHERE hostgroup=" + to_string(hg) + }; + const string check_queries { + "SELECT IIF((" + select_hg_queries + ")=" + queries_s + ",'TRUE','FALSE')" + }; + + int to = wait_for_cond(admin, check_queries, 3); + ok(to == EXIT_SUCCESS, "Queries counted on hg '%d' should be '%d'", hg, queries); + + if (to != EXIT_SUCCESS) { + dump_conn_stats(admin, {}); + } +}; + +void check_query_count(MYSQL* admin, vector queries, uint32_t hg) { + const string queries_s { + std::accumulate(queries.begin(), queries.end(), std::string(), + [](const std::string& str, const uint32_t& n) -> std::string { + return str + (str.length() > 0 ? "," : "") + std::to_string(n); + } + ) + }; + const string hg_s { to_string(hg) }; + + const string select_hg_queries { + "SELECT Queries FROM stats_mysql_connection_pool WHERE hostgroup=" + to_string(hg) + }; + const string check_queries { + "SELECT IIF((" + select_hg_queries + ") IN (" + queries_s + "),'TRUE','FALSE')" + }; + + int to = wait_for_cond(admin, check_queries, 3); + ok(to == EXIT_SUCCESS, "Queries counted on hg '%d' should be in '%s'", hg, queries_s.c_str()); + + if (to != EXIT_SUCCESS) { + dump_conn_stats(admin, {}); + } else { + dump_conn_stats(admin, { hg }); + } +}; diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index 0863dd33d..c490b9dfe 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -14,6 +14,7 @@ #include "sqlite3db.h" #include "command_line.h" +#include "json.hpp" inline std::string get_formatted_time() { time_t __timer; @@ -459,6 +460,11 @@ int extract_sqlite3_host_port(MYSQL* admin, std::pair& host_po */ std::vector split(const std::string& s, char delim); +/** + * @brief Joins the supplied list of words using the supplied delim. + */ +std::string join(std::string delim, const std::vector& words); + /** * @brief Gets the supplied environmental variable as a std::string. * @param var The variable to value to extract. @@ -518,4 +524,72 @@ enum SQ3_RES_T { */ sq3_res_t sqlite3_execute_stmt(sqlite3* db, const std::string& query); +/** + * @brief Returns a 'JSON' object holding 'PROXYSQL INTERNAL SESSION' contents. + * @param proxy And already openned connection to ProxySQL. + */ +nlohmann::json fetch_internal_session(MYSQL* proxy); + +/** + * @brief Returns a string table representation of the supplied resultset. + */ +std::string dump_as_table(MYSQL_RES* result); + +using mysql_row_t = std::vector; + +/** + * @brief Executes a DQL query and returns the contents of its resultset. + * @param conn An already opened MYSQL connection. + * @param query The DQL query to be executed. + * @param dump_res Wether or not to dump the resultset contents as a table to 'stderr'. + * @return A pair with the shape {err_code, contents}. + */ +std::pair> exec_dql_query(MYSQL* conn, const std::string& query, bool dump_res=false); + +struct POOL_STATS_IDX { + enum { + HOSTGROUP, + CONN_USED, + CONN_FREE, + CONN_OK, + CONN_ERR, + MAX_CONN_USED, + QUERIES, + }; +}; + +/** + * @brief Dumps a resultset with fields from the supplied hgs from 'stats_mysql_connection_pool'. + * @details The fetched fields are 'hostgroup,ConnUsed,ConnFree,ConnOk,ConnERR,MaxConnUsed,Queries'. + */ +int dump_conn_stats(MYSQL* admin, const std::vector hgs); + +using pool_state_t = std::map; + +/** + * @brief Fetches several fields from table 'stats_mysql_connection_pool' for supplied hostgroups. + * @details The fetched fields are 'hostgroup,ConnUsed,ConnFree,ConnOk,ConnERR,MaxConnUsed,Queries'. + * @param admin An already opened connection to Admin. + * @param hgs The hostgroups from which to fetch several fields. + * @return A pair of the shape {err_code, pool_state_t}. + */ +std::pair fetch_conn_stats(MYSQL* admin, const std::vector hgs); +/** + * @brief Waits until the condition specified by the 'query' holds, or 'timeout' is reached. + * @details Several details about the function impl: + * - Sleeps of 500ms are performed between each check. + * - The time and check being performed is always logged ahead. + * - If query execution fails, reason is logged, wait aborted and EXIT_FAILURE returned. + * @param mysql And already opened conn to ProxySQL in which the query is to be executed. + * @param query Query with the condition check, it's expected to return 'TRUE' when the check succeeds. + * @param timeout A timeout specified in seconds. + * @return EXIT_SUCCESS if the checks holds before the timeout, EXIT_FAILURE otherwise. + */ +int wait_for_cond(MYSQL* mysql, const std::string& query, uint32_t timeout); + +// Helpers using 'wait_for_cond' on 'stats_mysql_connection' +void check_conn_count(MYSQL* admin, const std::string& conn_type, uint32_t conn_num, int32_t hg=-1); +void check_query_count(MYSQL* admin, uint32_t queries, uint32_t hg); +void check_query_count(MYSQL* admin, std::vector queries, uint32_t hg); + #endif // #define UTILS_H diff --git a/test/tap/tests/reg_test_4264-commit_rollback-t.cpp b/test/tap/tests/reg_test_4264-commit_rollback-t.cpp new file mode 100644 index 000000000..9215e3650 --- /dev/null +++ b/test/tap/tests/reg_test_4264-commit_rollback-t.cpp @@ -0,0 +1,1195 @@ +/** + * @file reg_test_4264-commit_rollback-t.cpp + * @brief Verifies that 'COMMIT' and 'ROLLBACK' are executed in the correct backend connections when several + * connections are hold by a session. + * @details General test methodology: + * 0. Create database and tables for performing the tests + * 1. Configure the required 'mysql_servers' and 'mysql_query_rules'. + * 2. Extract data 'stats_mysql_connection_pool' (and or 'PROXYSQL INTERNAL SESSION'). + * 3. Perform operations (INSERT,COMMIT,ROLLBACK) in the target servers. + * 4. Extract data again from 'stats_mysql_connection_pool' (and or 'PROXYSQL INTERNAL SESSION'). + * + * Repeat the last three elements for every query cycle to test: + * - Simple BEGIN, 'COMMIT|ROLLBACK' - Explicit trxs. + * - Autocommit=0, Query, 'COMMIT|ROLLBACK' - Implicit trxs. + * - Failing queries - Unknown Transaction Status: + * + * Using the previous query cycles, check increasingly complex scenarios: + * - With persistent connections: + * + Check with explicit transaction in def/non-def hgs. + * + Check with explicit transaction in def/non-def hgs + error. + * + Check with implicit transaction in def/non-def hgs. + * + Check with implicit transaction in def/non-def hgs + error. + * + Check no transaction + error. + * - Without persistent connections: + * + Previous scenarios but on different backend connections. + * + Include a 'SAVEPOINT' in a third connection. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "tap.h" +#include "utils.h" +#include "command_line.h" +#include "proxysql_utils.h" +#include "json.hpp" + +using std::function; +using std::pair; +using std::vector; +using std::string; + +using std::to_string; +using nlohmann::json; + +const uint32_t DF_HG = 0; +const uint32_t TG_HG_1 = 1047; +const uint32_t TG_HG_2 = 1048; +const string TG_HG_STR { to_string(TG_HG_1) }; + +/** + * @details Flow for explicit and persistent trxs: + * - BEGIN -> Starts a trx, in default hostgroup. + * + Check that ConnUsed incremented in that hostgroup. + * + Check that query should have been issued in that hostgroup. + * - TG_HG_1 - INSERT INTO -> Should try to reach another hostgroup. Failing to do so due to persist. + * + Check that query have been executed in the 'BEGIN' hostgroup. + * - COMMIT|ROLLBACK -> Should be executed in original hostgroup. + * + Check that query have been executed in the 'BEGIN' hostgroup. + * + Check that ConnUsed have decreased after query. + */ +int explicit_trx_persist(CommandLine& cl, MYSQL* admin, MYSQL* proxy, const string& trx_cmd) { + const vector tg_hgs { DF_HG }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t df_hg_qs = std::stol(pre_pool_state.at(DF_HG)[POOL_STATS_IDX::QUERIES]); + + MYSQL_QUERY_T(proxy, "BEGIN"); + diag("Only connection should be in use for any hg"); + check_conn_count(admin, "ConnUsed", 1); + diag("Only connection should be in use for hg '%d'", DF_HG); + check_conn_count(admin, "ConnUsed", 1, DF_HG); + diag("Query should have been issued to hg '%d'", TG_HG_1); + check_query_count(admin, df_hg_qs + 1, DF_HG); + + diag("Query intentionally targeting unreachable hostgroup due to 'persist'"); + MYSQL_QUERY_T(proxy, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + check_query_count(admin, df_hg_qs + 2, DF_HG); + + MYSQL_QUERY_T(proxy, trx_cmd.c_str()); + check_query_count(admin, df_hg_qs + 3, DF_HG); + check_conn_count(admin, "ConnUsed", 0, DF_HG); + + return EXIT_SUCCESS; +} + +/** + * @details Same check as 'explicit_trx_persist' but trx is created in random hostgroup. + * Ensures that default hostgroup routing works as non-default routing. + */ +int explicit_trx_persist_2(CommandLine& cl, MYSQL* admin, MYSQL* proxy, const string& trx_cmd) { + const vector tg_hgs { DF_HG, TG_HG_1 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t df_hg_qs = std::stol(pre_pool_state.at(DF_HG)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + + MYSQL_QUERY_T(proxy, ("/* hostgroup=" + to_string(TG_HG_1) + "*/ BEGIN").c_str()); + diag("Only connection should be in use for hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + diag("Query should have been issued to hg '%d'", TG_HG_1); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + diag("Query intentionally targeting unreachable hostgroup due to 'persist'"); + MYSQL_QUERY_T(proxy, "DO 1"); + check_query_count(admin, tg_hg_1_qs + 2, TG_HG_1); + + MYSQL_QUERY_T(proxy, trx_cmd.c_str()); + check_query_count(admin, tg_hg_1_qs + 3, TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + return EXIT_SUCCESS; +} + +/** + * @details Tests that explicit transactions via 'BEGIN' and 'COMMIT' with + * 'transaction_persistent=1' should disable routing, and all operations + * should be done in the same backend connection. + */ +int explicit_trx_persist_c(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_trx_persist(cl, admin, proxy, "COMMIT"); +} + +int explicit_trx_persist_r(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_trx_persist(cl, admin, proxy, "ROLLBACK"); +} + +int explicit_trx_persist_2_c(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_trx_persist_2(cl, admin, proxy, "COMMIT"); +} + +int explicit_trx_persist_2_r(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_trx_persist_2(cl, admin, proxy, "ROLLBACK"); +} + +int implicit_trx_persist(CommandLine& cl, MYSQL* admin, MYSQL* proxy, const string& trx_cmd) { + const vector tg_hgs { DF_HG, TG_HG_1 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t df_hg_qs = std::stol(pre_pool_state.at(DF_HG)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + + MYSQL_QUERY_T(proxy, "SET autocommit=0"); + + diag("No conns should be in use for any hostgroup"); + check_conn_count(admin, "ConnUsed", 0); + diag("No conns should be in use for hg '%d'", DF_HG); + check_conn_count(admin, "ConnUsed", 0, DF_HG); + + diag("No queries should have been issued to hg '%d'", DF_HG); + check_query_count(admin, df_hg_qs, DF_HG); + + MYSQL_QUERY_T(proxy, "INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + check_query_count(admin, df_hg_qs + 1, DF_HG); + check_conn_count(admin, "ConnUsed", 1, DF_HG); + + MYSQL_QUERY_T(proxy, trx_cmd.c_str()); + check_query_count(admin, df_hg_qs + 2, DF_HG); + check_conn_count(admin, "ConnUsed", 0, DF_HG); + + return EXIT_SUCCESS; +} + +int implicit_trx_persist_c(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return implicit_trx_persist(cl, admin, proxy, "COMMIT"); +} + +int implicit_trx_persist_r(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return implicit_trx_persist(cl, admin, proxy, "ROLLBACK"); +} + +int explicit_trx_persist_no_def_hg(CommandLine& cl, MYSQL* admin, MYSQL* proxy, const string& trx_cmd) { + const vector tg_hgs { TG_HG_1 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + + MYSQL_QUERY_T(proxy, ("/* hostgroup=" + to_string(TG_HG_1) + "*/ BEGIN").c_str()); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + diag("Query intentionally targeting unreachable hostgroup due to 'persist'"); + MYSQL_QUERY_T(proxy, "/* TG_HG_2 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + check_query_count(admin, tg_hg_1_qs + 2, TG_HG_1); + + MYSQL_QUERY_T(proxy, trx_cmd.c_str()); + check_query_count(admin, tg_hg_1_qs + 3, TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + return EXIT_SUCCESS; +} + +int explicit_trx_persist_no_def_hg_c(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_trx_persist_no_def_hg(cl, admin, proxy, "COMMIT"); +} + +int explicit_trx_persist_no_def_hg_r(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_trx_persist_no_def_hg(cl, admin, proxy, "ROLLBACK"); +} + +int implicit_trx_persist_no_def_hg(CommandLine& cl, MYSQL* admin, MYSQL* proxy, const string& trx_cmd) { + const vector tg_hgs { DF_HG, TG_HG_1 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t df_hg_qs = std::stol(pre_pool_state.at(DF_HG)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + + MYSQL_QUERY_T(proxy, "SET autocommit=0"); + + diag("No conns should be in use for hg '%d'", DF_HG); + check_conn_count(admin, "ConnUsed", 0, DF_HG); + diag("No queries should have been issued to hg '%d'", DF_HG); + check_query_count(admin, df_hg_qs, DF_HG); + + MYSQL_QUERY_T(proxy, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + MYSQL_QUERY_T(proxy, trx_cmd.c_str()); + check_query_count(admin, tg_hg_1_qs + 2, TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + return EXIT_SUCCESS; +} + +int implicit_trx_persist_no_def_hg_c(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return implicit_trx_persist_no_def_hg(cl, admin, proxy, "COMMIT"); +} + +int implicit_trx_persist_no_def_hg_r(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return implicit_trx_persist_no_def_hg(cl, admin, proxy, "ROLLBACK"); +} + +/** + * @details Flow for explicit and persistent trxs: + * - BEGIN -> Starts a trx, in default hostgroup. + * + Check that ConnUsed incremented in that hostgroup. + * + Check that query should have been issued in that hostgroup. + * - TG_HG_1 - INSERT INTO -> Should succeed to execute in hostgroup 'N' (no-persist). + * + Check that query have been executed in the 'N' hostgroup. + * - COMMIT|ROLLBACK -> Should be executed in original hostgroup. + * + Check that query have been executed in the 'BEGIN' hostgroup. + * + Check that ConnUsed have decreased after query. + */ +int explicit_trx_no_persist(CommandLine& cl, MYSQL* admin, MYSQL*, const string& trx_cmd) { + MYSQL* proxy_sbtest = mysql_init(NULL); + + if (!mysql_real_connect(proxy_sbtest, cl.host, "sbtest1", "sbtest1", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_sbtest)); + return EXIT_FAILURE; + } + + const vector tg_hgs { DF_HG, TG_HG_1 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + const uint32_t df_hg_qs = std::stol(pre_pool_state.at(DF_HG)[POOL_STATS_IDX::QUERIES]); + + // Started transaction in 'DF_HG' + MYSQL_QUERY_T(proxy_sbtest, "BEGIN"); + check_conn_count(admin, "ConnUsed", 1, DF_HG); + check_query_count(admin, df_hg_qs + 1, DF_HG); + + // Query redirected to 'TG_HG' imposed by query rule + MYSQL_QUERY_T(proxy_sbtest, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + // Query redirected to 'DF_HG' where trx was started + MYSQL_QUERY_T(proxy_sbtest, trx_cmd.c_str()); + check_query_count(admin, df_hg_qs + 2, DF_HG); + check_conn_count(admin, "ConnUsed", 0, DF_HG); + + mysql_close(proxy_sbtest); + + return EXIT_SUCCESS; +}; + +/** + * @details Same check as 'explicit_trx_no_persist' but trx is created in random hostgroup. + * Ensures that default hostgroup routing works as non-default routing. + */ +int explicit_trx_no_persist_2(CommandLine& cl, MYSQL* admin, MYSQL*, const string& trx_cmd) { + MYSQL* sbtest = mysql_init(NULL); + + if (!mysql_real_connect(sbtest, cl.host, "sbtest1", "sbtest1", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(sbtest)); + return EXIT_FAILURE; + } + + const vector tg_hgs { DF_HG, TG_HG_1 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t df_hg_qs = std::stol(pre_pool_state.at(DF_HG)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + + MYSQL_QUERY_T(sbtest, ("/* hostgroup=" + to_string(TG_HG_1) + "*/ BEGIN").c_str()); + diag("Only connection should be in use for hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + diag("Query should have been issued to hg '%d'", TG_HG_1); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + diag("Query intentionally targeting unreachable hostgroup due to 'persist'"); + MYSQL_QUERY_T(sbtest, "DO 1"); + check_query_count(admin, df_hg_qs + 1, DF_HG); + + MYSQL_QUERY_T(sbtest, trx_cmd.c_str()); + check_query_count(admin, tg_hg_1_qs + 2, TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + mysql_close(sbtest); + + return EXIT_SUCCESS; +} + +int explicit_trx_no_persist_c(CommandLine& cl, MYSQL* admin, MYSQL*) { + return explicit_trx_no_persist(cl, admin, nullptr, "COMMIT"); +} + +int explicit_trx_no_persist_r(CommandLine& cl, MYSQL* admin, MYSQL*) { + return explicit_trx_no_persist(cl, admin, nullptr, "ROLLBACK"); +} + +int explicit_trx_no_persist_2_c(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_trx_no_persist_2(cl, admin, proxy, "COMMIT"); +} + +int explicit_trx_no_persist_2_r(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_trx_no_persist_2(cl, admin, proxy, "ROLLBACK"); +} + +int explicit_trx_no_persist_no_def_hg(CommandLine& cl, MYSQL* admin, MYSQL*, const string& trx_cmd) { + MYSQL* proxy_sbtest = mysql_init(NULL); + + if (!mysql_real_connect(proxy_sbtest, cl.host, "sbtest1", "sbtest1", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_sbtest)); + return EXIT_FAILURE; + } + + const vector tg_hgs { TG_HG_1, TG_HG_2 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_2_qs = std::stol(pre_pool_state.at(TG_HG_2)[POOL_STATS_IDX::QUERIES]); + + // Started transaction in 'TG_HG_2' + MYSQL_QUERY_T(proxy_sbtest, ("/* hostgroup=" + to_string(TG_HG_2) + " */ BEGIN").c_str()); + check_conn_count(admin, "ConnUsed", 1, TG_HG_2); + check_query_count(admin, tg_hg_2_qs + 1, TG_HG_2); + + // Query redirected to 'TG_HG' imposed by query rule + MYSQL_QUERY_T(proxy_sbtest, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + // Query redirected to 'TG_HG_2' where trx was started + MYSQL_QUERY_T(proxy_sbtest, trx_cmd.c_str()); + check_query_count(admin, tg_hg_2_qs + 2, TG_HG_2); + check_conn_count(admin, "ConnUsed", 0, DF_HG); + + mysql_close(proxy_sbtest); + + return EXIT_SUCCESS; +}; + +int explicit_trx_no_persist_no_def_hg_c(CommandLine& cl, MYSQL* admin, MYSQL*) { + return explicit_trx_no_persist_no_def_hg(cl, admin, nullptr, "COMMIT"); +} + +int explicit_trx_no_persist_no_def_hg_r(CommandLine& cl, MYSQL* admin, MYSQL*) { + return explicit_trx_no_persist_no_def_hg(cl, admin, nullptr, "ROLLBACK"); +} + +/** + * @details Checks that implicit transactions with no persistence execute the rollback in the correct + * hostgroup. + */ +int implicit_trx_no_persist_no_def_hg(CommandLine& cl, MYSQL* admin, MYSQL*, const string& trx_cmd) { + MYSQL* proxy_sbtest = mysql_init(NULL); + + if (!mysql_real_connect(proxy_sbtest, cl.host, "sbtest1", "sbtest1", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_sbtest)); + return EXIT_FAILURE; + } + + const vector tg_hgs { TG_HG_1, TG_HG_2 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_2_qs = std::stol(pre_pool_state.at(TG_HG_2)[POOL_STATS_IDX::QUERIES]); + + // Started transaction in 'DF_HG' + MYSQL_QUERY_T(proxy_sbtest, "SET autocommit=0"); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + check_query_count(admin, tg_hg_1_qs, TG_HG_1); + + // Query redirected to 'TG_HG_1' imposed by query rule, trx started + MYSQL_QUERY_T(proxy_sbtest, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + // Query redirected to 'TG_HG_2' imposed by query rule, non-persistent conn, another trx started + MYSQL_QUERY_T(proxy_sbtest, "/* TG_HG_2 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + + diag("Dump 'conn_pool' stats after previous queries"); + dump_conn_stats(admin, { TG_HG_1, TG_HG_2 }); + + diag("Checking that trx was started for previous query on hg '%d'", TG_HG_2); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + diag("Checking that trx was started for previous query on hg '%d'", TG_HG_2); + check_query_count(admin, tg_hg_2_qs + 1, TG_HG_2); + check_conn_count(admin, "ConnUsed", 1, TG_HG_2); + + diag("Checking that we currently have two globally started trx"); + check_conn_count(admin, "ConnUsed", 2); + + // Since ProxySQL cannot issue multiple 'COMMIT|ROLLBACK' as a response to a client one, only one of the + // oppened trxs will received the closing statement. + MYSQL_QUERY_T(proxy_sbtest, trx_cmd.c_str()); + check_conn_count(admin, "ConnUsed", 1, TG_HG_2); + + MYSQL_QUERY_T(proxy_sbtest, trx_cmd.c_str()); + check_conn_count(admin, "ConnUsed", 0, TG_HG_2); + + mysql_close(proxy_sbtest); + + return EXIT_SUCCESS; +}; + +int implicit_trx_no_persist_no_def_hg_c(CommandLine& cl, MYSQL* admin, MYSQL*) { + return implicit_trx_no_persist_no_def_hg(cl, admin, nullptr, "COMMIT"); +} + +int implicit_trx_no_persist_no_def_hg_r(CommandLine& cl, MYSQL* admin, MYSQL*) { + return implicit_trx_no_persist_no_def_hg(cl, admin, nullptr, "ROLLBACK"); +} + +/** + * @details Flow for persistent-implicit transaction: + * - BEGIN executed in hg 'N': + * + Check that ConnUsed incremented in that hostgroup. + * + Check that query should have been issued in that hostgroup. + * - Query failing to be executed in different hg from BEGIN: + * + Should target other hg than 'N' but due to persistent be executed in 'N'. + * + Check that conns in use have increased in tg hg. + * + Check that queries have increased in tg hg. + * - COMMIT|ROLLBACK -> Should be executed in hg from prev query. + * + Check that command is executed in hg from previous query. + * + Check that conns used have decreased in hg. + */ +int explicit_unknown_trx_persist_no_def_hg( + CommandLine& cl, MYSQL* admin, MYSQL* proxy, const string& trx_cmd +) { + diag("Ensure 'autocommit=1' for reused connection"); + MYSQL_QUERY_T(proxy, "SET autocommit=1"); + + diag("Initial insert to ensure that 'id=1' is taken in the table"); + MYSQL_QUERY_T(proxy, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + + diag("Checking that a trx wasn't started for previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + const vector tg_hgs { TG_HG_1 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + + MYSQL_QUERY_T(proxy, ("/* hostgroup=" + to_string(TG_HG_1) + "*/ BEGIN").c_str()); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + diag("Query intentionally targeting unreachable hostgroup due to 'persist'"); + int rc = mysql_query_t( + proxy, "/* TG_HG_2 */ INSERT INTO test.commit_rollback (id,k,c,p) VALUES (1,1,'foo','bar')" + ); + int err_code = mysql_errno(proxy); + ok(rc != 0 && err_code == 1062, "Insert should failed - exp_err: 1062, act_err: %d", err_code); + + diag("Queries should have been issued to hg '%d'", TG_HG_1); + check_query_count(admin, tg_hg_1_qs + 2, TG_HG_1); + + diag("Checking that trx was started for previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + diag("Issuing '%s' should end the 'unknown-status' trx due to error", trx_cmd.c_str()); + MYSQL_QUERY_T(proxy, trx_cmd.c_str()); + check_query_count(admin, tg_hg_1_qs + 3, TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + return EXIT_SUCCESS; +} + +int explicit_unknown_trx_persist_no_def_hg_c(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_unknown_trx_persist_no_def_hg(cl, admin, proxy, "COMMIT"); +} + +int explicit_unknown_trx_persist_no_def_hg_r(CommandLine& cl, MYSQL* admin, MYSQL* proxy) { + return explicit_unknown_trx_persist_no_def_hg(cl, admin, proxy, "ROLLBACK"); +} + +/** + * @details Flow for persistent-implicit transaction with unknown state: + * - SET autocommit=0 + * + Check that conns didn't increase. + * + Check that query haven't been issued, intercepted by ProxySQL. + * - Query failing to be executed in non-def hg -> Unknown trx state + * + Check that conns in use have increased in tg hg. + * + Check that queries have increased in tg hg. + * - COMMIT|ROLLBACK -> Should be executed in hg from prev query. + * + Check that command is executed in hg from previous query. + * + Check that conns used have decreased in hg. + */ +int implicit_unknown_trx_persist_no_def_hg( + CommandLine& cl, MYSQL* admin, MYSQL* proxy, const string& trx_cmd +) { + diag("Ensure 'autocommit=1' for reused connection"); + MYSQL_QUERY_T(proxy, "SET autocommit=1"); + + diag("Initial insert to ensure that 'id=1' is taken in the table"); + MYSQL_QUERY_T(proxy, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + + diag("Checking that a trx wasn't started for previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + const vector tg_hgs { TG_HG_1 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + + MYSQL_QUERY_T(proxy, "SET autocommit=0"); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + check_query_count(admin, tg_hg_1_qs, TG_HG_1); + + int rc = mysql_query_t( + proxy, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (id,k,c,p) VALUES (1,1,'foo','bar')" + ); + int err_code = mysql_errno(proxy); + ok(rc != 0 && err_code == 1062, "Insert should failed - exp_err: 1062, act_err: %d", err_code); + + diag("Query should have been issued to hg '%d'", TG_HG_1); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + diag("Checking that trx was started for previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + diag("Issuing '%s' should end the 'unknown-status' trx due to error", trx_cmd.c_str()); + MYSQL_QUERY_T(proxy, trx_cmd.c_str()); + check_query_count(admin, tg_hg_1_qs + 2, TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + return EXIT_SUCCESS; +} + +int implicit_unknown_trx_persist_no_def_hg_c(CommandLine& cl, MYSQL* admin, MYSQL*) { + return implicit_unknown_trx_persist_no_def_hg(cl, admin, nullptr, "COMMIT"); +} + +int implicit_unknown_trx_persist_no_def_hg_r(CommandLine& cl, MYSQL* admin, MYSQL*) { + return implicit_unknown_trx_persist_no_def_hg(cl, admin, nullptr, "ROLLBACK"); +} + +/** + * @details Flow for explicit, and unknown non-persistent trxs: + * - BEGIN + * - Execute failing query in another hg ('N') due to non-persist ('unknown trx status'). + * - COMMIT|ROLLBACK + * + Should be executed in BEGIN hg. + * - COMMIT|ROLLBACK + * + Should be executed in trx with 'unknown_transaction_status'. + */ +int explicit_and_unknown_trx_no_persist_no_def_hg( + CommandLine& cl, MYSQL* admin, MYSQL*, const string& trx_cmd +) { + MYSQL* proxy_sbtest = mysql_init(NULL); + + if (!mysql_real_connect(proxy_sbtest, cl.host, "sbtest1", "sbtest1", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_sbtest)); + return EXIT_FAILURE; + } + + diag("Initial insert to ensure that 'id=1' is taken in the table"); + MYSQL_QUERY_T( + proxy_sbtest, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')" + ); + + const vector tg_hgs { DF_HG, TG_HG_1, TG_HG_2 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_2_qs = std::stol(pre_pool_state.at(TG_HG_2)[POOL_STATS_IDX::QUERIES]); + + MYSQL_QUERY_T(proxy_sbtest, ("/* hostgroup=" + to_string(TG_HG_1) + "*/ BEGIN").c_str()); + diag("Only connection should be in use for hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + diag("Query should have been issued to hg '%d'", TG_HG_1); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + int rc = mysql_query_t( + proxy_sbtest, "/* TG_HG_2 */ INSERT INTO test.commit_rollback (id,k,c,p) VALUES (1,1,'foo','bar')" + ); + int err_code = mysql_errno(proxy_sbtest); + ok(rc != 0 && err_code == 1062, "Insert should failed - exp_err: 1062, act_err: %d", err_code); + + diag("Queries should have been issued to hg '%d'", TG_HG_2); + check_query_count(admin, tg_hg_2_qs + 1, TG_HG_2); + + diag("Checking that conn was flagged as 'unknown_transaction_status' on hg '%d'", TG_HG_2); + check_conn_count(admin, "ConnUsed", 1, TG_HG_2); + + diag("Issuing '%s' should end the initial explicit transaction first", trx_cmd.c_str()); + MYSQL_QUERY_T(proxy_sbtest, trx_cmd.c_str()); + check_query_count(admin, tg_hg_1_qs + 2, TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + diag("Issuing '%s' should end the 'unknown-status' trx due to error", trx_cmd.c_str()); + MYSQL_QUERY_T(proxy_sbtest, trx_cmd.c_str()); + check_query_count(admin, tg_hg_2_qs + 2, TG_HG_2); + check_conn_count(admin, "ConnUsed", 0, TG_HG_2); + + mysql_close(proxy_sbtest); + + return EXIT_SUCCESS; +} + +int explicit_and_unknown_trx_no_persist_no_def_hg_c(CommandLine& cl, MYSQL* admin, MYSQL*) { + return explicit_and_unknown_trx_no_persist_no_def_hg(cl, admin, nullptr, "COMMIT"); +} + +int explicit_and_unknown_trx_no_persist_no_def_hg_r(CommandLine& cl, MYSQL* admin, MYSQL*) { + return explicit_and_unknown_trx_no_persist_no_def_hg(cl, admin, nullptr, "ROLLBACK"); +} + +/** + * @details Flow for implicit, and unknown non-persistent trxs: + * - SET autcommit=0 + * - Execute query in hg ('N'), this creates a trx. + * - Execute failing query in another hg ('M') due to non-persist ('unknown trx status'). + * - COMMIT|ROLLBACK + * + Should be executed in hg 'N'. + * - COMMIT|ROLLBACK + * + Should be executed in trx with 'unknown_transaction_status', hg 'M'. + */ +int implicit_and_unknown_trx_no_persist_no_def_hg( + CommandLine& cl, MYSQL* admin, MYSQL*, const string& trx_cmd +) { + MYSQL* sbtest = mysql_init(NULL); + + if (!mysql_real_connect(sbtest, cl.host, "sbtest1", "sbtest1", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(sbtest)); + return EXIT_FAILURE; + } + + diag("Initial insert to ensure that 'id=1' is taken in the table"); + MYSQL_QUERY_T( + sbtest, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')" + ); + + const vector tg_hgs { TG_HG_1, TG_HG_2 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_2_qs = std::stol(pre_pool_state.at(TG_HG_2)[POOL_STATS_IDX::QUERIES]); + + diag("Checking that a trx wasn't started for previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + MYSQL_QUERY_T(sbtest, "SET autocommit=0"); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + check_query_count(admin, tg_hg_1_qs, TG_HG_1); + + MYSQL_QUERY_T(sbtest, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + + diag("Queries should have been issued to hg '%d'", TG_HG_1); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + diag("Checking that trx was started for previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + int rc = mysql_query_t( + sbtest, "/* TG_HG_2 */ INSERT INTO test.commit_rollback (id,k,c,p) VALUES (1,1,'foo','bar')" + ); + int err_code = mysql_errno(sbtest); + ok(rc != 0 && err_code == 1062, "Insert should failed - exp_err: 1062, act_err: %d", err_code); + + diag("Queries should have been issued to hg '%d'", TG_HG_2); + check_query_count(admin, tg_hg_2_qs + 1, TG_HG_2); + + diag("Checking that conn was flagged as 'unknown_transaction_status' on hg '%d'", TG_HG_2); + check_conn_count(admin, "ConnUsed", 1, TG_HG_2); + + diag("Issuing '%s' should end the initial explicit transaction first", trx_cmd.c_str()); + MYSQL_QUERY_T(sbtest, trx_cmd.c_str()); + check_query_count(admin, tg_hg_1_qs + 2, TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + diag("Issuing '%s' should end the 'unknown-status' trx due to error", trx_cmd.c_str()); + MYSQL_QUERY_T(sbtest, trx_cmd.c_str()); + check_query_count(admin, tg_hg_2_qs + 2, TG_HG_2); + check_conn_count(admin, "ConnUsed", 0, TG_HG_2); + + mysql_close(sbtest); + + return EXIT_SUCCESS; +} + +int implicit_and_unknown_trx_no_persist_no_def_hg_c(CommandLine& cl, MYSQL* admin, MYSQL*) { + return implicit_and_unknown_trx_no_persist_no_def_hg(cl, admin, nullptr, "COMMIT"); +} + +int implicit_and_unknown_trx_no_persist_no_def_hg_r(CommandLine& cl, MYSQL* admin, MYSQL*) { + return implicit_and_unknown_trx_no_persist_no_def_hg(cl, admin, nullptr, "ROLLBACK"); +} + +/** + * @details This test involves the three different logics for trx detection. In a non-persistent session: + * - Savepoint creation in TG_HG_1 + * + Check that 'SAVEPOINT' is detected in the conn + * - Transaction started for TG_HG_2 + * + Check that transaction is detected query properly routed + * - Error on DF_HG + * + Check that error provoques retaining of the conn due to unknown status. + * - Three 'COMMIT|ROLLBACK' are issued + * + Check that each command is issued in the correct conn + * + * For the final 'COMMIT|ROLLBACK' commands it's expected that: + * * First two commands hit either 'SAVEPOINT' or known trx conn. + * * Third command hits the conn with 'unknown trx' status. + */ +int implicit_trx_and_savepoints_no_persist_no_def_hg_( + CommandLine& cl, MYSQL* admin, MYSQL*, const string& trx_cmd +) { + MYSQL* sbtest = mysql_init(NULL); + + if (!mysql_real_connect(sbtest, cl.host, "sbtest1", "sbtest1", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(sbtest)); + return EXIT_FAILURE; + } + + diag("Initial insert to ensure that 'id=1' is taken in the table"); + MYSQL_QUERY_T( + sbtest, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')" + ); + + pool_state_t pre_pool_state {}; + uint32_t df_hg_qs = 0; + uint32_t tg_hg_1_qs = 0; + uint32_t tg_hg_2_qs = 0; + + auto op_0 = [&] () -> int { + const vector tg_hgs { DF_HG, TG_HG_1, TG_HG_2 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + + pre_pool_state = pre_pool_state_res.second; + df_hg_qs = std::stol(pre_pool_state.at(DF_HG)[POOL_STATS_IDX::QUERIES]); + tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + tg_hg_2_qs = std::stol(pre_pool_state.at(TG_HG_2)[POOL_STATS_IDX::QUERIES]); + + diag("Checking that a trx wasn't started for previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + return EXIT_SUCCESS; + }; + + auto op_1 = [&] () -> int { + // DOC-NOTE: Force refresh 'active_transactions' field in 'INTERNAL SESSION'. Active transactions + // field is only refreshed if a query is executed in a backend connection which doesn't have the + // 'SERVER_STATUS_IN_TRANS' flag active. Thus, ProxySQL is forced to look for other "potential active + // transactions" for the session. + { + MYSQL_QUERY_T(sbtest, ("/* hostgroup=" + to_string(TG_HG_1) + " */ SET autocommit=1").c_str()); + MYSQL_QUERY_T(sbtest, ("/* hostgroup=" + to_string(TG_HG_1) + " */ DO 1").c_str()); + } + + json j_session = fetch_internal_session(sbtest); + int prev_trxs = -1; + + try { + prev_trxs = j_session["active_transactions"]; + } catch (std::exception& e) { + diag("ERROR: Accessing 'INTERNAL SESSION' fields failed with error - %s", e.what()); + return EXIT_FAILURE; + } + + // DOC-NOTE: This autocommit is not forwarded, because in the flow of `op_1, op_2, op_3` no + // transaction is started, thus, ProxySQL wont forward it, instead, will send it with the next query, + // which won't count as a `query_sent`. That's the reason for the interval for the the `SAVEPOINT` + // query to start at `tg_hg_1_qs + 2`. The only autocommit values that are directly forwared, are the + // ones that can potentially end an ongoing transaction. + MYSQL_QUERY_T(sbtest, "SET autocommit=0"); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + check_query_count(admin, {tg_hg_1_qs + 1, tg_hg_1_qs + 2}, TG_HG_1); + + MYSQL_QUERY_T(sbtest, ("/* hostgroup=" + to_string(TG_HG_1) + " */ SAVEPOINT s1").c_str()); + + diag("Queries should have been issued to hg '%d'", TG_HG_1); + check_query_count(admin, {tg_hg_1_qs + 2, tg_hg_1_qs + 3}, TG_HG_1); + + diag("Checking that conn is kept ('has_savepoint') due to previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + j_session = fetch_internal_session(sbtest); + bool has_savepoint = false; + int after_trxs = -1; + + try { + has_savepoint = j_session["backends"][0]["conn"]["status"]["has_savepoint"]; + after_trxs = j_session["active_transactions"]; + } catch (std::exception& e) { + diag("ERROR: Accessing 'INTERNAL SESSION' fields failed with error - %s", e.what()); + return EXIT_FAILURE; + } + + bool ok_res = has_savepoint == true && prev_trxs == after_trxs; + + ok( + ok_res, + "Savepoint should be present, but no trxs due to MySQL bug #107875 -" + " savepoint: %d, pre_trxs: %d, after_trxs: %d", + has_savepoint, prev_trxs, after_trxs + ); + if (!ok_res) { + dump_conn_stats(admin, {}); + } + + return EXIT_SUCCESS; + }; + + auto op_2 = [&] () -> int { + MYSQL_QUERY_T(sbtest, "SET autocommit=0"); + MYSQL_QUERY_T(sbtest, "/* TG_HG_2 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + + check_query_count(admin, {tg_hg_2_qs + 1, tg_hg_2_qs + 2}, TG_HG_2); + check_conn_count(admin, "ConnUsed", 1, TG_HG_2); + + return EXIT_SUCCESS; + }; + + auto op_3 = [&] () -> int { + MYSQL_QUERY_T(sbtest, "SET autocommit=1"); + int rc = mysql_query_t(sbtest, "INSERT INTO test.commit_rollback (id,k,c,p) VALUES (1,1,'foo','bar')"); + + int err_code = mysql_errno(sbtest); + ok(rc != 0 && err_code == 1062, "Insert should failed - exp_err: 1062, act_err: %d", err_code); + + // DOC-NOTE: This autocommit is potentially forwared to the backend and counted as 'query_sent', this + // is because, in the cases this is not the first operation, and 'autocommit=0' have been previously + // executed in the conn, there would be an implicit ongoing transaction when this 'autocommit=1' is + // received, since this change in autocommit will end the current transaction, ProxySQL is forced to + // send it to the backend (in contrast to store it for later applying). This is the reason for the + // query interval of `{df_hg_qs + 1, df_hg_qs + 2}`. + diag("Checking the two previous queries were issued - AUTOCOMMIT + INSERT"); + check_query_count(admin, {df_hg_qs + 1, df_hg_qs + 2}, DF_HG); + check_conn_count(admin, "ConnUsed", 1, DF_HG); + + return EXIT_SUCCESS; + }; + + auto op_4 = [&] () -> int { + MYSQL_QUERY_T(sbtest, trx_cmd.c_str()); + check_conn_count(admin, "ConnUsed", 2); + check_conn_count(admin, "ConnUsed", 1, DF_HG); + + MYSQL_QUERY_T(sbtest, trx_cmd.c_str()); + check_conn_count(admin, "ConnUsed", 1); + check_conn_count(admin, "ConnUsed", 1, DF_HG); + + MYSQL_QUERY_T(sbtest, trx_cmd.c_str()); + check_conn_count(admin, "ConnUsed", 0); + check_conn_count(admin, "ConnUsed", 0, DF_HG); + + return EXIT_SUCCESS; + }; + + vector, string>> ops { + { op_1, "OP1 - SavePoint creation in HG '" + to_string(TG_HG_1) + "'" }, + { op_2, "OP2 - Trx creation with 'autocommit=0' + INSERT in HG '" + to_string(TG_HG_2) + "'" }, + { op_3, "OP3 - Unknown transaction status with failing 'INSERT' in HG '" + to_string(DF_HG) + "'" }, + }; + + vector> permutations { get_permutations(vector {1,2,3}) }; + + for (const auto& p : permutations) { + fprintf(stderr, "\n"); + const string p_str { + std::accumulate(p.begin(), p.end(), std::string(), + [](const std::string& str, const uint32_t& n) -> std::string { + return str + (str.length() > 0 ? "," : "") + std::to_string(n); + } + ) + }; + + diag("Executing test permutation '%s'", p_str.c_str()); + ok(op_0() == EXIT_SUCCESS, "Fetching stats and setup operation succeeded"); + + const auto& p_op_1 { ops[p[0] - 1] }; + const auto& p_op_2 { ops[p[1] - 1] }; + const auto& p_op_3 { ops[p[2] - 1] }; + + diag("Executing operation - %s", p_op_1.second.c_str()); + ok(p_op_1.first() == EXIT_SUCCESS, "Operation should exit successfully") ; + + diag("Executing operation - %s", p_op_2.second.c_str()); + ok(p_op_2.first() == EXIT_SUCCESS, "Operation should exit successfully") ; + + diag("Executing operation - %s", p_op_3.second.c_str()); + ok(p_op_3.first() == EXIT_SUCCESS, "Operation should exit successfully") ; + + ok(op_4() == EXIT_SUCCESS, "Final '%s' commands executed in correct hgs", trx_cmd.c_str()); + } + + mysql_close(sbtest); + + return EXIT_SUCCESS; +} + +/** + * @details This test involves the three different logics for trx detection. In a non-persistent session: + * - Savepoint creation in TG_HG_1 + * + Check that 'SAVEPOINT' is detected in the conn + * - Transaction started for TG_HG_2 + * + Check that transaction is detected query properly routed + * - Error on DF_HG + * + Check that error provoques retaining of the conn due to unknown status. + * - Three 'COMMIT|ROLLBACK' are issued + * + Check that each command is issued in the correct conn + * + * For the final 'COMMIT|ROLLBACK' commands it's expected that: + * * First two commands hit either 'SAVEPOINT' or known trx conn. + * * Third command hits the conn with 'unknown trx' status. + */ +int implicit_trx_and_savepoints_no_persist_no_def_hg( + CommandLine& cl, MYSQL* admin, MYSQL*, const string& trx_cmd +) { + MYSQL* sbtest = mysql_init(NULL); + + if (!mysql_real_connect(sbtest, cl.host, "sbtest1", "sbtest1", NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(sbtest)); + return EXIT_FAILURE; + } + + diag("Initial insert to ensure that 'id=1' is taken in the table"); + MYSQL_QUERY_T( + sbtest, "/* TG_HG_1 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')" + ); + + const vector tg_hgs { DF_HG, TG_HG_1, TG_HG_2 }; + const pair pre_pool_state_res { fetch_conn_stats(admin, tg_hgs) }; + if (pre_pool_state_res.first) { return EXIT_FAILURE; } + + const pool_state_t& pre_pool_state { pre_pool_state_res.second }; + const uint32_t df_hg_qs = std::stol(pre_pool_state.at(DF_HG)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_1_qs = std::stol(pre_pool_state.at(TG_HG_1)[POOL_STATS_IDX::QUERIES]); + const uint32_t tg_hg_2_qs = std::stol(pre_pool_state.at(TG_HG_2)[POOL_STATS_IDX::QUERIES]); + + diag("Checking that a trx wasn't started for previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + + MYSQL_QUERY_T(sbtest, "SET autocommit=0"); + check_conn_count(admin, "ConnUsed", 0, TG_HG_1); + check_query_count(admin, tg_hg_1_qs, TG_HG_1); + + MYSQL_QUERY_T(sbtest, ("/* hostgroup=" + to_string(TG_HG_1) + " */ SAVEPOINT s1").c_str()); + + diag("Queries should have been issued to hg '%d'", TG_HG_1); + check_query_count(admin, tg_hg_1_qs + 1, TG_HG_1); + + diag("Checking that conn is kept ('has_savepoint') due to previous query on hg '%d'", TG_HG_1); + check_conn_count(admin, "ConnUsed", 1, TG_HG_1); + + json j_session = fetch_internal_session(sbtest); + bool has_savepoint = false; + int trxs = -1; + + try { + has_savepoint = j_session["backends"][0]["conn"]["status"]["has_savepoint"]; + trxs = j_session["active_transactions"]; + } catch (std::exception& e) { + diag("Accessing 'INTERNAL SESSION' fields failed with error - %s", e.what()); + } + + ok( + has_savepoint == true && trxs == 0, + "Savepoint should be present, but no trxs due to MySQL bug #107875 - savepoint: %d, trxs: %d", + has_savepoint, trxs + ); + + MYSQL_QUERY_T(sbtest, "/* TG_HG_2 */ INSERT INTO test.commit_rollback (k,c,p) VALUES (1,'foo','bar')"); + check_query_count(admin, tg_hg_2_qs + 1, TG_HG_2); + check_conn_count(admin, "ConnUsed", 1, TG_HG_2); + + MYSQL_QUERY_T(sbtest, "SET autocommit=1"); + int rc = mysql_query_t(sbtest, "INSERT INTO test.commit_rollback (id,k,c,p) VALUES (1,1,'foo','bar')"); + int err_code = mysql_errno(sbtest); + ok(rc != 0 && err_code == 1062, "Insert should failed - exp_err: 1062, act_err: %d", err_code); + + diag("Checking the two previous queries were issued - AUTOCOMMIT + INSERT"); + check_query_count(admin, df_hg_qs + 2, DF_HG); + check_conn_count(admin, "ConnUsed", 1, DF_HG); + MYSQL_QUERY_T(sbtest, "SET autocommit=0"); + + MYSQL_QUERY_T(sbtest, trx_cmd.c_str()); + check_conn_count(admin, "ConnUsed", 2); + check_conn_count(admin, "ConnUsed", 1, DF_HG); + + MYSQL_QUERY_T(sbtest, trx_cmd.c_str()); + check_conn_count(admin, "ConnUsed", 1); + check_conn_count(admin, "ConnUsed", 1, DF_HG); + + MYSQL_QUERY_T(sbtest, trx_cmd.c_str()); + check_conn_count(admin, "ConnUsed", 0); + check_conn_count(admin, "ConnUsed", 0, DF_HG); + + mysql_close(sbtest); + + return EXIT_SUCCESS; +} + +int implicit_trx_and_savepoints_no_persist_no_def_hg_c(CommandLine& cl, MYSQL* admin, MYSQL*) { + return implicit_trx_and_savepoints_no_persist_no_def_hg_(cl, admin, nullptr, "COMMIT"); +} + +int implicit_trx_and_savepoints_no_persist_no_def_hg_r(CommandLine& cl, MYSQL* admin, MYSQL*) { + return implicit_trx_and_savepoints_no_persist_no_def_hg(cl, admin, nullptr, "ROLLBACK"); +} + +struct test_case_t { + string name; + function fn; +}; + +#define create_test_case(name) { #name, name } + +const vector test_cases { + create_test_case(explicit_trx_persist_c), + create_test_case(explicit_trx_persist_r), + create_test_case(explicit_trx_persist_2_c), + create_test_case(explicit_trx_persist_2_r), + create_test_case(implicit_trx_persist_c), + create_test_case(implicit_trx_persist_r), + create_test_case(explicit_trx_persist_no_def_hg_c), + create_test_case(explicit_trx_persist_no_def_hg_r), + create_test_case(implicit_trx_persist_no_def_hg_c), + create_test_case(implicit_trx_persist_no_def_hg_r), + create_test_case(explicit_trx_no_persist_c), + create_test_case(explicit_trx_no_persist_r), + create_test_case(explicit_trx_no_persist_2_c), + create_test_case(explicit_trx_no_persist_2_r), + create_test_case(explicit_trx_no_persist_no_def_hg_c), + create_test_case(explicit_trx_no_persist_no_def_hg_r), + create_test_case(implicit_trx_no_persist_no_def_hg_c), + create_test_case(implicit_trx_no_persist_no_def_hg_r), + create_test_case(explicit_unknown_trx_persist_no_def_hg_c), + create_test_case(explicit_unknown_trx_persist_no_def_hg_r), + create_test_case(explicit_and_unknown_trx_no_persist_no_def_hg_c), + create_test_case(explicit_and_unknown_trx_no_persist_no_def_hg_r), + create_test_case(implicit_and_unknown_trx_no_persist_no_def_hg_c), + create_test_case(implicit_and_unknown_trx_no_persist_no_def_hg_r), + create_test_case(implicit_trx_and_savepoints_no_persist_no_def_hg_c), + create_test_case(implicit_trx_and_savepoints_no_persist_no_def_hg_r), +}; + +int prepare_tables_and_config(MYSQL* admin, MYSQL* proxy) { + MYSQL_QUERY_T(proxy, "CREATE DATABASE IF NOT EXISTS test"); + MYSQL_QUERY_T(proxy, "DROP TABLE IF EXISTS test.commit_rollback"); + MYSQL_QUERY_T(proxy, + "CREATE TABLE test.commit_rollback (" + " id INTEGER NOT NULL AUTO_INCREMENT, " + " k INTEGER DEFAULT 0 NOT NULL," + " c CHAR(120) DEFAULT '' NOT NULL," + " p CHAR(60) DEFAULT '' NOT NULL," + " PRIMARY KEY (id)" + ")" + ); + + const auto build_server_copy_query = [] (uint32_t tg_hg, uint32_t og_hg) { + return cstr_format( + "INSERT INTO mysql_servers (hostgroup_id,hostname,port)" + " SELECT %d,hostname,port FROM mysql_servers WHERE hostgroup_id=%d", + tg_hg, og_hg + ).str; + }; + + MYSQL_QUERY_T(admin, ("DELETE FROM mysql_servers WHERE hostgroup_id=" + to_string(TG_HG_1)).c_str()); + MYSQL_QUERY_T(admin, ("DELETE FROM mysql_servers WHERE hostgroup_id=" + to_string(TG_HG_2)).c_str()); + + MYSQL_QUERY_T(admin, build_server_copy_query(TG_HG_1, DF_HG).c_str()); + MYSQL_QUERY_T(admin, build_server_copy_query(TG_HG_2, DF_HG).c_str()); + MYSQL_QUERY_T(admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + MYSQL_QUERY_T(admin, "SET mysql-auto_increment_delay_multiplex=0"); + MYSQL_QUERY_T(admin, "LOAD MYSQL VARIABLES TO RUNTIME"); + + MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES FROM DISK"); + MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); + MYSQL_QUERY_T( + admin, + string { + "INSERT INTO mysql_query_rules (active,match_pattern,destination_hostgroup,apply) VALUES" + " (1,'/\\* TG_HG_1 \\*/ INSERT INTO .*'," + to_string(TG_HG_1) + ",1)" + }.c_str() + ); + MYSQL_QUERY_T( + admin, + string { + "INSERT INTO mysql_query_rules (active,match_pattern,destination_hostgroup,apply) VALUES" + " (1,'/\\* TG_HG_2 \\*/ INSERT INTO .*'," + to_string(TG_HG_2) + ",1)" + }.c_str() + ); + MYSQL_QUERY_T(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); + + MYSQL_QUERY_T(admin, "UPDATE mysql_users SET transaction_persistent=0 WHERE username='sbtest1'"); + MYSQL_QUERY_T(admin, "LOAD MYSQL USERS TO RUNTIME"); + + 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; + } + + 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* 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; + } + + int prep_res = prepare_tables_and_config(admin, proxy); + if (prep_res) { + goto cleanup; + } + + for (const auto test : test_cases) { + fprintf(stderr, "\n"); + diag("Starting test '%s'", test.name.c_str()); + test.fn(cl, admin, proxy); + } + +cleanup: + + mysql_close(proxy); + mysql_close(admin); + + return exit_status(); +} From 3843a54bede315ca4e2d0827539a9bc4bf40eeed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 28 Jul 2023 19:19:53 +0200 Subject: [PATCH 6/9] Remove duplicate code from several tests --- .../reg_test_3184-set_wait_timeout-t.cpp | 25 +---------------- .../reg_test_3493-USE_with_comment-t.cpp | 10 +------ .../test_auto_increment_delay_multiplex-t.cpp | 26 ++---------------- ...ult_value_transaction_isolation_attr-t.cpp | 27 ++++--------------- .../test_keep_multiplexing_variables-t.cpp | 15 +---------- 5 files changed, 10 insertions(+), 93 deletions(-) diff --git a/test/tap/tests/reg_test_3184-set_wait_timeout-t.cpp b/test/tap/tests/reg_test_3184-set_wait_timeout-t.cpp index cb3f967d0..ff5b5cdcc 100644 --- a/test/tap/tests/reg_test_3184-set_wait_timeout-t.cpp +++ b/test/tap/tests/reg_test_3184-set_wait_timeout-t.cpp @@ -23,24 +23,6 @@ using std::string; using namespace nlohmann; - -/** - * @brief Helper function to convert a 'MYSQL_RES' into a - * nlohmann::json. - * - * @param result The 'MYSQL_RES*' to be converted into JSON. - * @param j 'nlohmann::json' output parameter holding the - * converted 'MYSQL_RES' supplied. - */ -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]); - } -} - /** * @brief Valid variations of 'SET wait_timeout' supported * by ProxySQL to be ignored. @@ -96,12 +78,7 @@ int main(int argc, char** argv) { int query_err = mysql_query(proxysql_mysql, set_wait_timeout.c_str()); ok (query_err == 0, "Query '%s' should be properly executed.", set_wait_timeout.c_str()); - MYSQL_QUERY(proxysql_mysql, "PROXYSQL INTERNAL SESSION"); - json j_status {}; - MYSQL_RES* int_session_res = mysql_store_result(proxysql_mysql); - parse_result_json_column(int_session_res, j_status); - mysql_free_result(int_session_res); - + json j_status = fetch_internal_session(proxysql_mysql); bool found_backends = j_status.contains("backends"); ok(found_backends == false, "No backends should be found for the current connection."); } diff --git a/test/tap/tests/reg_test_3493-USE_with_comment-t.cpp b/test/tap/tests/reg_test_3493-USE_with_comment-t.cpp index 9ae225108..ee83a67f7 100644 --- a/test/tap/tests/reg_test_3493-USE_with_comment-t.cpp +++ b/test/tap/tests/reg_test_3493-USE_with_comment-t.cpp @@ -45,15 +45,7 @@ void parse_result_json_column(MYSQL_RES *result, json& j) { int get_session_schemaname(MYSQL* proxysql, std::string& schemaname) { int res = EXIT_FAILURE; - json j_status; - int query_res = mysql_query(proxysql, "PROXYSQL INTERNAL SESSION"); - if (query_res) { - return query_res; - } - - MYSQL_RES* tr_res = mysql_store_result(proxysql); - parse_result_json_column(tr_res, j_status); - mysql_free_result(tr_res); + json j_status = fetch_internal_session(proxysql); try { schemaname = j_status["client"]["userinfo"]["schemaname"]; diff --git a/test/tap/tests/test_auto_increment_delay_multiplex-t.cpp b/test/tap/tests/test_auto_increment_delay_multiplex-t.cpp index a6b321bec..1ffd35e0c 100644 --- a/test/tap/tests/test_auto_increment_delay_multiplex-t.cpp +++ b/test/tap/tests/test_auto_increment_delay_multiplex-t.cpp @@ -82,20 +82,10 @@ int get_query_result(MYSQL* mysql, const string& query, uint64_t& out_val) { #define log_err(err_msg) fprintf(stderr, "File %s, line %d, Error: \"%s\"\n", __FILE__, __LINE__, err_msg); int get_conn_auto_inc_delay_token(MYSQL* proxy_mysql, int& out_auto_inc_delay) { - MYSQL_QUERY(proxy_mysql, "PROXYSQL INTERNAL SESSION"); - MYSQL_RES* my_res = mysql_store_result(proxy_mysql); - vector int_sess_res = extract_mysql_rows(my_res); - mysql_free_result(my_res); - int cur_auto_inc_delay_mult = 0; - if (int_sess_res.empty()) { - log_err("Empty result received from 'PROXYSQL INTERNAL SESSION'"); - return EXIT_FAILURE; - } - try { - nlohmann::json j_int_sess = nlohmann::json::parse(int_sess_res[0][0]); + nlohmann::json j_int_sess = fetch_internal_session(proxy_mysql); nlohmann::json backend_conns = j_int_sess.at("backends"); nlohmann::json m_off_conn {}; @@ -124,20 +114,8 @@ int get_conn_auto_inc_delay_token(MYSQL* proxy_mysql, int& out_auto_inc_delay) { } int get_session_backends(MYSQL* proxy_mysql,vector& out_backend_conns) { - MYSQL_QUERY(proxy_mysql, "PROXYSQL INTERNAL SESSION"); - MYSQL_RES* my_res = mysql_store_result(proxy_mysql); - vector int_sess_res = extract_mysql_rows(my_res); - mysql_free_result(my_res); - - int cur_auto_inc_delay_mult = 0; - - if (int_sess_res.empty()) { - log_err("Empty result received from 'PROXYSQL INTERNAL SESSION'"); - return EXIT_FAILURE; - } - try { - nlohmann::json j_int_sess = nlohmann::json::parse(int_sess_res[0][0]); + nlohmann::json j_int_sess = fetch_internal_session(proxy_mysql); nlohmann::json backend_conns = j_int_sess.at("backends"); vector _out_conns {}; diff --git a/test/tap/tests/test_default_value_transaction_isolation_attr-t.cpp b/test/tap/tests/test_default_value_transaction_isolation_attr-t.cpp index 342205bcb..a84fe833f 100644 --- a/test/tap/tests/test_default_value_transaction_isolation_attr-t.cpp +++ b/test/tap/tests/test_default_value_transaction_isolation_attr-t.cpp @@ -33,23 +33,6 @@ using std::string; using namespace nlohmann; -/** - * @brief Helper function to convert a 'MYSQL_RES' into a - * nlohmann::json. - * - * @param result The 'MYSQL_RES*' to be converted into JSON. - * @param j 'nlohmann::json' output parameter holding the - * converted 'MYSQL_RES' supplied. - */ -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]); - } -} - using user_attributes = std::tuple; /** @@ -87,11 +70,7 @@ int check_front_conn_isolation_level( const std::string& exp_iso_level, const bool set_via_attr ) { - MYSQL_QUERY(proxysql_mysql, "PROXYSQL INTERNAL SESSION"); - json j_status {}; - MYSQL_RES* int_session_res = mysql_store_result(proxysql_mysql); - parse_result_json_column(int_session_res, j_status); - mysql_free_result(int_session_res); + json j_status = fetch_internal_session(proxysql_mysql); try { std::string front_conn_isolation_level = @@ -145,6 +124,7 @@ int check_backend_conn_isolation_level( // Verify that the query produced a correct result if (trx_iso_row && trx_iso_row[0]) { trx_iso_val = std::string { trx_iso_row[0] }; + mysql_free_result(trx_iso_res); } else { const std::string err_msg { "Empty result received from query '" + select_trx_iso_query + "'" @@ -335,5 +315,8 @@ int main(int argc, char** argv) { mysql_close(proxysql_mysql); } + mysql_close(proxysql_admin); + mysql_close(mysql_server); + return exit_status(); } diff --git a/test/tap/tests/test_keep_multiplexing_variables-t.cpp b/test/tap/tests/test_keep_multiplexing_variables-t.cpp index 2ed928549..ef4995bc9 100644 --- a/test/tap/tests/test_keep_multiplexing_variables-t.cpp +++ b/test/tap/tests/test_keep_multiplexing_variables-t.cpp @@ -18,15 +18,6 @@ using std::string; using namespace nlohmann; -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]); - } -} - std::vector select_queries { "select @@session.autocommit, @@session.big_tables, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database,@@transaction_isolation, @@version,@@session.transaction_isolation", "select @@autocommit, @@sql_mode, @@big_tables, @@autocommit,@@bulk_insert_buffer_size, @@character_set_database,@@session.transaction_isolation, @@version,@@transaction_isolation", @@ -53,11 +44,7 @@ int check_multiplexing_disabled(const CommandLine& cl, const std::string query, MYSQL_RES* dummy_res = mysql_store_result(proxysql_mysql); mysql_free_result(dummy_res); - MYSQL_QUERY(proxysql_mysql, "PROXYSQL INTERNAL SESSION"); - json j_status {}; - MYSQL_RES* int_session_res = mysql_store_result(proxysql_mysql); - parse_result_json_column(int_session_res, j_status); - mysql_free_result(int_session_res); + json j_status = fetch_internal_session(proxysql_mysql); if (j_status.contains("backends")) { for (auto& backend : j_status["backends"]) { From 0d8860e8a219bcbca13f8c071dc2ca408cb4f86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 28 Jul 2023 19:23:57 +0200 Subject: [PATCH 7/9] Remove old commented code for 'IsActiveTransaction' impl --- include/mysql_connection.h | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/include/mysql_connection.h b/include/mysql_connection.h index 472ba2118..9fd9a66eb 100644 --- a/include/mysql_connection.h +++ b/include/mysql_connection.h @@ -223,16 +223,7 @@ class MySQL_Connection { * connections which holds 'unknown_transaction_status' as potentially active transactions. * @return True if the connection is in potentially in an active transaction. */ - bool IsActiveTransaction(); /* { - bool ret=false; - if (mysql) { - ret = (mysql->server_status & SERVER_STATUS_IN_TRANS); - if (ret == false && (mysql)->net.last_errno) { - ret = true; - } - } - return ret; - } */ + bool IsActiveTransaction(); bool IsServerOffline(); bool IsAutoCommit(); bool AutocommitFalse_AndSavepoint(); From d36605efbca0ef71e4a3981cb69e540b36805ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 28 Jul 2023 19:39:41 +0200 Subject: [PATCH 8/9] Add missing 'plan' to regression test for #4264 --- test/tap/tests/reg_test_4264-commit_rollback-t.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/tap/tests/reg_test_4264-commit_rollback-t.cpp b/test/tap/tests/reg_test_4264-commit_rollback-t.cpp index 9215e3650..15369d889 100644 --- a/test/tap/tests/reg_test_4264-commit_rollback-t.cpp +++ b/test/tap/tests/reg_test_4264-commit_rollback-t.cpp @@ -1158,6 +1158,8 @@ int prepare_tables_and_config(MYSQL* admin, MYSQL* proxy) { int main(int argc, char** argv) { CommandLine cl; + plan(313); + if (cl.getEnv()) { diag("Failed to get the required environmental variables."); return EXIT_FAILURE; From c5295254beeeea4f3eae1ea4ec477ebf47f790d8 Mon Sep 17 00:00:00 2001 From: Miro Stauder Date: Tue, 1 Aug 2023 00:02:12 +0200 Subject: [PATCH 9/9] Update ci-taptests.yml --- .github/workflows/ci-taptests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-taptests.yml b/.github/workflows/ci-taptests.yml index f2a94043b..48ac21bba 100644 --- a/.github/workflows/ci-taptests.yml +++ b/.github/workflows/ci-taptests.yml @@ -73,10 +73,10 @@ jobs: run: | # apply patches for PATCH in $(cd jenkins-build-scripts/test-scripts/patches; find . -type f); do - if [[ $PATCH =~ \.patch ]]; then - patch proxysql/test/tap/${PATCH%.patch} jenkins-build-scripts/test-scripts/patches/${PATCH} || true + if [[ $PATCH =~ \.patch$ ]]; then + patch proxysql/${PATCH%.patch} jenkins-build-scripts/test-scripts/patches/${PATCH} || true elif [[ ! -f jenkins-build-scripts/test-scripts/patches/${PATCH#./}.patch ]]; then - cp jenkins-build-scripts/test-scripts/patches/${PATCH#./} proxysql/test/tap/${PATCH#./} || true + cp -v jenkins-build-scripts/test-scripts/patches/${PATCH#./} proxysql/${PATCH#./} || true fi done # patch multi port listening