Skip to content

Commit

Permalink
Fix Admin support for MySQL client '8.1.0' - Closes #4300
Browse files Browse the repository at this point in the history
MySQL client check command for dollars quote support ('SELECT $$'), is
now intercepted in both MySQL and Admin interfaces. The error reported
to the client is dependent of 'mysql-server_version' variable value.
  • Loading branch information
JavierJF committed Jul 31, 2023
1 parent c640f1b commit ecf4f69
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 3 deletions.
2 changes: 1 addition & 1 deletion include/proxysql_admin.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ class ProxySQL_Admin {
void admin_shutdown();
bool is_command(std::string);
void send_MySQL_OK(MySQL_Protocol *myprot, char *msg, int rows=0);
void send_MySQL_ERR(MySQL_Protocol *myprot, char *msg);
void send_MySQL_ERR(MySQL_Protocol *myprot, char *msg, uint32_t code=1045);
#ifdef DEBUG
// these two following functions used to just call and return one function each
// this approach was replaced when we introduced debug filters
Expand Down
35 changes: 35 additions & 0 deletions include/proxysql_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,39 @@ std::string replace_str(const std::string& str, const std::string& match, const
std::string generate_multi_rows_query(int rows, int params);

void close_all_non_term_fd(std::vector<int> excludeFDs);

/**
* @brief Suggested implementation of 'mismatch_' from ['cppreference'](https://en.cppreference.com/w/cpp/algorithm/mismatch).
*
* @param first1 begin of the first range of the elements.
* @param last1 end of the first range of the elements.
* @param first2 begin of the second range of the elements.
* @param last2 end of the second range of the elements.
* @param p binary predicate which returns `true` if the elements should be treated as equal.
*
* @return std::pair with iterators to the first two non-equal elements.
*/
template<class InputIt1, class InputIt2, class BinaryPredicate>
std::pair<InputIt1, InputIt2> mismatch_(
InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPredicate p
) {
while (first1 != last1 && first2 != last2 && p(*first1, *first2)) {
++first1, ++first2;
}
return std::make_pair(first1, first2);
}

/**
* @brief Returns a sorted copy in ascending order of the supplied version numbers.
* @details Expected version numbers formats are: ['N', 'N.N', 'N.N.N', ...]
*/
std::vector<std::string> sort_versions(std::vector<std::string> versions);

/**
* @brief Returns the expected error for query 'SELECT $$'.
* @param version The 'server_version' for which the error should match.
* @return A pair of the shape '{err_code,err_msg}'.
*/
std::pair<int,const char*> get_dollar_quote_error(const char* version);

#endif
16 changes: 16 additions & 0 deletions lib/MySQL_Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,22 @@ bool MySQL_Session::handler_special_queries(PtrSize_t *pkt) {
l_free(pkt->size,pkt->ptr);
return true;
}
// MySQL client check command for dollars quote support, starting at version '8.1.0'. See #4300.
if ((pkt->size == strlen("SELECT $$") + 5) && strncasecmp("SELECT $$", (char*)pkt->ptr + 5, pkt->size - 5) == 0) {
pair<int,const char*> err_info { get_dollar_quote_error(mysql_thread___server_version) };

client_myds->DSS=STATE_QUERY_SENT_NET;
client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, err_info.first, (char *)"HY000", err_info.second, true);
client_myds->DSS=STATE_SLEEP;
status=WAITING_CLIENT_DATA;

if (mirror==false) {
RequestEnd(NULL);
}
l_free(pkt->size,pkt->ptr);

return true;
}
if (locked_on_hostgroup >= 0 && (strncasecmp((char *)"SET ",(char *)pkt->ptr+5,4)==0)) {
// this is a circuit breaker, we will send everything to the backend
//
Expand Down
12 changes: 10 additions & 2 deletions lib/ProxySQL_Admin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4561,6 +4561,14 @@ void admin_session_handler(MySQL_Session *sess, void *_pa, PtrSize_t *pkt) {
goto __run_query;
}

// MySQL client check command for dollars quote support, starting at version '8.1.0'. See #4300.
if (!strncasecmp("SELECT $$", query_no_space, strlen("SELECT $$"))) {
pair<int,const char*> err_info { get_dollar_quote_error(mysql_thread___server_version) };
SPA->send_MySQL_ERR(&sess->client_myds->myprot, const_cast<char*>(err_info.second), err_info.first);
run_query=false;
goto __run_query;
}

if (query_no_space_length==SELECT_VERSION_COMMENT_LEN) {
if (!strncasecmp(SELECT_VERSION_COMMENT, query_no_space, query_no_space_length)) {
l_free(query_length,query);
Expand Down Expand Up @@ -11166,14 +11174,14 @@ void ProxySQL_Admin::send_MySQL_OK(MySQL_Protocol *myprot, char *msg, int rows)
myds->DSS=STATE_SLEEP;
}

void ProxySQL_Admin::send_MySQL_ERR(MySQL_Protocol *myprot, char *msg) {
void ProxySQL_Admin::send_MySQL_ERR(MySQL_Protocol *myprot, char *msg, uint32_t code) {
assert(myprot);
MySQL_Data_Stream *myds=myprot->get_myds();
myds->DSS=STATE_QUERY_SENT_DS;
char *a = (char *)"ProxySQL Admin Error: ";
char *new_msg = (char *)malloc(strlen(msg)+strlen(a)+1);
sprintf(new_msg, "%s%s", a, msg);
myprot->generate_pkt_ERR(true,NULL,NULL,1,1045,(char *)"28000",new_msg);
myprot->generate_pkt_ERR(true,NULL,NULL,1,code,(char *)"28000",new_msg);
free(new_msg);
myds->DSS=STATE_SLEEP;
}
Expand Down
45 changes: 45 additions & 0 deletions lib/proxysql_utils.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "proxysql_utils.h"
#include "mysqld_error.h"

#include <functional>
#include <sstream>
Expand Down Expand Up @@ -431,3 +432,47 @@ void close_all_non_term_fd(std::vector<int> excludeFDs) {
}
}
}

vector<string> sort_versions(vector<string> versions) {
std::sort(
versions.begin(), versions.end(),
[](const string& v1, const string& v2) {
const auto result =
mismatch_(
v1.cbegin(), v1.cend(), v2.cbegin(), v2.cend(),
[](const unsigned char lhs, const unsigned char rhs) {
return tolower(lhs) == tolower(rhs);
}
);

const bool not_equal = result.second != v2.cend();
const bool fst_shorter = result.first == v1.cend();
const bool fst_lesser = std::tolower(*result.first) < std::tolower(*result.second);

return not_equal && (fst_shorter || fst_lesser);
}
);

return versions;
}

std::pair<int,const char*> get_dollar_quote_error(const char* version) {
const char* ER_PARSE_MSG {
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server"
" version for the right syntax to use near '$$' at line 1'"
};

if (strcasecmp(version,"8.1.0") == 0) {
return { ER_PARSE_ERROR, ER_PARSE_MSG };
} else {
const vector<string> sorted { sort_versions({"8.1.0", version}) };

if (sorted[0] == "8.1.0") {
// SQLSTATE: 42000
return { ER_PARSE_ERROR, ER_PARSE_MSG };
} else {
// SQLSTATE: 42S22
return { ER_BAD_FIELD_ERROR, "Unknown column '$$' in 'field list'" };
}
}
}

0 comments on commit ecf4f69

Please sign in to comment.