Character set handling implementation

pull/2650/head
Valentin Rakush 6 years ago committed by val
parent c28deb7c7d
commit 61c1913334

@ -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<enum session_status> previous_status;
void * operator new(size_t);
void operator delete(void *);

@ -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);

@ -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);

@ -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;

@ -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" } ,
};

@ -6,6 +6,9 @@
#include "MySQL_Data_Stream.h"
#include "MySQL_Authentication.hpp"
#include "MySQL_LDAP_Authentication.hpp"
#include "MySQL_Variables.h"
#include <sstream>
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) {

@ -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<int> 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<int> 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<int> 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

@ -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; i<SQL_NAME_LAST; i++) {
char buf[128];
sprintf(buf, "default_%s", mysql_tracked_variables[i].internal_variable_name);
@ -2589,19 +2589,6 @@ bool MySQL_Threads_Handler::set_variable(char *name, char *value) { // this is t
return false;
}
}
if (!strcasecmp(name,"default_charset")) {
if (vallen) {
MARIADB_CHARSET_INFO * c=proxysql_find_charset_name(value);
if (c) {
variables.default_charset=c->nr;
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;i<l+SQL_NAME_LAST;i++) {
ret[i]=(i == l+SQL_NAME_LAST-1 ? NULL : strdup(mysql_thread_variables_names[i]));
ret[i]=(i == l+SQL_NAME_LAST-1 ? NULL : strdup(mysql_thread_variables_names[i-SQL_NAME_LAST]));
}
return ret;
}
@ -3309,8 +3296,35 @@ MySQL_Session * MySQL_Thread::create_new_session_and_client_data_stream(int _fd)
sess->client_myds->myprot.init(&sess->client_myds, sess->client_myds->myconn->userinfo, sess);
proxy_warning("TRACE : new_client\n");
for (int i=0; i<SQL_NAME_LAST; i++) {
sess->mysql_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");

@ -5,6 +5,11 @@
#include "MySQL_Data_Stream.h"
#include "SpookyV2.h"
#include <sstream>
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; i<SQL_NAME_LAST; i++) {
@ -126,29 +128,46 @@ bool MySQL_Variables::update_variable(session_status status, int &_rc) {
}
}
assert(idx != SQL_NAME_LAST);
updaters[idx]->update_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;
}

@ -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<SQLite3_row *>::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();
}

@ -2,19 +2,64 @@
#include "cpp.h"
#include "SpookyV2.h"
#include <fcntl.h>
#include <sstream>
#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);

@ -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();

@ -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);

@ -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<std::string> split(const std::string& s, char delimiter)
{
std::vector<std::string> 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<std::string> cs = {"latin1", "utf8", "utf8mb4", "latin2", "latin7"};
for (i=0; i<count; i++) {
MYSQL *mysql=mysql_init(NULL);
std::string nextcs = cs[i%cs.size()];
mysql_options(mysql, MYSQL_SET_CHARSET_NAME, nextcs.c_str());
if (mysql==NULL) {
exit(EXIT_FAILURE);
}
@ -331,16 +362,20 @@ void * my_conn_thread(void *arg) {
vars = varsperconn[r1];
}
if (mysql_query(mysql, testCases[r2].command.c_str())) {
if (silent==0) {
fprintf(stderr,"%s\n", mysql_error(mysql));
std::vector<std::string> 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<std::mutex> 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;

@ -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'}"

Can't render this file because it has a wrong number of fields in line 4.
Loading…
Cancel
Save