Merge pull request #3309 from sysown/v2.1.1-1574

Closes #1574: Packets out of order. expected 1 received 27.
pull/3326/head^2
René Cannaò 5 years ago committed by GitHub
commit b233819514
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
deps/Makefile vendored

@ -187,6 +187,7 @@ mariadb-client-library/mariadb_client/libmariadb/libmariadbclient.a: libssl/open
cd mariadb-client-library/mariadb_client && patch unittest/libmariadb/basic-t.c < ../unittest_basic-t.c.patch
cd mariadb-client-library/mariadb_client && patch unittest/libmariadb/charset.c < ../unittest_charset.c.patch
cd mariadb-client-library/mariadb_client && patch -p0 < ../client_deprecate_eof.patch
cd mariadb-client-library/mariadb_client && patch -p0 < ../cr_new_stmt_metadata_removal.patch
cd mariadb-client-library/mariadb_client && CC=${CC} CXX=${CXX} ${MAKE} mariadbclient
# cd mariadb-client-library/mariadb_client/include && make my_config.h

@ -0,0 +1,81 @@
diff --git libmariadb/mariadb_stmt.c libmariadb/mariadb_stmt.c
index bbc2831..2168065 100644
--- libmariadb/mariadb_stmt.c
+++ libmariadb/mariadb_stmt.c
@@ -2013,7 +2013,61 @@ int stmt_read_execute_response(MYSQL_STMT *stmt)
stmt->state= MYSQL_STMT_WAITING_USE_OR_STORE;
/* in certain cases parameter types can change: For example see bug
4026 (SELECT ?), so we need to update field information */
- if (mysql->field_count == stmt->field_count)
+
+ /* ProxySQL #1574: We never set the CR_NEW_STMT_METADATA error because
+ we get the information about the changes on the metadata from the
+ column definitions in the resulset itself. This change allows ProxySQL
+ to avoid a extra query for MEDATADA fetch that otherwise would need
+ to be performed in case of 'CR_NEW_STMT_METADATA' being returned
+ from the library.
+ */
+ /*********************************************************************/
+ if (mysql->field_count != stmt->field_count)
+ {
+ MA_MEM_ROOT *fields_ma_alloc_root=
+ &((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root;
+ uint i;
+
+ // 'ma_free_root' will free all the allocated memory for 'fields_ma_alloc_root'
+ // for this reason we need to allocate again all the fields and also allocate
+ // the required space for 'MYSQL_BIND'.
+ ma_free_root(fields_ma_alloc_root, MYF(0));
+ if (!(stmt->bind= (MYSQL_BIND *)ma_alloc_root(fields_ma_alloc_root,
+ sizeof(MYSQL_BIND) * mysql->field_count)) ||
+ !(stmt->fields= (MYSQL_FIELD *)ma_alloc_root(fields_ma_alloc_root,
+ sizeof(MYSQL_FIELD) * mysql->field_count)))
+ {
+ SET_CLIENT_STMT_ERROR(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
+ return(1);
+ }
+ memset(stmt->bind, 0, sizeof(MYSQL_BIND) * mysql->field_count);
+ stmt->field_count= mysql->field_count;
+
+ // We need to copy again all the data of freed fields in case the number
+ // of fields doesn't match because the number of fields isn't the expected.
+ /*********************************************************************/
+ for (i=0; i < stmt->field_count; i++)
+ {
+ memcpy(&stmt->fields[i], &mysql->fields[i], sizeof(MYSQL_FIELD));
+
+ stmt->fields[i].extension= 0; /* not in use yet */
+ if (mysql->fields[i].db)
+ stmt->fields[i].db= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].db);
+ if (mysql->fields[i].table)
+ stmt->fields[i].table= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].table);
+ if (mysql->fields[i].org_table)
+ stmt->fields[i].org_table= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].org_table);
+ if (mysql->fields[i].name)
+ stmt->fields[i].name= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].name);
+ if (mysql->fields[i].org_name)
+ stmt->fields[i].org_name= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].org_name);
+ if (mysql->fields[i].catalog)
+ stmt->fields[i].catalog= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].catalog);
+ if (mysql->fields[i].def)
+ stmt->fields[i].def= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].def);
+ }
+ /*********************************************************************/
+ }
{
uint i;
for (i=0; i < stmt->field_count; i++)
@@ -2025,12 +2079,8 @@ int stmt_read_execute_response(MYSQL_STMT *stmt)
stmt->fields[i].charsetnr= mysql->fields[i].charsetnr;
stmt->fields[i].max_length= mysql->fields[i].max_length;
}
- } else
- {
- /* table was altered, see test_wl4166_2 */
- SET_CLIENT_STMT_ERROR(stmt, CR_NEW_STMT_METADATA, SQLSTATE_UNKNOWN, 0);
- return(1);
}
+ /*********************************************************************/
}
return(0);
}

@ -934,7 +934,9 @@ class PS_global_stats {
unsigned long long ref_count_client;
unsigned long long ref_count_server;
char *query;
PS_global_stats(uint64_t stmt_id, char *s, char *u, uint64_t d, char *q, unsigned long long ref_c, unsigned long long ref_s) {
uint64_t num_columns;
uint64_t num_params;
PS_global_stats(uint64_t stmt_id, char *s, char *u, uint64_t d, char *q, unsigned long long ref_c, unsigned long long ref_s, uint64_t columns, uint64_t params) {
statement_id = stmt_id;
digest=d;
query=strndup(q, mysql_thread___query_digests_max_digest_length);
@ -942,6 +944,8 @@ class PS_global_stats {
schemaname=strdup(s);
ref_count_client = ref_c;
ref_count_server = ref_s;
num_columns = columns;
num_params = params;
}
~PS_global_stats() {
if (query) {
@ -959,7 +963,7 @@ class PS_global_stats {
}
char **get_row() {
char buf[128];
char **pta=(char **)malloc(sizeof(char *)*7);
char **pta=(char **)malloc(sizeof(char *)*9);
sprintf(buf,"%lu",statement_id);
pta[0]=strdup(buf);
assert(schemaname);
@ -976,6 +980,10 @@ class PS_global_stats {
pta[5]=strdup(buf);
sprintf(buf,"%llu",ref_count_server);
pta[6]=strdup(buf);
sprintf(buf,"%lu",num_columns);
pta[7]=strdup(buf);
sprintf(buf,"%lu",num_params);
pta[8]=strdup(buf);
return pta;
}
@ -992,7 +1000,7 @@ class PS_global_stats {
SQLite3_result * MySQL_STMT_Manager_v14::get_prepared_statements_global_infos() {
proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 4, "Dumping current prepared statements global info\n");
SQLite3_result *result=new SQLite3_result(7);
SQLite3_result *result=new SQLite3_result(9);
rdlock();
result->add_column_definition(SQLITE_TEXT,"stmt_id");
result->add_column_definition(SQLITE_TEXT,"schemaname");
@ -1001,13 +1009,15 @@ SQLite3_result * MySQL_STMT_Manager_v14::get_prepared_statements_global_infos()
result->add_column_definition(SQLITE_TEXT,"query");
result->add_column_definition(SQLITE_TEXT,"ref_count_client");
result->add_column_definition(SQLITE_TEXT,"ref_count_server");
result->add_column_definition(SQLITE_TEXT,"num_columns");
result->add_column_definition(SQLITE_TEXT,"num_params");
for (std::map<uint64_t, MySQL_STMT_Global_info *>::iterator it = map_stmt_id_to_info.begin();
it != map_stmt_id_to_info.end(); ++it) {
MySQL_STMT_Global_info *a = it->second;
PS_global_stats * pgs = new PS_global_stats(a->statement_id,
a->schemaname, a->username,
a->hash, a->query,
a->ref_count_client, a->ref_count_server);
a->ref_count_client, a->ref_count_server, a->num_columns, a->num_params);
char **pta = pgs->get_row();
result->add_row(pta);
pgs->free_row(pta);

@ -3861,6 +3861,20 @@ bool MySQL_Session::handler_rc0_PROCESSING_STMT_PREPARE(enum session_status& st,
// this function used to be inline
void MySQL_Session::handler_rc0_PROCESSING_STMT_EXECUTE(MySQL_Data_Stream *myds) {
thread->status_variables.stvar[st_var_backend_stmt_execute]++;
// See issue #1574. Metadata needs to be updated in case of need also
// during STMT_EXECUTE, so a failure in the prepared statement
// metadata cache is only hit once. This way we ensure that the next
// 'PREPARE' will be answered with the properly updated metadata.
/********************************************************************/
// Lock the global statement manager
GloMyStmt->wrlock();
// Update the global prepared statement metadata
MySQL_STMT_Global_info *stmt_info = GloMyStmt->find_prepared_statement_by_stmt_id(CurrentQuery.stmt_global_id, false);
stmt_info->update_metadata(CurrentQuery.mysql_stmt);
// Unlock the global statement manager
GloMyStmt->unlock();
/********************************************************************/
MySQL_Stmt_Result_to_MySQL_wire(CurrentQuery.mysql_stmt, myds->myconn);
LogQuery(myds);
if (CurrentQuery.stmt_meta) {

@ -506,7 +506,7 @@ static int http_handler(void *cls, struct MHD_Connection *connection, const char
#endif /* PROXYSQLCLICKHOUSE */
#define ADMIN_SQLITE_TABLE_STATS_MYSQL_PREPARED_STATEMENTS_INFO "CREATE TABLE stats_mysql_prepared_statements_info (global_stmt_id INT NOT NULL , schemaname VARCHAR NOT NULL , username VARCHAR NOT NULL , digest VARCHAR NOT NULL , ref_count_client INT NOT NULL , ref_count_server INT NOT NULL , query VARCHAR NOT NULL)"
#define ADMIN_SQLITE_TABLE_STATS_MYSQL_PREPARED_STATEMENTS_INFO "CREATE TABLE stats_mysql_prepared_statements_info (global_stmt_id INT NOT NULL , schemaname VARCHAR NOT NULL , username VARCHAR NOT NULL , digest VARCHAR NOT NULL , ref_count_client INT NOT NULL , ref_count_server INT NOT NULL , num_columns INT NOT NULL, num_params INT NOT NULL, query VARCHAR NOT NULL)"
static char * admin_variables_names[]= {
(char *)"admin_credentials",
@ -12125,9 +12125,10 @@ void ProxySQL_Admin::stats___mysql_prepared_statements_info() {
char *query1=NULL;
char *query32=NULL;
statsdb->execute("DELETE FROM stats_mysql_prepared_statements_info");
query1=(char *)"INSERT INTO stats_mysql_prepared_statements_info VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)";
query32=(char *)"INSERT INTO stats_mysql_prepared_statements_info VALUES (?1,?2,?3,?4,?5,?6,?7),(?8,?9,?10,?11,?12,?13,?14),(?15,?16,?17,?18,?19,?20,?21),(?22,?23,?24,?25,?26,?27,?28),(?29,?30,?31,?32,?33,?34,?35),(?36,?37,?38,?39,?40,?41,?42),(?43,?44,?45,?46,?47,?48,?49),(?50,?51,?52,?53,?54,?55,?56),(?57,?58,?59,?60,?61,?62,?63),(?64,?65,?66,?67,?68,?69,?70),(?71,?72,?73,?74,?75,?76,?77),(?78,?79,?80,?81,?82,?83,?84),(?85,?86,?87,?88,?89,?90,?91),(?92,?93,?94,?95,?96,?97,?98),(?99,?100,?101,?102,?103,?104,?105),(?106,?107,?108,?109,?110,?111,?112),(?113,?114,?115,?116,?117,?118,?119),(?120,?121,?122,?123,?124,?125,?126),(?127,?128,?129,?130,?131,?132,?133),(?134,?135,?136,?137,?138,?139,?140),(?141,?142,?143,?144,?145,?146,?147),(?148,?149,?150,?151,?152,?153,?154),(?155,?156,?157,?158,?159,?160,?161),(?162,?163,?164,?165,?166,?167,?168),(?169,?170,?171,?172,?173,?174,?175),(?176,?177,?178,?179,?180,?181,?182),(?183,?184,?185,?186,?187,?188,?189),(?190,?191,?192,?193,?194,?195,?196),(?197,?198,?199,?200,?201,?202,?203),(?204,?205,?206,?207,?208,?209,?210),(?211,?212,?213,?214,?215,?216,?217),(?218,?219,?220,?221,?222,?223,?224)";
query1=(char *)"INSERT INTO stats_mysql_prepared_statements_info VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)";
query32=(char *)"INSERT INTO stats_mysql_prepared_statements_info VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9), (?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18), (?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27), (?28, ?29, ?30, ?31, ?32, ?33, ?34, ?35, ?36), (?37, ?38, ?39, ?40, ?41, ?42, ?43, ?44, ?45), (?46, ?47, ?48, ?49, ?50, ?51, ?52, ?53, ?54), (?55, ?56, ?57, ?58, ?59, ?60, ?61, ?62, ?63), (?64, ?65, ?66, ?67, ?68, ?69, ?70, ?71, ?72), (?73, ?74, ?75, ?76, ?77, ?78, ?79, ?80, ?81), (?82, ?83, ?84, ?85, ?86, ?87, ?88, ?89, ?90), (?91, ?92, ?93, ?94, ?95, ?96, ?97, ?98, ?99), (?100, ?101, ?102, ?103, ?104, ?105, ?106, ?107, ?108), (?109, ?110, ?111, ?112, ?113, ?114, ?115, ?116, ?117), (?118, ?119, ?120, ?121, ?122, ?123, ?124, ?125, ?126), (?127, ?128, ?129, ?130, ?131, ?132, ?133, ?134, ?135), (?136, ?137, ?138, ?139, ?140, ?141, ?142, ?143, ?144), (?145, ?146, ?147, ?148, ?149, ?150, ?151, ?152, ?153), (?154, ?155, ?156, ?157, ?158, ?159, ?160, ?161, ?162), (?163, ?164, ?165, ?166, ?167, ?168, ?169, ?170, ?171), (?172, ?173, ?174, ?175, ?176, ?177, ?178, ?179, ?180), (?181, ?182, ?183, ?184, ?185, ?186, ?187, ?188, ?189), (?190, ?191, ?192, ?193, ?194, ?195, ?196, ?197, ?198), (?199, ?200, ?201, ?202, ?203, ?204, ?205, ?206, ?207), (?208, ?209, ?210, ?211, ?212, ?213, ?214, ?215, ?216), (?217, ?218, ?219, ?220, ?221, ?222, ?223, ?224, ?225), (?226, ?227, ?228, ?229, ?230, ?231, ?232, ?233, ?234), (?235, ?236, ?237, ?238, ?239, ?240, ?241, ?242, ?243), (?244, ?245, ?246, ?247, ?248, ?249, ?250, ?251, ?252), (?253, ?254, ?255, ?256, ?257, ?258, ?259, ?260, ?261), (?262, ?263, ?264, ?265, ?266, ?267, ?268, ?269, ?270), (?271, ?272, ?273, ?274, ?275, ?276, ?277, ?278, ?279), (?280, ?281, ?282, ?283, ?284, ?285, ?286, ?287, ?288)";
//rc=(*proxy_sqlite3_prepare_v2)(mydb3, query1, -1, &statement1, 0);
//rc=sqlite3_prepare_v2(mydb3, query1, -1, &statement1, 0);
rc = statsdb->prepare_v2(query1, &statement1);
ASSERT_SQLITE_OK(rc, statsdb);
//rc=(*proxy_sqlite3_prepare_v2)(mydb3, query32, -1, &statement32, 0);
@ -12140,26 +12141,30 @@ void ProxySQL_Admin::stats___mysql_prepared_statements_info() {
SQLite3_row *r1=*it;
int idx=row_idx%32;
if (row_idx<max_bulk_row_idx) { // bulk
rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*7)+1, atoll(r1->fields[0])); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_text)(statement32, (idx*7)+2, r1->fields[1], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_text)(statement32, (idx*7)+3, r1->fields[2], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_text)(statement32, (idx*7)+4, r1->fields[3], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*7)+5, atoll(r1->fields[5])); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_int64)(statement32, (idx*7)+6, atoll(r1->fields[6])); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_text)(statement32, (idx*7)+7, r1->fields[4], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement32, (idx*9)+1, atoll(r1->fields[0])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_text(statement32, (idx*9)+2, r1->fields[1], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_text(statement32, (idx*9)+3, r1->fields[2], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_text(statement32, (idx*9)+4, r1->fields[3], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement32, (idx*9)+5, atoll(r1->fields[5])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement32, (idx*9)+6, atoll(r1->fields[6])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement32, (idx*9)+7, atoll(r1->fields[7])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement32, (idx*9)+8, atoll(r1->fields[8])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_text(statement32, (idx*9)+9, r1->fields[4], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
if (idx==31) {
SAFE_SQLITE3_STEP2(statement32);
rc=(*proxy_sqlite3_clear_bindings)(statement32); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_reset)(statement32); ASSERT_SQLITE_OK(rc, statsdb);
}
} else { // single row
rc=(*proxy_sqlite3_bind_int64)(statement1, 1, atoll(r1->fields[0])); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_text)(statement1, 2, r1->fields[1], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_text)(statement1, 3, r1->fields[2], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_text)(statement1, 4, r1->fields[3], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_int64)(statement1, 5, atoll(r1->fields[5])); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_int64)(statement1, 6, atoll(r1->fields[6])); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_bind_text)(statement1, 7, r1->fields[4], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement1, 1, atoll(r1->fields[0])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_text(statement1, 2, r1->fields[1], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_text(statement1, 3, r1->fields[2], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_text(statement1, 4, r1->fields[3], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement1, 5, atoll(r1->fields[5])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement1, 6, atoll(r1->fields[6])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement1, 7, atoll(r1->fields[7])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_int64(statement1, 8, atoll(r1->fields[8])); ASSERT_SQLITE_OK(rc, statsdb);
rc=sqlite3_bind_text(statement1, 9, r1->fields[4], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
SAFE_SQLITE3_STEP2(statement1);
rc=(*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, statsdb);
rc=(*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, statsdb);

@ -115,3 +115,9 @@ create_connection_annotation: test_connection_annotation-t.cpp
test_set_collation-t: test_set_collation-t.cpp $(TAP_LIBDIR)/libtap.a
g++ test_set_collation-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) -std=c++11 $(MYLIBS) -ltap -Wl,--no-as-needed -ldl -lpthread -o test_set_collation-t -DGITVERSION=\"$(GIT_VERSION)\"
reg_test_1574-stmt_metadata-t: reg_test_1574-stmt_metadata-t.cpp $(TAP_LIBDIR)/libtap.a
g++ -DTEST_AURORA -DDEBUG reg_test_1574-stmt_metadata-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) -std=c++11 $(MYLIBS) -ltap -Wl,--no-as-needed -ldl -lpthread -o reg_test_1574-stmt_metadata-t -DGITVERSION=\"$(GIT_VERSION)\"
reg_test_1574-mariadb_read_stmt_execute_response-t: reg_test_1574-mariadb_read_stmt_execute_response-t.cpp $(TAP_LIBDIR)/libtap.a
g++ -DTEST_AURORA -DDEBUG reg_test_1574-mariadb_read_stmt_execute_response-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) -std=c++11 $(MYLIBS) -ltap -Wl,--no-as-needed -ldl -lpthread -o reg_test_1574-mariadb_read_stmt_execute_response-t -DGITVERSION=\"$(GIT_VERSION)\"

@ -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…
Cancel
Save