From 99e0e2364657952858f66f4f95cd55c76adf63d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 23 Mar 2021 19:39:18 +0100 Subject: [PATCH 1/3] Closes #2543: Added support for two new queries for Admin required for C# connector Added support for queries: - "SELECT @@max_allowed_packet, @@character_set_client, @@character_set_connection, @@license, @@sql_mode, @@lower_case_table_names" - "SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())" --- lib/ProxySQL_Admin.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index 1e2736bda..6e9d73720 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -3444,6 +3444,58 @@ SQLite3_result * ProxySQL_Admin::generate_show_table_status(const char *tablenam return result; } +/** + * @brief Helper function to format the received hours into a string + * in the format ('HH'|'0H'|'-0H'|'-HH'). Depending on the supplied + * number digit count and sign. + * @param num A number to be converted to described format. + * @return std::string holding the converted number. + */ +const std::string format_timezone_hours(const int num) { + std::string result {}; + + const std::string base_num = std::to_string(num); + + if (num < 10 && num > 0) { + result = "0" + base_num; + } else if (num > -10 && num < 0) { + result = base_num.substr(0, 1) + "0" + base_num.substr(1); + } else if (num <= -10) { + result = base_num; + } + + return result; +} + +/** + * @brief Helper function that converts the current timezone + * expressed in seconds into a string of the format: + * - 'hours' + ':00:00'. + * Following the same pattern as the possible values returned by the SQL query + * 'SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())' in a MySQL server. + * @return A string holding the specified representation of the + * supplied timezone. + */ +std::string timediff_timezone_offset() { + // explecitly call 'tzset' to make sure '::timezone' is set + tzset(); + + // get the global variable + long int timezone = ::timezone; + + // first negate the received number + timezone = -timezone; + + // transform into hours + int timezone_offset_hours = timezone / 3600; + + // create an string with the resulting 'hours' + ':00:00' + std::string time_zone_offset { + format_timezone_hours(timezone_offset_hours) + ":00:00" + }; + + return time_zone_offset; +} void admin_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *pkt) { @@ -4050,6 +4102,46 @@ void admin_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *pkt) { goto __run_query; } + // implementation for 'SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())' in order to support'csharp' connector. See #2543 + if (!strncasecmp("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())", query_no_space, strlen("SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())"))) { + l_free(query_length,query); + char *query1=(char*)"SELECT '%s' as 'TIMEDIFF(NOW(), UTC_TIMESTAMP()'"; + + // compute the timezone diff + std::string timezone_offset_str = timediff_timezone_offset(); + char *query2=(char *)malloc(strlen(query1) + strlen(timezone_offset_str.c_str()) + 1); + + // format the query + sprintf(query2, query1, timezone_offset_str.c_str()); + + // copy the resulting query + query=l_strdup(query2); + query_length=strlen(query2) + 1; + + // free the buffer used to format + free(query2); + goto __run_query; + } + + // implementation for '"select @@max_allowed_packet, @@character_set_client, @@character_set_connection, @@license, @@sql_mode, @@lower_case_table_names"' + // in order to support 'csharp' connector. See #2543 + if ( + !strncasecmp( + "select @@max_allowed_packet, @@character_set_client, @@character_set_connection, @@license, @@sql_mode, @@lower_case_table_names", + query_no_space, + strlen("select @@max_allowed_packet, @@character_set_client, @@character_set_connection, @@license, @@sql_mode, @@lower_case_table_names") + ) + ) { + l_free(query_length,query); + char *query1= + const_cast( + "select '67108864' as '@@max_allowed_packet', 'utf8' as '@@character_set_client', 'utf8' as '@@character_set_connection', '' as '@@license', '' as '@@sql_mode', '' as '@@lower_case_table_names'" + ); + query=l_strdup(query1); + query_length=strlen(query1)+1; + goto __run_query; + } + if (query_no_space_length==SELECT_DB_USER_LEN) { if (!strncasecmp(SELECT_DB_USER, query_no_space, query_no_space_length)) { l_free(query_length,query); From 4664ec312059290d61dd91ea7f81526db43dd777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 23 Mar 2021 19:55:44 +0100 Subject: [PATCH 2/3] Added new test file 'test_csharp_connector_support-t' to test queries to support 'csharp_connector' --- .../tests/test_csharp_connector_support-t.cpp | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 test/tap/tests/test_csharp_connector_support-t.cpp diff --git a/test/tap/tests/test_csharp_connector_support-t.cpp b/test/tap/tests/test_csharp_connector_support-t.cpp new file mode 100644 index 000000000..6c4856f07 --- /dev/null +++ b/test/tap/tests/test_csharp_connector_support-t.cpp @@ -0,0 +1,117 @@ +/** + * @file test_csharp_connector_support-t.cpp + * @brief This test verifies the new added queries for supporting C# connector for the 'Admin module'. + */ + +#include +#include +#include +#include + +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return -1; + } + + plan(4); + + MYSQL* proxysql_admin = mysql_init(NULL); + if (!proxysql_admin) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); + return -1; + } + 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; + } + + // Test the new introduced query "SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())" + int query_res = mysql_query(proxysql_admin, "SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())"); + ok( + query_res == 0, + "Query \"SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())\" should succeed." + ); + + if (query_res == 0) { + MYSQL_RES* select_res = mysql_store_result(proxysql_admin); + unsigned int num_fields = mysql_num_fields(select_res); + MYSQL_ROW select_row = mysql_fetch_row(select_res); + + if (select_row && num_fields == 1) { + std::string select_row_str { select_row[0] }; + bool exp_concat_res = true; + + ok(exp_concat_res, "Output received for \"SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())\" was: %s", select_row_str.c_str()); + } + + mysql_free_result(select_res); + } else { + ok(false, "Query result for \"SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP())\" should be 0. Was: %d", query_res); + } + + MYSQL_RES* select_res = NULL; + int max_allowed_packet = 0; + std::string character_set_client {}; + std::string character_set_connection {}; + std::string license {}; + std::string sql_mode {}; + std::string lower_case_table_names {}; + + // Test the new introduced query "SELECT @@max_allowed_packet, @@character_set_client, @@character_set_connection, @@license, @@sql_mode, @@lower_case_table_names" + query_res = mysql_query(proxysql_admin, "SELECT @@max_allowed_packet, @@character_set_client, @@character_set_connection, @@license, @@sql_mode, @@lower_case_table_names"); + ok( + query_res == 0, + "Query \"SELECT @@max_allowed_packet, @@character_set_client, @@character_set_connection, @@license, @@sql_mode, @@lower_case_table_names\" should succeed." + ); + + if (query_res == 0) { + select_res = mysql_store_result(proxysql_admin); + unsigned int select_num_fields = mysql_num_fields(select_res); + + if (select_res && select_num_fields == 6) { + MYSQL_ROW select_row = mysql_fetch_row(select_res); + + max_allowed_packet = atoi(select_row[0]); + character_set_client = select_row[1]; + character_set_connection = select_row[2]; + license = select_row[3]; + sql_mode = select_row[4]; + lower_case_table_names = select_row[5]; + + bool expected_values = + max_allowed_packet == 67108864 && + character_set_client == "utf8" && + character_set_connection == "utf8" && + license == "" && + sql_mode == "" && + lower_case_table_names == ""; + + ok( + expected_values, + "Query result for \"SELECT @@max_allowed_packet, @@character_set_client, @@character_set_connection, @@license, @@sql_mode, @@lower_case_table_names\" should match the expected hardcoded values:\n" + " (expected=(@@max_allowed_packet:67108864, @@character_set_client:'utf8', @@character_set_connection='utf8', @@license='', @@sql_mode='', @@lower_case_table_names=''),\n" + " (actual=(@@max_allowed_packet:%d, @@character_set_client:'%s', @@character_set_connection='%s', @@license='%s', @@sql_mode='%s', @@lower_case_table_names='%s')))", + max_allowed_packet, + character_set_client.c_str(), + character_set_connection.c_str(), + license.c_str(), + sql_mode.c_str(), + lower_case_table_names.c_str() + ); + + } else { + ok(false, "Invalid resulset. Expected 'num_fields' = 6, not %d", select_num_fields); + } + } + + mysql_free_result(select_res); + + return exit_status(); +} From 5d0fd055343b661462e21ff5b452549d3af28fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 23 Mar 2021 23:08:31 +0100 Subject: [PATCH 3/3] Fixed improperly handled case for 'format_timezone_hours' when hours parameter to be formatted is '0' --- lib/ProxySQL_Admin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index 6e9d73720..7c55b6bbf 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -3456,7 +3456,7 @@ const std::string format_timezone_hours(const int num) { const std::string base_num = std::to_string(num); - if (num < 10 && num > 0) { + if (num < 10 && num >= 0) { result = "0" + base_num; } else if (num > -10 && num < 0) { result = base_num.substr(0, 1) + "0" + base_num.substr(1);