Merge branch 'v2.x' into v2.x-PS-err3024

pull/3548/head
René Cannaò 5 years ago committed by GitHub
commit e38562df40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -535,6 +535,7 @@ class MySQL_Threads_Handler
bool client_session_track_gtid;
bool enable_client_deprecate_eof;
bool enable_server_deprecate_eof;
bool enable_load_data_local_infile;
bool log_mysql_warnings_enabled;
} variables;
struct {

@ -798,6 +798,7 @@ __thread int mysql_thread___query_digests_grouping_limit;
__thread bool mysql_thread___enable_client_deprecate_eof;
__thread bool mysql_thread___enable_server_deprecate_eof;
__thread bool mysql_thread___log_mysql_warnings_enabled;
__thread bool mysql_thread___enable_load_data_local_infile;
/* variables used for Query Cache */
__thread int mysql_thread___query_cache_size_MB;
@ -949,6 +950,7 @@ extern __thread int mysql_thread___query_digests_grouping_limit;
extern __thread bool mysql_thread___enable_client_deprecate_eof;
extern __thread bool mysql_thread___enable_server_deprecate_eof;
extern __thread bool mysql_thread___log_mysql_warnings_enabled;
extern __thread bool mysql_thread___enable_load_data_local_infile;
/* variables used for Query Cache */
extern __thread int mysql_thread___query_cache_size_MB;

@ -1334,15 +1334,29 @@ bool MySQL_Session::handler_special_queries(PtrSize_t *pkt) {
}
// 'LOAD DATA LOCAL INFILE' is unsupported. We report an specific error to inform clients about this fact. For more context see #833.
if ( (pkt->size >= 22 + 5) && (strncasecmp((char *)"LOAD DATA LOCAL INFILE",(char *)pkt->ptr+5, 22)==0) ) {
client_myds->DSS=STATE_QUERY_SENT_NET;
client_myds->myprot.generate_pkt_ERR(true,NULL,NULL,1,1047,(char *)"HY000",(char *)"Unsupported 'LOAD DATA LOCAL INFILE' command",true);
client_myds->DSS=STATE_SLEEP;
status=WAITING_CLIENT_DATA;
if (mirror==false) {
RequestEnd(NULL);
if (mysql_thread___enable_load_data_local_infile == false) {
client_myds->DSS=STATE_QUERY_SENT_NET;
client_myds->myprot.generate_pkt_ERR(true,NULL,NULL,1,1047,(char *)"HY000",(char *)"Unsupported 'LOAD DATA LOCAL INFILE' command",true);
client_myds->DSS=STATE_SLEEP;
status=WAITING_CLIENT_DATA;
if (mirror==false) {
RequestEnd(NULL);
}
l_free(pkt->size,pkt->ptr);
return true;
} else {
if (mysql_thread___verbose_query_error) {
proxy_warning(
"Command '%.*s' refers to file in ProxySQL instance, NOT on client side!\n",
pkt->size - sizeof(mysql_hdr) - 1,
static_cast<char*>(pkt->ptr) + 5
);
} else {
proxy_warning(
"Command 'LOAD DATA LOCAL INFILE' refers to file in ProxySQL instance, NOT on client side!\n"
);
}
}
l_free(pkt->size,pkt->ptr);
return true;
}
return false;

@ -421,6 +421,7 @@ static char * mysql_thread_variables_names[]= {
(char *)"connect_timeout_server_max",
(char *)"enable_client_deprecate_eof",
(char *)"enable_server_deprecate_eof",
(char *)"enable_load_data_local_infile",
(char *)"eventslog_filename",
(char *)"eventslog_filesize",
(char *)"eventslog_default_log",
@ -1158,6 +1159,7 @@ MySQL_Threads_Handler::MySQL_Threads_Handler() {
variables.query_digests_grouping_limit = 3;
variables.enable_client_deprecate_eof=true;
variables.enable_server_deprecate_eof=true;
variables.enable_load_data_local_infile=false;
variables.log_mysql_warnings_enabled=false;
// status variables
status_variables.mirror_sessions_current=0;
@ -1970,6 +1972,7 @@ char ** MySQL_Threads_Handler::get_variables_list() {
VariablesPointers_bool["default_reconnect"] = make_tuple(&variables.default_reconnect, false);
VariablesPointers_bool["enable_client_deprecate_eof"] = make_tuple(&variables.enable_client_deprecate_eof, false);
VariablesPointers_bool["enable_server_deprecate_eof"] = make_tuple(&variables.enable_server_deprecate_eof, false);
VariablesPointers_bool["enable_load_data_local_infile"] = make_tuple(&variables.enable_load_data_local_infile, false);
VariablesPointers_bool["enforce_autocommit_on_reads"] = make_tuple(&variables.enforce_autocommit_on_reads, false);
VariablesPointers_bool["firewall_whitelist_enabled"] = make_tuple(&variables.firewall_whitelist_enabled, false);
VariablesPointers_bool["kill_backend_connection_when_disconnect"] = make_tuple(&variables.kill_backend_connection_when_disconnect, false);
@ -3655,6 +3658,7 @@ void MySQL_Thread::refresh_variables() {
mysql_thread___default_reconnect=(bool)GloMTH->get_variable_int((char *)"default_reconnect");
mysql_thread___enable_client_deprecate_eof=(bool)GloMTH->get_variable_int((char *)"enable_client_deprecate_eof");
mysql_thread___enable_server_deprecate_eof=(bool)GloMTH->get_variable_int((char *)"enable_server_deprecate_eof");
mysql_thread___enable_load_data_local_infile=(bool)GloMTH->get_variable_int((char *)"enable_load_data_local_infile");
mysql_thread___log_mysql_warnings_enabled=(bool)GloMTH->get_variable_int((char *)"log_mysql_warnings_enabled");
#ifdef DEBUG
mysql_thread___session_debug=(bool)GloMTH->get_variable_int((char *)"session_debug");

@ -1352,9 +1352,16 @@ void MySQL_Data_Stream::return_MySQL_Connection_To_Pool() {
unsigned long long intv = mysql_thread___connection_max_age_ms;
intv *= 1000;
if (
( (intv) && (mc->last_time_used > mc->creation_time + intv) )
(( (intv) && (mc->last_time_used > mc->creation_time + intv) )
||
( mc->local_stmts->get_num_backend_stmts() > (unsigned int)GloMTH->variables.max_stmts_per_connection )
( mc->local_stmts->get_num_backend_stmts() > (unsigned int)GloMTH->variables.max_stmts_per_connection ))
&&
// NOTE: If the current session if in 'PINGING_SERVER' status, there is
// no need to reset the session. The destruction and creation of a new
// session in case this session has exceeded the time specified by
// 'connection_max_age_ms' will be deferred to the next time the session
// is used outside 'PINGING_SERVER' operation. For more context see #3502.
sess->status != PINGING_SERVER
) {
if (mysql_thread___reset_connection_algorithm == 2) {
sess->create_new_session_and_reset_connection(this);

@ -765,6 +765,14 @@ static void * sqlite3server_main_loop(void *arg)
}
fds[i].revents=0;
}
// NOTE: In case the address imposed by 'sqliteserver-mysql_ifaces' isn't avaible,
// a infinite loop could take place if 'POLLNVAL' is not checked here.
// This means that trying to set a 'mysql_ifaces' to an address that is
// already taken will result into an 'assert' in ProxySQL side.
if (nfds == 1 && fds[0].revents == POLLNVAL) {
proxy_error("revents==POLLNVAL for FD=%d, events=%d\n", fds[i].fd, fds[i].events);
assert(fds[0].revents != POLLNVAL);
}
__end_while_pool:
if (S_amll.get_version()!=version) {
S_amll.wrlock();

@ -470,3 +470,25 @@ int exec(const std::string& cmd, std::string& result) {
}
return err;
}
std::vector<mysql_res_row> extract_mysql_rows(MYSQL_RES* my_res) {
if (my_res == nullptr) { return {}; }
std::vector<mysql_res_row> result {};
MYSQL_ROW row = nullptr;
uint32_t num_fields = mysql_num_fields(my_res);
while ((row = mysql_fetch_row(my_res))) {
mysql_res_row row_values {};
uint64_t *lengths = mysql_fetch_lengths(my_res);
for (uint32_t i = 0; i < num_fields; i++) {
std::string field_val(row[i], lengths[i]);
row_values.push_back(field_val);
}
result.push_back(row_values);
}
return result;
};

@ -70,8 +70,19 @@ int execvp(const std::string& file, const std::vector<const char*>& argv, std::s
int exec(const std::string& cmd, std::string& result);
// create table test.sbtest1 with num_rows rows
int create_table_test_sbtest1(int num_rows, MYSQL *mysql);
using mysql_res_row = std::vector<std::string>;
/**
* @brief Function that extracts the provided 'MYSQL_RES' into a vector of vector of
* strings.
* @param my_res The 'MYSQL_RES' for which to extract the values. In case of
* being NULL an empty vector is returned.
* @return The extracted values of all the rows present in the resultset.
*/
std::vector<mysql_res_row> extract_mysql_rows(MYSQL_RES* my_res);
#endif // #define UTILS_H

@ -0,0 +1,4 @@
1,"a string","100.20"
2,"a string containing a , comma","102.20"
3,"a string containing a \" quote","102.20"
4,"a string containing a \", quote and comma","102.20"

@ -0,0 +1,473 @@
/**
* @file test_sqlite3_server-t.cpp
* @brief Test to perform multiple operations over ProxySQL SQLite3 server.
* @details It performs the following operations:
* - Connects to sqlite3 with a wrong username.
* - Connects to sqlite3 with a right username but wrong password.
* - Successfully connects to sqlite3 and runs:
* + SHOW SCHEMAS
* + SHOW DATABASES
* + SELECT DATABASE()
* + select DATABASE(), USER() limit 1
* + select @@version_comment limit 1
* + select @@character_set_client, @@character_set_connection, @@character_set_server, @@character_set_database limit 1
* - Successfully connects to sqlite3 and runs a variety of queries:
* + CREATE TABLE, SHOW CREATE TABLE, INSERT, SELECT, DROP TABLE...
* + Queries that induce errors: syntax error, duplicate keys, etc...
* - Changes 'sqliteserver-mysql_ifaces' and tries to connect to the new interface.
* - Connects to ProxySQL Admin and performs the following operations:
* + LOAD|SAVE SQLITESERVER TO|FROM RUNTIME|MEMORY|DISK
*
* NOTE: 'sqliteserver-read_only' is completely omitted from this test because
* it's **currently unused**.
*/
#include <cstring>
#include <vector>
#include <tuple>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mysql.h>
#include <mysql/mysqld_error.h>
#include "tap.h"
#include "command_line.h"
#include "utils.h"
using query_spec = std::tuple<std::string, int>;
const int sqlite3_port = 0;
/**
* @brief Extract the current 'sqliteserver-mysql_ifaces' from ProxySQL config.
* @param proxysql_admin An already opened connection to ProxySQL Admin.
* @return EXIT_SUCCESS, or one of the following error codes:
* - EINVAL if supplied 'proxysql_admin' is NULL.
* - '-1' in case of ProxySQL returns an 'NULL' row for the query selecting
* the variable 'sqliteserver-read_only'.
* - EXIT_FAILURE in case other operation failed.
*/
int get_sqlite3_ifaces(MYSQL* proxysql_admin, std::string& sqlite3_ifaces) {
if (proxysql_admin == NULL) {
return EINVAL;
}
int res = EXIT_FAILURE;
MYSQL_QUERY(
proxysql_admin,
"SELECT * FROM global_variables WHERE Variable_name='sqliteserver-mysql_ifaces'"
);
MYSQL_RES* admin_res = mysql_store_result(proxysql_admin);
if (!admin_res) {
diag("'mysql_store_result' at line %d failed: %s", __LINE__, mysql_error(proxysql_admin));
goto cleanup;
}
{
MYSQL_ROW row = mysql_fetch_row(admin_res);
if (!row || row[0] == nullptr || row[1] == nullptr) {
diag("'mysql_fetch_row' at line %d returned 'NULL'", __LINE__);
res = -1;
goto cleanup;
}
std::string _sqlite3_ifaces { row[1] };
sqlite3_ifaces = _sqlite3_ifaces;
res = EXIT_SUCCESS;
}
cleanup:
return res;
}
void fetch_and_discard_results(MYSQL_RES* result, bool verbose=false) {
MYSQL_ROW row = nullptr;
unsigned int num_fields = 0;
unsigned int i = 0;
unsigned int j = 0;
num_fields = mysql_num_fields(result);
while ((row = mysql_fetch_row(result))) {
unsigned long *lengths = mysql_fetch_lengths(result);
if (verbose) {
printf("# RowNum_%d: ", j);
}
for(i = 0; i < num_fields; i++) {
if (verbose) {
printf("[%.*s] ", (int) lengths[i], row[i] ? row[i] : "NULL");
}
}
if (verbose) {
printf("\n");
}
j++;
}
}
/**
* @brief Execute the supplied queries and check that the return codes are the
* ones specified.
*
* @param proxysql_sqlite3 An already opened MYSQL connection to ProxySQL
* SQLite3 server.
* @param queries The queries to be performed and check.
*/
void execute_and_check_queries(MYSQL* proxysql_sqlite3, const std::vector<query_spec>& queries) {
for (const auto& supp_query : queries) {
const std::string query = std::get<0>(supp_query);
const int exp_err_code = std::get<1>(supp_query);
int query_err = mysql_query(proxysql_sqlite3, query.c_str());
MYSQL_RES* result = mysql_store_result(proxysql_sqlite3);
if (result) {
fetch_and_discard_results(result, true);
mysql_free_result(result);
}
int m_errno = mysql_errno(proxysql_sqlite3);
const char* m_error = mysql_error(proxysql_sqlite3);
if (exp_err_code == 0) {
ok(
exp_err_code == m_errno,
"Query '%s' should succeed. Error code: (Expected:'%d' == Actual:'%d')",
query.c_str(), exp_err_code, m_errno
);
} else {
ok(
exp_err_code == m_errno,
"Query '%s' should fail. Error code: (Expected:'%d' == Actual:'%d'), Err: '%s'",
query.c_str(), exp_err_code, m_errno, m_error
);
}
}
}
int extract_sqlite3_host_port(MYSQL* proxysql_admin, std::pair<std::string, int>& host_port) {
if (proxysql_admin == nullptr) { return EINVAL; }
int res = EXIT_SUCCESS;
std::string sqlite3_ifaces {};
int ifaces_err = get_sqlite3_ifaces(proxysql_admin, sqlite3_ifaces);
// ProxySQL is likely to have been launched without "--sqlite3-server" flag
if (ifaces_err == -1) {
diag("ProxySQL was launched without '--sqlite3-server' flag");
res = EXIT_FAILURE;
return res;
}
// Extract the correct port to connect to SQLite server
std::string::size_type colon_pos = sqlite3_ifaces.find(":");
if (colon_pos == std::string::npos) {
diag("ProxySQL returned a malformed 'sqliteserver-mysql_ifaces': %s", sqlite3_ifaces.c_str());
res = EXIT_FAILURE;
return res;
}
std::string sqlite3_host { sqlite3_ifaces.substr(0, colon_pos) };
std::string sqlite3_port { sqlite3_ifaces.substr(colon_pos + 1) };
// Check that port has valid conversion
char* end_pos = nullptr;
int i_sqlite3_port = std::strtol(sqlite3_port.c_str(), &end_pos, 10);
if (errno == ERANGE || (end_pos != &sqlite3_port.back() + 1)) {
diag(
"ProxySQL returned a invalid port number within 'sqliteserver-mysql_ifaces': %s",
sqlite3_ifaces.c_str()
);
res = EXIT_FAILURE;
return res;
}
if (res == EXIT_SUCCESS) {
host_port = { sqlite3_host, i_sqlite3_port };
}
return res;
}
/**
* @brief List of the pairs holding a series of queries that should be
* successfully performed against ProxySQL SQLite3 server.
*/
std::vector<query_spec> successful_queries {
std::make_tuple<std::string, int>("SHOW SCHEMAS", 0),
std::make_tuple<std::string, int>("SHOW DATABASES", 0),
std::make_tuple<std::string, int>("SELECT DATABASE()", 0),
std::make_tuple<std::string, int>("SELECT DATABASE(), USER() LIMIT 1", 0),
std::make_tuple<std::string, int>("SELECT @@version_comment LIMIT 1", 0),
std::make_tuple<std::string, int>(
"SELECT @@character_set_client, @@character_set_connection,"
" @@character_set_server, @@character_set_database LIMIT 1",
0
),
std::make_tuple<std::string, int>(
"CREATE TABLE IF NOT EXISTS test_sqlite3_server_p0712("
" c1 INTEGER PRIMARY KEY AUTOINCREMENT,"
" c2 VARCHAR(100),"
" c3 VARCHAR(100)"
")",
0
),
std::make_tuple<std::string, int>("SHOW CREATE TABLE test_sqlite3_server_p0712", 0),
std::make_tuple<std::string, int>("SHOW TABLES", 0),
std::make_tuple<std::string, int>(
"INSERT INTO test_sqlite3_server_p0712"
" (c2, c3) VALUES ('1234', '1234')",
0
),
std::make_tuple<std::string, int>(
"INSERT INTO test_sqlite3_server_p0712"
" (c2, c3) VALUES ('123555555', '12355555')",
0
),
std::make_tuple<std::string, int>(
"DELETE FROM test_sqlite3_server_p0712",
0
),
std::make_tuple<std::string, int>("DROP TABLE test_sqlite3_server_p0712", 0),
std::make_tuple<std::string, int>("SHOW TABLES", 0),
};
/**
* @brief List of the pairs holding a series of queries in which *some*
* should fail when executed against ProxySQL SQLite3 server.
*/
std::vector<query_spec> unsuccessful_queries {
std::make_tuple<std::string, int>("SHOW CHEMAS", 1045),
std::make_tuple<std::string, int>("SHOW DAABASES", 1045),
std::make_tuple<std::string, int>("SELECT DAABASE()", 1045),
std::make_tuple<std::string, int>("SELECT DAABASE(), USER() LIMIT 1", 1045),
std::make_tuple<std::string, int>("SHOW CREATE TABLE test_sqlite3_server_p0712", 0),
std::make_tuple<std::string, int>(
"CREATE TABLE IF NOT EXISTS test_sqlite3_server_p0712("
" c1 INTEGER PRIMARY KEY AUTOINCREMENT,"
" c2 VARCHAR(100),"
" c3 VARCHAR(100)"
")",
0
),
std::make_tuple<std::string, int>(
"INSERT INTO test_sqlite3_server_p0712"
" (c2, c3) VALUES ('1234', '1234')",
0
),
std::make_tuple<std::string, int>(
"INSERT INTO test_sqlite3_server_p0712"
" (c1, c2, c3) VALUES (1, '1235', '1235')",
1045
),
std::make_tuple<std::string, int>(
"USE foobar",
1045
),
std::make_tuple<std::string, int>(
"DROP TABLE test_sqlite3_server_p0712_non_existent",
1045
),
std::make_tuple<std::string, int>(
"DROP TABLE test_sqlite3_server_p0712",
0
),
};
/**
* @brief Perform several admin queries to exercise more paths.
*/
std::vector<std::string> admin_queries {
"LOAD SQLITESERVER VARIABLES FROM DISK",
"LOAD SQLITESERVER VARIABLES TO RUNTIME",
"SAVE SQLITESERVER VARIABLES FROM RUNTIME",
"SAVE SQLITESERVER VARIABLES TO DISK"
};
/**
* @brief Perform several admin queries to exercise more paths.
*/
std::vector<std::string> sqlite_intf_queries {
"SET sqliteserver-mysql_ifaces='127.0.0.1:6035'",
"LOAD SQLITESERVER VARIABLES TO RUNTIME"
};
int main(int argc, char** argv) {
CommandLine cl;
// plan as many tests as queries
plan(
2 /* Fail to connect with wrong username and password */ + successful_queries.size()
+ unsuccessful_queries.size() + admin_queries.size() + sqlite_intf_queries.size()
+ 1 /* Connect to new setup interface */
);
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
return EXIT_FAILURE;
}
MYSQL* proxysql_admin = mysql_init(NULL);
// Connect to ProxySQL Admin and check current SQLite3 configuration
if (
!mysql_real_connect(
proxysql_admin, 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(proxysql_admin)
);
return EXIT_FAILURE;
}
{
std::pair<std::string, int> host_port {};
int host_port_err = extract_sqlite3_host_port(proxysql_admin, host_port);
if (host_port_err) {
diag("Failed to get and parse 'sqliteserver-mysql_ifaces' at line '%d'", __LINE__);
goto cleanup;
}
MYSQL* proxysql_sqlite3 = mysql_init(NULL);
// Connect with invalid username
std::string inv_user_err {};
bool failed_to_connect = false;
if (
!mysql_real_connect(
proxysql_sqlite3, host_port.first.c_str(), "foobar_user", cl.password,
NULL, host_port.second, NULL, 0
)
) {
inv_user_err = mysql_error(proxysql_sqlite3);
failed_to_connect = true;
}
ok(
failed_to_connect,
"An invalid user should fail to connect to SQLite3 server, error was: %s",
inv_user_err.c_str()
);
// Reinitialize MYSQL handle
mysql_close(proxysql_sqlite3);
proxysql_sqlite3 = mysql_init(NULL);
// Connect with invalid password
std::string inv_pass_err {};
failed_to_connect = false;
if (
!mysql_real_connect(
proxysql_sqlite3, host_port.first.c_str(), cl.username, "foobar_pass",
NULL, host_port.second, NULL, 0
)
) {
inv_pass_err = mysql_error(proxysql_sqlite3);
failed_to_connect = true;
}
ok(
failed_to_connect,
"An invalid user should fail to connect to SQLite3 server, error was: %s",
inv_pass_err.c_str()
);
// Reinitialize MYSQL handle
mysql_close(proxysql_sqlite3);
proxysql_sqlite3 = mysql_init(NULL);
// Correctly connect to SQLite3 server
if (
!mysql_real_connect(
proxysql_sqlite3, host_port.first.c_str(), cl.username, cl.password,
NULL, host_port.second, NULL, 0
)
) {
fprintf(
stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__,
mysql_error(proxysql_sqlite3)
);
goto cleanup;
}
diag("Started performing successful queries");
execute_and_check_queries(proxysql_sqlite3, successful_queries);
diag("Started performing failing queries");
execute_and_check_queries(proxysql_sqlite3, unsuccessful_queries);
// Reinitialize MYSQL handle
mysql_close(proxysql_sqlite3);
proxysql_sqlite3 = mysql_init(NULL);
// Change SQLite interface and connect to new port
for (const auto& admin_query : sqlite_intf_queries) {
int query_err = mysql_query(proxysql_admin, admin_query.c_str());
ok(
query_err == 0, "Admin query '%s' should succeed. Line: %d, Err: '%s'",
admin_query.c_str(), __LINE__, mysql_error(proxysql_admin)
);
}
// NOTE: Wait for ProxySQL to reconfigure, changing SQLite3 interface.
// Trying to perform a connection immediately after changing the
// interface could lead to 'EADDRINUSE' in ProxySQL side.
sleep(1);
// Connect to the new interface
std::pair<std::string, int> new_host_port {};
int ext_intf_err = extract_sqlite3_host_port(proxysql_admin, new_host_port);
if (ext_intf_err) {
diag("Failed to get and parse 'sqliteserver-mysql_ifaces' at line '%d'", __LINE__);
goto cleanup;
}
// Connect with invalid username
bool success_to_connect = true;
std::string new_intf_conn_err {};
if (
!mysql_real_connect(
proxysql_sqlite3, new_host_port.first.c_str(), cl.username, cl.password,
NULL, new_host_port.second, NULL, 0
)
) {
new_intf_conn_err = mysql_error(proxysql_sqlite3);
success_to_connect = false;
}
ok(
success_to_connect,
"A connection to the new selected interface should success, error was: '%s'",
new_intf_conn_err.c_str()
);
mysql_close(proxysql_sqlite3);
// Perform the final Admin queries
for (const auto& admin_query : admin_queries) {
int query_err = mysql_query(proxysql_admin, admin_query.c_str());
ok(
query_err == 0, "Admin query '%s' should succeed. Line: %d, Err: '%s'",
admin_query.c_str(), __LINE__, mysql_error(proxysql_admin)
);
}
}
cleanup:
mysql_close(proxysql_admin);
return exit_status();
}

@ -1,9 +1,12 @@
/**
* @file test_unsupported_queries-t.cpp
* @brief Simple test to check that unsupported queries by ProxySQL return the expected error codes.
* @brief Test to check that unsupported queries, and queries that can be
* enabled or disabled via configuration variables, return the expected error
* codes, and perform correctly when enabled.
*/
#include <cstring>
#include <functional>
#include <vector>
#include <tuple>
#include <string>
@ -12,8 +15,10 @@
#include <mysql.h>
#include <mysql/mysqld_error.h>
#include "tap.h"
#include "command_line.h"
#include "json.hpp"
#include "proxysql_utils.h"
#include "tap.h"
#include "utils.h"
/**
@ -21,16 +26,538 @@
* together with the error code that they should return.
*/
std::vector<std::tuple<std::string, int, std::string>> unsupported_queries {
std::make_tuple<std::string, int, std::string>("LOAD DATA LOCAL INFILE", 1047, "Unsupported 'LOAD DATA LOCAL INFILE' command"),
std::make_tuple<std::string, int, std::string>("LOAD DATA LOCAL INFILE 'data.txt' INTO TABLE db.test_table", 1047, "Unsupported 'LOAD DATA LOCAL INFILE' command"),
std::make_tuple<std::string, int, std::string>("LOAD DATA LOCAL INFILE '/tmp/test.txt' INTO TABLE test IGNORE 1 LINES", 1047, "Unsupported 'LOAD DATA LOCAL INFILE' command"),
std::make_tuple<std::string, int, std::string>(
"LOAD DATA LOCAL INFILE",
1047,
"Unsupported 'LOAD DATA LOCAL INFILE' command"
),
std::make_tuple<std::string, int, std::string>(
"LOAD DATA LOCAL INFILE 'data.txt' INTO TABLE db.test_table",
1047,
"Unsupported 'LOAD DATA LOCAL INFILE' command"
),
std::make_tuple<std::string, int, std::string>(
"LOAD DATA LOCAL INFILE '/tmp/test.txt' INTO TABLE test IGNORE 1 LINES",
1047,
"Unsupported 'LOAD DATA LOCAL INFILE' command"
),
};
/**
* @brief Type holding the required information for identifying, enabling and
* disabling a query which support can be enabled and disabled by ProxySQL.
*/
using query_test_info =
std::tuple<
// Query to be tested
std::string,
// Variable name enabling / disabling the query
std::string,
// Value for enabling the query
std::string,
// Value for diabling the query
std::string,
// Expected error code in case of failure
int,
// Function performing an internal 'ok' test checking that the
// enabled / disabled query responds as expected
std::function<void(const CommandLine&, MYSQL*, int, bool)>
>;
// "SET mysql-enable_load_data_local_infile='true'",
/**
* @brief Extract the current value for a given 'variable_name' from
* ProxySQL current configuration, either MEMORY or RUNTIME.
* @param proxysql_admin An already opened connection to ProxySQL Admin.
* @param variable_name The name of the variable to be retrieved from ProxySQL
* config.
* @param variable_value Reference to string acting as output parameter which
* will content the value of the specified variable.
* @return EXIT_SUCCESS, or one of the following error codes:
* - EINVAL if supplied 'proxysql_admin' is NULL.
* - '-1' in case of ProxySQL returns an 'NULL' row for the query selecting
* the variable 'sqliteserver-read_only'.
* - EXIT_FAILURE in case other operation failed.
*/
int get_variable_value(
MYSQL* proxysql_admin, const std::string& variable_name,
std::string& variable_value, bool runtime=false
) {
if (proxysql_admin == NULL) {
return EINVAL;
}
int res = EXIT_FAILURE;
const std::string t_select_var_query {
"SELECT * FROM %sglobal_variables WHERE Variable_name='%s'"
};
std::string select_var_query {};
if (runtime) {
string_format(t_select_var_query, select_var_query, "runtime_", variable_name.c_str());
} else {
string_format(t_select_var_query, select_var_query, "", variable_name.c_str());
}
MYSQL_QUERY(proxysql_admin, select_var_query.c_str());
MYSQL_RES* admin_res = mysql_store_result(proxysql_admin);
if (!admin_res) {
diag("'mysql_store_result' at line %d failed: %s", __LINE__, mysql_error(proxysql_admin));
goto cleanup;
}
{
MYSQL_ROW row = mysql_fetch_row(admin_res);
if (!row || row[0] == nullptr || row[1] == nullptr) {
diag("'mysql_fetch_row' at line %d returned 'NULL'", __LINE__);
res = -1;
goto cleanup;
}
// Extract the result
std::string _variable_value { row[1] };
variable_value = _variable_value;
res = EXIT_SUCCESS;
}
cleanup:
return res;
}
/**
* @brief Enable the query based using the information supplied in the
* 'query_info' parameter, and verifies that the value of the query has properly
* change at runtime.
*
* @param proxysql_admin An already oppened connection to ProxySQL Admin.
* @param query_info Information about the query to be enabled.
*
* @return True if the query was properly enabled, false if not.
*/
bool enable_query(MYSQL* proxysql_admin, const query_test_info& query_info, bool enable=true) {
std::string exp_var_value {};
// In case of false, we choose the value for disabling the variable
if (enable == true) {
exp_var_value = std::get<2>(query_info);
} else {
exp_var_value = std::get<3>(query_info);
}
std::vector<std::string> enabling_queries {
"SET " + std::get<1>(query_info) + " = " + exp_var_value,
"LOAD MYSQL VARIABLES TO RUNTIME"
};
bool query_enabling_succeed = true;
for (const auto& query : enabling_queries) {
int query_res = mysql_query(proxysql_admin, query.c_str());
if (query_res) {
diag(
"Query '%s' for enabling query '%s' enabling at line '%d', with error: '%s'",
query.c_str(), std::get<0>(query_info).c_str(), __LINE__,
mysql_error(proxysql_admin)
);
query_enabling_succeed = false;
goto exit;
}
}
{
std::string variable_value {};
int var_err = get_variable_value(
proxysql_admin, std::get<1>(query_info), variable_value, true
);
if (var_err) {
diag(
"Getting value for variable '%s', failed with error: '%d'",
std::get<1>(query_info).c_str(), var_err
);
query_enabling_succeed = false;
goto exit;
}
// perform a final conversion in case it's required for the exp value
std::string f_exp_var_value {};
if (exp_var_value == "'true'") {
f_exp_var_value = "true";
} else if (exp_var_value == "'false'") {
f_exp_var_value = "false";
} else {
f_exp_var_value = exp_var_value;
}
if (variable_value != f_exp_var_value) {
query_enabling_succeed = false;
diag(
"Variable value doesn't match expected: (Exp: '%s', Act: '%s')",
exp_var_value.c_str(), variable_value.c_str()
);
goto exit;
}
}
exit:
return query_enabling_succeed;
}
// ******************* QUERIES TESTING FUNCTIONS ******************** //
const std::vector<std::string> prepare_table_queries {
"CREATE DATABASE IF NOT EXISTS test",
"DROP TABLE IF EXISTS test.load_data_local",
"CREATE TABLE IF NOT EXISTS test.load_data_local ("
" c1 INT NOT NULL AUTO_INCREMENT PRIMARY KEY, c2 VARCHAR(100), c3 VARCHAR(100))",
};
using mysql_res_row = std::vector<std::string>;
/**
* @brief Helper function that performs the actual check for 'test_load_data_local_infile'.
*
* @param cl CommandLine parameters required for the test.
* @param proxysql An already oppened connection to ProxySQL.
* @param exp_err The expected error code in case we are testing for failure,
* '0' by default.
* @param test_for_success Select the operation mode of the test, 'true' for
* testing for success, 'false' for failure. It's 'true' by default.
*/
void helper_test_load_data_local_infile(
const CommandLine& cl, MYSQL* proxysql, int exp_err=0, bool test_for_success=true
) {
std::string datafile {
std::string { cl.workdir } + "load_data_local_datadir/insert_data.txt"
};
bool table_prep_success = true;
for (const auto& query : prepare_table_queries) {
int query_res = mysql_query(proxysql, query.c_str());
if (query_res) {
diag(
"Query '%s' for table preparation failed at line '%d', with error: '%s'",
query.c_str(), __LINE__, mysql_error(proxysql)
);
table_prep_success = false;
break;
}
}
if (table_prep_success) {
std::string t_load_data_command {
"LOAD DATA LOCAL INFILE \"%s\" INTO TABLE test.load_data_local"
" FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n'"
};
std::string load_data_command {};
string_format(t_load_data_command, load_data_command, datafile.c_str());
int load_data_res =
mysql_query(proxysql, load_data_command.c_str());
if (test_for_success) {
if (load_data_res) {
diag(
load_data_command.c_str(), __LINE__, mysql_error(proxysql)
);
}
if (load_data_res == EXIT_SUCCESS) {
diag(
"Supplied query '%s' succeeded, performing check on data...",
load_data_command.c_str()
);
} else {
diag(
"Supplied query '%s' failed, check not going to be performed. Error was: '%s'.",
load_data_command.c_str(), mysql_error(proxysql)
);
}
// Check that the data has actually been loaded to the database
// NOTE: Specifically target 'hostgroup=0' to avoid replication lag.
int myerr = mysql_query(proxysql, "SELECT * /*+ ;hostgroup=0 */ FROM test.load_data_local");
if (myerr) {
diag(
"Query 'SELECT * FROM test.load_data_local' for table preparation failed"
" at line '%d', with error: '%s'", __LINE__, mysql_error(proxysql)
);
} else {
MYSQL_RES* result = mysql_store_result(proxysql);
std::vector<mysql_res_row> rows_res { extract_mysql_rows(result) };
std::vector<mysql_res_row> exp_rows {
{ "1","a string","100.20" },
{ "2","a string containing a , comma","102.20" },
{ "3","a string containing a \" quote","102.20" },
{ "4","a string containing a \", quote and comma","102.20" }
};
std::string exp_rows_str { "{\n" };
for (const auto& exp_row : exp_rows) {
std::string exp_row_str { nlohmann::json(exp_row).dump() };
exp_rows_str += " " + exp_row_str + ",\n";
}
exp_rows_str += "}\n";
diag("Expected values for rows were: \n%s", exp_rows_str.c_str());
std::string act_rows_str { "{\n" };
for (const auto& act_row : rows_res) {
std::string act_row_str { nlohmann::json(act_row).dump() };
act_rows_str += " " + act_row_str + ",\n";
}
act_rows_str += "}\n";
diag("Actual values for found rows were: \n%s", act_rows_str.c_str());
bool equal = false;
if (!rows_res.empty()) {
equal = std::equal(exp_rows.begin(), exp_rows.end(), rows_res.begin());
}
ok(equal, "The selected ROWS were equal to the expected ones");
}
} else {
if (load_data_res) {
diag(
load_data_command.c_str(), __LINE__, mysql_error(proxysql)
);
}
int my_errno = mysql_errno(proxysql);
ok(
my_errno == exp_err,
"Query '%s' should fail. ErrCode: '%d', and error: '%s'",
load_data_command.c_str(), my_errno, mysql_error(proxysql)
);
}
}
}
/**
* @brief Perform the same test as 'test_load_data_local_infile', but with
* 'mysql-verbose_query_error' set to 'true'. This test only purpose is
* to exercise the code performing the additional extra logging.
*/
void test_verbose_error_load_data_local_infile(
const CommandLine& cl, MYSQL* proxysql, int exp_err=0, bool test_for_success=true
) {
MYSQL* proxysql_admin = mysql_init(NULL);
if (
!mysql_real_connect(
proxysql_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0
)
) {
diag("File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
return;
}
std::vector<std::string> verbose_query_error_true {
"SET mysql-verbose_query_error='true'",
"LOAD MYSQL VARIABLES TO RUNTIME"
};
for (const auto& query : verbose_query_error_true) {
int query_err = mysql_query(proxysql_admin, query.c_str());
if (query_err) {
diag("File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
return;
}
}
helper_test_load_data_local_infile(cl, proxysql, exp_err, test_for_success);
std::vector<std::string> verbose_query_error_false {
"SET mysql-verbose_query_error='false'",
"LOAD MYSQL VARIABLES TO RUNTIME"
};
for (const auto& query : verbose_query_error_false) {
int query_err = mysql_query(proxysql_admin, query.c_str());
if (query_err) {
diag("File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
return;
}
}
mysql_close(proxysql_admin);
}
/**
* @brief Test that the query 'LOAD DATA LOCAL INFILE' performs correctly when
* enabled, and returns the proper error code when disabled. Performs one
* 'ok()' call in case everything went as expected, and several 'diag()' call
* in case of errors.
*
* @param cl CommandLine parameters required for the test.
* @param proxysql An already oppened connection to ProxySQL.
* @param exp_err The expected error code in case we are testing for failure,
* '0' by default.
* @param test_for_success Select the operation mode of the test, 'true' for
* testing for success, 'false' for failure. It's 'true' by default.
*/
void test_load_data_local_infile(
const CommandLine& cl, MYSQL* proxysql, int exp_err=0, bool test_for_success=true
) {
helper_test_load_data_local_infile(cl, proxysql, exp_err, test_for_success);
}
/**
* @brief Analogous function to 'test_load_data_local_infile' but it
* deliberately provides a non-existing file as an argument to make the query
* fail.
*
* @details This way we make sure that ProxySQL is exhibiting proper behavior
* for this unsupported query that can be misused.
* @param cl CommandLine parameters required for the test.
* @param proxysql An already oppened connection to ProxySQL.
* @param exp_err The expected error code in case we are testing for failure,
* '0' by default.
* @param test_for_success Select the operation mode of the test, 'true' for
* testing for success, 'false' for failure. It's 'true' by default.
*/
void test_failing_load_data_local_infile(
const CommandLine& cl, MYSQL* proxysql, int exp_err=0, bool test_for_success=true
) {
// Supply an invalid file
std::string datafile {
std::string { cl.workdir } + "load_data_local_datadir/non_existing_file.txt"
};
bool table_prep_success = true;
for (const auto& query : prepare_table_queries) {
int query_res = mysql_query(proxysql, query.c_str());
if (query_res) {
diag(
"Query '%s' for table preparation failed at line '%d', with error: '%s'",
query.c_str(), __LINE__, mysql_error(proxysql)
);
table_prep_success = false;
break;
}
}
if (table_prep_success) {
std::string t_load_data_command {
"LOAD DATA LOCAL INFILE \"%s\" INTO TABLE test.load_data_local"
};
std::string load_data_command {};
string_format(t_load_data_command, load_data_command, datafile.c_str());
int load_data_res =
mysql_query(proxysql, load_data_command.c_str());
if (test_for_success) {
if (load_data_res) {
diag(
load_data_command.c_str(), __LINE__, mysql_error(proxysql)
);
}
int my_errno = mysql_errno(proxysql);
ok(
(load_data_res != EXIT_SUCCESS) && my_errno == 2,
"Query '%s' should fail. ErrCode: '%d', and error: '%s'",
load_data_command.c_str(), mysql_errno(proxysql), mysql_error(proxysql)
);
} else {
if (load_data_res) {
diag(
load_data_command.c_str(), __LINE__, mysql_error(proxysql)
);
}
int my_errno = mysql_errno(proxysql);
ok(
my_errno == exp_err,
"Query '%s' should fail. ErrCode: '%d', and error: '%s'",
load_data_command.c_str(), my_errno, mysql_error(proxysql)
);
}
}
}
// ****************************************************************** //
// ********************* QUERIES TESTS INFO ************************ //
/**
* @brief List of queries which need to be check before performing the
* 'unsupported' checks.
*/
std::vector<query_test_info> queries_tests_info {
std::make_tuple<
std::string, std::string, std::string, std::string, int,
std::function<void(const CommandLine&, MYSQL*, int, bool)>
>(
// Query to be tested
"LOAD DATA LOCAL INFILE",
// Variable name enabling / disabling the query
"mysql-enable_load_data_local_infile",
// Value for enabling the query
"'true'",
// Value for diabling the query
"'false'",
// Expected error code in case of failure
1047,
// Function performing an internal 'ok' test checking that the
// enabled / disabled query responds as expected
test_load_data_local_infile
),
std::make_tuple<
std::string, std::string, std::string, std::string, int,
std::function<void(const CommandLine&, MYSQL*, int, bool)>
>(
// Query to be tested
"LOAD DATA LOCAL INFILE",
// Variable name enabling / disabling the query
"mysql-enable_load_data_local_infile",
// Value for enabling the query
"'true'",
// Value for diabling the query
"'false'",
// Expected error code in case of failure
1047,
// Function performing an internal 'ok' test checking that the
// enabled / disabled query responds as expected
test_failing_load_data_local_infile
),
std::make_tuple<
std::string, std::string, std::string, std::string, int,
std::function<void(const CommandLine&, MYSQL*, int, bool)>
>(
// Query to be tested
"LOAD DATA LOCAL INFILE",
// Variable name enabling / disabling the query
"mysql-enable_load_data_local_infile",
// Value for enabling the query
"'true'",
// Value for diabling the query
"'false'",
// Expected error code in case of failure
1047,
// Function performing an internal 'ok' test checking that the
// enabled / disabled query responds as expected
test_verbose_error_load_data_local_infile
),
};
// ****************************************************************** //
int main(int argc, char** argv) {
CommandLine cl;
// plan as many tests as queries
plan(unsupported_queries.size());
plan(unsupported_queries.size() + 4 * queries_tests_info.size());
if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
@ -48,7 +575,7 @@ int main(int argc, char** argv) {
if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
return -1;
return EXIT_FAILURE;
}
int query_err = mysql_query(proxysql_mysql, query.c_str());
@ -68,5 +595,56 @@ int main(int argc, char** argv) {
mysql_close(proxysql_mysql);
}
// Create required connection to ProxySQL admin required to perform the
// tests for conditionally enabled queries.
MYSQL* proxysql_admin = mysql_init(NULL);
if (
!mysql_real_connect(
proxysql_admin, 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(proxysql_admin));
return EXIT_FAILURE;
}
// Enable and test the queries that can be conditionally enabled
for (const auto& query_test_info : queries_tests_info) {
MYSQL* proxysql_mysql = mysql_init(NULL);
// extract the tuple elements
const std::string query = std::get<0>(query_test_info);
const std::string variable_name = std::get<1>(query_test_info);
int exp_err = std::get<4>(query_test_info);
const auto& testing_fn = std::get<5>(query_test_info);
if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
return EXIT_FAILURE;
}
bool query_enabling_succeed = enable_query(proxysql_admin, query_test_info, true);
ok(
query_enabling_succeed, "Enabling query '%s' should succeed.",
std::get<0>(query_test_info).c_str()
);
// Check that the query is now properly supported
testing_fn(cl, proxysql_mysql, 0, true);
bool query_disabling_succeed = enable_query(proxysql_admin, query_test_info, false);
ok(
query_disabling_succeed, "Disabling query '%s' should succeed.",
std::get<0>(query_test_info).c_str()
);
// Check that the query is now failing
testing_fn(cl, proxysql_mysql, exp_err, false);
mysql_close(proxysql_mysql);
}
mysql_close(proxysql_admin);
return exit_status();
}

Loading…
Cancel
Save