diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index 661e4f968..8d52046d6 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -128,17 +128,13 @@ class MySQL_Session bool handler_again___status_SETTING_CHARACTER_SET_RESULTS(int *); bool handler_again___status_SETTING_SESSION_TRACK_GTIDS(int *); bool handler_again___status_SETTING_MULTI_STMT(int *_rc); - bool handler_again___status_SETTING_CHARSET(int *_rc); bool handler_again___status_SETTING_SQL_AUTO_IS_NULL(int *); - bool handler_again___status_SETTING_SQL_SELECT_LIMIT(int *); - bool handler_again___status_SETTING_SQL_SAFE_UPDATES(int *); bool handler_again___status_SETTING_COLLATION_CONNECTION(int *); bool handler_again___status_SETTING_NET_WRITE_TIMEOUT(int *); bool handler_again___status_SETTING_MAX_JOIN_SIZE(int *); bool handler_again___status_CHANGING_SCHEMA(int *); bool handler_again___status_CONNECTING_SERVER(int *); bool handler_again___status_CHANGING_USER_SERVER(int *); - bool handler_again___status_CHANGING_CHARSET(int *); bool handler_again___status_CHANGING_AUTOCOMMIT(int *); void init(); void reset(); @@ -147,6 +143,7 @@ class MySQL_Session public: bool handler_again___status_SETTING_GENERIC_VARIABLE(int *_rc, const char *var_name, const char *var_value, bool no_quote=false, bool set_transaction=false); + bool handler_again___status_CHANGING_CHARSET(int *); std::stack previous_status; void * operator new(size_t); void operator delete(void *); diff --git a/include/MySQL_Thread.h b/include/MySQL_Thread.h index 8b0d4fc30..116305d63 100644 --- a/include/MySQL_Thread.h +++ b/include/MySQL_Thread.h @@ -400,7 +400,7 @@ class MySQL_Threads_Handler void wrunlock(); void commit(); char *get_variable(char *name); - bool set_variable(char *name, char *value); + bool set_variable(char *name, const char *value); char **get_variables_list(); bool has_variable(const char * name); diff --git a/include/MySQL_Variables.h b/include/MySQL_Variables.h index e8d1f951c..0ec9d4f28 100644 --- a/include/MySQL_Variables.h +++ b/include/MySQL_Variables.h @@ -14,6 +14,7 @@ class Updater { public: virtual bool verify_variables(MySQL_Session* session, int idx) = 0; virtual bool update_server_variable(MySQL_Session* session, int idx, int &_rc) = 0; + virtual ~Updater(); }; class Generic_Updater : public Updater { @@ -35,7 +36,7 @@ public: MySQL_Variables(MySQL_Session* session); virtual ~MySQL_Variables(); - void client_set_value(int idx, const char* value); + void client_set_value(int idx, const std::string& value); const char* client_get_value(int idx); uint32_t client_get_hash(int idx); diff --git a/include/mysql_connection.h b/include/mysql_connection.h index 590331eaa..331f90f1f 100644 --- a/include/mysql_connection.h +++ b/include/mysql_connection.h @@ -21,7 +21,7 @@ using json = nlohmann::json; class Variable { public: - char *value; + char *value = (char*)""; uint32_t hash; void fill_server_internal_session(json &j, int conn_num, int idx); void fill_client_internal_session(json &j, int idx); @@ -32,7 +32,8 @@ public: enum charset_action { UNKNOWN, NAMES, - CHARSET + CHARSET, + CONNECT_START }; class MySQL_Connection_userinfo { @@ -78,8 +79,6 @@ class MySQL_Connection { char *ldap_user_variable_value; bool ldap_user_variable_sent; uint8_t protocol_version; - unsigned int charset; - enum charset_action charset_action; uint8_t sql_log_bin; int8_t last_set_autocommit; bool autocommit; diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index d7df6b25d..8a0ff521c 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -147,11 +147,17 @@ enum MySQL_DS_type { enum variable_name { + SQL_CHARACTER_SET, + SQL_CHARACTER_ACTION, + SQL_SET_NAMES, SQL_SAFE_UPDATES, SQL_SELECT_LIMIT, SQL_SQL_MODE, SQL_TIME_ZONE, SQL_CHARACTER_SET_RESULTS, + SQL_CHARACTER_SET_CONNECTION, + SQL_CHARACTER_SET_CLIENT, + SQL_CHARACTER_SET_DATABASE, SQL_ISOLATION_LEVEL, SQL_TRANSACTION_READ, SQL_SESSION_TRACK_GTIDS, @@ -185,6 +191,9 @@ enum session_status { SETTING_ISOLATION_LEVEL, SETTING_TRANSACTION_READ, SETTING_CHARACTER_SET_RESULTS, + SETTING_CHARACTER_SET_CONNECTION, + SETTING_CHARACTER_SET_CLIENT, + SETTING_CHARACTER_SET_DATABASE, SETTING_SESSION_TRACK_GTIDS, SETTING_SQL_AUTO_IS_NULL, SETTING_SQL_SELECT_LIMIT, @@ -198,6 +207,7 @@ enum session_status { PROCESSING_STMT_EXECUTE, SETTING_VARIABLE, SETTING_MULTIPLE_VARIABLES, + SETTING_SET_NAMES, NONE }; @@ -724,7 +734,6 @@ __thread int mysql_thread___set_query_lock_on_hostgroup; __thread int mysql_thread___reset_connection_algorithm; __thread uint32_t mysql_thread___server_capabilities; __thread int mysql_thread___auto_increment_delay_multiplex; -__thread unsigned int mysql_thread___default_charset; __thread unsigned int mysql_thread___handle_unknown_charset; __thread int mysql_thread___poll_timeout; __thread int mysql_thread___poll_timeout_on_failure; @@ -869,7 +878,6 @@ extern __thread int mysql_thread___set_query_lock_on_hostgroup; extern __thread int mysql_thread___reset_connection_algorithm; extern __thread uint32_t mysql_thread___server_capabilities; extern __thread int mysql_thread___auto_increment_delay_multiplex; -extern __thread unsigned int mysql_thread___default_charset; extern __thread unsigned int mysql_thread___handle_unknown_charset; extern __thread int mysql_thread___poll_timeout; extern __thread int mysql_thread___poll_timeout_on_failure; @@ -967,17 +975,31 @@ extern __thread unsigned int g_seed; #ifndef MYSQL_TRACKED_VARIABLES #define MYSQL_TRACKED_VARIABLES #ifdef PROXYSQL_EXTERN +// field_1: index number +// field_2: what status should be changed after setting this variables +// field_3: if the variable needs to be quoted +// field_4: if related to SET TRANSACTION statement . if false , it will be execute "SET varname = varvalue" . If true, "SET varname varvalue" +// field_5: if true, some special handling is required +// field_6: what variable name (or string) will be used when setting it to backend +// field_7: variable name as displayed in admin , WITHOUT "default_" +// field_8: default value mysql_variable_st mysql_tracked_variables[] { - { SQL_SAFE_UPDATES, SETTING_SQL_SAFE_UPDATES , true, false, false, (char *)"sql_safe_update", (char *)"sql_safe_update", (char *)"OFF" } , - { SQL_SELECT_LIMIT, SETTING_SQL_SELECT_LIMIT , true, false, false, (char *)"sql_select_limit", (char *)"sql_select_limit", (char *)"DEFAULT" } , - { SQL_SQL_MODE, SETTING_SQL_MODE , false, false, false, (char *)"sql_mode" , (char *)"sql_mode" , (char *)"" } , - { SQL_TIME_ZONE, SETTING_TIME_ZONE , false, false, false, (char *)"time_zone", (char *)"time_zone", (char *)"SYSTEM" } , - { SQL_CHARACTER_SET_RESULTS, SETTING_CHARACTER_SET_RESULTS, true, false, false, (char *)"character_set_results", (char *)"character_set_results", (char *)"NULL" } , + { SQL_CHARACTER_SET, SETTING_CHARSET, false, true, false, (char *)"CHARSET", (char *)"CHARSET", (char *)"UTF8" } , // should be before SQL_CHARACTER_SET_RESULTS + { SQL_CHARACTER_ACTION, NONE, false, false, false, (char *)"action", (char *)"action", (char *)"1" } , + { SQL_SET_NAMES, SETTING_SET_NAMES, false, false, false, (char *)"names", (char *)"names", (char *)"DEFAULT" } , + { SQL_SAFE_UPDATES, SETTING_SQL_SAFE_UPDATES , true, false, false, (char *)"sql_safe_updates", (char *)"sql_safe_updates", (char *)"OFF" } , + { SQL_SELECT_LIMIT, SETTING_SQL_SELECT_LIMIT , false, false, false, (char *)"sql_select_limit", (char *)"sql_select_limit", (char *)"DEFAULT" } , + { SQL_SQL_MODE, SETTING_SQL_MODE , true, false, false, (char *)"sql_mode" , (char *)"sql_mode" , (char *)"" } , + { SQL_TIME_ZONE, SETTING_TIME_ZONE , true, false, false, (char *)"time_zone", (char *)"time_zone", (char *)"SYSTEM" } , + { SQL_CHARACTER_SET_RESULTS, SETTING_CHARACTER_SET_RESULTS, false, false, false, (char *)"character_set_results", (char *)"character_set_results", (char *)"UTF8" } , + { SQL_CHARACTER_SET_CONNECTION, SETTING_CHARACTER_SET_CONNECTION, false, false, false, (char *)"character_set_connection", (char *)"character_set_connection", (char *)"UTF8" } , + { SQL_CHARACTER_SET_CLIENT, SETTING_CHARACTER_SET_CLIENT, false, false, false, (char *)"character_set_client", (char *)"character_set_client", (char *)"UTF8" } , + { SQL_CHARACTER_SET_DATABASE, SETTING_CHARACTER_SET_DATABASE, false, false, false, (char *)"character_set_database", (char *)"character_set_database", (char *)"UTF8" } , { SQL_ISOLATION_LEVEL, SETTING_ISOLATION_LEVEL, false, true, true, (char *)"SESSION TRANSACTION ISOLATION LEVEL", (char *)"isolation_level", (char *)"READ COMMITTED" } , { SQL_TRANSACTION_READ, SETTING_TRANSACTION_READ, false, true, true, (char *)"SESSION TRANSACTION READ", (char *)"transaction_read", (char *)"WRITE" } , { SQL_SESSION_TRACK_GTIDS, SETTING_SESSION_TRACK_GTIDS, true, false, false, (char *)"session_track_gtids" , (char *)"session_track_gtids" , (char *)"OFF" } , { SQL_SQL_AUTO_IS_NULL, SETTING_SQL_AUTO_IS_NULL, true, false, false, (char *)"sql_auto_is_null", (char *)"sql_auto_is_null", (char *)"OFF" } , - { SQL_COLLATION_CONNECTION, SETTING_COLLATION_CONNECTION, true, false, false, (char *)"COLLATION_CONNECTION", (char *)"collation_connection", (char *)"" } , + { SQL_COLLATION_CONNECTION, SETTING_COLLATION_CONNECTION, true, false, false, (char *)"COLLATION_CONNECTION", (char *)"collation_connection", (char *)"utf8_general_ci" } , { SQL_NET_WRITE_TIMEOUT, SETTING_NET_WRITE_TIMEOUT, false, false, false, (char *)"NET_WRITE_TIMEOUT", (char *)"net_write_timeout", (char *)"60" } , { SQL_MAX_JOIN_SIZE, SETTING_MAX_JOIN_SIZE, false, false, false, (char *)"MAX_JOIN_SIZE", (char *)"max_join_size", (char *)"18446744073709551615" } , }; diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index 40a00b719..db363abac 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -6,6 +6,9 @@ #include "MySQL_Data_Stream.h" #include "MySQL_Authentication.hpp" #include "MySQL_LDAP_Authentication.hpp" +#include "MySQL_Variables.h" + +#include extern MySQL_Authentication *GloMyAuth; extern MySQL_LDAP_Authentication *GloMyLdapAuth; @@ -31,6 +34,7 @@ typedef uint8_t uchar; #endif extern const MARIADB_CHARSET_INFO * proxysql_find_charset_nr(unsigned int nr); +MARIADB_CHARSET_INFO * proxysql_find_charset_name(const char *name); #ifdef DEBUG static void __dump_pkt(const char *func, unsigned char *_ptr, unsigned int len) { @@ -1212,7 +1216,14 @@ bool MySQL_Protocol::generate_pkt_initial_handshake(bool send, void **ptr, unsig mysql_thread___server_capabilities |= CLIENT_MYSQL | CLIENT_PLUGIN_AUTH | CLIENT_RESERVED; (*myds)->myconn->options.server_capabilities=mysql_thread___server_capabilities; memcpy(_ptr+l,&mysql_thread___server_capabilities, sizeof(mysql_thread___server_capabilities)/2); l+=sizeof(mysql_thread___server_capabilities)/2; - uint8_t uint8_charset = mysql_thread___default_charset & 255; + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_name(mysql_thread___default_variables[SQL_CHARACTER_SET]); + if (!ci) { + proxy_error("Cannot find character set for name [%s]. Configuration error. Check [%s] global variable.\n", + mysql_thread___default_variables[SQL_CHARACTER_SET], mysql_tracked_variables[SQL_CHARACTER_SET].internal_variable_name); + assert(0); + } + uint8_t uint8_charset = ci->nr & 255; memcpy(_ptr+l,&uint8_charset, sizeof(uint8_charset)); l+=sizeof(uint8_charset); memcpy(_ptr+l,&server_status, sizeof(server_status)); l+=sizeof(server_status); memcpy(_ptr+l,"\x8f\x80\x15",3); l+=3; @@ -1558,7 +1569,13 @@ bool MySQL_Protocol::process_pkt_handshake_response(unsigned char *pkt, unsigned } // see bug #810 if (charset==0) { - charset=mysql_thread___default_charset; + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_name(mysql_thread___default_variables[SQL_CHARACTER_SET]); + if (!ci) { + proxy_error("Cannot find charset [%s]\n", mysql_thread___default_variables[SQL_CHARACTER_SET]); + assert(0); + } + charset=ci->nr; } (*myds)->tmp_charset=charset; pkt += 24; @@ -1952,7 +1969,21 @@ __exit_do_auth: assert(sess->client_myds); myconn=sess->client_myds->myconn; assert(myconn); - myconn->set_charset(charset, NAMES); + myconn->set_charset(charset, CONNECT_START); + { + std::stringstream ss; + ss << charset; + + /* We are processing handshake from client. Client sends us a character set it will use in communication. + * we store this character set in the client's variables to use later in multiplexing with different backends + */ + sess->mysql_variables->client_set_value(SQL_CHARACTER_SET_RESULTS, ss.str().c_str()); + sess->mysql_variables->client_set_value(SQL_CHARACTER_SET_CLIENT, ss.str().c_str()); + sess->mysql_variables->client_set_value(SQL_CHARACTER_SET_CONNECTION, ss.str().c_str()); + sess->mysql_variables->client_set_value(SQL_COLLATION_CONNECTION, ss.str().c_str()); + } + if (sess->mysql_variables) + proxy_warning("TRACE : LOGIN charset server %s, client %s, handshake %d\n", sess->mysql_variables->server_get_value(SQL_CHARACTER_SET), sess->mysql_variables->client_get_value(SQL_CHARACTER_SET), charset); // enable compression if (capabilities & CLIENT_COMPRESS) { if (myconn->options.server_capabilities & CLIENT_COMPRESS) { diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index d7a6360cd..f0e746a74 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -59,6 +59,7 @@ static inline char is_normal_char(char c) { extern const MARIADB_CHARSET_INFO * proxysql_find_charset_name(const char * const name); extern MARIADB_CHARSET_INFO * proxysql_find_charset_collate_names(const char *csname, const char *collatename); extern const MARIADB_CHARSET_INFO * proxysql_find_charset_nr(unsigned int nr); +extern MARIADB_CHARSET_INFO * proxysql_find_charset_collate(const char *collatename); extern MySQL_Authentication *GloMyAuth; extern MySQL_LDAP_Authentication *GloMyLdapAuth; @@ -962,7 +963,6 @@ void MySQL_Session::generate_proxysql_internal_session_json(json &j) { for (auto idx = 0; idx < SQL_NAME_LAST; idx++) { client_myds->myconn->variables[idx].fill_client_internal_session(j, idx); } - j["conn"]["charset"] = client_myds->myconn->options.charset; j["conn"]["sql_log_bin"] = client_myds->myconn->options.sql_log_bin; j["conn"]["autocommit"] = ( client_myds->myconn->options.autocommit ? "ON" : "OFF" ); j["conn"]["client_flag"]["value"] = client_myds->myconn->options.client_flag; @@ -1575,35 +1575,6 @@ void MySQL_Session::handler_again___new_thread_to_kill_connection() { // true should jump to handler_again #define NEXT_IMMEDIATE_NEW(new_st) do { set_status(new_st); return true; } while (0) -bool MySQL_Session::handler_again___verify_backend_charset() { - proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session %p , client charset: %u , backend charset: %u\n", this, client_myds->myconn->options.charset, mybe->server_myds->myconn->mysql->charset->nr); - if (client_myds->myconn->options.charset != mybe->server_myds->myconn->mysql->charset->nr || client_myds->myconn->options.charset_action != mybe->server_myds->myconn->options.charset_action) { - //previous_status.push(PROCESSING_QUERY); - switch(status) { // this switch can be replaced with a simple previous_status.push(status), but it is here for readibility - case PROCESSING_QUERY: - previous_status.push(PROCESSING_QUERY); - break; - case PROCESSING_STMT_PREPARE: - previous_status.push(PROCESSING_STMT_PREPARE); - break; - case PROCESSING_STMT_EXECUTE: - previous_status.push(PROCESSING_STMT_EXECUTE); - break; - default: - assert(0); - break; - } - if (client_myds->myconn->options.charset_action == NAMES) { - NEXT_IMMEDIATE_NEW(CHANGING_CHARSET); - } else if (client_myds->myconn->options.charset_action == CHARSET) { - NEXT_IMMEDIATE_NEW(SETTING_CHARSET); - } else { - assert(0); - } - } - return false; -} - bool MySQL_Session::handler_again___verify_backend_sql_log_bin() { if (client_myds->myconn->options.sql_log_bin != mybe->server_myds->myconn->options.sql_log_bin) { mybe->server_myds->myconn->options.sql_log_bin = client_myds->myconn->options.sql_log_bin; @@ -2191,6 +2162,7 @@ bool MySQL_Session::handler_again___status_SETTING_GENERIC_VARIABLE(int *_rc, co query=NULL; } if (rc==0) { + proxy_warning("TRACE : tid [%lu] END SET VAR %s value %s\n", thread_session_id, var_name, var_value); myds->revents|=POLLOUT; // we also set again POLLOUT to send a query immediately! myds->DSS = STATE_MARIADB_GENERIC; st=previous_status.top(); @@ -2286,29 +2258,6 @@ bool MySQL_Session::handler_again___status_SETTING_MULTI_STMT(int *_rc) { return ret; } -bool MySQL_Session::handler_again___status_SETTING_CHARSET(int *_rc) { - bool ret=false; - assert(mybe->server_myds->myconn); - mybe->server_myds->myconn->set_charset(client_myds->myconn->options.charset, CHARSET); - const MARIADB_CHARSET_INFO * c = proxysql_find_charset_nr(client_myds->myconn->options.charset); - ret = handler_again___status_SETTING_GENERIC_VARIABLE(_rc, (char *)"CHARSET", (char*)c->csname, false, true); - return ret; -} - -bool MySQL_Session::handler_again___status_SETTING_SQL_SELECT_LIMIT(int *_rc) { - bool ret=false; - assert(mybe->server_myds->myconn); - ret = handler_again___status_SETTING_GENERIC_VARIABLE(_rc, (char *)"SQL_SELECT_LIMIT", mysql_variables->server_get_value(SQL_SELECT_LIMIT), true); - return ret; -} - -bool MySQL_Session::handler_again___status_SETTING_SQL_SAFE_UPDATES(int *_rc) { - bool ret=false; - assert(mybe->server_myds->myconn); - ret = handler_again___status_SETTING_GENERIC_VARIABLE(_rc, (char *)"SQL_SAFE_UPDATES", mysql_variables->server_get_value(SQL_SAFE_UPDATES), true); - return ret; -} - bool MySQL_Session::handler_again___status_CHANGING_SCHEMA(int *_rc) { bool ret=false; //fprintf(stderr,"CHANGING_SCHEMA\n"); @@ -2369,6 +2318,7 @@ bool MySQL_Session::handler_again___status_CHANGING_SCHEMA(int *_rc) { bool MySQL_Session::handler_again___status_CONNECTING_SERVER(int *_rc) { + proxy_warning("TRACE : CONNECTING TO SERVER\n"); //fprintf(stderr,"CONNECTING_SERVER\n"); unsigned long long curtime=monotonic_time(); thread->atomic_curtime=curtime; @@ -2414,6 +2364,12 @@ bool MySQL_Session::handler_again___status_CONNECTING_SERVER(int *_rc) { NEXT_IMMEDIATE_NEW(WAITING_CLIENT_DATA); } } + proxy_warning("TRACE : HAVE CONNECTION START\n"); + std::vector vars = {SQL_CHARACTER_SET, SQL_CHARACTER_ACTION, SQL_CHARACTER_SET_RESULTS, SQL_CHARACTER_SET_CONNECTION, SQL_COLLATION_CONNECTION, SQL_CHARACTER_SET_CLIENT, SQL_CHARACTER_SET_DATABASE }; + for (auto i : vars) { + proxy_warning("TRACE : tid [%lu] variable name %24s\tclient value [%5s]\tserver value [%5s]\n", thread_session_id, mysql_tracked_variables[i].set_variable_name, mysql_variables->client_get_value(i), mysql_variables->server_get_value(i)); + } + if (mybe->server_myds->myconn==NULL) { pause_until=thread->curtime+mysql_thread___connect_retries_delay*1000; *_rc=1; @@ -2607,14 +2563,17 @@ bool MySQL_Session::handler_again___status_CHANGING_CHARSET(int *_rc) { MySQL_Connection *myconn=myds->myconn; char msg[128]; const MARIADB_CHARSET_INFO *ci = NULL; - const char* replace_collation = ""; - const char* not_supported_collation = ""; + const char* replace_collation = NULL; + const char* not_supported_collation = NULL; + unsigned int replace_collation_nr = 0; + std::stringstream ss; /* Validate that server can support client's charset */ - if (client_myds->myconn->options.charset >= 255 && myconn->mysql->server_version[0] != '8') { + int charset = atoi(mysql_variables->client_get_value(SQL_CHARACTER_SET)); + if ( charset >= 255 && myconn->mysql->server_version[0] != '8') { switch(mysql_thread___handle_unknown_charset) { case HANDLE_UNKNOWN_CHARSET__DISCONNECT_CLIENT: - snprintf(msg,sizeof(msg),"Can't initialize character set %d",client_myds->myconn->options.charset); + snprintf(msg,sizeof(msg),"Can't initialize character set %s",mysql_variables->client_get_value(SQL_CHARACTER_SET)); proxy_error("Can't initialize character set on %s, %d: Error %d (%s). Closing client connection %s:%d.\n", myconn->parent->address, myconn->parent->port, 2019, msg, client_myds->addr.addr, client_myds->addr.port); myds->destroy_MySQL_Connection_From_Pool(false); @@ -2622,20 +2581,42 @@ bool MySQL_Session::handler_again___status_CHANGING_CHARSET(int *_rc) { *_rc=-1; return false; case HANDLE_UNKNOWN_CHARSET__REPLACE_WITH_DEFAULT_VERBOSE: - ci = proxysql_find_charset_nr(client_myds->myconn->options.charset); - if (ci) not_supported_collation = ci->name; - - ci = proxysql_find_charset_nr(mysql_thread___default_charset); - if (ci) replace_collation = ci->name; - - proxy_warning("Server doesn't support collation (%d) %s. Replacing it with the configured default (%d) %s. Client %s:%d\n", - client_myds->myconn->options.charset, not_supported_collation, - mysql_thread___default_charset, replace_collation, client_myds->addr.addr, client_myds->addr.port); + ci = proxysql_find_charset_nr(atoi(mysql_variables->client_get_value(SQL_CHARACTER_SET))); + if (!ci) { + proxy_error("Cannot find character set [%s]\n", mysql_variables->client_get_value(SQL_CHARACTER_SET)); + assert(0); + } + not_supported_collation = ci->name; - client_myds->myconn->options.charset=mysql_thread___default_charset; + ci = proxysql_find_charset_name(mysql_thread___default_variables[SQL_CHARACTER_SET]); + if (!ci) { + proxy_error("Cannot find character set [%s]\n", mysql_thread___default_variables[SQL_CHARACTER_SET]); + assert(0); + } + replace_collation = ci->name; + replace_collation_nr = ci->nr; + + proxy_warning("Server doesn't support collation (%s) %s. Replacing it with the configured default (%d) %s. Client %s:%d\n", + mysql_variables->client_get_value(SQL_CHARACTER_SET), not_supported_collation, + replace_collation_nr, replace_collation, client_myds->addr.addr, client_myds->addr.port); + + ss << replace_collation_nr; + mysql_variables->client_set_value(SQL_CHARACTER_SET, ss.str()); + mysql_variables->client_set_value(SQL_CHARACTER_SET_RESULTS, ss.str()); + mysql_variables->client_set_value(SQL_CHARACTER_SET_CLIENT, ss.str()); + mysql_variables->client_set_value(SQL_CHARACTER_SET_CONNECTION, ss.str()); + mysql_variables->client_set_value(SQL_COLLATION_CONNECTION, ss.str()); break; case HANDLE_UNKNOWN_CHARSET__REPLACE_WITH_DEFAULT: - client_myds->myconn->options.charset=mysql_thread___default_charset; + ci = proxysql_find_charset_name(mysql_thread___default_variables[SQL_CHARACTER_SET]); + if (!ci) { + proxy_error("Cannot filnd charset [%s]\n", mysql_thread___default_variables[SQL_CHARACTER_SET]); + assert(0); + } + replace_collation_nr = ci->nr; + + ss << replace_collation_nr; + mysql_variables->client_set_value(SQL_CHARACTER_SET, ss.str()); break; default: proxy_error("Wrong configuration of the handle_unknown_charset\n"); @@ -2645,64 +2626,64 @@ bool MySQL_Session::handler_again___status_CHANGING_CHARSET(int *_rc) { myds->DSS=STATE_MARIADB_QUERY; enum session_status st=status; - if (client_myds->myconn->options.charset_action == NAMES) { - if (myds->mypolls==NULL) { - thread->mypolls.add(POLLIN|POLLOUT, mybe->server_myds->fd, mybe->server_myds, thread->curtime); - } - int rc=myconn->async_set_names(myds->revents, client_myds->myconn->options.charset); - if (rc==0) { - __sync_fetch_and_add(&MyHGM->status.backend_set_names, 1); - myds->DSS = STATE_MARIADB_GENERIC; - st=previous_status.top(); - previous_status.pop(); - NEXT_IMMEDIATE_NEW(st); - } else { - if (rc==-1) { - // the command failed - int myerr=mysql_errno(myconn->mysql); - if (myerr >= 2000) { - if (myerr == 2019) { - proxy_error("Client trying to set a charset/collation (%u) not supported by backend (%s:%d). Changing it to %u\n", client_myds->myconn->options.charset, myconn->parent->address, myconn->parent->port, mysql_thread___default_charset); - client_myds->myconn->options.charset = mysql_thread___default_charset; - } - bool retry_conn=false; - // client error, serious - proxy_error("Detected a broken connection during SET NAMES on %s , %d : %d, %s\n", myconn->parent->address, myconn->parent->port, myerr, mysql_error(myconn->mysql)); - if ((myds->myconn->reusable==true) && myds->myconn->IsActiveTransaction()==false && myds->myconn->MultiplexDisabled()==false) { - retry_conn=true; - } - myds->destroy_MySQL_Connection_From_Pool(false); - myds->fd=0; - if (retry_conn) { - myds->DSS=STATE_NOT_INITIALIZED; - //previous_status.push(PROCESSING_QUERY); - NEXT_IMMEDIATE_NEW(CONNECTING_SERVER); - } - *_rc=-1; - return false; - } else { - proxy_warning("Error during SET NAMES: %d, %s\n", myerr, mysql_error(myconn->mysql)); - // we won't go back to PROCESSING_QUERY - st=previous_status.top(); - previous_status.pop(); - char sqlstate[10]; - sprintf(sqlstate,"%s",mysql_sqlstate(myconn->mysql)); - client_myds->myprot.generate_pkt_ERR(true,NULL,NULL,1,mysql_errno(myconn->mysql),sqlstate,mysql_error(myconn->mysql)); - myds->destroy_MySQL_Connection_From_Pool(true); - myds->fd=0; - status=WAITING_CLIENT_DATA; - client_myds->DSS=STATE_SLEEP; - RequestEnd(myds); - } - } else { - // rc==1 , nothing to do for now - } - } - } else if (client_myds->myconn->options.charset_action == CHARSET) { + if (myds->mypolls==NULL) { + thread->mypolls.add(POLLIN|POLLOUT, mybe->server_myds->fd, mybe->server_myds, thread->curtime); + } + int rc=myconn->async_set_names(myds->revents, atoi(mysql_variables->client_get_value(SQL_CHARACTER_SET))); + + if (rc==0) { + proxy_warning("TRACE : END SET NAMES %s\n", mysql_variables->client_get_value(SQL_CHARACTER_SET)); + __sync_fetch_and_add(&MyHGM->status.backend_set_names, 1); myds->DSS = STATE_MARIADB_GENERIC; st=previous_status.top(); previous_status.pop(); NEXT_IMMEDIATE_NEW(st); + } else { + if (rc==-1) { + // the command failed + int myerr=mysql_errno(myconn->mysql); + if (myerr >= 2000) { + if (myerr == 2019) { + ci = proxysql_find_charset_name(mysql_thread___default_variables[SQL_CHARACTER_SET]); + if (ci) replace_collation_nr = ci->nr; + proxy_error("Client trying to set a charset/collation (%u) not supported by backend (%s:%d). Changing it to %u\n", mysql_variables->client_get_value(SQL_CHARACTER_SET), myconn->parent->address, myconn->parent->port, replace_collation_nr); + + ss.clear(); + ss << replace_collation_nr; + mysql_variables->client_set_value(SQL_CHARACTER_SET, ss.str()); + } + bool retry_conn=false; + // client error, serious + proxy_error("Detected a broken connection during SET NAMES on %s , %d : %d, %s\n", myconn->parent->address, myconn->parent->port, myerr, mysql_error(myconn->mysql)); + if ((myds->myconn->reusable==true) && myds->myconn->IsActiveTransaction()==false && myds->myconn->MultiplexDisabled()==false) { + retry_conn=true; + } + myds->destroy_MySQL_Connection_From_Pool(false); + myds->fd=0; + if (retry_conn) { + myds->DSS=STATE_NOT_INITIALIZED; + //previous_status.push(PROCESSING_QUERY); + NEXT_IMMEDIATE_NEW(CONNECTING_SERVER); + } + *_rc=-1; + return false; + } else { + proxy_warning("Error during SET NAMES: %d, %s\n", myerr, mysql_error(myconn->mysql)); + // we won't go back to PROCESSING_QUERY + st=previous_status.top(); + previous_status.pop(); + char sqlstate[10]; + sprintf(sqlstate,"%s",mysql_sqlstate(myconn->mysql)); + client_myds->myprot.generate_pkt_ERR(true,NULL,NULL,1,mysql_errno(myconn->mysql),sqlstate,mysql_error(myconn->mysql)); + myds->destroy_MySQL_Connection_From_Pool(true); + myds->fd=0; + status=WAITING_CLIENT_DATA; + client_myds->DSS=STATE_SLEEP; + RequestEnd(myds); + } + } else { + // rc==1 , nothing to do for now + } } return false; } @@ -3614,14 +3595,15 @@ handler_again: } if (locked_on_hostgroup == -1 || locked_on_hostgroup_and_all_variables_set == false ) { - if (handler_again___verify_backend_charset()) { - goto handler_again; - } + // Verify and update only variables: no SET NAMES, no SET CHARSET + static const std::vector variables = {SQL_SAFE_UPDATES, SQL_SELECT_LIMIT, SQL_SQL_MODE, SQL_TIME_ZONE, SQL_CHARACTER_SET_RESULTS, + SQL_CHARACTER_SET_CONNECTION, SQL_CHARACTER_SET_CLIENT, SQL_CHARACTER_SET_DATABASE, SQL_ISOLATION_LEVEL, SQL_TRANSACTION_READ, + SQL_SESSION_TRACK_GTIDS, SQL_SQL_AUTO_IS_NULL, SQL_COLLATION_CONNECTION, SQL_NET_WRITE_TIMEOUT, SQL_MAX_JOIN_SIZE }; - for (auto i = 0; i < SQL_NAME_LAST; i++) { - // FIXME: for now it seems broken - if(mysql_variables->verify_variable(i)) + for (auto i : variables) { + if(mysql_variables->verify_variable(i)) { goto handler_again; + } } if (handler_again___verify_backend_sql_log_bin()) { @@ -4130,17 +4112,6 @@ handler_again: } } break; - case SETTING_CHARSET: - { - int rc=0; - if (handler_again___status_SETTING_CHARSET(&rc)) - goto handler_again; // we changed status - if (rc==-1) { // we have an error we can't handle - handler_ret = -1; - return handler_ret; - } - } - break; case SETTING_MULTI_STMT: { @@ -4171,6 +4142,9 @@ handler_again: case SETTING_SQL_SAFE_UPDATES: case SETTING_TIME_ZONE: case SETTING_CHARACTER_SET_RESULTS: + case SETTING_CHARACTER_SET_CONNECTION: + case SETTING_CHARACTER_SET_CLIENT: + case SETTING_CHARACTER_SET_DATABASE: case SETTING_ISOLATION_LEVEL: case SETTING_TRANSACTION_READ: case SETTING_SESSION_TRACK_GTIDS: @@ -4178,6 +4152,8 @@ handler_again: case SETTING_COLLATION_CONNECTION: case SETTING_NET_WRITE_TIMEOUT: case SETTING_MAX_JOIN_SIZE: + case SETTING_CHARSET: + case SETTING_SET_NAMES: for (auto i = 0; i < SQL_NAME_LAST; i++) { int rc = 0; if (mysql_variables->update_variable(status, rc)) { @@ -5133,7 +5109,10 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C return false; } if (mysql_variables->client_get_hash(idx) != var_value_int) { - mysql_variables->client_set_value(idx, value1.c_str()); + if (__tmp_value == 0) + mysql_variables->client_set_value(idx, "OFF"); + else + mysql_variables->client_set_value(idx, "ON"); proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Changing connection %s to %s\n", var.c_str(), value1.c_str()); } exit_after_SetParse = true; @@ -5248,7 +5227,7 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C uint32_t var_value_int=SpookyHash::Hash32(value1.c_str(),value1.length(),10); int idx = SQL_NAME_LAST; for (int i = 0 ; i < SQL_NAME_LAST ; i++) { - if (!strcmp(var.c_str(), mysql_tracked_variables[i].set_variable_name)) { + if (!strcasecmp(var.c_str(), mysql_tracked_variables[i].set_variable_name)) { idx = mysql_tracked_variables[i].idx; break; } @@ -5267,7 +5246,9 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C unable_to_parse_set_statement(lock_hostgroup); return false; } - } else if ( (var == "character_set_results") || ( var == "collation_connection" ) ) { + } else if ( (var == "character_set_results") || ( var == "collation_connection" ) || + (var == "character_set_connection") || (var == "character_set_client") || + (var == "character_set_database")) { std::string value1 = *values; int vl = strlen(value1.c_str()); const char *v = value1.c_str(); @@ -5282,7 +5263,7 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C uint32_t var_value_int=SpookyHash::Hash32(value1.c_str(),value1.length(),10); int idx = SQL_NAME_LAST; for (int i = 0 ; i < SQL_NAME_LAST ; i++) { - if (!strcmp(var.c_str(), mysql_tracked_variables[i].set_variable_name)) { + if (!strcasecmp(var.c_str(), mysql_tracked_variables[i].set_variable_name)) { idx = mysql_tracked_variables[i].idx; break; } @@ -5293,8 +5274,42 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C return false; } if (mysql_variables->client_get_hash(idx) != var_value_int) { - mysql_variables->client_set_value(idx, value1.c_str()); - proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Changing connection %s to %s\n", var.c_str(), value1.c_str()); + const MARIADB_CHARSET_INFO *ci = NULL; + if (var == "character_set_results" || var == "character_set_connection" || + var == "character_set_client" || var == "character_set_database") { + ci = proxysql_find_charset_name(value1.c_str()); + } + else if (var == "collation_connection") + ci = proxysql_find_charset_collate(value1.c_str()); + + if (!ci) { + if (var == "character_set_results") { + if (!strcasecmp("NULL", value1.c_str())) { + mysql_variables->client_set_value(idx, "-1"); + } else if (!strcasecmp("binary", value1.c_str())) { + mysql_variables->client_set_value(idx, "-2"); + } else { + proxy_error("Cannot find charset/collation [%s]\n", value1.c_str()); + assert(0); + } + } + } else { + std::stringstream ss; + ss << ci->nr; + /* changing collation_connection the character_set_connection will be changed as well + * and vice versa + */ + if (var == "collation_connection") + mysql_variables->client_set_value(SQL_CHARACTER_SET_CONNECTION, ss.str().c_str()); + if (var == "character_set_connection") + mysql_variables->client_set_value(SQL_COLLATION_CONNECTION, ss.str().c_str()); + + /* this is explicit statement from client. we do not multiplex, therefor we must + * remember client's choice in the client's variable for future use in verifications, multiplexing etc. + */ + mysql_variables->client_set_value(idx, ss.str().c_str()); + proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Changing connection %s to %s\n", var.c_str(), value1.c_str()); + } } exit_after_SetParse = true; } else { @@ -5922,6 +5937,12 @@ void MySQL_Session::handler___client_DSS_QUERY_SENT___server_DSS_NOT_INITIALIZED mybe->server_myds->assign_fd_from_mysql_conn(); mybe->server_myds->myds_type=MYDS_BACKEND; mybe->server_myds->DSS=STATE_READY; + + std::vector vars = {SQL_CHARACTER_SET, SQL_CHARACTER_ACTION, SQL_CHARACTER_SET_RESULTS, SQL_CHARACTER_SET_CONNECTION, SQL_COLLATION_CONNECTION, SQL_CHARACTER_SET_CLIENT, SQL_CHARACTER_SET_DATABASE }; + for (auto i : vars) { + proxy_warning("TRACE : tid [%lu], variable name %24s\tclient value [%5s]\tserver value [%5s]\n", thread_session_id, mysql_tracked_variables[i].set_variable_name, mysql_variables->client_get_value(i), mysql_variables->server_get_value(i)); + } + if (session_fast_forward==true) { status=FAST_FORWARD; mybe->server_myds->myconn->reusable=false; // the connection cannot be usable anymore diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index 0feda0ada..760bf4dda 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -70,6 +70,17 @@ MARIADB_CHARSET_INFO * proxysql_find_charset_collate_names(const char *csname, c return NULL; } +MARIADB_CHARSET_INFO * proxysql_find_charset_collate(const char *collatename) { + MARIADB_CHARSET_INFO *c = (MARIADB_CHARSET_INFO *)mariadb_compiled_charsets; + do { + if (!strcasecmp(c->name, collatename)) { + return c; + } + ++c; + } while (c[0].nr != 0); + return NULL; +} + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -582,7 +593,6 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() { variables.ping_interval_server_msec=10000; variables.ping_timeout_server=200; variables.default_schema=strdup((char *)"information_schema"); - variables.default_charset=33; variables.handle_unknown_charset=1; variables.interfaces=strdup((char *)""); variables.server_version=strdup((char *)"5.5.30"); @@ -815,7 +825,6 @@ uint16_t MySQL_Threads_Handler::get_variable_uint16(char *name) { } unsigned int MySQL_Threads_Handler::get_variable_uint(char *name) { - if (!strcasecmp(name,"default_charset")) return variables.default_charset; if (!strcasecmp(name,"handle_unknown_charset")) return variables.handle_unknown_charset; proxy_error("Not existing variable: %s\n", name); assert(0); return 0; @@ -1231,14 +1240,6 @@ char * MySQL_Threads_Handler::get_variable(char *name) { // this is the public f return strdup((variables.monitor_wait_timeout ? "true" : "false")); } } - if (!strcasecmp(name,"default_charset")) { - const MARIADB_CHARSET_INFO *c = proxysql_find_charset_nr(variables.default_charset); - if (!c) { - proxy_error("Not existing charset number %u\n", variables.default_charset); - assert(c); - } - return strdup(c->csname); - } if (!strcasecmp(name, "handle_unknown_charset")) { sprintf(intbuf, "%d",variables.handle_unknown_charset); return strdup(intbuf); @@ -1567,7 +1568,7 @@ char * MySQL_Threads_Handler::get_variable(char *name) { // this is the public f -bool MySQL_Threads_Handler::set_variable(char *name, char *value) { // this is the public function, accessible from admin +bool MySQL_Threads_Handler::set_variable(char *name, const char *value) { // this is the public function, accessible from admin // IN: // name: variable name // value: variable value @@ -2416,7 +2417,6 @@ bool MySQL_Threads_Handler::set_variable(char *name, char *value) { // this is t return true; } - for (int i=0; inr; - return true; - } else { - return false; - } - } else { - return false; - } - } if (!strcasecmp(name,"handle_unknown_charset")) { uint8_t intv=atoi(value); if (intv >= 0 && intv < HANDLE_UNKNOWN_CHARSET__MAX_HANDLE_VALUE) { @@ -2967,10 +2954,10 @@ char ** MySQL_Threads_Handler::get_variables_list() { for (i=0; i < SQL_NAME_LAST ; i++) { char * m = (char *)malloc(strlen(mysql_tracked_variables[i].internal_variable_name)+1+strlen((char *)"default_")); sprintf(m,"default_%s", mysql_tracked_variables[i].internal_variable_name); - ret[i] == m; + ret[i] = m; } for (i=SQL_NAME_LAST;iclient_myds->myprot.init(&sess->client_myds, sess->client_myds->myconn->userinfo, sess); + proxy_warning("TRACE : new_client\n"); for (int i=0; imysql_variables->client_set_value(i, mysql_thread___default_variables[i]); + if (i == SQL_CHARACTER_SET || i == SQL_CHARACTER_SET_RESULTS || + i == SQL_CHARACTER_SET_CONNECTION || i == SQL_CHARACTER_SET_CLIENT || + i == SQL_CHARACTER_SET_DATABASE) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_name(mysql_thread___default_variables[i]); + if (!ci) { + proxy_error("Cannot find character set for name [%s]. Configuration error. Check [%s] global variable. Using character set 33.\n", + mysql_thread___default_variables[i], mysql_tracked_variables[i].internal_variable_name); + assert(0); + } + std::stringstream ss; + ss << ci->nr; + sess->mysql_variables->client_set_value(i, ss.str()); + } else if (i == SQL_COLLATION_CONNECTION) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_collate(mysql_thread___default_variables[i]); + if (!ci) { + proxy_error("Cannot find character set for name [%s]. Configuration error. Check [%s] global variable. Using character set 33.\n", + mysql_thread___default_variables[SQL_COLLATION_CONNECTION], mysql_tracked_variables[SQL_COLLATION_CONNECTION].internal_variable_name); + assert(0); + } + std::stringstream ss; + ss << ci->nr; + sess->mysql_variables->client_set_value(i, ss.str()); + } else { + sess->mysql_variables->client_set_value(i, mysql_thread___default_variables[i]); + } } return sess; @@ -3353,7 +3367,7 @@ bool MySQL_Thread::init() { match_regexes=(Session_Regex **)malloc(sizeof(Session_Regex *)*4); match_regexes[0]=new Session_Regex((char *)"^SET (|SESSION |@@|@@session.)SQL_LOG_BIN( *)(:|)=( *)"); - match_regexes[1]=new Session_Regex((char *)"^SET (|SESSION |@@|@@session.)(SQL_MODE|TIME_ZONE|CHARACTER_SET_RESULTS|SESSION_TRACK_GTIDS|SQL_AUTO_IS_NULL|SQL_SELECT_LIMIT|SQL_SAFE_UPDATES|COLLATION_CONNECTION|NET_WRITE_TIMEOUT|TX_ISOLATION|MAX_JOIN_SIZE( *)(:|)=( *))"); + match_regexes[1]=new Session_Regex((char *)"^SET (|SESSION |@@|@@session.)(SQL_MODE|TIME_ZONE|CHARACTER_SET_RESULTS|CHARACTER_SET_CLIENT|CHARACTER_SET_DATABASE|SESSION_TRACK_GTIDS|SQL_AUTO_IS_NULL|SQL_SELECT_LIMIT|SQL_SAFE_UPDATES|COLLATION_CONNECTION|CHARACTER_SET_CONNECTION|NET_WRITE_TIMEOUT|TX_ISOLATION|MAX_JOIN_SIZE( *)(:|)=( *))"); match_regexes[2]=new Session_Regex((char *)"^SET(?: +)(|SESSION +)TRANSACTION(?: +)(?:(?:(ISOLATION(?: +)LEVEL)(?: +)(REPEATABLE(?: +)READ|READ(?: +)COMMITTED|READ(?: +)UNCOMMITTED|SERIALIZABLE))|(?:(READ)(?: +)(WRITE|ONLY)))"); match_regexes[3]=new Session_Regex((char *)"^(set)(?: +)((charset)|(character +set))(?: )"); @@ -4435,7 +4449,6 @@ void MySQL_Thread::refresh_variables() { if (mysql_thread___keep_multiplexing_variables) free(mysql_thread___keep_multiplexing_variables); mysql_thread___keep_multiplexing_variables=GloMTH->get_variable_string((char *)"keep_multiplexing_variables"); mysql_thread___server_capabilities=GloMTH->get_variable_uint16((char *)"server_capabilities"); - mysql_thread___default_charset=GloMTH->get_variable_uint((char *)"default_charset"); mysql_thread___handle_unknown_charset=GloMTH->get_variable_uint((char *)"handle_unknown_charset"); mysql_thread___poll_timeout=GloMTH->get_variable_int((char *)"poll_timeout"); mysql_thread___poll_timeout_on_failure=GloMTH->get_variable_int((char *)"poll_timeout_on_failure"); diff --git a/lib/MySQL_Variables.cpp b/lib/MySQL_Variables.cpp index 5409f76c7..40849caf7 100644 --- a/lib/MySQL_Variables.cpp +++ b/lib/MySQL_Variables.cpp @@ -5,6 +5,11 @@ #include "MySQL_Data_Stream.h" #include "SpookyV2.h" +#include + +extern const MARIADB_CHARSET_INFO * proxysql_find_charset_nr(unsigned int nr); +extern MARIADB_CHARSET_INFO * proxysql_find_charset_name(const char *name); + MySQL_Variables::MySQL_Variables(MySQL_Session* _session) { assert(_session); session = _session; @@ -16,15 +21,20 @@ MySQL_Variables::MySQL_Variables(MySQL_Session* _session) { case SQL_SQL_MODE: case SQL_TIME_ZONE: case SQL_CHARACTER_SET_RESULTS: + case SQL_CHARACTER_SET_CONNECTION: + case SQL_CHARACTER_SET_CLIENT: + case SQL_CHARACTER_SET_DATABASE: case SQL_ISOLATION_LEVEL: case SQL_TRANSACTION_READ: case SQL_SESSION_TRACK_GTIDS: case SQL_SQL_AUTO_IS_NULL: + case SQL_COLLATION_CONNECTION: + case SQL_NET_WRITE_TIMEOUT: + case SQL_MAX_JOIN_SIZE: updaters[i] = new Generic_Updater(); break; default: - proxy_error("Wrong variable index\n"); - assert(0); + updaters[i] = NULL; } } } @@ -34,24 +44,63 @@ MySQL_Variables::~MySQL_Variables() { delete u; } -void MySQL_Variables::client_set_value(int idx, const char* value) { - session->client_myds->myconn->variables[idx].hash = SpookyHash::Hash32(value,strlen(value),10); +void print_backtrace(void); + +void MySQL_Variables::client_set_value(int idx, const std::string& value) { + if (!session || !session->client_myds || !session->client_myds->myconn) return; + session->client_myds->myconn->variables[idx].hash = SpookyHash::Hash32(value.c_str(),strlen(value.c_str()),10); + + switch (idx) { + case SQL_CHARACTER_ACTION: + // SET NAMES command from client + if (value == "1") { + if (session->mysql_variables->client_get_value(SQL_CHARACTER_SET)) { + session->mysql_variables->client_set_value(SQL_CHARACTER_SET_RESULTS, session->mysql_variables->client_get_value(SQL_CHARACTER_SET)); + session->mysql_variables->client_set_value(SQL_CHARACTER_SET_CLIENT, session->mysql_variables->client_get_value(SQL_CHARACTER_SET)); + session->mysql_variables->client_set_value(SQL_CHARACTER_SET_CONNECTION, session->mysql_variables->client_get_value(SQL_CHARACTER_SET)); + session->mysql_variables->client_set_value(SQL_COLLATION_CONNECTION, session->mysql_variables->client_get_value(SQL_CHARACTER_SET)); + } + } + // SET CHARSET command from client + else if (value == "2") { + if (session->mysql_variables->client_get_value(SQL_CHARACTER_SET)) { + session->mysql_variables->client_set_value(SQL_CHARACTER_SET_RESULTS, session->mysql_variables->client_get_value(SQL_CHARACTER_SET)); + session->mysql_variables->client_set_value(SQL_CHARACTER_SET_CLIENT, session->mysql_variables->client_get_value(SQL_CHARACTER_SET)); + } + if (session->mysql_variables->client_get_value(SQL_CHARACTER_SET_DATABASE)) { + session->mysql_variables->client_set_value(SQL_CHARACTER_SET_CONNECTION, session->mysql_variables->client_get_value(SQL_CHARACTER_SET_DATABASE)); + session->mysql_variables->client_set_value(SQL_COLLATION_CONNECTION, session->mysql_variables->client_get_value(SQL_CHARACTER_SET_DATABASE)); + } + } + // SET NAMES during handshake etc. + else if (value == "3") { + if (session->mysql_variables->server_get_value(SQL_CHARACTER_SET)) { + session->mysql_variables->client_set_value(SQL_CHARACTER_SET_RESULTS, session->mysql_variables->server_get_value(SQL_CHARACTER_SET)); + session->mysql_variables->client_set_value(SQL_CHARACTER_SET_CLIENT, session->mysql_variables->server_get_value(SQL_CHARACTER_SET)); + session->mysql_variables->client_set_value(SQL_CHARACTER_SET_CONNECTION, session->mysql_variables->server_get_value(SQL_CHARACTER_SET)); + session->mysql_variables->client_set_value(SQL_COLLATION_CONNECTION, session->mysql_variables->server_get_value(SQL_CHARACTER_SET)); + } + } + } if (session->client_myds->myconn->variables[idx].value) { free(session->client_myds->myconn->variables[idx].value); } - session->client_myds->myconn->variables[idx].value = strdup(value); + session->client_myds->myconn->variables[idx].value = strdup(value.c_str()); } const char* MySQL_Variables::client_get_value(int idx) { + if (!session || !session->client_myds || !session->client_myds->myconn) return NULL; return session->client_myds->myconn->variables[idx].value; } uint32_t MySQL_Variables::client_get_hash(int idx) { + if (!session || !session->client_myds || !session->client_myds->myconn) return 0; return session->client_myds->myconn->variables[idx].hash; } void MySQL_Variables::server_set_value(int idx, const char* value) { + if (!session || !session->mybe || !session->mybe->server_myds || !session->mybe->server_myds->myconn || !value) return; session->mybe->server_myds->myconn->variables[idx].hash = SpookyHash::Hash32(value,strlen(value),10); if (session->mybe->server_myds->myconn->variables[idx].value) { @@ -61,62 +110,15 @@ void MySQL_Variables::server_set_value(int idx, const char* value) { } const char* MySQL_Variables::server_get_value(int idx) { + if (!session || !session->mybe || !session->mybe->server_myds || !session->mybe->server_myds->myconn) return NULL; return session->mybe->server_myds->myconn->variables[idx].value; } uint32_t MySQL_Variables::server_get_hash(int idx) { + if (!session || !session->mybe || !session->mybe->server_myds || !session->mybe->server_myds->myconn) return 0; return session->mybe->server_myds->myconn->variables[idx].hash; } -bool MySQL_Variables::verify_generic_variable(uint32_t *be_int, char **be_var, char *def, uint32_t *fe_int, char *fe_var, enum session_status next_sess_status) { - // be_int = backend int (hash) - // be_var = backend value - // def = default - // fe_int = frontend int (has) - // fe_var = frontend value - if (*be_int == 0) { - // it is the first time we use this backend. Set value to default - if (*be_var) { - free(*be_var); - *be_var = NULL; - } - *be_var = strdup(def); - uint32_t tmp_int = SpookyHash::Hash32(*be_var, strlen(*be_var), 10); - *be_int = tmp_int; - } - if (*fe_int) { - if (*fe_int != *be_int) { - { - *be_int = *fe_int; - if (*be_var) { - free(*be_var); - *be_var = NULL; - } - if (fe_var) { - *be_var = strdup(fe_var); - } - } - switch(session->status) { // this switch can be replaced with a simple previous_status.push(status), but it is here for readibility - case PROCESSING_QUERY: - session->previous_status.push(PROCESSING_QUERY); - break; - case PROCESSING_STMT_PREPARE: - session->previous_status.push(PROCESSING_STMT_PREPARE); - break; - case PROCESSING_STMT_EXECUTE: - session->previous_status.push(PROCESSING_STMT_EXECUTE); - break; - default: - assert(0); - break; - } - session->set_status(next_sess_status); - return true; - } - } - return false; -} - bool MySQL_Variables::update_variable(session_status status, int &_rc) { int idx = SQL_NAME_LAST; for (int i=0; iupdate_server_variable(session, idx, _rc); + return updaters[idx]->update_server_variable(session, idx, _rc); } bool MySQL_Variables::verify_variable(int idx) { - int rc = 0; - auto ret = updaters[idx]->verify_variables(session, idx); - if (ret) { - // FIXME - //update_variable(rc); - } + auto ret = false; + if (updaters[idx] && updaters[idx]) + ret = updaters[idx]->verify_variables(session, idx); return ret; } +/* + * Updaters for different variables + */ + +Updater::~Updater() {} + + bool Generic_Updater::verify_variables(MySQL_Session* session, int idx) { - auto ret = session->mysql_variables->verify_generic_variable( - &session->mybe->server_myds->myconn->variables[idx].hash, - &session->mybe->server_myds->myconn->variables[idx].value, - mysql_thread___default_variables[idx], - &session->client_myds->myconn->variables[idx].hash, - session->client_myds->myconn->variables[idx].value, - mysql_tracked_variables[idx].status - ); - return ret; + if ( !session->mysql_variables->server_get_value(idx) || strcmp(session->mysql_variables->client_get_value(idx), session->mysql_variables->server_get_value(idx))) { + switch(session->status) { // this switch can be replaced with a simple previous_status.push(status), but it is here for readibility + case PROCESSING_QUERY: + session->previous_status.push(PROCESSING_QUERY); + break; + case PROCESSING_STMT_PREPARE: + session->previous_status.push(PROCESSING_STMT_PREPARE); + break; + case PROCESSING_STMT_EXECUTE: + session->previous_status.push(PROCESSING_STMT_EXECUTE); + break; + default: + proxy_error("Wrong status %d\n", session->status); + assert(0); + break; + } + session->set_status(mysql_tracked_variables[idx].status); + proxy_warning("TRACE: tid [%lu] setting SERVER variable %d, value %s\n", session->thread_session_id, idx, session->mysql_variables->client_get_value(idx)); + session->mysql_variables->server_set_value(idx, session->mysql_variables->client_get_value(idx)); + return true; + } + return false; } bool Generic_Updater::update_server_variable(MySQL_Session* session, int idx, int &_rc) { @@ -156,6 +175,67 @@ bool Generic_Updater::update_server_variable(MySQL_Session* session, int idx, in if (mysql_tracked_variables[idx].quote) no_quote = false; bool st = mysql_tracked_variables[idx].set_transaction; const char * set_var_name = mysql_tracked_variables[idx].set_variable_name; - auto ret = session->handler_again___status_SETTING_GENERIC_VARIABLE(&_rc, set_var_name, session->mysql_variables->server_get_value(idx), no_quote, st); + bool ret = false; + + /* character set variables store collation id in the char* string, but we set character_set_% command + * uses character set name or collation name. This branch convert collation id to character set name + * or collation name for further execution on backend + */ + if (idx==SQL_CHARACTER_SET_RESULTS) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_nr(atoi(session->mysql_variables->client_get_value(SQL_CHARACTER_SET_RESULTS))); + + /* CHARACTER_SET_RESULTS may have "NULL" and "binary" as parameter value. + * -1 - NULL + * -2 - binary + * + * TODO: current implementation is not nice. Think about nicer implementation + */ + if (!ci) { + if (!strcmp(session->mysql_variables->client_get_value(SQL_CHARACTER_SET_RESULTS), "-1")) { + session->mysql_variables->server_set_value(idx, session->mysql_variables->client_get_value(idx)); + ret = session->handler_again___status_SETTING_GENERIC_VARIABLE(&_rc, set_var_name, "NULL", no_quote, st); + } + else if (!strcmp(session->mysql_variables->client_get_value(SQL_CHARACTER_SET_RESULTS), "-2")) { + session->mysql_variables->server_set_value(idx, session->mysql_variables->client_get_value(idx)); + ret = session->handler_again___status_SETTING_GENERIC_VARIABLE(&_rc, set_var_name, "binary", no_quote, st); + } + } else { + session->mysql_variables->server_set_value(idx, session->mysql_variables->client_get_value(idx)); + ret = session->handler_again___status_SETTING_GENERIC_VARIABLE(&_rc, set_var_name, ci->csname, no_quote, st); + } + } else if (idx==SQL_COLLATION_CONNECTION) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_nr(atoi(session->mysql_variables->client_get_value(SQL_COLLATION_CONNECTION))); + + std::stringstream ss; + ss << ci->nr; + + session->mysql_variables->server_set_value(idx, session->mysql_variables->client_get_value(idx)); + ret = session->handler_again___status_SETTING_GENERIC_VARIABLE(&_rc, set_var_name, ci->name, no_quote, st); + } else if (idx==SQL_CHARACTER_SET_CONNECTION) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_nr(atoi(session->mysql_variables->client_get_value(SQL_CHARACTER_SET_CONNECTION))); + + unsigned int nr = ci->nr; + std::stringstream ss; + ss << nr; + + session->mysql_variables->server_set_value(idx, session->mysql_variables->client_get_value(idx)); + ret = session->handler_again___status_SETTING_GENERIC_VARIABLE(&_rc, set_var_name, ci->csname, no_quote, st); + } else if (idx==SQL_CHARACTER_SET_CLIENT || idx==SQL_CHARACTER_SET_DATABASE) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_nr(atoi(session->mysql_variables->client_get_value(idx))); + + std::stringstream ss; + ss << ci->nr; + session->mysql_variables->server_set_value(idx, session->mysql_variables->client_get_value(idx)); + ret = session->handler_again___status_SETTING_GENERIC_VARIABLE(&_rc, set_var_name, ci->csname, no_quote, st); + } else { + session->mysql_variables->server_set_value(idx, session->mysql_variables->client_get_value(idx)); + ret = session->handler_again___status_SETTING_GENERIC_VARIABLE(&_rc, set_var_name, session->mysql_variables->server_get_value(idx), no_quote, st); + } return ret; } + + diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index 827d0fdda..c1a8fa85b 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -67,6 +67,8 @@ extern char *ssl_cert_fp; extern char *ssl_ca_fp; +MARIADB_CHARSET_INFO * proxysql_find_charset_name(const char *name); + static long get_file_size (const char *filename) { FILE *fp; @@ -5441,37 +5443,77 @@ void ProxySQL_Admin::flush_mysql_variables___database_to_runtime(SQLite3DB *db, GloMTH->wrlock(); for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { SQLite3_row *r=*it; - bool rc=GloMTH->set_variable(r->fields[0],r->fields[1]); - if (rc==false) { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - if (replace) { - char *val=GloMTH->get_variable(r->fields[0]); - char q[1000]; - if (val) { - if (strcmp(val,r->fields[1])) { - proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],r->fields[1], val); - sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-%s\",\"%s\")",r->fields[0],val); - db->execute(q); - } - free(val); - } else { - if (strcmp(r->fields[0],(char *)"session_debug")==0) { - sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"mysql-%s\"",r->fields[0]); - db->execute(q); + const char *value = r->fields[1]; + if (!strcasecmp(r->fields[0], "default_character_set_results") || !strcasecmp(r->fields[0], "default_character_set_client") || + !strcasecmp(r->fields[0], "default_character_set_database") || !strcasecmp(r->fields[0], "default_character_set_connection") || + !strcasecmp(r->fields[0], "default_charset")) { + const MARIADB_CHARSET_INFO *ci = NULL; + char q[1000]; + ci = proxysql_find_charset_name(value); + if (!ci) { + proxy_warning("The %s set to invalid value in the configuration file. Changing to default utf8\n", r->fields[0]); + sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-%s\",\"%s\")",r->fields[0],"utf8"); + db->execute(q); + value = "utf8"; + GloMTH->set_variable(r->fields[0],"utf8"); + } else { + GloMTH->set_variable(r->fields[0],ci->csname); + } + } else { + bool rc=GloMTH->set_variable(r->fields[0],value); + if (rc==false) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],value); + if (replace) { + char *val=GloMTH->get_variable(r->fields[0]); + char q[1000]; + if (val) { + if (strcmp(val,value)) { + proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],value, val); + sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-%s\",\"%s\")",r->fields[0],val); + db->execute(q); + } + free(val); } else { - proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0],r->fields[1]); + if (strcmp(r->fields[0],(char *)"session_debug")==0) { + sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"mysql-%s\"",r->fields[0]); + db->execute(q); + } else { + proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0],r->fields[1]); + } + sprintf(q,"DELETE FROM global_variables WHERE variable_name=\"mysql-%s\"",r->fields[0]); + db->execute(q); } - sprintf(q,"DELETE FROM global_variables WHERE variable_name=\"mysql-%s\"",r->fields[0]); - db->execute(q); + } + } else { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0],value); + if (strcmp(r->fields[0],(char *)"show_processlist_extended")==0) { + variables.mysql_show_processlist_extended = atoi(value); } } - } else { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - if (strcmp(r->fields[0],(char *)"show_processlist_extended")==0) { - variables.mysql_show_processlist_extended = atoi(r->fields[1]); - } } } + + const char* connection = GloMTH->get_variable_string((char *)"default_character_set_connection"); + const char* collation= GloMTH->get_variable_string((char *)"default_collation_connection"); + const MARIADB_CHARSET_INFO *ci = NULL; + char q[1000]; + ci = proxysql_find_charset_name(connection); + if (strcasecmp(ci->name, collation)) { + proxy_warning("Changing default_collation_connection to %s\n", ci->name); + bool rc=GloMTH->set_variable("default_collation_connection",ci->name); + sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-%s\",\"%s\")","default_collation_connection",ci->name); + GloMTH->set_variable("default_collation_connection",ci->name); + if (ci->nr == 45) { + rc=GloMTH->set_variable("default_collation_connection","utf8mb4_general_ci"); + db->execute("INSERT OR REPLACE INTO global_variables VALUES(\"mysql-default_collation_connection\",\"utf8mb4_general_ci\")"); + GloMTH->set_variable("default_collation_connection","utf8mb4_general_ci"); + } + } else { + GloMTH->set_variable("default_collation_connection",ci->name); + sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-%s\",\"%s\")","default_collation_connection",ci->name); + db->execute(q); + } + GloMTH->commit(); GloMTH->wrunlock(); } diff --git a/lib/mysql_connection.cpp b/lib/mysql_connection.cpp index c3ff461b6..6e85f21b6 100644 --- a/lib/mysql_connection.cpp +++ b/lib/mysql_connection.cpp @@ -2,19 +2,64 @@ #include "cpp.h" #include "SpookyV2.h" #include +#include #include "MySQL_PreparedStatement.h" #include "MySQL_Data_Stream.h" #include "query_processor.h" +#include "MySQL_Variables.h" extern const MARIADB_CHARSET_INFO * proxysql_find_charset_nr(unsigned int nr); +MARIADB_CHARSET_INFO * proxysql_find_charset_name(const char *name); void Variable::fill_server_internal_session(json &j, int conn_num, int idx) { - j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = std::string(value); + if (idx == SQL_CHARACTER_SET_RESULTS || idx == SQL_CHARACTER_SET_CONNECTION || + idx == SQL_CHARACTER_SET_CLIENT || idx == SQL_CHARACTER_SET_DATABASE) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_nr(atoi(value)); + if (!ci) { + proxy_error("Cannot find charset [%s] for variables %d\n", value, idx); + assert(0); + } + + j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = std::string((ci && ci->csname)?ci->csname:""); + } else if (idx == SQL_COLLATION_CONNECTION) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_nr(atoi(value)); + if (!ci) { + proxy_error("Cannot find charset [%s] for variable %d\n", value, idx); + assert(0); + } + + j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = std::string((ci && ci->name)?ci->name:""); + } else { + j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = std::string(value?value:""); + } } void Variable::fill_client_internal_session(json &j, int idx) { - j["conn"][mysql_tracked_variables[idx].internal_variable_name] = value; + if (idx == SQL_CHARACTER_SET_RESULTS || idx == SQL_CHARACTER_SET_CONNECTION || + idx == SQL_CHARACTER_SET_CLIENT || idx == SQL_CHARACTER_SET_DATABASE) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_nr(atoi(value)); + if (!ci) { + proxy_error("Cannot find charset [%s] for variables %d\n", value, idx); + assert(0); + } + + j["conn"][mysql_tracked_variables[idx].internal_variable_name] = (ci && ci->csname)?ci->csname:""; + + } else if (idx == SQL_COLLATION_CONNECTION) { + const MARIADB_CHARSET_INFO *ci = NULL; + ci = proxysql_find_charset_nr(atoi(value)); + if (!ci) { + assert(0); + } + + j["conn"][mysql_tracked_variables[idx].internal_variable_name] = (ci && ci->name)?ci->name:""; + } else { + j["conn"][mysql_tracked_variables[idx].internal_variable_name] = value?value:""; + } } #define PROXYSQL_USE_RESULT @@ -237,8 +282,6 @@ MySQL_Connection::MySQL_Connection() { // options.collation_connection_int=0; // options.net_write_timeout_int=0; // options.max_join_size_int=0; - options.charset=0; - options.charset_action=UNKNOWN; compression_pkt_id=0; mysql_result=NULL; query.ptr=NULL; @@ -343,10 +386,23 @@ bool MySQL_Connection::set_no_backslash_escapes(bool _ac) { return _ac; } +void print_backtrace(void); + unsigned int MySQL_Connection::set_charset(unsigned int _c, enum charset_action action) { proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 4, "Setting charset %d\n", _c); - options.charset=_c; - options.charset_action = action; + + // SQL_CHARACTER_SET should be set befor setting SQL_CHRACTER_ACTION + std::stringstream ss; + ss << _c; + myds->sess->mysql_variables->client_set_value(SQL_CHARACTER_SET, ss.str()); + + // When SQL_CHARACTER_ACTION is set character set variables are set according to + // SQL_CHRACTER_SET value + ss.str(std::string()); + ss.clear(); + ss << action; + myds->sess->mysql_variables->client_set_value(SQL_CHARACTER_ACTION, ss.str()); + return _c; } @@ -518,13 +574,25 @@ void MySQL_Connection::connect_start() { mysql_ssl_set(mysql, mysql_thread___ssl_p2s_key, mysql_thread___ssl_p2s_cert, mysql_thread___ssl_p2s_ca, NULL, mysql_thread___ssl_p2s_cipher); } unsigned int timeout= 1; + const char *csname = NULL; mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout); - const MARIADB_CHARSET_INFO * c = proxysql_find_charset_nr(mysql_thread___default_charset); + /* Take client character set and use it to connect to backend */ + if (myds && myds->sess) { + csname = myds->sess->mysql_variables->client_get_value(SQL_CHARACTER_SET); + } + + const MARIADB_CHARSET_INFO * c = NULL; + if (csname) + c = proxysql_find_charset_nr(atoi(csname)); + else + c = proxysql_find_charset_name(mysql_thread___default_variables[SQL_CHARACTER_SET]); + if (!c) { - proxy_error("Not existing charset number %u\n", mysql_thread___default_charset); + proxy_error("Not existing charset number %s\n", mysql_thread___default_variables[SQL_CHARACTER_SET]); assert(0); } - set_charset(c->nr, NAMES); + proxy_warning("TRACE : INITIAL ACTION client %s, server %s\n", myds->sess->mysql_variables->client_get_value(SQL_CHARACTER_ACTION), myds->sess->mysql_variables->server_get_value(SQL_CHARACTER_ACTION)); + set_charset(c->nr, CONNECT_START); mysql_options(mysql, MYSQL_SET_CHARSET_NAME, c->csname); unsigned long client_flags = 0; //if (mysql_thread___client_found_rows) @@ -652,12 +720,13 @@ void MySQL_Connection::set_autocommit_cont(short event) { void MySQL_Connection::set_names_start() { PROXY_TRACE(); - const MARIADB_CHARSET_INFO * c = proxysql_find_charset_nr(options.charset); + const MARIADB_CHARSET_INFO * c = proxysql_find_charset_nr(atoi(myds->sess->mysql_variables->client_get_value(SQL_CHARACTER_SET))); if (!c) { - proxy_error("Not existing charset number %u\n", options.charset); + proxy_error("Not existing charset number %u\n", atoi(myds->sess->mysql_variables->client_get_value(SQL_CHARACTER_SET))); assert(0); } - async_exit_status = mysql_set_character_set_start(&interr,mysql, NULL, options.charset); + proxy_warning("TRACE : START SET NAMES %s\n", myds->sess->mysql_variables->client_get_value(SQL_CHARACTER_SET)); + async_exit_status = mysql_set_character_set_start(&interr,mysql, NULL, atoi(myds->sess->mysql_variables->client_get_value(SQL_CHARACTER_SET))); } void MySQL_Connection::set_names_cont(short event) { @@ -679,6 +748,7 @@ void MySQL_Connection::set_query(char *stmt, unsigned long length) { void MySQL_Connection::real_query_start() { PROXY_TRACE(); async_exit_status = mysql_real_query_start(&interr , mysql, query.ptr, query.length); + proxy_warning("TRACE : START SET %s\n", query.ptr); } void MySQL_Connection::real_query_cont(short event) { @@ -1708,7 +1778,8 @@ int MySQL_Connection::async_set_names(short event, unsigned int c) { return -1; break; case ASYNC_IDLE: - set_charset(c, NAMES); + /* useless statement. should be removed after thorough testing */ + //set_charset(c, CONNECT_START); async_state_machine=ASYNC_SET_NAMES_START; default: handler(event); diff --git a/test/tap/tests/charset_unsigned_int-t.cpp b/test/tap/tests/charset_unsigned_int-t.cpp index 378d1d542..c423c6b1d 100644 --- a/test/tap/tests/charset_unsigned_int-t.cpp +++ b/test/tap/tests/charset_unsigned_int-t.cpp @@ -13,7 +13,7 @@ int main(int argc, char** argv) { CommandLine cl; - if(cl.parse(argc, argv)) + if(cl.getEnv()) return exit_status(); plan(6); @@ -29,6 +29,7 @@ int main(int argc, char** argv) { if (!mysqlAdmin) return exit_status(); if (!mysql_real_connect(mysqlAdmin, cl.host, "admin", "admin", NULL, 6032, NULL, 0)) return exit_status(); set_admin_global_variable(mysqlAdmin, "mysql-handle_unknown_charset", "1"); + set_admin_global_variable(mysqlAdmin, "mysql-default_charset", "utf8mb4"); if (mysql_query(mysqlAdmin, "load mysql variables to runtime")) return exit_status(); if (mysql_query(mysqlAdmin, "save mysql variables to disk")) return exit_status(); @@ -48,7 +49,7 @@ int main(int argc, char** argv) { if (version.data()[0] == '5') { ok(var_value.compare("utf8mb4_general_ci") == 0, "Backend is mysql version < 8.0. Actual collation %s", var_value.c_str()); // ok_2 } else { - ok(var_value.compare("utf8mb4_croatian_ci") == 0, "Backend is mysql version >= 8.0. Collation is set as expected to utf8mb4_croatian_ci"); // ok_2 + ok(var_value.compare("utf8mb4_croatian_ci") == 0, "Backend is mysql version >= 8.0. Actual collation %s",var_value.c_str()); // ok_2 } mysql_close(mysql); @@ -76,7 +77,9 @@ int main(int argc, char** argv) { mysql_close(mysql_a); - // Now default charset is utf8mb4 and new client connection should use it by default + //set_admin_global_variable(mysqlAdmin, "mysql-default_charset", "utf8mb4"); + //set_admin_global_variable(mysqlAdmin, "mysql-default_collation_connection", "latin1_swedish_ci"); + //if (mysql_query(mysqlAdmin, "load mysql variables to runtime")) return exit_status(); MYSQL* mysql_b = mysql_init(NULL); if (!mysql_b) return exit_status(); if (!mysql_real_connect(mysql_b, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) return exit_status(); @@ -94,6 +97,8 @@ int main(int argc, char** argv) { /* check initial options */ + //set_admin_global_variable(mysqlAdmin, "mysql-default_collation_connection", "utf8mb4_general_ci"); + //if (mysql_query(mysqlAdmin, "load mysql variables to runtime")) return exit_status(); MYSQL * mysql_c = mysql_init(NULL); if (!mysql_c) return exit_status(); if (mysql_options(mysql_c, MYSQL_SET_CHARSET_NAME, "utf8mb4")) return exit_status(); diff --git a/test/tap/tests/set_character_set-t.cpp b/test/tap/tests/set_character_set-t.cpp index 012f60619..69d0b3040 100644 --- a/test/tap/tests/set_character_set-t.cpp +++ b/test/tap/tests/set_character_set-t.cpp @@ -66,16 +66,16 @@ int main(int argc, char** argv) { std::string var_value; show_variable(mysql, var_charset_client, var_value); - ok(var_value.compare("utf8") == 0, "Initial client character set"); + ok(var_value.compare("utf8") == 0, "Initial client character set. Actual %s", var_value.c_str()); // ok_1 show_variable(mysql, var_charset_connection, var_value); - ok(var_value.compare("utf8") == 0, "Initial connection character set"); + ok(var_value.compare("utf8") == 0, "Initial connection character set. Actual %s", var_value.c_str()); // ok_2 show_variable(mysql, var_charset_results, var_value); - ok(var_value.compare("utf8") == 0, "Initial results character set"); + ok(var_value.compare("utf8") == 0, "Initial results character set. Actual %s", var_value.c_str()); // ok_3 show_variable(mysql, var_charset_database, var_value); - ok(var_value.compare("utf8") == 0, "Initial results character set"); + ok(var_value.compare("utf8") == 0, "Initial database character set. Actual %s", var_value.c_str()); // ok_4 if (mysql_query(mysql, "set character set latin1")) { fprintf(stderr, "SET CHARACTER SET : Error: %s\n", @@ -84,37 +84,37 @@ int main(int argc, char** argv) { } show_variable(mysql, var_charset_client, var_value); - ok(var_value.compare("latin1") == 0, "Client character set is changed"); + ok(var_value.compare("latin1") == 0, "Client character set is changed. Actual %s", var_value.c_str()); // ok_5 std::string db_charset_value; show_variable(mysql, var_charset_database, db_charset_value); show_variable(mysql, var_charset_database, var_value); - ok(var_value.compare("utf8") == 0, "Database character set is not changed"); + ok(var_value.compare("utf8") == 0, "Database character set is not changed. Actual %s", var_value.c_str()); // ok_6 show_variable(mysql, var_charset_connection, var_value); - ok(var_value.compare(db_charset_value) == 0, "Connection character set same as database charset"); + ok(var_value.compare(db_charset_value) == 0, "Connection character set same as database charset. Actual %s", var_value.c_str()); // ok_7 show_variable(mysql, var_charset_results, var_value); - ok(var_value.compare("latin1") == 0, "Results character set is changed"); + ok(var_value.compare("latin1") == 0, "Results character set is changed. Actual %s", var_value.c_str()); // ok_8 if (mysql_query(mysql, "set names latin1")) { - fprintf(stderr, "SET CHARACTER SET : Error: %s\n", + fprintf(stderr, "SET NAMES : Error: %s\n", mysql_error(mysql)); return exit_status(); } show_variable(mysql, var_charset_client, var_value); - ok(var_value.compare("latin1") == 0, "Client character set is correct"); + ok(var_value.compare("latin1") == 0, "Client character set is correct. Actual %s", var_value.c_str()); // ok_9 show_variable(mysql, var_charset_connection, var_value); - ok(var_value.compare("latin1") == 0, "Set names changed connection character set"); + ok(var_value.compare("latin1") == 0, "Set names changed connection character set. Actual %s", var_value.c_str()); // ok_10 show_variable(mysql, var_charset_results, var_value); - ok(var_value.compare("latin1") == 0, "Results character set is correct"); + ok(var_value.compare("latin1") == 0, "Results character set is correct. Actual %s", var_value.c_str()); // ok_11 show_variable(mysql, var_charset_database, var_value); - ok(var_value.compare("utf8") == 0, "Database character set is not changed by set names"); + ok(var_value.compare("utf8") == 0, "Database character set is not changed by set names. Actual %s", var_value.c_str()); // ok_12 mysql_close(mysql); diff --git a/test/tap/tests/set_testing-t.cpp b/test/tap/tests/set_testing-t.cpp index 1067ecf94..11170668b 100644 --- a/test/tap/tests/set_testing-t.cpp +++ b/test/tap/tests/set_testing-t.cpp @@ -17,6 +17,27 @@ #include "tap.h" #include "command_line.h" +#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)); \ + return exit_status(); \ + } \ + } while(0) + + +std::vector split(const std::string& s, char delimiter) +{ + std::vector tokens; + std::string token; + std::istringstream tokenStream(s); + while (std::getline(tokenStream, token, delimiter)) + { + tokens.push_back(token); + } + return tokens; +} using nlohmann::json; @@ -100,6 +121,7 @@ unsigned int connect_phase_completed = 0; unsigned int query_phase_completed = 0; __thread int g_seed; +std::mutex mtx_; inline int fastrand() { g_seed = (214013*g_seed+2531011); @@ -139,12 +161,14 @@ void dumpResult(MYSQL_RES *result) { } void queryVariables(MYSQL *mysql, json& j) { - char *query = (char*)"SELECT * FROM performance_schema.session_variables WHERE variable_name IN " + std::stringstream query; + query << "SELECT /* mysql " << mysql << " */ * FROM performance_schema.session_variables WHERE variable_name IN " " ('hostname', 'sql_log_bin', 'sql_mode', 'init_connect', 'time_zone', 'autocommit', 'sql_auto_is_null', " " 'sql_safe_updates', 'session_track_gtids', 'max_join_size', 'net_write_timeout', 'sql_select_limit', " " 'sql_select_limit', 'character_set_results', 'transaction_isolation', 'transaction_read_only', 'session_track_gtids', " - " 'sql_auto_is_null');"; - if (mysql_query(mysql, query)) { + " 'sql_auto_is_null', 'collation_connection', 'character_set_connection', 'character_set_client', 'character_set_database');"; + //fprintf(stderr, "TRACE : QUERY 3 : variables %s\n", query.str().c_str()); + if (mysql_query(mysql, query.str().c_str())) { if (silent==0) { fprintf(stderr,"%s\n", mysql_error(mysql)); } @@ -160,6 +184,7 @@ void queryVariables(MYSQL *mysql, json& j) { void queryInternalStatus(MYSQL *mysql, json& j) { char *query = (char*)"PROXYSQL INTERNAL SESSION"; + //fprintf(stderr, "TRACE : QUERY 4 : variables %s\n", query); if (mysql_query(mysql, query)) { if (silent==0) { fprintf(stderr,"%s\n", mysql_error(mysql)); @@ -300,8 +325,14 @@ void * my_conn_thread(void *arg) { if (mysqlconns==NULL) { exit(EXIT_FAILURE); } + + std::vector cs = {"latin1", "utf8", "utf8mb4", "latin2", "latin7"}; + for (i=0; i commands = split(testCases[r2].command.c_str(), ';'); + for (auto c : commands) { + if (mysql_query(mysql, c.c_str())) { + if (silent==0) { + fprintf(stderr,"%s\n", mysql_error(mysql)); + } + } else { + MYSQL_RES *result = mysql_store_result(mysql); + mysql_free_result(result); + select_OK++; + __sync_fetch_and_add(&g_select_OK,1); } - } else { - MYSQL_RES *result = mysql_store_result(mysql); - mysql_free_result(result); - select_OK++; - __sync_fetch_and_add(&g_select_OK,1); } + for (auto& el : testCases[r2].expected_vars.items()) { vars[el.key()] = el.value(); } @@ -349,7 +384,7 @@ void * my_conn_thread(void *arg) { usleep(sleepDelay * 1000); char query[128]; - sprintf(query, "SELECT %d;", sleepDelay); + sprintf(query, "SELECT /* %p */ %d;", mysql, sleepDelay); if (mysql_query(mysql,query)) { select_ERR++; __sync_fetch_and_add(&g_select_ERR,1); @@ -360,7 +395,6 @@ void * my_conn_thread(void *arg) { __sync_fetch_and_add(&g_select_OK,1); } - json mysql_vars; queryVariables(mysql, mysql_vars); @@ -385,15 +419,21 @@ void * my_conn_thread(void *arg) { testPassed = false; fprintf(stderr, "Test failed for this case %s->%s.\n\nmysql data %s\n\n proxysql data %s\n\n csv data %s\n\n\n", el.value().dump().c_str(), el.key().c_str(), mysql_vars.dump().c_str(), proxysql_vars.dump().c_str(), vars.dump().c_str()); + ok(testPassed, "mysql connection [%p], thread_id [%lu], command [%s]", mysql, mysql->thread_id, testCases[r2].command.c_str()); + exit(0); } } - ok(testPassed, "Test passed"); + { + std::lock_guard lock(mtx_); + ok(testPassed, "mysql connection [%p], thread_id [%lu], command [%s]", mysql, mysql->thread_id, testCases[r2].command.c_str()); + } } __sync_fetch_and_add(&query_phase_completed,1); return NULL; } + int main(int argc, char *argv[]) { CommandLine cl; std::string fileName("./tests/set_testing-t.csv"); @@ -401,6 +441,25 @@ int main(int argc, char *argv[]) { if(cl.getEnv()) return exit_status(); + MYSQL* mysqladmin = mysql_init(NULL); + if (!mysqladmin) + return exit_status(); + + if (!mysql_real_connect(mysqladmin, 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(mysqladmin)); + return exit_status(); + } + + MYSQL_QUERY(mysqladmin, "update global_variables set variable_value='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION' where variable_name='mysql-default_sql_mode'"); + MYSQL_QUERY(mysqladmin, "update global_variables set variable_value='OFF' where variable_name='mysql-default_sql_safe_update'"); + MYSQL_QUERY(mysqladmin, "update global_variables set variable_value='UTF8' where variable_name='mysql-default_character_set_results'"); + MYSQL_QUERY(mysqladmin, "update global_variables set variable_value='REPEATABLE READ' where variable_name='mysql-default_isolation_level'"); + MYSQL_QUERY(mysqladmin, "update global_variables set variable_value='REPEATABLE READ' where variable_name='mysql-default_tx_isolation'"); + MYSQL_QUERY(mysqladmin, "update global_variables set variable_value='utf8_general_ci' where variable_name='mysql-default_collation_connection'"); + MYSQL_QUERY(mysqladmin, "update global_variables set variable_value='true' where variable_name='mysql-enforce_autocommit_on_reads'"); + MYSQL_QUERY(mysqladmin, "load mysql variables to runtime"); + num_threads = 10; queries = 1000; queries_per_connections = 10; diff --git a/test/tap/tests/set_testing-t.csv b/test/tap/tests/set_testing-t.csv index 8b1ae4497..be2417729 100644 --- a/test/tap/tests/set_testing-t.csv +++ b/test/tap/tests/set_testing-t.csv @@ -1,17 +1,48 @@ +"set character_set_results='utf8'; set names latin7; set character_set_client='utf8mb4';", "{'character_set_results':'latin7', 'collation_connection':'latin7_general_ci', 'character_set_connection':'latin7', 'character_set_client':'utf8mb4'}" +"set character_set_client='utf8mb4'; set charset utf8; set character_set_connection='latin1';", "{'character_set_results':'utf8', 'collation_connection':'latin1_swedish_ci', 'character_set_connection':'latin1', 'character_set_client':'utf8'}" +"set character_set_database='utf8'; set character_set_connection='utf8mb4'; set charset utf8; set character_set_client='latin1';", "{'character_set_database':'utf8', 'character_set_results':'utf8', 'collation_connection':'utf8_general_ci', 'character_set_connection':'utf8', 'character_set_client':'latin1'}" +"set character_set_connection='utf8mb4'; set charset utf8; set character_set_client='latin1'; set collation_connection='latin1_swedish_ci';", "{'character_set_results':'utf8', 'collation_connection':'latin1_swedish_ci', 'character_set_connection':'latin1', 'character_set_client':'latin1'}""set names latin7", "{'character_set_results':'latin7', 'collation_connection':'latin7_general_ci', 'character_set_connection':'latin7', 'character_set_client':'latin7'}" +"set names latin2", "{'character_set_results':'latin2', 'collation_connection':'latin2_general_ci', 'character_set_connection':'latin2', 'character_set_client':'latin2'}" +"set names utf8mb4", "{'character_set_results':'utf8mb4', 'collation_connection':'utf8mb4_general_ci', 'character_set_connection':'utf8mb4', 'character_set_client':'utf8mb4'}" +"set names utf8; set character_set_connection='utf8mb4'; set character_set_database='latin1'; set charset utf8;", "{'character_set_results':'utf8', 'collation_connection':'latin1_swedish_ci', 'character_set_connection':'latin1', 'character_set_client':'utf8', 'character_set_database':'latin1'}" +"set character_set_client='utf8mb4'; set names utf8; set character_set_connection='latin1';", "{'character_set_results':'utf8', 'collation_connection':'latin1_swedish_ci', 'character_set_connection':'latin1', 'character_set_client':'utf8'}" +"set character_set_database='latin1'; set character_set_connection='utf8mb4'; set names utf8; set character_set_client='latin1';", "{'character_set_database':'latin1', 'character_set_results':'utf8', 'collation_connection':'utf8_general_ci', 'character_set_connection':'utf8', 'character_set_client':'latin1'}" +"set character_set_connection='utf8mb4'; set names utf8; set character_set_client='latin1'; set collation_connection='latin1_swedish_ci';", "{'character_set_results':'utf8', 'collation_connection':'latin1_swedish_ci', 'character_set_connection':'latin1', 'character_set_client':'latin1'}""set names latin7", "{'character_set_results':'latin7', 'collation_connection':'latin7_general_ci', 'character_set_connection':'latin7', 'character_set_client':'latin7'}" +"set collation_connection='utf8mb4_general_ci'", "{'collation_connection':'utf8mb4_general_ci', 'character_set_connection':'utf8mb4'}" +"set collation_connection='latin1_danish_ci'", "{'collation_connection':'latin1_danish_ci', 'character_set_connection':'latin1'}" +"set collation_connection='latin1_swedish_ci'", "{'collation_connection':'latin1_swedish_ci', 'character_set_connection':'latin1'}" +"set collation_connection='utf8_general_ci'", "{'collation_connection':'utf8_general_ci', 'character_set_connection':'utf8'}" +"SET collation_connection='latin1_swedish_ci'", "{'character_set_connection':'latin1', 'collation_connection':'latin1_swedish_ci'}" +"SET collation_connection='utf8_general_ci'", "{'character_set_connection':'utf8', 'collation_connection':'utf8_general_ci'}" +"SET collation_connection='utf8mb4_general_ci'", "{'character_set_connection':'utf8mb4', 'collation_connection':'utf8mb4_general_ci'}" +"SET character_set_connection='latin1'", "{'character_set_connection':'latin1', 'collation_connection':'latin1_swedish_ci'}" +"SET character_set_connection='utf8'", "{'character_set_connection':'utf8', 'collation_connection':'utf8_general_ci'}" +"SET character_set_connection='utf8mb4'", "{'character_set_connection':'utf8mb4', 'collation_connection':'utf8mb4_general_ci'}" +"SET character_set_results='latin1'", "{'character_set_results':'latin1'}" +"SET character_set_results='utf8'", "{'character_set_results':'utf8'}" +"SET character_set_results='utf8mb4'", "{'character_set_results':'utf8mb4'}" +"SET character_set_client='latin1'", "{'character_set_client':'latin1'}" +"SET character_set_client='utf8'", "{'character_set_client':'utf8'}" +"SET character_set_client='utf8mb4'", "{'character_set_client':'utf8mb4'}" +"SET character_set_database='latin1'", "{'character_set_database':'latin1'}" +"SET character_set_database='utf8'", "{'character_set_database':'utf8'}" +"SET character_set_database='utf8mb4'", "{'character_set_database':'utf8mb4'}" +"SET character_set_results='latin1'", "{'character_set_results':'latin1'}" +"SET character_set_results='utf8'", "{'character_set_results':'utf8'}" +"SET character_set_results='utf8mb4'", "{'character_set_results':'utf8mb4'}" "set sql_mode=''","{'sql_mode':''}" "SET sql_mode='PIPES_AS_CONCAT,NO_ENGINE_SUBSTITUTION'", "{'sql_mode':'PIPES_AS_CONCAT,NO_ENGINE_SUBSTITUTION'}" -"SET sql_log_bin=0", "{'sql_log_bin':'OFF'}" -"SET sql_log_bin=1", "{'sql_log_bin':'ON'}" "SET time_zone='+01:00'","{'time_zone':'+01:00'}" "SET time_zone='-03:00', sql_mode='ALLOW_INVALID_DATES'","{'time_zone':'-03:00', 'sql_mode':'ALLOW_INVALID_DATES'}" "SET autocommit=0","{'autocommit':'OFF'}" "SET time_zone='+04:00', sql_mode='NO_ENGINE_SUBSTITUTION'", "{'time_zone':'+04:00','sql_mode':'NO_ENGINE_SUBSTITUTION'}" +"SET sql_safe_updates='OFF'", "{'sql_safe_updates':'OFF'}" +"SET sql_safe_updates='ON'", "{'sql_safe_updates':'ON'}" "SET sql_safe_updates=0", "{'sql_safe_updates':'OFF'}" "SET sql_safe_updates=1", "{'sql_safe_updates':'ON'}" "SET sql_select_limit=1010", "{'sql_select_limit':'1010'}" -"SET character_set_results='latin1'", "{'character_set_results':'latin1'}" -"SET character_set_results='utf8'", "{'character_set_results':'utf8'}" -"SET character_set_results='utf8mb4'", "{'character_set_results':'utf8mb4'}" +"SET sql_select_limit=2020", "{'sql_select_limit':'2020'}" +"SET sql_select_limit=3030", "{'sql_select_limit':'3030'}" "SET session transaction read only", "{'transaction_read_only':'ON'}" "SET session transaction read write", "{'transaction_read_only':'OFF'}" "SET session transaction isolation level READ COMMITTED", "{'transaction_isolation':'READ-COMMITTED'}" @@ -26,4 +57,9 @@ "SET session_track_gtids=OWN_GTID", "{'session_track_gtids':'OWN_GTID'}" "SET sql_auto_is_null=OFF", "{'sql_auto_is_null':'OFF'}" "SET sql_auto_is_null=ON", "{'sql_auto_is_null':'ON'}" - +"set net_write_timeout=30", "{'net_write_timeout':'30'}" +"set net_write_timeout=60", "{'net_write_timeout':'60'}" +"set net_write_timeout=90", "{'net_write_timeout':'90'}" +"set max_join_size=20000", "{'max_join_size':'20000'}" +"set max_join_size=10000", "{'max_join_size':'10000'}" +"set max_join_size=18446744073709551615", "{'max_join_size':'18446744073709551615'}"