mirror of https://github.com/sysown/proxysql
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
587 lines
18 KiB
587 lines
18 KiB
/**
|
|
* @file test_unsupported_queries-t.cpp
|
|
* @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>
|
|
#include <stdio.h>
|
|
|
|
#include "mysql.h"
|
|
#include "mysqld_error.h"
|
|
|
|
#include "command_line.h"
|
|
#include "json.hpp"
|
|
#include "proxysql_utils.h"
|
|
#include "tap.h"
|
|
#include "utils.h"
|
|
|
|
/**
|
|
* @brief List of the pairs holding the unsupported queries to be executed by ProxySQL
|
|
* 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"
|
|
),
|
|
};
|
|
|
|
/**
|
|
* @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 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() + 4 * queries_tests_info.size());
|
|
|
|
if (cl.getEnv()) {
|
|
diag("Failed to get the required environmental variables.");
|
|
return -1;
|
|
}
|
|
|
|
// perform a different connection per query
|
|
for (const auto& unsupported_query : unsupported_queries) {
|
|
MYSQL* proxysql_mysql = mysql_init(NULL);
|
|
|
|
// extract the tuple elements
|
|
const std::string query = std::get<0>(unsupported_query);
|
|
const int exp_err_code = std::get<1>(unsupported_query);
|
|
const std::string exp_err_msg = std::get<2>(unsupported_query);
|
|
|
|
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;
|
|
}
|
|
|
|
int query_err = mysql_query(proxysql_mysql, query.c_str());
|
|
int m_errno = mysql_errno(proxysql_mysql);
|
|
const char* m_error = mysql_error(proxysql_mysql);
|
|
|
|
ok(
|
|
query_err && ( m_errno == exp_err_code ) && ( exp_err_msg == std::string { m_error } ),
|
|
"Unsupported query '%s' should fail. Error code: (Expected: '%d' == Actual:'%d'), Error msg: (Expected: '%s' == Actual:'%s')",
|
|
query.c_str(),
|
|
exp_err_code,
|
|
m_errno,
|
|
exp_err_msg.c_str(),
|
|
m_error
|
|
);
|
|
|
|
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();
|
|
}
|