diff --git a/deps/Makefile b/deps/Makefile index a04010b05..e692def15 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -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 diff --git a/deps/mariadb-client-library/cr_new_stmt_metadata_removal.patch b/deps/mariadb-client-library/cr_new_stmt_metadata_removal.patch new file mode 100644 index 000000000..883ed84d2 --- /dev/null +++ b/deps/mariadb-client-library/cr_new_stmt_metadata_removal.patch @@ -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); + } diff --git a/lib/MySQL_PreparedStatement.cpp b/lib/MySQL_PreparedStatement.cpp index 1f68eea22..5483e3b70 100644 --- a/lib/MySQL_PreparedStatement.cpp +++ b/lib/MySQL_PreparedStatement.cpp @@ -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::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); diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 593c454a3..504153c73 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -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) { diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index 22cc0971a..bce678fc3 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -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_idxfields[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); diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index 9305d3bd7..482186a85 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -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)\" diff --git a/test/tap/tests/reg_test_1574-mariadb_read_stmt_execute_response-t.cpp b/test/tap/tests/reg_test_1574-mariadb_read_stmt_execute_response-t.cpp new file mode 100644 index 000000000..120d0d236 --- /dev/null +++ b/test/tap/tests/reg_test_1574-mariadb_read_stmt_execute_response-t.cpp @@ -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 +#include +#include +#include +#include + +#include + +#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(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 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(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(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(); +} diff --git a/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp b/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp new file mode 100644 index 000000000..8cfdd8761 --- /dev/null +++ b/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +#include + +#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(query_t.size() + 20), '\0'); + + std::string rnd_str(static_cast(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(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(1)) && + (data_c1 == static_cast(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(); +}