From e958946b82781bb75a4ce23a99c4bc4348bd0980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Canna=C3=B2?= Date: Thu, 18 Jan 2024 03:55:46 +0000 Subject: [PATCH] Handle STATUS command without backends Handles special queries executed by the STATUS command in mysql cli . Specifically: - "select DATABASE(), USER() limit 1" - "select @@character_set_client, @@character_set_connection, @@character_set_server, @@character_set_database limit 1" Closes #4396 Closes #4426 --- include/MySQL_Session.h | 1 + include/sqlite3db.h | 1 + lib/MySQL_Session.cpp | 79 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index 22cf599717..5e0752fecc 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -133,6 +133,7 @@ class MySQL_Session void return_proxysql_internal(PtrSize_t *); bool handler_special_queries(PtrSize_t *); + bool handler_special_queries_STATUS(PtrSize_t *); /** * @brief Handles 'COMMIT|ROLLBACK' commands. * @details Forwarding the packet is required when there are active transactions. Since we are limited to diff --git a/include/sqlite3db.h b/include/sqlite3db.h index 977a8cc296..9aa2ac5f85 100644 --- a/include/sqlite3db.h +++ b/include/sqlite3db.h @@ -155,6 +155,7 @@ class SQLite3_result { void add_column_definition(int a, const char *b); int add_row(sqlite3_stmt *stmt, bool skip=false); int add_row(char **_fields); + int add_row(const char **_fields) { return add_row((char **)_fields); } int add_row(SQLite3_row *old_row); int add_row(const char* _field, ...); SQLite3_result(sqlite3_stmt *stmt); diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 61ea7d08eb..265696ce90 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -25,7 +25,10 @@ #define SELECT_VERSION_COMMENT "select @@version_comment limit 1" #define SELECT_VERSION_COMMENT_LEN 32 - +#define SELECT_DB_USER "select DATABASE(), USER() limit 1" +#define SELECT_DB_USER_LEN 33 +#define SELECT_CHARSET_STATUS "select @@character_set_client, @@character_set_connection, @@character_set_server, @@character_set_database limit 1" +#define SELECT_CHARSET_STATUS_LEN 115 #define PROXYSQL_VERSION_COMMENT "\x01\x00\x00\x01\x01\x27\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x11\x40\x40\x76\x65\x72\x73\x69\x6f\x6e\x5f\x63\x6f\x6d\x6d\x65\x6e\x74\x00\x0c\x21\x00\x18\x00\x00\x00\xfd\x00\x00\x1f\x00\x00\x05\x00\x00\x03\xfe\x00\x00\x02\x00\x0b\x00\x00\x04\x0a(ProxySQL)\x05\x00\x00\x05\xfe\x00\x00\x02\x00" #define PROXYSQL_VERSION_COMMENT_LEN 81 @@ -1320,9 +1323,83 @@ void MySQL_Session::return_proxysql_internal(PtrSize_t *pkt) { l_free(pkt->size,pkt->ptr); } +/** + * @brief Handles special queries executed by the STATUS command in mysql cli . + * Specifically: + * "select DATABASE(), USER() limit 1" + * "select @@character_set_client, @@character_set_connection, @@character_set_server, @@character_set_database limit 1" + * See Github issues 4396 and 4426 + * + * @param PtrSize_t The packet from the client + * + * @return True if the queries are handled + */ +bool MySQL_Session::handler_special_queries_STATUS(PtrSize_t *pkt) { + if (pkt->size == (SELECT_DB_USER_LEN+5)) { + if (strncasecmp(SELECT_DB_USER,(char *)pkt->ptr+5, SELECT_DB_USER_LEN)==0) { + SQLite3_result *resultset = new SQLite3_result(2); + resultset->add_column_definition(SQLITE_TEXT,"DATABASE()"); + resultset->add_column_definition(SQLITE_TEXT,"USER()"); + char *pta[2]; + pta[0] = client_myds->myconn->userinfo->username; + pta[1] = client_myds->myconn->userinfo->schemaname; + resultset->add_row(pta); + bool deprecate_eof_active = client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF; + SQLite3_to_MySQL(resultset, NULL, 0, &client_myds->myprot, false, deprecate_eof_active); + delete resultset; + l_free(pkt->size,pkt->ptr); + return true; + } + } + if (pkt->size == (SELECT_CHARSET_STATUS_LEN+5)) { + if (strncasecmp(SELECT_CHARSET_STATUS,(char *)pkt->ptr+5, SELECT_CHARSET_STATUS_LEN)==0) { + SQLite3_result *resultset = new SQLite3_result(4); + resultset->add_column_definition(SQLITE_TEXT,"@@character_set_client"); + resultset->add_column_definition(SQLITE_TEXT,"@@character_set_connection"); + resultset->add_column_definition(SQLITE_TEXT,"@@character_set_server"); + resultset->add_column_definition(SQLITE_TEXT,"@@character_set_database"); + + string vals[4]; + json j = {}; + MySQL_Connection *conn = client_myds->myconn; + // here we do a bit back and forth to and from JSON to reuse existing code instead of writing new code. + // This is not great for performance, but this query is rarely executed. + conn->variables[SQL_CHARACTER_SET_CLIENT].fill_client_internal_session(j, SQL_CHARACTER_SET_CLIENT); + conn->variables[SQL_CHARACTER_SET_CONNECTION].fill_client_internal_session(j, SQL_CHARACTER_SET_CONNECTION); + conn->variables[SQL_CHARACTER_SET_DATABASE].fill_client_internal_session(j, SQL_CHARACTER_SET_DATABASE); + + vals[0] = j["conn"][mysql_tracked_variables[SQL_CHARACTER_SET_CLIENT].internal_variable_name]; + vals[1] = j["conn"][mysql_tracked_variables[SQL_CHARACTER_SET_CONNECTION].internal_variable_name]; + vals[2] = string(mysql_thread___default_variables[SQL_CHARACTER_SET]); + vals[3] = j["conn"][mysql_tracked_variables[SQL_CHARACTER_SET_DATABASE].internal_variable_name]; + + const char *pta[4]; + for (int i=0; i<4; i++) { + pta[i] = vals[i].c_str(); + } + resultset->add_row(pta); + bool deprecate_eof_active = client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF; + SQLite3_to_MySQL(resultset, NULL, 0, &client_myds->myprot, false, deprecate_eof_active); + delete resultset; + l_free(pkt->size,pkt->ptr); + return true; + } + } + return false; +} + bool MySQL_Session::handler_special_queries(PtrSize_t *pkt) { bool deprecate_eof_active = client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF; + if ( + (pkt->size == (SELECT_DB_USER_LEN+5)) + || + (pkt->size == (SELECT_CHARSET_STATUS_LEN+5)) + ) { + if (handler_special_queries_STATUS(pkt) == true) { + return true; + } + } if (pkt->size>(5+18) && strncasecmp((char *)"PROXYSQL INTERNAL ",(char *)pkt->ptr+5,18)==0) { return_proxysql_internal(pkt); return true;