mirror of https://github.com/sysown/proxysql
Added tests for the changes in mariadb 'read_stmt_execute_response' and in ProxySQL 'PROCESSING_STMT_EXECUTE'
parent
5d0a9a2087
commit
1605ae5d89
@ -0,0 +1,131 @@
|
||||
/**
|
||||
* @file reg_test_1574-mariadb_read_stmt_execute_response-t.cpp
|
||||
* @brief This test is a regression test for issue #1574. In the fix for this issue some changes were
|
||||
* introduced into 'read_stmt_execute_response'. These modifications prevent 'mariadb client library'
|
||||
* from returning CR_NEW_STMT_METADATA in case 'stmt' and 'mysql' fields count doesn't match, instead,
|
||||
* replaces current stmt fields with the ones returned with the resulset.
|
||||
*
|
||||
* @details For checking that this behavior is correct, the test creates a fixed number of tables,
|
||||
* and prepares a query for all of them. Later the tables are altered, changing their number of
|
||||
* columns. Finally the statements are executed and the number of fields are check after the execute;
|
||||
* none error should take place during the executes and after each execute the number of fields
|
||||
* should match the new (altered) version of the table.
|
||||
*
|
||||
* @date 2021-02-15
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <mysql.h>
|
||||
|
||||
#include "tap.h"
|
||||
#include "command_line.h"
|
||||
#include "utils.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
const int STRING_SIZE=32;
|
||||
const int NUM_TEST_TABLES = 50;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
CommandLine cl;
|
||||
|
||||
if (cl.getEnv()) {
|
||||
diag("Failed to get the required environmental variables.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
plan(50);
|
||||
|
||||
MYSQL* mysql = mysql_init(NULL);
|
||||
if (!mysql) {
|
||||
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql));
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
if (!mysql_real_connect(mysql, cl.host, cl.username, cl.password, NULL, 13306, NULL, 0)) {
|
||||
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql));
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
MYSQL_QUERY(mysql, "CREATE DATABASE IF NOT EXISTS test");
|
||||
std::string create_table_query_t =
|
||||
"CREATE TEMPORARY TABLE IF NOT EXISTS test.reg_test_read_execute_response_1574_%d (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `c2` varchar(32))";
|
||||
|
||||
for (int i = 0; i < NUM_TEST_TABLES; i++) {
|
||||
std::string create_table_query (static_cast<std::size_t>(create_table_query_t.size() + 10), '\0');
|
||||
snprintf(&create_table_query[0], create_table_query.size(), create_table_query_t.c_str(), i);
|
||||
MYSQL_QUERY(mysql, create_table_query.c_str());
|
||||
}
|
||||
|
||||
std::string select_query_t = "SELECT * FROM test.reg_test_read_execute_response_1574_%d";
|
||||
std::vector<MYSQL_STMT*> stmts {};
|
||||
|
||||
// Initialize and prepare all the statements
|
||||
for (int i = 0; i < NUM_TEST_TABLES; i++) {
|
||||
MYSQL_STMT* stmt = mysql_stmt_init(mysql);
|
||||
if (!stmt) {
|
||||
ok(false, "mysql_stmt_init(), out of memory\n");
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
std::string select_query (static_cast<std::size_t>(select_query_t.size() + 10), '\0');
|
||||
snprintf(&select_query[0], select_query.size(), select_query_t.c_str(), i);
|
||||
|
||||
// Prepare all the statements
|
||||
if (mysql_stmt_prepare(stmt, select_query.c_str(), strlen(select_query.c_str()))) {
|
||||
diag("select_query: %s", select_query.c_str());
|
||||
ok(false, "mysql_stmt_prepare at line %d failed: %s\n", __LINE__ , mysql_error(mysql));
|
||||
mysql_close(mysql);
|
||||
mysql_library_end();
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
stmts.push_back(stmt);
|
||||
}
|
||||
|
||||
// Alter the tables either dropping or adding columns
|
||||
for (int i = 0; i < NUM_TEST_TABLES; i++) {
|
||||
std::string alter_table_query_t {};
|
||||
|
||||
if (i % 2 == 0) {
|
||||
alter_table_query_t = "ALTER TABLE test.reg_test_read_execute_response_1574_%d ADD c1 BIGINT AFTER id";
|
||||
} else {
|
||||
alter_table_query_t = "ALTER TABLE test.reg_test_read_execute_response_1574_%d DROP COLUMN c2";
|
||||
}
|
||||
|
||||
std::string alter_table_query (static_cast<std::size_t>(alter_table_query_t.size() + 10), '\0');
|
||||
snprintf(&alter_table_query[0], alter_table_query.size(), alter_table_query_t.c_str(), i);
|
||||
|
||||
MYSQL_QUERY(mysql, alter_table_query.c_str());
|
||||
}
|
||||
|
||||
// Execute the prepared statement and check that the field count is correct after doing the execute
|
||||
for (int i = 0; i < NUM_TEST_TABLES; i++) {
|
||||
MYSQL_STMT* stmt = stmts[i];
|
||||
if (mysql_stmt_execute(stmt)) {
|
||||
ok(false, "mysql_stmt_execute at line %d failed: %s\n", __LINE__ , mysql_stmt_error(stmt));
|
||||
}
|
||||
|
||||
int field_count = mysql_stmt_field_count(stmt);
|
||||
if (i % 2 == 0) {
|
||||
ok(field_count == 3, "Field count should be '3' in case of 'i % 2' being '0'");
|
||||
} else {
|
||||
ok(field_count == 1, "Field count should be '1' in case of 'i % 2' being '1'");
|
||||
}
|
||||
|
||||
if (mysql_stmt_close(stmt))
|
||||
{
|
||||
ok(false, "mysql_stmt_close at line %d failed: %s\n", __LINE__ , mysql_error(mysql));
|
||||
return exit_status();
|
||||
}
|
||||
}
|
||||
|
||||
mysql_close(mysql);
|
||||
|
||||
return exit_status();
|
||||
}
|
||||
@ -0,0 +1,197 @@
|
||||
/**
|
||||
* @file reg_test_1574-stmt_metadata-t.cpp
|
||||
* @brief This test is a regression test for issue #1574.
|
||||
* @details The test checks that the metadata for a prepared statement is properly updated by ProxySQL
|
||||
* after a single 'mysql_stmt_execute' and that the resulset received by the client is correct.
|
||||
* The test performs the following actions:
|
||||
* 1. Prepares a prepared statement to 'SELECT *' from a table with only 2 columns.
|
||||
* 2. Runs an 'ALTER TABLE' on that table, adding a new column and inserts new data into it.
|
||||
* 3. Check the ProxySQL holds the old METADATA information in 'stats_mysql_prepared_statements_info'.
|
||||
* 4. Performs an execute of the prepared statement.
|
||||
* 5. Check the ProxySQL holds the new METADATA information in 'stats_mysql_prepared_statements_info'.
|
||||
* 6. Checks that the results retrieve from the execute holds the correct information.
|
||||
* @date 2021-02-15
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <mysql.h>
|
||||
|
||||
#include "tap.h"
|
||||
#include "command_line.h"
|
||||
#include "utils.h"
|
||||
|
||||
using std::string;
|
||||
|
||||
const int STRING_SIZE=32;
|
||||
|
||||
int g_seed = 0;
|
||||
|
||||
inline int fastrand() {
|
||||
g_seed = (214013*g_seed+2531011);
|
||||
return (g_seed>>16)&0x7FFF;
|
||||
}
|
||||
|
||||
void gen_random_str(char *s, const int len) {
|
||||
g_seed = time(NULL) ^ getpid() ^ pthread_self();
|
||||
static const char alphanum[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
s[i] = alphanum[fastrand() % (sizeof(alphanum) - 1)];
|
||||
}
|
||||
|
||||
s[len] = 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
CommandLine cl;
|
||||
|
||||
if (cl.getEnv()) {
|
||||
diag("Failed to get the required environmental variables.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
plan(3);
|
||||
|
||||
MYSQL* proxysql_mysql = mysql_init(NULL);
|
||||
if (!proxysql_mysql) {
|
||||
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
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_status();
|
||||
}
|
||||
|
||||
MYSQL* proxysql_admin = mysql_init(NULL);
|
||||
if (!proxysql_admin) {
|
||||
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
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_status();
|
||||
}
|
||||
|
||||
MYSQL_QUERY(proxysql_mysql, "CREATE DATABASE IF NOT EXISTS test");
|
||||
MYSQL_QUERY(proxysql_mysql, "DROP TABLE IF EXISTS test.reg_test_1574");
|
||||
MYSQL_QUERY(proxysql_mysql, "CREATE TABLE IF NOT EXISTS test.reg_test_1574 (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `c2` varchar(32))");
|
||||
|
||||
MYSQL_STMT *stmt = mysql_stmt_init(proxysql_mysql);
|
||||
if (!stmt) {
|
||||
ok(false, " mysql_stmt_init(), out of memory\n");
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
std::string query_t = "SELECT /* %s */ * FROM test.reg_test_1574";
|
||||
std::string query (static_cast<std::size_t>(query_t.size() + 20), '\0');
|
||||
|
||||
std::string rnd_str(static_cast<std::size_t>(20), '\0');
|
||||
gen_random_str(&rnd_str[0], 20);
|
||||
|
||||
snprintf(&query[0], query.size(), query_t.c_str(), rnd_str.c_str());
|
||||
|
||||
if (mysql_stmt_prepare(stmt, query.c_str(), strlen(query.c_str()))) {
|
||||
ok(false, "mysql_stmt_prepare at line %d failed: %s\n", __LINE__ , mysql_error(proxysql_mysql));
|
||||
mysql_close(proxysql_mysql);
|
||||
mysql_library_end();
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
MYSQL_QUERY(proxysql_mysql, "ALTER TABLE test.reg_test_1574 ADD c1 BIGINT AFTER id");
|
||||
MYSQL_QUERY(proxysql_mysql, "INSERT INTO test.reg_test_1574 (c1,c2) VALUES (100, 'abcde')");
|
||||
|
||||
// Check that ProxySQL cached metadata for the query has the old information
|
||||
std::string num_columns_query_t = "SELECT num_columns FROM stats.stats_mysql_prepared_statements_info WHERE query='%s'";
|
||||
std::string num_columns_query (static_cast<std::size_t>(num_columns_query_t.size() + query.size()), '\0');
|
||||
snprintf(&num_columns_query[0], num_columns_query.size(), num_columns_query_t.c_str(), query.c_str());
|
||||
|
||||
// Admin query checking for old metadata number of columns
|
||||
MYSQL_QUERY(proxysql_admin, num_columns_query.c_str());
|
||||
MYSQL_RES* result = mysql_store_result(proxysql_admin);
|
||||
MYSQL_ROW row = mysql_fetch_row(result);
|
||||
int num_columns = atoi(row[0]);
|
||||
|
||||
ok(num_columns == 2, "Number of 'num_columns' in prepared statement metadata *before* execute should be: (Exp '2' == Actual: %d)", num_columns);
|
||||
mysql_free_result(result);
|
||||
|
||||
if (mysql_stmt_execute(stmt)) {
|
||||
ok(false, "mysql_stmt_execute at line %d failed: %s\n", __LINE__ , mysql_stmt_error(stmt));
|
||||
}
|
||||
|
||||
// Admin query checking for new metadata number of columns
|
||||
MYSQL_QUERY(proxysql_admin, num_columns_query.c_str());
|
||||
result = mysql_store_result(proxysql_admin);
|
||||
row = mysql_fetch_row(result);
|
||||
num_columns = atoi(row[0]);
|
||||
|
||||
ok(num_columns == 3, "Number of 'num_columns' in prepared statement metadata *after* execute should be: (Exp '3' == Actual: %d)", num_columns);
|
||||
mysql_free_result(result);
|
||||
|
||||
MYSQL_BIND bind[3];
|
||||
int data_id;
|
||||
int64_t data_c1;
|
||||
char data_c2[STRING_SIZE];
|
||||
char is_null[3];
|
||||
long unsigned int length[3];
|
||||
char error[3];
|
||||
memset(bind, 0, sizeof(bind));
|
||||
|
||||
bind[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
bind[0].buffer = (char *)&data_id;
|
||||
bind[0].buffer_length = sizeof(int);
|
||||
bind[0].is_null = &is_null[0];
|
||||
bind[0].length = &length[0];
|
||||
|
||||
bind[1].buffer_type = MYSQL_TYPE_LONGLONG;
|
||||
bind[1].buffer = (char *)&data_c1;
|
||||
bind[1].buffer_length = sizeof(int64_t);
|
||||
bind[1].is_null = &is_null[1];
|
||||
bind[1].length = &length[1];
|
||||
|
||||
bind[2].buffer_type = MYSQL_TYPE_STRING;
|
||||
bind[2].buffer = (char *)&data_c2;
|
||||
bind[2].buffer_length = STRING_SIZE;
|
||||
bind[2].is_null = &is_null[2];
|
||||
bind[2].length = &length[2];
|
||||
bind[2].error = &error[2];
|
||||
|
||||
if (mysql_stmt_bind_result(stmt, bind)) {
|
||||
ok(false, "mysql_stmt_bind_result at line %d failed: %s\n", __LINE__ , mysql_stmt_error(stmt));
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
if (mysql_stmt_fetch(stmt)) {
|
||||
ok(false, "mysql_stmt_fetch at line %d failed: %s\n", __LINE__ , mysql_stmt_error(stmt));
|
||||
return exit_status();
|
||||
}
|
||||
|
||||
bool data_match_expected =
|
||||
(data_id == static_cast<int64_t>(1)) &&
|
||||
(data_c1 == static_cast<int64_t>(100)) &&
|
||||
(strcmp(data_c2, "abcde") == 0);
|
||||
|
||||
ok(
|
||||
data_match_expected,
|
||||
"Prepared statement result matches expected - Exp=(id:1, c1:100, c2:'abcde'), Act=(id:%d, c1:%d, c2:'%s')",
|
||||
data_id,
|
||||
data_c1,
|
||||
data_c2
|
||||
);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(proxysql_mysql);
|
||||
mysql_close(proxysql_admin);
|
||||
|
||||
return exit_status();
|
||||
}
|
||||
Loading…
Reference in new issue