diff --git a/deps/mariadb-client-library/client_deprecate_eof.patch b/deps/mariadb-client-library/client_deprecate_eof.patch index f7f11b3b61..2c7a611f50 100644 --- a/deps/mariadb-client-library/client_deprecate_eof.patch +++ b/deps/mariadb-client-library/client_deprecate_eof.patch @@ -653,7 +653,7 @@ index 0aaaf1a..229023b 100644 { /* allocate space for rows */ if (!(current= (MYSQL_ROWS *)ma_alloc_root(&result->alloc, sizeof(MYSQL_ROWS) + packet_len))) -@@ -276,10 +284,14 @@ int mthd_stmt_read_all_rows(MYSQL_STMT *stmt) +@@ -276,10 +284,16 @@ int mthd_stmt_read_all_rows(MYSQL_STMT *stmt) { *pprevious= 0; /* sace status info */ @@ -664,6 +664,8 @@ index 0aaaf1a..229023b 100644 + + if (stmt->mysql->server_capabilities & CLIENT_DEPRECATE_EOF && !is_data_packet) { + ma_read_ok_packet(stmt->mysql, p + 1, packet_len); ++ stmt->upsert_status.warning_count= stmt->mysql->warning_count; ++ stmt->upsert_status.server_status= stmt->mysql->server_status; + } else { + stmt->upsert_status.warning_count= stmt->mysql->warning_count= uint2korr(p + 1); + stmt->upsert_status.server_status= stmt->mysql->server_status= uint2korr(p + 3); diff --git a/include/MySQL_HostGroups_Manager.h b/include/MySQL_HostGroups_Manager.h index c2df41d6da..1f5c1d8dcc 100644 --- a/include/MySQL_HostGroups_Manager.h +++ b/include/MySQL_HostGroups_Manager.h @@ -237,6 +237,7 @@ class MyHGC { // MySQL Host Group Container uint32_t throttle_connections_per_sec; int8_t autocommit; int8_t free_connections_pct; + int8_t handle_warnings; bool multiplex; bool connection_warming; bool configured; // this variable controls if attributes are configured or not. If not configured, they do not apply @@ -249,6 +250,10 @@ class MyHGC { // MySQL Host Group Container int32_t use_ssl; } servers_defaults; void reset_attributes(); + inline + bool handle_warnings_enabled() const { + return attributes.configured == true && attributes.handle_warnings != -1 ? attributes.handle_warnings : mysql_thread___handle_warnings; + } MyHGC(int); ~MyHGC(); MySrvC *get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, MySQL_Session *sess); diff --git a/include/MySQL_Protocol.h b/include/MySQL_Protocol.h index a7b68994c3..ad849a124f 100644 --- a/include/MySQL_Protocol.h +++ b/include/MySQL_Protocol.h @@ -51,7 +51,7 @@ class MySQL_ResultSet { unsigned int add_row(MYSQL_ROWS *rows); unsigned int add_row(MYSQL_ROW row); unsigned int add_row2(MYSQL_ROWS *row, unsigned char *offset); - void add_eof(); + void add_eof(bool suppress_warning_count=false); void remove_last_eof(); void add_err(MySQL_Data_Stream *_myds); bool get_resultset(PtrSizeArray *PSarrayFinal); diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index 7adaa4b044..d2de482c79 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -24,6 +24,12 @@ enum proxysql_session_type { PROXYSQL_SESSION_NONE }; +enum ps_type : uint8_t { + ps_type_not_set = 0x0, + ps_type_prepare_stmt = 0x1, + ps_type_execute_stmt = 0x2 +}; + std::string proxysql_session_type_str(enum proxysql_session_type session_type); // these structs will be used for various regex hardcoded @@ -121,7 +127,7 @@ class MySQL_Session void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_SET_OPTION(PtrSize_t *); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STATISTICS(PtrSize_t *); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_PROCESS_KILL(PtrSize_t *); - bool handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(PtrSize_t *, bool *lock_hostgroup, bool ps=false); + bool handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(PtrSize_t *, bool *lock_hostgroup, ps_type prepare_stmt_type=ps_type_not_set); void handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED__get_connection(); @@ -274,6 +280,7 @@ class MySQL_Session int to_process; int pending_connect; enum proxysql_session_type session_type; + int warning_in_hg; // bool bool autocommit; @@ -334,7 +341,7 @@ class MySQL_Session MySQL_Backend * find_or_create_backend(int, MySQL_Data_Stream *_myds=NULL); void SQLite3_to_MySQL(SQLite3_result *, char *, int , MySQL_Protocol *, bool in_transaction=false, bool deprecate_eof_active=false); - void MySQL_Result_to_MySQL_wire(MYSQL *mysql, MySQL_ResultSet *MyRS, MySQL_Data_Stream *_myds=NULL); + void MySQL_Result_to_MySQL_wire(MYSQL *mysql, MySQL_ResultSet *MyRS, unsigned int warning_count, MySQL_Data_Stream *_myds=NULL); void MySQL_Stmt_Result_to_MySQL_wire(MYSQL_STMT *stmt, MySQL_Connection *myconn); unsigned int NumActiveTransactions(bool check_savpoint=false); bool HasOfflineBackends(); @@ -379,6 +386,7 @@ class MySQL_Session bool has_any_backend(); void detected_broken_connection(const char *file, unsigned int line, const char *func, const char *action, MySQL_Connection *myconn, int myerr, const char *message, bool verbose=false); void generate_status_one_hostgroup(int hid, std::string& s); + void reset_warning_hostgroup_flag_and_release_connection(); friend void SQLite3_Server_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *pkt); }; diff --git a/include/MySQL_Thread.h b/include/MySQL_Thread.h index 2b60ebb033..9ca92cf13a 100644 --- a/include/MySQL_Thread.h +++ b/include/MySQL_Thread.h @@ -581,6 +581,7 @@ class MySQL_Threads_Handler char * ssl_p2s_crlpath; int query_cache_size_MB; int query_cache_soft_ttl_pct; + int query_cache_handle_warnings; int min_num_servers_lantency_awareness; int aurora_max_lag_ms_only_read_from_replicas; bool stats_time_backend_query; @@ -593,6 +594,7 @@ class MySQL_Threads_Handler bool enable_load_data_local_infile; bool log_mysql_warnings_enabled; int data_packets_history_size; + int handle_warnings; } variables; struct { unsigned int mirror_sessions_current; diff --git a/include/mysql_connection.h b/include/mysql_connection.h index 9fd9a66eba..501ccd7610 100644 --- a/include/mysql_connection.h +++ b/include/mysql_connection.h @@ -19,6 +19,7 @@ using json = nlohmann::json; #define STATUS_MYSQL_CONNECTION_FOUND_ROWS 0x00000200 #define STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG 0x00000400 #define STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT 0x00000800 +#define STATUS_MYSQL_CONNECTION_HAS_WARNINGS 0x00001000 class Variable { public: @@ -53,6 +54,8 @@ class MySQL_Connection_userinfo { class MySQL_Connection { private: + void update_warning_count_from_connection(); + void update_warning_count_from_statement(); bool is_expired(unsigned long long timeout); unsigned long long inserted_into_pool; public: @@ -128,6 +131,7 @@ class MySQL_Connection { } statuses; unsigned long largest_query_length; + unsigned int warning_count; /** * @brief This represents the internal knowledge of ProxySQL about the connection. It keeps track of those * states which *are not reflected* into 'server_status', but are relevant for connection handling. diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index d8ba71318b..d2abf393ea 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -865,10 +865,12 @@ __thread bool mysql_thread___log_mysql_warnings_enabled; __thread bool mysql_thread___enable_load_data_local_infile; __thread int mysql_thread___client_host_cache_size; __thread int mysql_thread___client_host_error_counts; +__thread int mysql_thread___handle_warnings; /* variables used for Query Cache */ __thread int mysql_thread___query_cache_size_MB; __thread int mysql_thread___query_cache_soft_ttl_pct; +__thread int mysql_thread___query_cache_handle_warnings; /* variables used for SSL , from proxy to server (p2s) */ __thread char * mysql_thread___ssl_p2s_ca; @@ -1032,10 +1034,12 @@ extern __thread bool mysql_thread___log_mysql_warnings_enabled; extern __thread bool mysql_thread___enable_load_data_local_infile; extern __thread int mysql_thread___client_host_cache_size; extern __thread int mysql_thread___client_host_error_counts; +extern __thread int mysql_thread___handle_warnings; /* variables used for Query Cache */ extern __thread int mysql_thread___query_cache_size_MB; extern __thread int mysql_thread___query_cache_soft_ttl_pct; +extern __thread int mysql_thread___query_cache_handle_warnings; /* variables used for SSL , from proxy to server (p2s) */ extern __thread char * mysql_thread___ssl_p2s_ca; diff --git a/lib/MySQL_HostGroups_Manager.cpp b/lib/MySQL_HostGroups_Manager.cpp index 7fb83388a1..80f3608271 100644 --- a/lib/MySQL_HostGroups_Manager.cpp +++ b/lib/MySQL_HostGroups_Manager.cpp @@ -903,6 +903,7 @@ void MyHGC::reset_attributes() { attributes.throttle_connections_per_sec = 1000000; attributes.autocommit = -1; attributes.free_connections_pct = 10; + attributes.handle_warnings = -1; attributes.multiplex = true; attributes.connection_warming = false; free(attributes.init_connect); @@ -6931,7 +6932,8 @@ T j_get_srv_default_int_val( /** * @brief Initializes the supplied 'MyHGC' with the specified 'hostgroup_settings'. * @details Input verification is performed in the supplied 'hostgroup_settings'. It's expected to be a valid - * JSON. + * JSON that may contain the following fields: + * - handle_warnings: Value must be >= 0. * * In case input verification fails for a field, supplied 'MyHGC' is NOT updated for that field. An error * message is logged specifying the source of the error. @@ -6945,7 +6947,10 @@ void init_myhgc_hostgroup_settings(const char* hostgroup_settings, MyHGC* myhgc) if (hostgroup_settings[0] != '\0') { try { nlohmann::json j = nlohmann::json::parse(hostgroup_settings); - // fields to be populated + + const auto handle_warnings_check = [](int8_t handle_warnings) -> bool { return handle_warnings == 0 || handle_warnings == 1; }; + int8_t handle_warnings = j_get_srv_default_int_val(j, hid, "handle_warnings", handle_warnings_check); + myhgc->attributes.handle_warnings = handle_warnings; } catch (const json::exception& e) { proxy_error( diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index 0b820d2ab4..ddcbc84228 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -2726,7 +2726,9 @@ void MySQL_ResultSet::init(MySQL_Protocol *_myprot, MYSQL_RES *_res, MYSQL *_my, // up to 2.2.0 we used to add an EOF here. // due to bug #3547 we move the logic into add_eof() that can now handle also prepared statements PROXY_TRACE2(); - add_eof(); + // if the backend server has CLIENT_DEPRECATE_EOF enabled, and the client does not support + // CLIENT_DEPRECATE_EOF, warning_count will be excluded from the intermediate EOF packet + add_eof((mysql->server_capabilities & CLIENT_DEPRECATE_EOF)); } } @@ -2990,7 +2992,7 @@ unsigned int MySQL_ResultSet::add_row2(MYSQL_ROWS *row, unsigned char *offset) { return length; } -void MySQL_ResultSet::add_eof() { +void MySQL_ResultSet::add_eof(bool suppress_warning_count) { if (myprot) { unsigned int nTrx=myds->sess->NumActiveTransactions(); uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0 ); @@ -3001,11 +3003,17 @@ void MySQL_ResultSet::add_eof() { //PSarrayOUT->add(pkt.ptr,pkt.size); //sid++; //resultset_size+=pkt.size; - + + // Note: warnings count will only be sent to the client if mysql-query_digests is enabled + const MySQL_Backend* _mybe = myds->sess->mybe; + const MySQL_Data_Stream* _server_myds = (_mybe && _mybe->server_myds) ? _mybe->server_myds : nullptr; + const MySQL_Connection* _myconn = (_server_myds && _server_myds->myds_type == MYDS_BACKEND && _server_myds->myconn) ? + _server_myds->myconn : nullptr; + const unsigned int warning_count = (_myconn && suppress_warning_count == false) ? _myconn->warning_count : 0; if (deprecate_eof_active) { PtrSize_t pkt; buffer_to_PSarrayOut(); - myprot->generate_pkt_OK(false, &pkt.ptr, &pkt.size, sid, 0, 0, setStatus, 0, NULL, true); + myprot->generate_pkt_OK(false, &pkt.ptr, &pkt.size, sid, 0, 0, setStatus, warning_count, NULL, true); PSarrayOUT.add(pkt.ptr, pkt.size); resultset_size += pkt.size; } @@ -3015,7 +3023,7 @@ void MySQL_ResultSet::add_eof() { // note that EOF is added on a packet on its own, instead of using a buffer, // so that can be removed using remove_last_eof() buffer_to_PSarrayOut(); - myprot->generate_pkt_EOF(false, NULL, NULL, sid, 0, setStatus, this); + myprot->generate_pkt_EOF(false, NULL, NULL, sid, warning_count, setStatus, this); resultset_size += 9; buffer_to_PSarrayOut(); } diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 99afae7fbf..4dab93185e 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -588,6 +588,7 @@ MySQL_Session::MySQL_Session() { mirrorPkt.ptr=NULL; mirrorPkt.size=0; set_status(session_status___NONE); + warning_in_hg = -1; idle_since = 0; transaction_started_at = 0; @@ -640,6 +641,7 @@ void MySQL_Session::reset() { autocommit_handled=false; sending_set_autocommit=false; autocommit_on_hostgroup=-1; + warning_in_hg = -1; current_hostgroup=-1; default_hostgroup=-1; locked_on_hostgroup=-1; @@ -1124,6 +1126,7 @@ void MySQL_Session::generate_proxysql_internal_session_json(json &j) { j["last_HG_affected_rows"] = last_HG_affected_rows; j["active_transactions"] = active_transactions; j["transaction_time_ms"] = thread->curtime - transaction_started_at; + j["warning_in_hg"] = warning_in_hg; j["gtid"]["hid"] = gtid_hid; j["gtid"]["last"] = ( strlen(gtid_buf) ? gtid_buf : "" ); j["qpo"]["create_new_connection"] = qpo->create_new_conn; @@ -1246,6 +1249,8 @@ void MySQL_Session::generate_proxysql_internal_session_json(json &j) { j["backends"][i]["conn"]["status"]["no_multiplex_HG"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG); j["backends"][i]["conn"]["status"]["compression"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_COMPRESSION); j["backends"][i]["conn"]["status"]["prepared_statement"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT); + j["backends"][i]["conn"]["status"]["has_warnings"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_HAS_WARNINGS); + j["backends"][i]["conn"]["warning_count"] = _myconn->warning_count; j["backends"][i]["conn"]["MultiplexDisabled"] = _myconn->MultiplexDisabled(); j["backends"][i]["conn"]["ps"]["backend_stmt_to_global_ids"] = _myconn->local_stmts->backend_stmt_to_global_ids; j["backends"][i]["conn"]["ps"]["global_stmt_to_backend_ids"] = _myconn->local_stmts->global_stmt_to_backend_ids; @@ -1538,19 +1543,41 @@ bool MySQL_Session::handler_special_queries(PtrSize_t *pkt) { return true; } } - if ( (pkt->size == 18) && (strncasecmp((char *)"SHOW WARNINGS",(char *)pkt->ptr+5,13)==0) ) { - SQLite3_result * resultset=new SQLite3_result(3); - resultset->add_column_definition(SQLITE_TEXT,"Level"); - resultset->add_column_definition(SQLITE_TEXT,"Code"); - resultset->add_column_definition(SQLITE_TEXT,"Message"); + // if query digest is disabled, warnings in ProxySQL are also deactivated, + // resulting in an empty response being sent to the client. + if ((pkt->size == 18) && (strncasecmp((char*)"SHOW WARNINGS", (char*)pkt->ptr + 5, 13) == 0) && + CurrentQuery.QueryParserArgs.digest_text == nullptr) { + SQLite3_result* resultset = new SQLite3_result(3); + resultset->add_column_definition(SQLITE_TEXT, "Level"); + resultset->add_column_definition(SQLITE_TEXT, "Code"); + resultset->add_column_definition(SQLITE_TEXT, "Message"); SQLite3_to_MySQL(resultset, NULL, 0, &client_myds->myprot, false, deprecate_eof_active); delete resultset; - client_myds->DSS=STATE_SLEEP; - status=WAITING_CLIENT_DATA; - if (mirror==false) { + client_myds->DSS = STATE_SLEEP; + status = WAITING_CLIENT_DATA; + if (mirror == false) { RequestEnd(NULL); } - l_free(pkt->size,pkt->ptr); + l_free(pkt->size, pkt->ptr); + return true; + } + // if query digest is disabled, warnings in ProxySQL are also deactivated, + // resulting in zero warning count sent to the client. + if ((pkt->size == 27) && (strncasecmp((char*)"SHOW COUNT(*) WARNINGS", (char*)pkt->ptr + 5, 22) == 0) && + CurrentQuery.QueryParserArgs.digest_text == nullptr) { + SQLite3_result* resultset = new SQLite3_result(1); + resultset->add_column_definition(SQLITE_TEXT, "@@session.warning_count"); + char* pta[1]; + pta[0] = (char*)"0"; + resultset->add_row(pta); + SQLite3_to_MySQL(resultset, NULL, 0, &client_myds->myprot, false, deprecate_eof_active); + delete resultset; + client_myds->DSS = STATE_SLEEP; + status = WAITING_CLIENT_DATA; + if (mirror == false) { + RequestEnd(NULL); + } + l_free(pkt->size, pkt->ptr); return true; } // 'LOAD DATA LOCAL INFILE' is unsupported. We report an specific error to inform clients about this fact. For more context see #833. @@ -3261,7 +3288,8 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C (begint.tv_sec*1000000000+begint.tv_nsec); } assert(qpo); // GloQPro->process_mysql_query() should always return a qpo - rc_break=handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(&pkt, &lock_hostgroup); + // setting 'prepared' to prevent fetching results from the cache if the digest matches + rc_break=handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(&pkt, &lock_hostgroup, ps_type_prepare_stmt); if (rc_break==true) { return; } @@ -3429,7 +3457,7 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C CurrentQuery.stmt_meta=stmt_meta; //current_hostgroup=qpo->destination_hostgroup; - rc_break=handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(&pkt, &lock_hostgroup, true); + rc_break=handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(&pkt, &lock_hostgroup, ps_type_execute_stmt); if (rc_break==true) { return; } @@ -4572,9 +4600,9 @@ void MySQL_Session::handler_minus1_GenerateErrorMessage(MySQL_Data_Stream *myds, switch (status) { case PROCESSING_QUERY: if (myconn) { - MySQL_Result_to_MySQL_wire(myconn->mysql, myconn->MyRS, myds); + MySQL_Result_to_MySQL_wire(myconn->mysql, myconn->MyRS, myconn->warning_count, myds); } else { - MySQL_Result_to_MySQL_wire(NULL, NULL, myds); + MySQL_Result_to_MySQL_wire(NULL, NULL, 0, myds); } break; case PROCESSING_STMT_PREPARE: @@ -5050,7 +5078,7 @@ int MySQL_Session::handler() { switch (status) { case PROCESSING_QUERY: - MySQL_Result_to_MySQL_wire(myconn->mysql, myconn->MyRS, myconn->myds); + MySQL_Result_to_MySQL_wire(myconn->mysql, myconn->MyRS, myconn->warning_count, myconn->myds); break; case PROCESSING_STMT_PREPARE: { @@ -5139,7 +5167,7 @@ int MySQL_Session::handler() { break; // rc==2 : a multi-resultset (or multi statement) was detected, and the current statement is completed case 2: - MySQL_Result_to_MySQL_wire(myconn->mysql, myconn->MyRS, myconn->myds); + MySQL_Result_to_MySQL_wire(myconn->mysql, myconn->MyRS, myconn->warning_count, myconn->myds); if (myconn->MyRS) { // we also need to clear MyRS, so that the next staement will recreate it if needed if (myconn->MyRS_reuse) { delete myconn->MyRS_reuse; @@ -5965,7 +5993,7 @@ int MySQL_Session::handler_WCD_SS_MCQ_qpo_Parse_SQL_LOG_BIN(PtrSize_t *pkt, bool } */ -bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(PtrSize_t *pkt, bool *lock_hostgroup, bool prepared) { +bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(PtrSize_t *pkt, bool *lock_hostgroup, ps_type prepare_stmt_type) { /* lock_hostgroup: If this variable is set to true, this session will get lock to a @@ -5984,23 +6012,87 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C if (pkt->size > (unsigned int) mysql_thread___max_allowed_packet) { handler_WCD_SS_MCQ_qpo_LargePacket(pkt); + reset_warning_hostgroup_flag_and_release_connection(); return true; } if (qpo->OK_msg) { handler_WCD_SS_MCQ_qpo_OK_msg(pkt); + reset_warning_hostgroup_flag_and_release_connection(); return true; } if (qpo->error_msg) { handler_WCD_SS_MCQ_qpo_error_msg(pkt); + reset_warning_hostgroup_flag_and_release_connection(); return true; } - if (prepared) { // for prepared statement we exit here + if (prepare_stmt_type & ps_type_execute_stmt) { // for prepared statement execute we exit here + reset_warning_hostgroup_flag_and_release_connection(); goto __exit_set_destination_hostgroup; } + // handle warnings + if (CurrentQuery.QueryParserArgs.digest_text) { + const char* dig_text = CurrentQuery.QueryParserArgs.digest_text; + const size_t dig_len = strlen(dig_text); + + if (dig_len > 0) { + if ((dig_len == 13) && (strncasecmp(dig_text, "SHOW WARNINGS", 13) == 0)) { + proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Intercepted '%s'\n", dig_text); + if (warning_in_hg > -1) { + proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Changing current_hostgroup to '%d'\n", warning_in_hg); + current_hostgroup = warning_in_hg; + return false; + } else { + proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "No warnings were detected in the previous query. Sending an empty response.\n"); + std::unique_ptr resultset(new SQLite3_result(3)); + resultset->add_column_definition(SQLITE_TEXT, "Level"); + resultset->add_column_definition(SQLITE_TEXT, "Code"); + resultset->add_column_definition(SQLITE_TEXT, "Message"); + SQLite3_to_MySQL(resultset.get(), NULL, 0, &client_myds->myprot, false, (client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF)); + client_myds->DSS = STATE_SLEEP; + status = WAITING_CLIENT_DATA; + if (mirror == false) { + RequestEnd(NULL); + } + l_free(pkt->size, pkt->ptr); + return true; + } + } + + if ((dig_len == 22) && (strncasecmp(dig_text, "SHOW COUNT(*) WARNINGS", 22) == 0)) { + proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Intercepted '%s'\n", dig_text); + std::string warning_count = "0"; + if (warning_in_hg > -1) { + proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Changing current_hostgroup to '%d'\n", warning_in_hg); + current_hostgroup = warning_in_hg; + assert(mybe && mybe->server_myds && mybe->server_myds->myconn && mybe->server_myds->myconn->mysql); + warning_count = std::to_string(mybe->server_myds->myconn->warning_count); + } + else { + proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "No warnings were detected in the previous query. Sending an empty response.\n"); + } + std::unique_ptr resultset(new SQLite3_result(1)); + resultset->add_column_definition(SQLITE_TEXT, "@@session.warning_count"); + char* pta[1]; + pta[0] = (char*)warning_count.c_str(); + resultset->add_row(pta); + SQLite3_to_MySQL(resultset.get(), NULL, 0, &client_myds->myprot, false, (client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF)); + client_myds->DSS = STATE_SLEEP; + status = WAITING_CLIENT_DATA; + if (mirror == false) { + RequestEnd(NULL); + } + l_free(pkt->size, pkt->ptr); + return true; + } + } + } + + reset_warning_hostgroup_flag_and_release_connection(); + // handle here #509, #815 and #816 if (CurrentQuery.QueryParserArgs.digest_text) { char *dig=CurrentQuery.QueryParserArgs.digest_text; @@ -6861,12 +6953,12 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C } // handle command KILL #860 - if (prepared == false) { + //if (prepared == false) { if (handle_command_query_kill(pkt)) { return true; } - } - if (qpo->cache_ttl>0) { + //} + if (qpo->cache_ttl>0 && ((prepare_stmt_type & ps_type_prepare_stmt) == 0)) { bool deprecate_eof_active = client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF; uint32_t resbuf=0; unsigned char *aa=GloQC->get( @@ -7262,7 +7354,7 @@ void MySQL_Session::MySQL_Stmt_Result_to_MySQL_wire(MYSQL_STMT *stmt, MySQL_Conn setStatus |= SERVER_MORE_RESULTS_EXIST; setStatus |= ( mysql->server_status & ~SERVER_STATUS_AUTOCOMMIT ); // get flags from server_status but ignore autocommit setStatus = setStatus & ~SERVER_STATUS_CURSOR_EXISTS; // Do not send cursor #1128 - client_myds->myprot.generate_pkt_OK(true,NULL,NULL,client_myds->pkt_sid+1,num_rows,mysql->insert_id, setStatus , mysql->warning_count,mysql->info); + client_myds->myprot.generate_pkt_OK(true,NULL,NULL,client_myds->pkt_sid+1,num_rows,mysql->insert_id, setStatus , myconn ? myconn->warning_count : 0,mysql->info); client_myds->pkt_sid++; } else { // error @@ -7274,7 +7366,7 @@ void MySQL_Session::MySQL_Stmt_Result_to_MySQL_wire(MYSQL_STMT *stmt, MySQL_Conn } } -void MySQL_Session::MySQL_Result_to_MySQL_wire(MYSQL *mysql, MySQL_ResultSet *MyRS, MySQL_Data_Stream *_myds) { +void MySQL_Session::MySQL_Result_to_MySQL_wire(MYSQL *mysql, MySQL_ResultSet *MyRS, unsigned int warning_count, MySQL_Data_Stream *_myds) { if (mysql == NULL) { // error client_myds->myprot.generate_pkt_ERR(true,NULL,NULL,client_myds->pkt_sid+1, 2013, (char *)"HY000" ,(char *)"Lost connection to MySQL server during query"); @@ -7289,7 +7381,9 @@ void MySQL_Session::MySQL_Result_to_MySQL_wire(MYSQL *mysql, MySQL_ResultSet *My assert(resultset_completed); // the resultset should always be completed if MySQL_Result_to_MySQL_wire is called if (transfer_started==false) { // we have all the resultset when MySQL_Result_to_MySQL_wire was called if (qpo && qpo->cache_ttl>0 && com_field_list==false) { // the resultset should be cached - if (mysql_errno(mysql)==0) { // no errors + if (mysql_errno(mysql)==0 && + (mysql_warning_count(mysql)==0 || + mysql_thread___query_cache_handle_warnings==1)) { // no errors if ( (qpo->cache_empty_result==1) || ( @@ -7330,7 +7424,7 @@ void MySQL_Session::MySQL_Result_to_MySQL_wire(MYSQL *mysql, MySQL_ResultSet *My setStatus |= SERVER_MORE_RESULTS_EXIST; setStatus |= ( mysql->server_status & ~SERVER_STATUS_AUTOCOMMIT ); // get flags from server_status but ignore autocommit setStatus = setStatus & ~SERVER_STATUS_CURSOR_EXISTS; // Do not send cursor #1128 - client_myds->myprot.generate_pkt_OK(true,NULL,NULL,client_myds->pkt_sid+1,num_rows,mysql->insert_id, setStatus, mysql->warning_count,mysql->info); + client_myds->myprot.generate_pkt_OK(true,NULL,NULL,client_myds->pkt_sid+1,num_rows,mysql->insert_id, setStatus, warning_count, mysql->info); //client_myds->pkt_sid++; } else { // error @@ -8070,3 +8164,24 @@ void MySQL_Session::generate_status_one_hostgroup(int hid, std::string& s) { s = j_res.dump(); delete resultset; } + +void MySQL_Session::reset_warning_hostgroup_flag_and_release_connection() +{ + if (warning_in_hg > -1) { + // if we've reached this point, it means that warning was found in the previous query, but the + // current executed query is not 'SHOW WARNINGS' or 'SHOW COUNT(*) FROM WARNINGS', so we can safely reset warning_in_hg and + // return connection back to the connection pool. + MySQL_Backend* _mybe = find_backend(warning_in_hg); + if (_mybe) { + MySQL_Data_Stream* myds = _mybe->server_myds; + if (myds && myds->myconn) { + myds->myconn->warning_count = 0; + myds->myconn->set_status(false, STATUS_MYSQL_CONNECTION_HAS_WARNINGS); + if ((myds->myconn->reusable == true) && myds->myconn->IsActiveTransaction() == false && myds->myconn->MultiplexDisabled() == false) { + myds->return_MySQL_Connection_To_Pool(); + } + } + } + warning_in_hg = -1; + } +} diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index ce52e404d4..18efd1f6a5 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -553,6 +553,7 @@ static char * mysql_thread_variables_names[]= { (char *)"long_query_time", (char *)"query_cache_size_MB", (char *)"query_cache_soft_ttl_pct", + (char *)"query_cache_handle_warnings", (char *)"ping_interval_server_msec", (char *)"ping_timeout_server", (char *)"default_schema", @@ -603,6 +604,7 @@ static char * mysql_thread_variables_names[]= { (char *)"stats_time_query_processor", (char *)"query_cache_stores_empty_result", (char *)"data_packets_history_size", + (char *)"handle_warnings", NULL }; @@ -1066,6 +1068,7 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() { variables.query_retries_on_failure=1; variables.client_host_cache_size=0; variables.client_host_error_counts=0; + variables.handle_warnings=1; variables.connect_retries_on_failure=10; variables.connection_delay_multiplex_ms=0; variables.connection_max_age_ms=0; @@ -1148,6 +1151,7 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() { variables.long_query_time=1000; variables.query_cache_size_MB=256; variables.query_cache_soft_ttl_pct=0; + variables.query_cache_handle_warnings=0; variables.init_connect=NULL; variables.ldap_user_variable=NULL; variables.add_ldap_user_comment=NULL; @@ -2269,6 +2273,7 @@ char ** MySQL_Threads_Handler::get_variables_list() { VariablesPointers_int["ping_timeout_server"] = make_tuple(&variables.ping_timeout_server, 10, 600*1000, false); VariablesPointers_int["client_host_cache_size"] = make_tuple(&variables.client_host_cache_size, 0, 1024*1024, false); VariablesPointers_int["client_host_error_counts"] = make_tuple(&variables.client_host_error_counts, 0, 1024*1024, false); + VariablesPointers_int["handle_warnings"] = make_tuple(&variables.handle_warnings, 0, 1, false); // logs VariablesPointers_int["auditlog_filesize"] = make_tuple(&variables.auditlog_filesize, 1024*1024, 1*1024*1024*1024, false); @@ -2284,6 +2289,8 @@ char ** MySQL_Threads_Handler::get_variables_list() { VariablesPointers_int["max_transaction_time"] = make_tuple(&variables.max_transaction_time, 1000, 20*24*3600*1000, false); VariablesPointers_int["query_cache_size_mb"] = make_tuple(&variables.query_cache_size_MB, 0, 1024*10240, false); VariablesPointers_int["query_cache_soft_ttl_pct"] = make_tuple(&variables.query_cache_soft_ttl_pct, 0, 100, false); + VariablesPointers_int["query_cache_handle_warnings"] = make_tuple(&variables.query_cache_handle_warnings, 0, 1, false); + #ifdef IDLE_THREADS VariablesPointers_int["session_idle_ms"] = make_tuple(&variables.session_idle_ms, 1, 3600*1000, false); #endif // IDLE_THREADS @@ -4036,6 +4043,7 @@ void MySQL_Thread::refresh_variables() { mysql_thread___long_query_time=GloMTH->get_variable_int((char *)"long_query_time"); mysql_thread___query_cache_size_MB=GloMTH->get_variable_int((char *)"query_cache_size_MB"); mysql_thread___query_cache_soft_ttl_pct=GloMTH->get_variable_int((char *)"query_cache_soft_ttl_pct"); + mysql_thread___query_cache_handle_warnings =GloMTH->get_variable_int((char*)"query_cache_handle_warnings"); mysql_thread___ping_interval_server_msec=GloMTH->get_variable_int((char *)"ping_interval_server_msec"); mysql_thread___ping_timeout_server=GloMTH->get_variable_int((char *)"ping_timeout_server"); mysql_thread___shun_on_failures=GloMTH->get_variable_int((char *)"shun_on_failures"); @@ -4197,6 +4205,7 @@ void MySQL_Thread::refresh_variables() { mysql_thread___log_mysql_warnings_enabled=(bool)GloMTH->get_variable_int((char *)"log_mysql_warnings_enabled"); mysql_thread___client_host_cache_size=GloMTH->get_variable_int((char *)"client_host_cache_size"); mysql_thread___client_host_error_counts=GloMTH->get_variable_int((char *)"client_host_error_counts"); + mysql_thread___handle_warnings=GloMTH->get_variable_int((char*)"handle_warnings"); #ifdef DEBUG mysql_thread___session_debug=(bool)GloMTH->get_variable_int((char *)"session_debug"); #endif /* DEBUG */ diff --git a/lib/Query_Cache.cpp b/lib/Query_Cache.cpp index 60c0302abe..327a0f46f3 100644 --- a/lib/Query_Cache.cpp +++ b/lib/Query_Cache.cpp @@ -728,8 +728,34 @@ bool Query_Cache::set(uint64_t user_hash, const unsigned char *kp, uint32_t kl, if (hdr.pkt_length < 9 && *payload == 0xfe) { if (deprecate_eof_active) { entry->ok_pkt_offset = it - vp; + + // Reset the warning flags to zero before storing resultset in the cache + // Reason: When a warning flag is set, it may prompt the client to invoke "SHOW WARNINGS" or "SHOW COUNT(*) FROM WARNINGS". + // However, when retrieving data from the cache, it's possible that there are no warnings present + // that might be associated with previous interactions. + unsigned char* payload_temp = payload+1; + + // skip affected_rows + payload_temp += mysql_decode_length(payload_temp, nullptr); + + // skip last_insert_id + payload_temp += mysql_decode_length(payload_temp, nullptr); + + // skip stats_flags + payload_temp += sizeof(uint16_t); + + uint16_t warnings = 0; + memcpy(payload_temp, &warnings, sizeof(uint16_t)); + } else { entry->row_eof_pkt_offset = it - vp; + + // Reset the warning flags to zero before storing resultset in the cache + // Reason: When a warning flag is set, it may prompt the client to invoke "SHOW WARNINGS" or "SHOW COUNT(*) FROM WARNINGS". + // However, when retrieving data from the cache, it's possible that there are no warnings present + // that might be associated with previous interactions. + uint16_t warnings = 0; + memcpy((payload + 1), &warnings, sizeof(uint16_t)); } break; } else { diff --git a/lib/mysql_connection.cpp b/lib/mysql_connection.cpp index d1aa96368c..c5cb83a8c1 100644 --- a/lib/mysql_connection.cpp +++ b/lib/mysql_connection.cpp @@ -434,6 +434,7 @@ MySQL_Connection::MySQL_Connection() { query.stmt_meta=NULL; query.stmt_result=NULL; largest_query_length=0; + warning_count=0; multiplex_delayed=false; MyRS=NULL; MyRS_reuse=NULL; @@ -548,6 +549,36 @@ unsigned int MySQL_Connection::set_charset(unsigned int _c, enum charset_action return _c; } +void MySQL_Connection::update_warning_count_from_connection() { + // if a prepared statement was cached while 'mysql_thread_query_digest' was true, and subsequently, + // 'mysql_thread_query_digest' is set to false, fetching that statement from the cache may still contain the digest text. + // To prevent this, we will check the digest text in conjunction with 'mysql_thread_query_digest' to verify whether it + // is enabled or disabled. + if (myds && myds->sess && myds->sess->CurrentQuery.QueryParserArgs.digest_text) { + const char* dig_text = myds->sess->CurrentQuery.QueryParserArgs.digest_text; + const size_t dig_len = strlen(dig_text); + // SHOW WARNINGS doesn't have any impact warning count, + // so we are replication same behaviour here + if (parent->myhgc->handle_warnings_enabled() && + (dig_len != 13 || strncasecmp(dig_text, "SHOW WARNINGS", 13) != 0)) { + warning_count = mysql_warning_count(mysql); + } + } +} + +void MySQL_Connection::update_warning_count_from_statement() { + // if a prepared statement was cached while 'mysql_thread_query_digest' was true, and subsequently, + // 'mysql_thread_query_digest' is set to false, fetching that statement from the cache may still contain the digest text. + // To prevent this, we will check the digest text in conjunction with 'mysql_thread_query_digest' to verify whether it + // is enabled or disabled. + if (myds && myds->sess && myds->sess->CurrentQuery.stmt_info && myds->sess->CurrentQuery.stmt_info->digest_text && + mysql_thread___query_digests == true) { + if (parent->myhgc->handle_warnings_enabled()) { + warning_count = mysql_stmt_warning_count(query.stmt); + } + } +} + bool MySQL_Connection::is_expired(unsigned long long timeout) { // FIXME: here the check should be a sanity check // FIXME: for now this is just a temporary (and stupid) check @@ -1352,6 +1383,7 @@ MDB_ASYNC_ST MySQL_Connection::handler(short event) { if (query.stmt_result==NULL) { NEXT_IMMEDIATE(ASYNC_STMT_EXECUTE_END); } else { + update_warning_count_from_statement(); if (myds->sess->mirror==false) { if (MyRS_reuse == NULL) { MyRS = new MySQL_ResultSet(); @@ -1466,6 +1498,7 @@ MDB_ASYNC_ST MySQL_Connection::handler(short event) { NEXT_IMMEDIATE(ASYNC_STMT_EXECUTE_SUCCESSFUL); } */ + update_warning_count_from_statement(); break; // case ASYNC_STMT_EXECUTE_SUCCESSFUL: // break; @@ -1531,6 +1564,14 @@ MDB_ASYNC_ST MySQL_Connection::handler(short event) { if (mysql_result==NULL) { NEXT_IMMEDIATE(ASYNC_QUERY_END); } else { + // since 'add_eof' utilizes 'warning_count,' we are setting the 'warning_count' here + + // Note: There is a possibility of obtaining inaccurate warning_count and server_status at this point + // if the backend server has CLIENT_DEPRECATE_EOF enabled, and the client does not support CLIENT_DEPRECATE_EOF, + // especially when the query generates a warning. This information will be included in the intermediate EOF packet. + // Correct information becomes available only after fetching all rows, + // and the warning_count and status flag details are extracted from the final OK packet. + update_warning_count_from_connection(); if (myds->sess->mirror==false) { if (MyRS_reuse == NULL) { MyRS = new MySQL_ResultSet(); @@ -1633,8 +1674,11 @@ MDB_ASYNC_ST MySQL_Connection::handler(short event) { } } } + // since 'add_eof' utilizes 'warning_count,' we are setting the 'warning_count' here + update_warning_count_from_connection(); // we reach here if there was no error - MyRS->add_eof(); + // exclude warning_count from the OK/EOF packet for the ‘SHOW WARNINGS’ statement + MyRS->add_eof(query.length == 13 && strncasecmp(query.ptr, "SHOW WARNINGS", 13) == 0); NEXT_IMMEDIATE(ASYNC_QUERY_END); } } @@ -1645,6 +1689,7 @@ MDB_ASYNC_ST MySQL_Connection::handler(short event) { int _myerrno=mysql_errno(mysql); if (_myerrno == 0) { unknown_transaction_status = false; + update_warning_count_from_connection(); } else { compute_unknown_transaction_status(); } @@ -2448,7 +2493,10 @@ bool MySQL_Connection::MultiplexDisabled(bool check_delay_token) { // status_flags stores information about the status of the connection // can be used to determine if multiplexing can be enabled or not bool ret=false; - if (status_flags & (STATUS_MYSQL_CONNECTION_TRANSACTION|STATUS_MYSQL_CONNECTION_USER_VARIABLE|STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT|STATUS_MYSQL_CONNECTION_LOCK_TABLES|STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE|STATUS_MYSQL_CONNECTION_GET_LOCK|STATUS_MYSQL_CONNECTION_NO_MULTIPLEX|STATUS_MYSQL_CONNECTION_SQL_LOG_BIN0|STATUS_MYSQL_CONNECTION_FOUND_ROWS|STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG|STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT) ) { + if (status_flags & (STATUS_MYSQL_CONNECTION_TRANSACTION | STATUS_MYSQL_CONNECTION_USER_VARIABLE | STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT | + STATUS_MYSQL_CONNECTION_LOCK_TABLES | STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE | STATUS_MYSQL_CONNECTION_GET_LOCK | STATUS_MYSQL_CONNECTION_NO_MULTIPLEX | + STATUS_MYSQL_CONNECTION_SQL_LOG_BIN0 | STATUS_MYSQL_CONNECTION_FOUND_ROWS | STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG | + STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT | STATUS_MYSQL_CONNECTION_HAS_WARNINGS) ) { ret=true; } if (check_delay_token && auto_increment_delay_token) return true; @@ -2569,6 +2617,32 @@ void MySQL_Connection::ProcessQueryAndSetStatusFlags(char *query_digest_text) { } } } + // checking warnings and disabling multiplexing will be effective only when the mysql-query_digests is enabled + if (get_status(STATUS_MYSQL_CONNECTION_HAS_WARNINGS) == false) { + if (warning_count > 0) { + // 'warning_in_hg' will be used if the next query is 'SHOW WARNINGS' or + // 'SHOW COUNT(*) WARNINGS' + if (myds && myds->sess) + myds->sess->warning_in_hg = myds->sess->current_hostgroup; + // enabling multiplexing + set_status(true, STATUS_MYSQL_CONNECTION_HAS_WARNINGS); + } + } else { // reset warning_in_hg + const char* dig = query_digest_text; + const size_t dig_len = strlen(dig); + // disable multiplexing and reset the 'warning_in_hg' flag only when the current executed query is not + // 'SHOW WARNINGS' or 'SHOW COUNT(*) WARNINGS', as these queries do not clear the warning message list + // on backend. + if (!((dig_len == 22 && strncasecmp(dig, "SHOW COUNT(*) WARNINGS", 22) == 0) || + (dig_len == 13 && strncasecmp(dig, "SHOW WARNINGS", 13) == 0))) { + if (myds && myds->sess) + myds->sess->warning_in_hg = -1; + warning_count = 0; + // disabling multiplexing + set_status(false, STATUS_MYSQL_CONNECTION_HAS_WARNINGS); + } + } + if (get_status(STATUS_MYSQL_CONNECTION_USER_VARIABLE)==false) { // we search for variables only if not already set // if ( // strncasecmp(query_digest_text,"SELECT @@tx_isolation", strlen("SELECT @@tx_isolation")) @@ -2797,7 +2871,7 @@ void MySQL_Connection::reset() { set_status(old_compress,STATUS_MYSQL_CONNECTION_COMPRESSION); reusable=true; options.last_set_autocommit=-1; // never sent - + warning_count=0; delete local_stmts; local_stmts=new MySQL_STMTs_local_v14(false); creation_time = monotonic_time(); diff --git a/test/tap/tap/Makefile b/test/tap/tap/Makefile index 1858a60f66..9e58496e42 100644 --- a/test/tap/tap/Makefile +++ b/test/tap/tap/Makefile @@ -30,6 +30,7 @@ all: libtap.a libtap.so libssl.so.3 libcrypto.so.3 libcpp_dotenv.so .PHONY: clean clean: rm -f *.o libtap.a libtap.so || true + find . -name '*.so' -type f -delete || true find cpp-dotenv/dynamic -name '*.o' -or -name '*.a' -delete || true find cpp-dotenv/static -name '*.o' -or -name '*.a' -delete || true diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index f5fc3354c8..f33a90c198 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -31,6 +31,151 @@ using std::vector; using std::to_string; using nlohmann::json; +#define LAST_QUERY_EXECUTED_STR(mysql) (*static_cast(mysql->unused_0)) +#define STMT_VECTOR(stmt) (*static_cast*>(stmt->mysql->unused_3)) +#define STMT_EXECUTED_VECTOR(stmt) (*static_cast>*>(stmt->mysql->unused_4)) +#define LAST_QUERY_EXECUTED_PTR(mysql) (static_cast(mysql->unused_0)) +#define STMT_VECTOR_PTR(mysql) (static_cast*>(mysql->unused_3)) +#define STMT_EXECUTED_VECTOR_PTR(mysql) (static_cast>*>(mysql->unused_4)) + +#define STMT_FIND_INDEX(stmt,idx) const std::vector& vec_stmt = STMT_VECTOR(stmt); \ + for (size_t i = 0; i < vec_stmt.size(); i++) {\ + if (vec_stmt[i] == stmt) {\ + idx = i; \ + break; \ + }\ + } + +#define STMT_PUSH_QUERY(stmt,query) size_t idx = -1; \ + STMT_FIND_INDEX(stmt,idx);\ + if (idx == -1) {\ + STMT_VECTOR(stmt).emplace_back(stmt); \ + STMT_EXECUTED_VECTOR(stmt).emplace_back(strdup(query), &free);\ + } else {\ + STMT_EXECUTED_VECTOR(stmt)[idx] = std::unique_ptr(strdup(query), &free);\ + } + +#define STMT_LOAD_QUERY(stmt,query) size_t idx = -1; \ + STMT_FIND_INDEX(stmt,idx);\ + if (idx != -1) query = STMT_EXECUTED_VECTOR(stmt)[idx].get(); + +#define STMT_REMOVE(stmt) size_t idx = -1; \ + STMT_FIND_INDEX(stmt,idx);\ + if (idx != -1) {\ + std::vector& vec_stmt = STMT_VECTOR(stmt);\ + std::vector>& vec_query = STMT_EXECUTED_VECTOR(stmt);\ + if (idx != vec_stmt.size() - 1) {\ + vec_stmt[idx] = vec_stmt.back();\ + vec_query[idx] = std::move(vec_query.back());\ + }\ + vec_stmt.pop_back();\ + vec_query.pop_back();\ + } + +MYSQL* mysql_init_override(MYSQL* mysql, const char* file, int line) { + static bool init = false; + MYSQL* result = (*real_mysql_init)(mysql); + if (init == false) { + init = true; + fprintf(stdout, ">> [mysql_init] Override functions attached <<\n"); + } + result->unused_0 = new std::string; + result->unused_3 = nullptr; + result->unused_4 = nullptr; + return result; +} + +int mysql_query_override(MYSQL* mysql, const char* query, const char* file, int line) { + const int result = (*real_mysql_query)(mysql, query); + if (result == 0) { + LAST_QUERY_EXECUTED_STR(mysql) = query; + if (mysql_errno(mysql) == 0 && mysql_field_count(mysql) == 0 && mysql_warning_count(mysql) > 0) { + fprintf(stdout, "File %s, Line %d, [mysql_query] A warning was generated during the execution of the query:'%s', warning count:%d\n", + file, line, query, mysql_warning_count(mysql)); + } + } + return result; +} + +MYSQL_RES* mysql_store_result_override(MYSQL* mysql, const char* file, int line) { + MYSQL_RES* result = (*real_mysql_store_result)(mysql); + if (mysql_errno(mysql) == 0 && mysql_warning_count(mysql) > 0) { + fprintf(stdout, "File %s, Line %d, [mysql_store_result] A warning was generated during the execution of the query:'%s', warning count:%d\n", + file, line, LAST_QUERY_EXECUTED_STR(mysql).c_str(), mysql_warning_count(mysql)); + } + return result; +} + +void mysql_close_override(MYSQL* mysql, const char* file, int line) { + delete LAST_QUERY_EXECUTED_PTR(mysql); + if (STMT_VECTOR_PTR(mysql)) { + delete STMT_VECTOR_PTR(mysql); + delete STMT_EXECUTED_VECTOR_PTR(mysql); + } + (*real_mysql_close)(mysql); +} + +MYSQL_STMT* mysql_stmt_init_override(MYSQL* mysql, const char* file, int line) { + MYSQL_STMT* result = (*real_mysql_stmt_init)(mysql); + if (result->mysql->unused_3 == nullptr) { + std::vector* vec_stmt = new std::vector; + std::vector>* vec_query = + new std::vector>; + vec_stmt->reserve(64); + vec_query->reserve(64); + result->mysql->unused_3 = vec_stmt; + result->mysql->unused_4 = vec_query; + } + return result; +} + +int mysql_stmt_prepare_override(MYSQL_STMT* stmt, const char* stmt_str, unsigned long length, const char* file, int line) { + const int result = (*real_mysql_stmt_prepare)(stmt, stmt_str, length); + if (result == 0) { + STMT_PUSH_QUERY(stmt,stmt_str); + // mysql_stmt_warning_count is not available in MySQL connector + if (mysql_stmt_errno(stmt) == 0 && /*mysql_stmt_warning_count(stmt)*/mysql_warning_count(stmt->mysql) > 0) { + fprintf(stdout, "File %s, Line %d, [mysql_stmt_prepare] A warning was generated during the execution of the query:'%s', warning count:%d\n", + file, line, stmt_str, /*mysql_stmt_warning_count(stmt)*/mysql_warning_count(stmt->mysql)); + } + } + return result; +} + +int mysql_stmt_execute_override(MYSQL_STMT* stmt, const char* file, int line) { + const int result = (*real_mysql_stmt_execute)(stmt); + if (result == 0) { + // mysql_stmt_warning_count is not available in MySQL connector + if (mysql_stmt_errno(stmt) == 0 && mysql_stmt_field_count(stmt) == 0 && + /*mysql_stmt_warning_count(stmt)*/mysql_warning_count(stmt->mysql) > 0) { + char* query = nullptr; + STMT_LOAD_QUERY(stmt, query); + fprintf(stdout, "File %s, Line %d, [mysql_stmt_execute] A warning was generated during the execution of the query:'%s', warning count:%d\n", + file, line, (query ? query : ""), /*mysql_stmt_warning_count(stmt)*/mysql_warning_count(stmt->mysql)); + } + } + return result; +} + +int mysql_stmt_store_result_override(MYSQL_STMT* stmt, const char* file, int line) { + const int result = (*real_mysql_stmt_store_result)(stmt); + if (result == 0) { + // mysql_stmt_warning_count is not available in MySQL connector + if (mysql_stmt_errno(stmt) == 0 && /*mysql_stmt_warning_count(stmt)*/mysql_warning_count(stmt->mysql) > 0) { + char* query = nullptr; + STMT_LOAD_QUERY(stmt, query); + fprintf(stdout, "File %s, Line %d, [mysql_stmt_store_result] A warning was generated during the execution of the query:'%s', warning count:%d\n", + file, line, (query ? query : ""), /*mysql_stmt_warning_count(stmt)*/mysql_warning_count(stmt->mysql)); + } + } + return result; +} + +my_bool mysql_stmt_close_override(MYSQL_STMT* stmt, const char* file, int line) { + STMT_REMOVE(stmt) + return (*real_mysql_stmt_close)(stmt); +} + std::size_t count_matches(const string& str, const string& substr) { std::size_t result = 0; std::size_t pos = 0; diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index da13c69e8f..d028c1f277 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -16,6 +16,41 @@ #include "command_line.h" #include "json.hpp" +#ifndef DISABLE_WARNING_COUNT_LOGGING +/* We are overriding some of the mariadb APIs to extract the warning count and print it in the log. + This override will apply to all TAP tests, except when the TAP test is linked with the MySQL client library (LIBMYSQL_HELPER defined). +*/ +static MYSQL* (*real_mysql_init)(MYSQL* mysql) = &mysql_init; +static int (*real_mysql_query)(MYSQL* mysql, const char* query) = &mysql_query; +static MYSQL_RES* (*real_mysql_store_result)(MYSQL* mysql) = &mysql_store_result; +static void (*real_mysql_close)(MYSQL* mysql) = &mysql_close; +static MYSQL_STMT* (*real_mysql_stmt_init)(MYSQL* mysql) = &mysql_stmt_init; +static int (*real_mysql_stmt_prepare)(MYSQL_STMT* stmt, const char* stmt_str, unsigned long length) = &mysql_stmt_prepare; +static int (*real_mysql_stmt_execute)(MYSQL_STMT* stmt) = &mysql_stmt_execute; +static int (*real_mysql_stmt_store_result)(MYSQL_STMT* stmt) = &mysql_stmt_store_result; +static my_bool (*real_mysql_stmt_close)(MYSQL_STMT* stmt) = &mysql_stmt_close; + +MYSQL* mysql_init_override(MYSQL* mysql, const char* file, int line); +int mysql_query_override(MYSQL* mysql, const char* query, const char* file, int line); +MYSQL_RES* mysql_store_result_override(MYSQL* mysql, const char* file, int line); +void mysql_close_override(MYSQL* mysql, const char* file, int line); +MYSQL_STMT* mysql_stmt_init_override(MYSQL* mysql, const char* file, int line); +int mysql_stmt_prepare_override(MYSQL_STMT* stmt, const char* stmt_str, unsigned long length, const char* file, int line); +int mysql_stmt_execute_override(MYSQL_STMT* stmt, const char* file, int line); +int mysql_stmt_store_result_override(MYSQL_STMT* stmt, const char* file, int line); +my_bool mysql_stmt_close_override(MYSQL_STMT* stmt, const char* file, int line); + +#define mysql_init(mysql) mysql_init_override(mysql,__FILE__,__LINE__) +#define mysql_query(mysql,query) mysql_query_override(mysql,query,__FILE__,__LINE__) +#define mysql_store_result(mysql) mysql_store_result_override(mysql,__FILE__,__LINE__) +#define mysql_close(mysql) mysql_close_override(mysql,__FILE__,__LINE__) +#define mysql_stmt_init(mysql) mysql_stmt_init_override(mysql,__FILE__,__LINE__) +#define mysql_stmt_prepare(stmt,stmt_str,length) mysql_stmt_prepare_override(stmt,stmt_str,length,__FILE__,__LINE__) +#define mysql_stmt_execute(stmt) mysql_stmt_execute_override(stmt,__FILE__,__LINE__) +#define mysql_stmt_store_result(stmt) mysql_stmt_store_result_override(stmt,__FILE__,__LINE__) +#define mysql_stmt_close(stmt) mysql_stmt_close_override(stmt,__FILE__,__LINE__) +#endif + inline std::string get_formatted_time() { time_t __timer; char __buffer[30]; diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index d401704d2d..5a4d57bc0c 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -207,28 +207,28 @@ setparser_test3: setparser_test3.cpp $(TAP_LIBDIR)/libtap.a $(LDIR)/set_parser.c CUSTOMARGS=-DGITVERSION=\"$(GIT_VERSION)\" -I$(SQLITE3_DIR) -I$(IDIR) -I$(CURL_IDIR) -I$(JSON_IDIR) -I../tap -L$(TAP_LIBDIR) -L$(CURL_LDIR) -Wl,-Bstatic -lcurl -Wl,-Bdynamic -ltap -lcpp_dotenv -lpthread -std=c++11 -lz -ldl reg_test_3504-change_user_libmariadb_helper: reg_test_3504-change_user_helper.cpp $(TAP_LIBDIR)/libtap.so - $(CXX) -DDEBUG reg_test_3504-change_user_helper.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) $(MYLIBS) -std=c++11 $(STATIC_LIBS) -o reg_test_3504-change_user_libmariadb_helper -DGITVERSION=\"$(GIT_VERSION)\" + $(CXX) -DDISABLE_WARNING_COUNT_LOGGING -DDEBUG reg_test_3504-change_user_helper.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) $(MYLIBS) -std=c++11 $(STATIC_LIBS) -o reg_test_3504-change_user_libmariadb_helper -DGITVERSION=\"$(GIT_VERSION)\" reg_test_3504-change_user_libmysql_helper: reg_test_3504-change_user_helper.cpp - $(CXX) -DLIBMYSQL_HELPER reg_test_3504-change_user_helper.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o reg_test_3504-change_user_libmysql_helper + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING reg_test_3504-change_user_helper.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o reg_test_3504-change_user_libmysql_helper test_clickhouse_server_libmysql-t: test_clickhouse_server-t.cpp - $(CXX) -DLIBMYSQL_HELPER -DDEBUG test_clickhouse_server-t.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o test_clickhouse_server_libmysql-t + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING -DDEBUG test_clickhouse_server-t.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o test_clickhouse_server_libmysql-t reg_test_stmt_resultset_err_no_rows_libmysql-t: reg_test_stmt_resultset_err_no_rows-t.cpp - $(CXX) -DLIBMYSQL_HELPER reg_test_stmt_resultset_err_no_rows-t.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o reg_test_stmt_resultset_err_no_rows_libmysql-t + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING reg_test_stmt_resultset_err_no_rows-t.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o reg_test_stmt_resultset_err_no_rows_libmysql-t reg_test_mariadb_stmt_store_result_libmysql-t: reg_test_mariadb_stmt_store_result-t.cpp $(TAP_LIBDIR)/libtap.a - $(CXX) -DLIBMYSQL_HELPER reg_test_mariadb_stmt_store_result-t.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o reg_test_mariadb_stmt_store_result_libmysql-t + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING reg_test_mariadb_stmt_store_result-t.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o reg_test_mariadb_stmt_store_result_libmysql-t reg_test_mariadb_stmt_store_result_async-t: reg_test_mariadb_stmt_store_result-t.cpp $(TAP_LIBDIR)/libtap.a $(CXX) -DASYNC_API reg_test_mariadb_stmt_store_result-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) $(MYLIBS) -lpthread -ldl -std=c++11 -ltap $(STATIC_LIBS) -o reg_test_mariadb_stmt_store_result_async-t prepare_statement_err3024_libmysql-t: prepare_statement_err3024-t.cpp $(TAP_LIBDIR)/libtap.a - $(CXX) -DLIBMYSQL_HELPER prepare_statement_err3024-t.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o prepare_statement_err3024_libmysql-t + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING prepare_statement_err3024-t.cpp -I/usr/include/mysql $(CUSTOMARGS) -lmysqlclient -o prepare_statement_err3024_libmysql-t prepare_statement_err3024_async-t: prepare_statement_err3024-t.cpp $(TAP_LIBDIR)/libtap.a $(CXX) -DASYNC_API prepare_statement_err3024-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) $(MYLIBS) -lpthread -ldl -std=c++11 -ltap $(STATIC_LIBS) -o prepare_statement_err3024_async-t -DGITVERSION=\"$(GIT_VERSION)\" test_wexecvp_syscall_failures-t: test_wexecvp_syscall_failures-t.cpp $(TAP_LIBDIR)/libtap.a - $(CXX) $^ $(INCLUDEDIRS) $(LDIRS) $(OPT) $(MYLIBS) -std=c++11 -Wl,--wrap=pipe,--wrap=fcntl,--wrap=read,--wrap=poll -lpthread -ldl -ltap $(STATIC_LIBS) -o $@ + $(CXX) test_wexecvp_syscall_failures-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) $(MYLIBS) -std=c++11 -Wl,--wrap=pipe,--wrap=fcntl,--wrap=read,--wrap=poll -lpthread -ldl -ltap $(STATIC_LIBS) -o $@ diff --git a/test/tap/tests/README.md b/test/tap/tests/README.md new file mode 100644 index 0000000000..a34fe9b621 --- /dev/null +++ b/test/tap/tests/README.md @@ -0,0 +1,13 @@ +## Warning Count Logging in ProxySQL TAP Tests + +With the exception of a few, all TAP tests are now geared up to log warning count and the query that triggered the warning during its execution. + +## Working + +The method for extracting both the warning count and the associated query in all TAP tests involves overriding of specific APIs of MariaDB client library. This method facilitates the seamless extraction of both the warning count and the query. + +## Default Settings + +By default, the logging of both the warning count and the associated query is activated for all TAP tests. + +However, there are specific tests where logging is intentionally disabled. If needed, you have the flexibility to disable the logging by defining the preprocessor directive 'DISABLE_WARNING_COUNT_LOGGING'. diff --git a/test/tap/tests/reg_test_3427-stmt_first_comment1-t.cpp b/test/tap/tests/reg_test_3427-stmt_first_comment1-t.cpp index 2e2e505374..c4b6f9e077 100644 --- a/test/tap/tests/reg_test_3427-stmt_first_comment1-t.cpp +++ b/test/tap/tests/reg_test_3427-stmt_first_comment1-t.cpp @@ -175,7 +175,6 @@ int main(int argc, char** argv) { ok(rc == 0, "mysql_stmt_prepare() succeeded"); if (rc) { diag("mysql_stmt_prepare at line %d failed: %s", __LINE__ , mysql_error(proxysql_mysql)); - mysql_close(proxysql_mysql); res = EXIT_FAILURE; goto exit; } else { diff --git a/test/tap/tests/set_testing-240-t.cpp b/test/tap/tests/set_testing-240-t.cpp index 6d8dc01160..bf9056dd0e 100644 --- a/test/tap/tests/set_testing-240-t.cpp +++ b/test/tap/tests/set_testing-240-t.cpp @@ -39,6 +39,7 @@ int queries_per_connections=10; //unsigned int num_threads=5; unsigned int num_threads=20; int count=20; +int total_conn_having_client_deprecate_eof_support = (count * 0.2); // 20% of connections will have CLIENT_DEPRECATE_EOF flag enabled char *username=NULL; char *password=NULL; char *host=(char *)"localhost"; @@ -137,8 +138,14 @@ void * my_conn_thread(void *arg) { if (mysql==NULL) { exit(EXIT_FAILURE); } + + if (i < total_conn_having_client_deprecate_eof_support) { + // enable 'CLIENT_DEPRECATE_EOF' support + mysql->options.client_flag |= CLIENT_DEPRECATE_EOF; + } int port = local ? 0 : ( cl.port + rand()%multiport ); MYSQL *rc=mysql_real_connect(mysql, cl.host, cl.username, cl.password, schema, port, NULL, 0); + if (rc==NULL) { if (silent==0) { fprintf(stderr,"Error while connecting on %s:%d : %s\n", cl.host , port , mysql_error(mysql)); diff --git a/test/tap/tests/test_cluster_sync-t.cpp b/test/tap/tests/test_cluster_sync-t.cpp index c0985e0a70..3d7128ca54 100644 --- a/test/tap/tests/test_cluster_sync-t.cpp +++ b/test/tap/tests/test_cluster_sync-t.cpp @@ -1234,7 +1234,7 @@ int main(int, char**) { std::make_tuple(18, 2, -1, 20, "SET sql_mode = \"\"", 0, 0, 100, "", "", ""), std::make_tuple(19, 2, -1, 20, "SET sql_mode = \"\"", 0, 0, 100, "{}", "{}", "{}"), std::make_tuple(20, 0, 0, 30, "SET long_query_time = 0", 1, 0, 123, "{\"session_variables\":[\"tmp_table_size\",\"join_buffer_size\"]}", "", ""), - std::make_tuple(21, 2, -1, 50, "SET sql_mode = \"\"", 1, 0, 125, "{\"session_variables\":[\"tmp_table_size\",\"join_buffer_size\"]}", "{}", ""), + std::make_tuple(21, 2, -1, 50, "SET sql_mode = \"\"", 1, 0, 125, "{\"session_variables\":[\"tmp_table_size\",\"join_buffer_size\"]}", "{\"handle_warnings\":1}", ""), std::make_tuple(22, 3, -1, 40, "SET sql_mode = \"\"", 1, 0, 124, "{\"session_variables\":[\"tmp_table_size\",\"join_buffer_size\"]}", "", "{\"weight\": 100, \"max_connections\": 1000}") }; std::vector insert_mysql_hostgroup_attributes_queries{}; @@ -2299,6 +2299,8 @@ int main(int, char**) { std::make_tuple("mysql-auto_increment_delay_multiplex" , "6" ), std::make_tuple("mysql-long_query_time" , "1001" ), // here std::make_tuple("mysql-query_cache_size_MB" , "256" ), + std::make_tuple("mysql-query_cache_handle_warnings" , "1" ), + std::make_tuple("mysql-handle_warnings" , "1" ), std::make_tuple("mysql-poll_timeout_on_failure" , "100" ), std::make_tuple("mysql-keep_multiplexing_variables" , "tx_isolation,version" ), std::make_tuple("mysql-kill_backend_connection_when_disconnect" , "true" ), diff --git a/test/tap/tests/test_mysql_hostgroup_attributes-1-t.cpp b/test/tap/tests/test_mysql_hostgroup_attributes-1-t.cpp index 8f90fdf712..31adbb1f4d 100644 --- a/test/tap/tests/test_mysql_hostgroup_attributes-1-t.cpp +++ b/test/tap/tests/test_mysql_hostgroup_attributes-1-t.cpp @@ -79,12 +79,12 @@ int main(int argc, char** argv) { "(20,3,-1,40,'SET sql_mode=\"\"',1,0,124,'{\"session_variables\":[\"tmp_table_size\",\"join_buffer_size\"]}','','{\"weight\": 100, \"max_connections\": 1000}','servers defaults')" }, { - "0x1A6E38CBB2E44672", + "0xF33858C81FDA7372", "INSERT INTO mysql_hostgroup_attributes VALUES (19,1,1,10,'',1,1,10000,'','','','')," "(18,2,-1,20,'SET sql_mode=\"\"',0,0,100,'','','','hello world')," "(17,0,0,30,'SET long_query_time=0',1,0,123,'{\"session_variables\":[\"tmp_table_size\",\"join_buffer_size\"]}','','','filtering variables')," "(20,3,-1,40,'SET sql_mode=\"\"',1,0,124,'{\"session_variables\":[\"tmp_table_size\",\"join_buffer_size\"]}','','{\"weight\": 100, \"max_connections\": 1000}','servers defaults')," - "(21,2,-1,50,'SET sql_mode=\"\"',1,0,125,'{\"session_variables\":[\"tmp_table_size\",\"join_buffer_size\"]}','{}','{\"weight\": 100, \"max_connections\": 1000}','hostgroup settings')" + "(21,2,-1,50,'SET sql_mode=\"\"',1,0,125,'{\"session_variables\":[\"tmp_table_size\",\"join_buffer_size\"]}','{\"handle_warnings\": 1}','{\"weight\": 100, \"max_connections\": 1000}','hostgroup settings')" } }; diff --git a/test/tap/tests/test_warnings-t.cpp b/test/tap/tests/test_warnings-t.cpp new file mode 100644 index 0000000000..65ed4c76d7 --- /dev/null +++ b/test/tap/tests/test_warnings-t.cpp @@ -0,0 +1,562 @@ +/** + * @file test-warnings-t.cpp + * @brief This test will test warnings support in ProxySQL + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "json.hpp" +#include +#include +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +using LEVEL = std::string; +using CODE = int; +using MESSAGE = std::string; + +#define MYSQL_QUERY__(mysql, query) \ + do { \ + if (mysql_query(mysql, query)) { \ + fprintf(stderr, "File %s, line %d, Error: %s\n", \ + __FILE__, __LINE__, mysql_error(mysql)); \ + goto cleanup; \ + } \ + } while(0) + +#define MYSQL_CLEAR_RESULT(mysql) mysql_free_result(mysql_store_result(mysql)); +#define MYSQL_CLEAR_STMT_RESULT(stmt) mysql_stmt_store_result(stmt); \ + mysql_stmt_free_result(stmt); + +#define INIT_QUERY_TEXT(QUERY, IS_SELECT) {QUERY, IS_SELECT, false} +#define INIT_QUERY_PREPARE_STMT(QUERY, IS_SELECT) {QUERY, IS_SELECT, true} + +enum MultiplexStatus { + kNotApplicable = 0, + kMultiplexingDisabled = (1 << 0), + kMultiplexingEnabled = (1 << 1), + kHasWarnings = (1 << 2), + kUserVariables = (1 << 3) +}; + +enum ConnectionType { + kAdmin = 0, + kMySQL = 1 +}; + +enum class WarningCheckType { + kNotApplicable = 0, + kConnection = (1 << 0), + kCountQuery = (1 << 1), + kShowWarnings = (1 << 2), + kAll = (kConnection | kCountQuery | kShowWarnings) +}; + +struct QueryInfo { + const char* query; + bool is_select; + bool prepare_stmt; +}; + +struct WarningCheckInfo { + WarningCheckType type; + int warning_count; + std::vector warning_codes; +}; + +struct Connection { + ConnectionType conn_type; + size_t id; +}; + +struct TestInfo { + Connection conn; + QueryInfo query_info; + WarningCheckInfo warning_check_info; + int multiplex_status; +}; + +#define MYSQL_CONN_DEFAULT {ConnectionType::kMySQL, 0} +#define ADMIN_CONN_DEFAULT {ConnectionType::kAdmin, 0} +#define MYSQL_CONN(ID) {ConnectionType::kMySQL, ID} +#define ADMIN_CONN(ID) {ConnectionType::kAdmin, ID} + +CommandLine cl; +std::array,2> conn_pool; + +MYSQL* get_connection(const Connection& conn, bool enable_client_deprecate_eof) { + auto& my_conn = conn_pool[conn.conn_type]; + const auto& itr = my_conn.find(conn.id); + if (itr != my_conn.end()) { + return itr->second; + } + // Initialize connection + MYSQL* proxysql = mysql_init(NULL); + if (!proxysql) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql)); + return NULL; + } + + if (enable_client_deprecate_eof) { + // enable 'CLIENT_DEPRECATE_EOF' support + proxysql->options.client_flag |= CLIENT_DEPRECATE_EOF; + } + + if (conn.conn_type == kAdmin) { + // Connnect to ProxySQL + if (!mysql_real_connect(proxysql, 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)); + return NULL; + } + } else if (conn.conn_type == kMySQL) { + // Connect to ProxySQL + if (!mysql_real_connect(proxysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql)); + return NULL; + } + } + my_conn[conn.id] = proxysql; + return proxysql; +} + +void parse_result_json_column(MYSQL_RES* result, nlohmann::json& j) { + if (!result) return; + while (MYSQL_ROW row = mysql_fetch_row(result)) { + j = nlohmann::json::parse(row[0]); + } +} + +int execute_query(MYSQL* proxysql, const QueryInfo& query_info) { + MYSQL_QUERY(proxysql, query_info.query); + if (query_info.is_select) { + MYSQL_CLEAR_RESULT(proxysql); + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + return EXIT_SUCCESS; +} + +int prepare_and_execute_stmt(MYSQL* mysql, const QueryInfo& query_info, MYSQL_STMT** stmt_out) { + assert(stmt_out); + MYSQL_STMT* stmt = mysql_stmt_init(mysql); + if (!stmt) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); + return EXIT_FAILURE; + } + if (mysql_stmt_prepare(stmt, query_info.query, strlen(query_info.query))) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_stmt_error(stmt)); + mysql_stmt_close(stmt); + return EXIT_FAILURE; + } + if (mysql_stmt_execute(stmt) != 0) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_stmt_error(stmt)); + mysql_stmt_close(stmt); + return EXIT_FAILURE; + } + if (query_info.is_select) { + MYSQL_CLEAR_STMT_RESULT(stmt); + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + *stmt_out = stmt; + return EXIT_SUCCESS; +} + +// get warning count from MySQL connection (MYSQL::warning_count) +int get_warnings_count_from_connection(MYSQL* mysql) { + return mysql_warning_count(mysql); +} + +// get warning count from statement (MYSQL_STMT::mysql_upsert_status::warning_count) +int get_warnings_count_from_statement(MYSQL_STMT* stmt) { + return mysql_stmt_warning_count(stmt); +} + +// retrieve warning count through a query. This action does not clear the warning message list. +int get_warnings_count(MYSQL* mysql) { + MYSQL_QUERY(mysql, "SHOW COUNT(*) WARNINGS"); + MYSQL_RES* mysql_result = mysql_use_result(mysql); + if (!mysql_result) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); + return -1; + } + MYSQL_ROW row = mysql_fetch_row(mysql_result); + const int warning_count = atoi(row[0]); + if (mysql_result) { + mysql_free_result(mysql_result); + mysql_result = nullptr; + } + return warning_count; +} + +// retrieve warning message list. This action does not clear the warning message list. +int get_warnings(MYSQL* mysql, std::list>& warning_list) { + MYSQL_QUERY(mysql, "SHOW WARNINGS"); + MYSQL_RES* mysql_result = mysql_use_result(mysql); + unsigned long fetched_row_count = 0; + while (MYSQL_ROW row = mysql_fetch_row(mysql_result)) { + fetched_row_count++; + warning_list.emplace_back(std::make_tuple(std::string(row[0]),atoi(row[1]),std::string(row[2]))); + } + if (mysql_result) { + mysql_free_result(mysql_result); + mysql_result = nullptr; + } + return fetched_row_count; +} + +// check multiplexing status +int check_proxysql_internal_session(MYSQL* proxysql, int exp_status) { + nlohmann::json j_status{}; + MYSQL_RES* res = nullptr; + int status{}; + + bool found_backend = false; + + MYSQL_QUERY(proxysql, "PROXYSQL INTERNAL SESSION"); + res = mysql_store_result(proxysql); + parse_result_json_column(res, j_status); + mysql_free_result(res); + + + if (j_status.contains("backends")) { + for (auto& backend : j_status["backends"]) { + if (backend != nullptr && backend.contains("conn")) { + found_backend = true; + + if (backend["conn"]["MultiplexDisabled"] == true) { + status |= MultiplexStatus::kMultiplexingEnabled; + } + + if (backend["conn"]["status"]["has_warnings"] == true && + backend["conn"]["warning_count"] > 0 && + j_status["warning_in_hg"] != -1) { + status |= MultiplexStatus::kHasWarnings; + } + + if (backend["conn"]["status"]["user_variable"] == true) { + status |= MultiplexStatus::kUserVariables; + } + } + } + } + + if (found_backend == false) { + status |= MultiplexStatus::kMultiplexingDisabled; + } + + ok(status == exp_status, "Multiplex status matches. Expected status:'%d' Actual status:'%d'", exp_status, status); + + return EXIT_SUCCESS; +} + +const std::vector mysql_variable_test = { + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("DELETE FROM mysql_query_rules", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL QUERY RULES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("DELETE FROM mysql_hostgroup_attributes", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL SERVERS TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-handle_warnings=0", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1/0", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("DO 1/0", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-handle_warnings=1", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1" , true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1/0", false), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1" , true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("DO 1/0", false), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("DO 1", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) } +}; + +const std::vector hostgroup_attributes_test = { + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-handle_warnings=1", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("DELETE FROM mysql_hostgroup_attributes", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("INSERT INTO mysql_hostgroup_attributes (hostgroup_id, hostgroup_settings) VALUES (0, '{\"handle_warnings\":0}')", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL SERVERS TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + // Hostgroup attributes take precedence and should override the global variable value for the specified hostgroup. + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1/0", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled)}, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled)}, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("DO 1/0", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled)}, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled)}, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("DO 1", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-handle_warnings=0", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("DELETE FROM mysql_hostgroup_attributes", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("INSERT INTO mysql_hostgroup_attributes (hostgroup_id, hostgroup_settings) VALUES (0, '{\"handle_warnings\":1}')", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL SERVERS TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1/0", false), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("DO 1/0", false), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) } +}; + +const std::vector random_test = { + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1" , true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1/0", false), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1" , false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SET character_set_database='latin1'", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1" , true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1" , true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("DO 1/0", false), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("DO 1" , false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SET character_set_database='latin2'", false), {WarningCheckType::kAll, 1, {1681}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1" , true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) } +}; + +const std::vector insert_test = { + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SET sql_mode='ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DROP DATABASE IF EXISTS testdb", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("CREATE DATABASE testdb", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("CREATE TABLE testdb.t1 (a TINYINT NOT NULL, b CHAR(4))", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("INSERT INTO testdb.t1 VALUES(10, 'mysql'), (NULL, 'test'), (300, 'xyz')", false), {WarningCheckType::kAll, 3, {1265,1048,1264}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("INSERT INTO testdb.t1 VALUES(10, 'mysql'), (NULL, 'test'), (300, 'xyz')", false), {WarningCheckType::kAll, 3, {1265,1048,1264}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DROP DATABASE IF EXISTS testdb", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kMultiplexingDisabled) } +}; + +const std::vector query_cache_test = { + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-query_cache_handle_warnings=0", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("DELETE FROM mysql_query_rules", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("INSERT INTO mysql_query_rules (rule_id,active,match_digest,cache_ttl,apply) VALUES (1,1,'SELECT ?/?',60000,1)", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL QUERY RULES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("PROXYSQL FLUSH QUERY CACHE", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + // this entry should not be saved in cache + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + // to check if prepare statement conflicts with cache + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + // { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-query_cache_handle_warnings=1", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + // resultset will be retrived from cache, with warning count zero + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + // to check if prepare statement conflicts with cache + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("DELETE FROM mysql_query_rules", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL QUERY RULES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("PROXYSQL FLUSH QUERY CACHE", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) } +}; + +const std::vector query_digest_test = { + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-query_digests='false'", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1/0", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("DO 1/0", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-query_digests='true'", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1/0", false), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) } +}; + +const std::vector warning_log_test = { + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-log_mysql_warnings_enabled='true'", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1/0", false), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("DO 1", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_PREPARE_STMT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingDisabled) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("SET mysql-log_mysql_warnings_enabled='false'", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) }, + { ADMIN_CONN_DEFAULT, INIT_QUERY_TEXT("LOAD MYSQL VARIABLES TO RUNTIME", false), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kNotApplicable) } +}; + +const std::vector multiplexing_test = { + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT @@sql_mode", true), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables | MultiplexStatus::kHasWarnings) }, + { MYSQL_CONN_DEFAULT, INIT_QUERY_TEXT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables) }, + { MYSQL_CONN(1), INIT_QUERY_TEXT("SELECT @@sql_mode", true), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables)}, + { MYSQL_CONN(1), INIT_QUERY_TEXT("DO 1/0", false), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables | MultiplexStatus::kHasWarnings)}, + { MYSQL_CONN(1), INIT_QUERY_TEXT("DO 1", false), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables)}, + { MYSQL_CONN(2), INIT_QUERY_PREPARE_STMT("SELECT @@sql_mode", true), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables)}, + { MYSQL_CONN(2), INIT_QUERY_PREPARE_STMT("SELECT 1/0", true), {WarningCheckType::kAll, 1, {1365}}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables | MultiplexStatus::kHasWarnings)}, + { MYSQL_CONN(2), INIT_QUERY_PREPARE_STMT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables) }, + { MYSQL_CONN(3), INIT_QUERY_PREPARE_STMT("SET @test_variable = 44", true), {WarningCheckType::kNotApplicable}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables)}, + { MYSQL_CONN(3), INIT_QUERY_PREPARE_STMT("SELECT 1", true), {WarningCheckType::kAll, 0}, (MultiplexStatus::kMultiplexingEnabled | MultiplexStatus::kUserVariables) } +}; + +#define IS_BIT_MASK_SET(variable,flag) ((variable & static_cast(flag)) == static_cast(flag)) + +// base case +size_t check_count() { return 0; } + +template +size_t check_count(First&& first, Rest&&... rest) { + + size_t count = 0; + + for (const auto& val : first) { + if (val.warning_check_info.type != WarningCheckType::kNotApplicable) { + if (val.warning_check_info.type == WarningCheckType::kAll) + count += 3; + else + count += 1; + count += val.warning_check_info.warning_codes.size(); + } + if (val.multiplex_status != 0) + count += 1; + } + return (count + check_count(rest...)); +} + +template +constexpr size_t test_size(Args&&... args) { + return sizeof...(args); +} + +#define TESTS_COMBINED mysql_variable_test, hostgroup_attributes_test, random_test, insert_test, query_digest_test, \ + query_cache_test, warning_log_test, multiplexing_test + +void execute_tests(const std::vector>>& all_tests, bool enable_client_deprecate_eof) { + for (const auto& test : all_tests) { + diag("Executing [%s] test... [CLIENT_DEPRECATE_EOF=%s]", test.first, (enable_client_deprecate_eof ? "TRUE" : "FALSE")); + for (const auto& test_info : test.second) { + MYSQL_STMT* stmt = nullptr; + MYSQL* mysql = get_connection(test_info.conn, enable_client_deprecate_eof); + if (!mysql) { + goto __exit; + } + if (test_info.query_info.prepare_stmt) { + if (prepare_and_execute_stmt(mysql, test_info.query_info, &stmt) == EXIT_FAILURE) + goto __exit; + } else { + if (execute_query(mysql, test_info.query_info) == EXIT_FAILURE) + goto __exit; + } + + const int check_type = static_cast(test_info.warning_check_info.type); + + if (IS_BIT_MASK_SET(check_type, WarningCheckType::kConnection)) { + int count = get_warnings_count_from_connection(mysql); + if (test_info.query_info.prepare_stmt) { + count &= get_warnings_count_from_statement(stmt); + } + ok((count == test_info.warning_check_info.warning_count), "Connection warning count should match. Expected count:'%d' Actual count:'%d'", test_info.warning_check_info.warning_count, count); + } + if (IS_BIT_MASK_SET(check_type, WarningCheckType::kCountQuery)) { + const int count = get_warnings_count(mysql); + ok((count == test_info.warning_check_info.warning_count), "Warnings count via query should match. Expected count:'%d' Actual count:'%d'", test_info.warning_check_info.warning_count, count); + } + if (IS_BIT_MASK_SET(check_type, WarningCheckType::kShowWarnings)) { + std::list> warnings_list; + + const int count = get_warnings(mysql, warnings_list); + ok((count == test_info.warning_check_info.warning_count), "Fetched warning messages count should match. Expected count:'%d' Actual count:'%d'", test_info.warning_check_info.warning_count, count); + + if (test_info.warning_check_info.warning_codes.empty() == false) { + for (const auto& warnings : warnings_list) { + const int exp_code = std::get<1>(warnings); + bool match_found = false; + for (const int code : test_info.warning_check_info.warning_codes) { + if (exp_code == code) { + match_found = true; + break; + } + } + ok(match_found, "Warning code '%d' should match", exp_code); + } + } + } + + if (test_info.multiplex_status != MultiplexStatus::kNotApplicable) { + if (check_proxysql_internal_session(mysql, test_info.multiplex_status) != EXIT_SUCCESS) { + if (stmt) + mysql_stmt_close(stmt); + goto __exit; + } + } + + if (stmt) + mysql_stmt_close(stmt); + } + } + +__exit: + for (const auto& mysql_conn : conn_pool[kAdmin]) { + mysql_close(mysql_conn.second); + } + conn_pool[kAdmin].clear(); + for (const auto& mysql_conn : conn_pool[kMySQL]) { + mysql_close(mysql_conn.second); + } + conn_pool[kMySQL].clear(); +} + +int main(int argc, char** argv) { + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return -1; + } + + plan(check_count(TESTS_COMBINED)*2); // also check with client_deprecate_eof flag + + /*plan((20 + 6) + // mysql variable test: 20 warning checks, 6 multiplex status checks + (20 + 6) + // hostgroup attributes test: 20 warning checks, 6 multiplex status checks + (14 + 4) + // random test: 14 warning checks, 4 multiplex status checks + (9 + 4) + // insert test: 9 warning checks, 4 multiplex status checks + (3 + 1) + // query digest test: 3 warning checks, 1 multiplex status checks + (18 + 5) + // query cache test: 18 warning checks, 5 multiplex status checks + (7 + 2) + // warning log test: 7 warning checks, 2 multiplex status checks + (7 + 3)); // multiplexing test: 7 warning checks, 3 multiplex status checks + */ + + std::vector>> all_tests(test_size(TESTS_COMBINED)); + + all_tests[0].first = "MYSQL VARIABLE (mysql-handle_warnings)"; + all_tests[0].second.insert(all_tests[0].second.end(), mysql_variable_test.begin(), mysql_variable_test.end()); + + all_tests[1].first = "HOSTGROUP ATTRIBUTES (handle_warnings)"; + all_tests[1].second.insert(all_tests[1].second.end(), hostgroup_attributes_test.begin(), hostgroup_attributes_test.end()); + + all_tests[2].first = "RANDOM"; + all_tests[2].second.insert(all_tests[2].second.end(), random_test.begin(), random_test.end()); + + all_tests[3].first = "INSERT"; + all_tests[3].second.insert(all_tests[3].second.end(), insert_test.begin(), insert_test.end()); + + all_tests[4].first = "QUERY_DIGEST"; + all_tests[4].second.insert(all_tests[4].second.end(), query_digest_test.begin(), query_digest_test.end()); + + all_tests[5].first = "QUERY_CACHE"; + all_tests[5].second.insert(all_tests[5].second.end(), query_cache_test.begin(), query_cache_test.end()); + + all_tests[6].first = "WARNING_LOGS"; + all_tests[6].second.insert(all_tests[6].second.end(), warning_log_test.begin(), warning_log_test.end()); + + all_tests[7].first = "MULTIPLEXING"; + all_tests[7].second.insert(all_tests[7].second.end(), multiplexing_test.begin(), multiplexing_test.end()); + + execute_tests(all_tests, false); + execute_tests(all_tests, true); + + return exit_status(); +}