diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index 745f7b35b..e1ce3d8fa 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -72,6 +72,7 @@ class MySQL_Session void handler___client_DSS_QUERY_SENT___send_CHANGE_USER_to_backend(); void handler___client_DSS_QUERY_SENT___send_SET_NAMES_to_backend(); + bool handler_special_queries(PtrSize_t *); // void return_MySQL_Connection_To_Poll(MySQL_Data_Stream *); diff --git a/include/mysql_connection.h b/include/mysql_connection.h index 834f10efe..702fd0d87 100644 --- a/include/mysql_connection.h +++ b/include/mysql_connection.h @@ -105,6 +105,7 @@ class MySQL_Connection { int async_connect(short event); int async_change_user(short event); int async_select_db(short event); + int async_set_names(short event, uint8_t nr); int async_query(short event, char *stmt, unsigned long length); int async_ping(short event); void async_free_result(); diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 5f082d5c5..f2847ec44 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -8,6 +8,8 @@ #define EXPMARIA +extern const CHARSET_INFO * proxysql_find_charset_name(const char * const name); + extern MySQL_Authentication *GloMyAuth; class KillArgs { @@ -253,6 +255,40 @@ void MySQL_Session::writeout() { proxy_debug(PROXY_DEBUG_NET,1,"Thread=%p, Session=%p -- Writeout Session %p\n" , this->thread, this, this); } +bool MySQL_Session::handler_special_queries(PtrSize_t *pkt) { + if (pkt->size==SELECT_VERSION_COMMENT_LEN+5 && strncmp((char *)SELECT_VERSION_COMMENT,(char *)pkt->ptr+5,pkt->size-5)==0) { + PtrSize_t pkt_2; + pkt_2.size=PROXYSQL_VERSION_COMMENT_LEN; + pkt_2.ptr=l_alloc(pkt_2.size); + memcpy(pkt_2.ptr,PROXYSQL_VERSION_COMMENT,pkt_2.size); + status=WAITING_CLIENT_DATA; + client_myds->DSS=STATE_SLEEP; + client_myds->PSarrayOUT->add(pkt_2.ptr,pkt_2.size); + l_free(pkt->size,pkt->ptr); + return true; + } + if ( (pkt->size < 25) && (pkt->size > 15) && (strncasecmp((char *)"SET NAMES ",(char *)pkt->ptr+5,10)==0) ) { + char *name=strndup((char *)pkt->ptr+15,pkt->size-15); + const CHARSET_INFO * c = proxysql_find_charset_name(name); + client_myds->DSS=STATE_QUERY_SENT_NET; + if (!c) { + char *m=(char *)"Unknown character set: '%s'"; + char *errmsg=(char *)malloc(strlen(name)+strlen(m)); + sprintf(errmsg,m,name); + client_myds->myprot.generate_pkt_ERR(true,NULL,NULL,1,1115,(char *)"#42000",errmsg); + free(errmsg); + } else { + client_myds->myconn->set_charset(c->nr); + client_myds->myprot.generate_pkt_OK(true,NULL,NULL,1,0,0,0,0,NULL); + } + client_myds->DSS=STATE_SLEEP; + status=WAITING_CLIENT_DATA; + l_free(pkt->size,pkt->ptr); + free(name); + return true; + } + return false; +} int MySQL_Session::handler() { bool wrong_pass=false; @@ -377,6 +413,7 @@ __get_pkts_from_client: } #endif /* DEBUG */ if (admin==false) { + bool rc_break=false; if (session_fast_forward==false) { if (mysql_thread___commands_stats==true) { CurrentQuery.init((unsigned char *)pkt.ptr,pkt.size,true); @@ -387,6 +424,11 @@ __get_pkts_from_client: //client_myds->myprot.process_pkt_COM_QUERY((unsigned char *)pkt.ptr,pkt.size); } } + rc_break=handler_special_queries(&pkt); + if (rc_break==true) { + break; + } +/* //if (strncmp((char *)"select @@version_comment limit 1",(char *)pkt.ptr+5,pkt.size-5)==0) { if (pkt.size==SELECT_VERSION_COMMENT_LEN+5 && strncmp((char *)SELECT_VERSION_COMMENT,(char *)pkt.ptr+5,pkt.size-5)==0) { PtrSize_t pkt_2; @@ -400,9 +442,10 @@ __get_pkts_from_client: l_free(pkt.size,pkt.ptr); break; } +*/ qpo=GloQPro->process_mysql_query(this,pkt.ptr,pkt.size,false); if (qpo) { - bool rc_break=handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(&pkt); + rc_break=handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_qpo(&pkt); if (rc_break==true) { break; } } mybe=find_or_create_backend(current_hostgroup); @@ -639,6 +682,10 @@ handler_again: NEXT_IMMEDIATE(CHANGING_SCHEMA); } } + if (client_myds->myconn->options.charset != mybe->server_myds->myconn->mysql->charset->nr) { + previous_status.push(PROCESSING_QUERY); + NEXT_IMMEDIATE(CHANGING_CHARSET); + } status=PROCESSING_QUERY; mybe->server_myds->max_connect_time=0; // we insert it in mypolls only if not already there @@ -826,6 +873,62 @@ handler_again: } break; + case CHANGING_CHARSET: + //fprintf(stderr,"CHANGING_SCHEMA\n"); + assert(mybe->server_myds->myconn); + { + MySQL_Data_Stream *myds=mybe->server_myds; + MySQL_Connection *myconn=myds->myconn; + myds->DSS=STATE_MARIADB_QUERY; + enum session_status st=status; + 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) { + st=previous_status.top(); + previous_status.pop(); + NEXT_IMMEDIATE(st); + } else { + if (rc==-1) { + // the command failed + int myerr=mysql_errno(myconn->mysql); + if (myerr > 2000) { + bool retry_conn=false; + // client error, serious + proxy_error("Detected a broken connection during SET NAMES: %d, %s\n", myerr, mysql_error(myconn->mysql)); + //if ((myds->myconn->reusable==true) && ((myds->myprot.prot_status & SERVER_STATUS_IN_TRANS)==0)) { + if ((myds->myconn->reusable==true) && myds->myconn->IsActiveTransaction()==false) { + retry_conn=true; + } + myds->destroy_MySQL_Connection_From_Pool(); + myds->fd=0; + if (retry_conn) { + myds->DSS=STATE_NOT_INITIALIZED; + //previous_status.push(PROCESSING_QUERY); + NEXT_IMMEDIATE(CONNECTING_SERVER); + } + return -1; + } 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(); + myds->fd=0; + status=WAITING_CLIENT_DATA; + client_myds->DSS=STATE_SLEEP; + } + } else { + // rc==1 , nothing to do for now + } + } + } + break; + case CHANGING_SCHEMA: //fprintf(stderr,"CHANGING_SCHEMA\n"); assert(mybe->server_myds->myconn); @@ -1164,6 +1267,7 @@ __exit_DSS__STATE_NOT_INITIALIZED: */ break; case CHANGING_CHARSET: +/* if (myds->revents) { myconn->handler(myds->revents); if (myconn->async_state_machine==ASYNC_SET_NAMES_SUCCESSFUL) { @@ -1176,7 +1280,7 @@ __exit_DSS__STATE_NOT_INITIALIZED: #else myds->DSS=STATE_READY; status=WAITING_CLIENT_DATA; -#endif /* EXPMARIA */ +#endif // EXPMARIA unsigned int k; PtrSize_t pkt2; for (k=0; kserver_myds->PSarrayOUTpending->len;) { @@ -1191,6 +1295,7 @@ __exit_DSS__STATE_NOT_INITIALIZED: return -1; } } +*/ break; default: assert(0); diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index 01c851beb..c2eeab532 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -420,7 +420,7 @@ char * MySQL_Threads_Handler::get_variable(char *name) { // this is the public f if (!strcasecmp(name,"default_charset")) { const CHARSET_INFO *c = proxysql_find_charset_nr(variables.default_charset); if (!c) { - proxy_error("Not existing charset number %u\n"); + proxy_error("Not existing charset number %u\n", variables.default_charset); assert(c); } return strdup(c->csname); diff --git a/lib/mysql_connection.cpp b/lib/mysql_connection.cpp index adc25b7f4..58c4c7c55 100644 --- a/lib/mysql_connection.cpp +++ b/lib/mysql_connection.cpp @@ -2,6 +2,9 @@ #include "cpp.h" #include "SpookyV2.h" +extern const CHARSET_INFO * proxysql_find_charset_nr(unsigned int nr); + + // Bug https://mariadb.atlassian.net/browse/CONC-136 //int STDCALL mysql_select_db_start(int *ret, MYSQL *mysql, const char *db); //int STDCALL mysql_select_db_cont(int *ret, MYSQL *mysql, int ready_status); @@ -243,6 +246,12 @@ void MySQL_Connection::connect_start() { mysql=mysql_init(NULL); assert(mysql); mysql_options(mysql, MYSQL_OPT_NONBLOCK, 0); + const CHARSET_INFO * c = proxysql_find_charset_nr(mysql_thread___default_charset); + if (!c) { + proxy_error("Not existing charset number %u\n", mysql_thread___default_charset); + assert(0); + } + mysql_options(mysql, MYSQL_SET_CHARSET_NAME, c->csname); if (parent->port) { async_exit_status=mysql_real_connect_start(&ret_mysql, mysql, parent->address, userinfo->username, userinfo->password, userinfo->schemaname, parent->port, NULL, 0); } else { @@ -294,7 +303,12 @@ void MySQL_Connection::initdb_cont(short event) { // FIXME: UTF8 is hardcoded for now, needs to be dynamic void MySQL_Connection::set_names_start() { PROXY_TRACE(); - async_exit_status = mysql_set_character_set_start(&interr,mysql,"UTF8"); + const CHARSET_INFO * c = proxysql_find_charset_nr(options.charset); + if (!c) { + proxy_error("Not existing charset number %u\n", options.charset); + assert(0); + } + async_exit_status = mysql_set_character_set_start(&interr,mysql, c->csname); } void MySQL_Connection::set_names_cont(short event) { @@ -740,6 +754,42 @@ int MySQL_Connection::async_select_db(short event) { return 1; } +int MySQL_Connection::async_set_names(short event, uint8_t c) { + PROXY_TRACE(); + assert(mysql); + assert(ret_mysql); + switch (async_state_machine) { + case ASYNC_SET_NAMES_SUCCESSFUL: + async_state_machine=ASYNC_IDLE; + return 0; + break; + case ASYNC_SET_NAMES_FAILED: + return -1; + break; + case ASYNC_IDLE: + set_charset(c); + async_state_machine=ASYNC_SET_NAMES_START; + default: + handler(event); + break; + } + + // check again + switch (async_state_machine) { + case ASYNC_SET_NAMES_SUCCESSFUL: + async_state_machine=ASYNC_IDLE; + return 0; + break; + case ASYNC_SET_NAMES_FAILED: + return -1; + break; + default: + return 1; + break; + } + return 1; +} + void MySQL_Connection::async_free_result() { PROXY_TRACE(); assert(mysql);