Skip to content

Commit

Permalink
Merge pull request #3970 from sysown/v2.x-stmt_execute_conn_status
Browse files Browse the repository at this point in the history
Fix several queries not disabling multiplexing for `STMT_EXECUTE`
  • Loading branch information
renecannao authored Sep 14, 2022
2 parents e62714c + 4fa9706 commit 8c48b37
Show file tree
Hide file tree
Showing 4 changed files with 1,171 additions and 612 deletions.
3 changes: 3 additions & 0 deletions lib/MySQL_Logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,9 @@ void MySQL_Logger::audit_set_datadir(char *s) {
void MySQL_Logger::log_request(MySQL_Session *sess, MySQL_Data_Stream *myds) {
if (events.enabled==false) return;
if (events.logfile==NULL) return;
// 'MySQL_Session::client_myds' could be NULL in case of 'RequestEnd' being called over a freshly created session
// due to a failed 'CONNECTION_RESET'. Because this scenario isn't a client request, we just return.
if (sess->client_myds==NULL || sess->client_myds->myconn== NULL) return;

MySQL_Connection_userinfo *ui=sess->client_myds->myconn->userinfo;

Expand Down
9 changes: 8 additions & 1 deletion lib/MySQL_Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7181,7 +7181,14 @@ void MySQL_Session::LogQuery(MySQL_Data_Stream *myds) {
// this should become the place to hook other functions
void MySQL_Session::RequestEnd(MySQL_Data_Stream *myds) {
// check if multiplexing needs to be disabled
char *qdt=CurrentQuery.get_digest_text();
char *qdt = NULL;

if (status != PROCESSING_STMT_EXECUTE) {
qdt = CurrentQuery.get_digest_text();
} else {
qdt = CurrentQuery.stmt_info->digest_text;
}

if (qdt && myds && myds->myconn) {
myds->myconn->ProcessQueryAndSetStatusFlags(qdt);
}
Expand Down
268 changes: 268 additions & 0 deletions test/tap/tests/reg_test_sql_calc_found_rows-t.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/**
* @file reg_test_stmt_dis_multiplex-t.cpp
* @brief This is a simple regression test checking that 'STMT_EXECUTE' for queries holding
* 'SQL_CALC_FOUND_ROWS' disable multiplexing and return the expected results when used in combination with
* 'FOUND_ROWS()'. Unlike other tests, this test doesn't rely on 'PROXYSQL INTERNAL SESSION' info.
*/

#include <cstring>
#include <vector>
#include <string>
#include <stdio.h>
#include <utility>
#include <unistd.h>

#include <mysql.h>
#include <mysql/mysqld_error.h>

#include "json.hpp"

#include "tap.h"
#include "command_line.h"
#include "utils.h"

using std::vector;
using std::string;
using std::pair;

using nlohmann::json;

int get_stmt_result(MYSQL_STMT* stmt, int64_t& out_data) {
MYSQL_BIND bind[1];
int64_t data_c;

char is_null[1];
long unsigned int length[1];
char error[1];

memset(bind, 0, sizeof(bind));

bind[0].buffer_type = MYSQL_TYPE_LONG;
bind[0].buffer = (char *)&data_c;
bind[0].buffer_length = sizeof(int);
bind[0].is_null = &is_null[0];
bind[0].length = &length[0];

if (mysql_stmt_bind_result(stmt, bind)) {
diag("'mysql_stmt_bind_result' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt));
return EXIT_FAILURE;
}

while (!mysql_stmt_fetch(stmt)) {}

out_data = data_c;

return EXIT_SUCCESS;
}

int main(int argc, char** argv) {
CommandLine cl;

if (cl.getEnv()) {
diag("Failed to get the required environmental variables.");
return EXIT_FAILURE;
}

plan(6);

diag("Checking that 'SQL_CALC_FOUND_ROWS' and 'FOUND_ROWS()' returns expected results for STMT");

const char* Q_CALC_FOUND_ROWS_1 { "SELECT SQL_CALC_FOUND_ROWS 1" };
const char* Q_CALC_FOUND_ROWS_2 { "SELECT SQL_CALC_FOUND_ROWS 3 UNION SELECT 4" };
const char* Q_FOUND_ROWS { "SELECT FOUND_ROWS()" };

// 1. Prepare the 'SQL_CALC_FOUND_ROWS' stmt in a connection
MYSQL* proxy_mysql = mysql_init(NULL);

diag("%s: Openning initial connection...", tap_curtime().c_str());
if (!mysql_real_connect(proxy_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(proxy_mysql));
return EXIT_FAILURE;
}

MYSQL_STMT* stmt_1 = mysql_stmt_init(proxy_mysql);
MYSQL_STMT* stmt_2 = mysql_stmt_init(proxy_mysql);
MYSQL_STMT* stmt_3 = nullptr;

diag("%s: Issuing the prepare for `%s` in init conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_1);
int my_err = mysql_stmt_prepare(stmt_1, Q_CALC_FOUND_ROWS_1, strlen(Q_CALC_FOUND_ROWS_1));
if (my_err) {
diag(
"'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'",
Q_CALC_FOUND_ROWS_1, mysql_errno(proxy_mysql), mysql_error(proxy_mysql)
);
goto cleanup;
}

diag("%s: Issuing the prepare for `%s` in init conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_2);
my_err = mysql_stmt_prepare(stmt_2, Q_CALC_FOUND_ROWS_2, strlen(Q_CALC_FOUND_ROWS_2));
if (my_err) {
diag(
"'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'",
Q_CALC_FOUND_ROWS_1, mysql_errno(proxy_mysql), mysql_error(proxy_mysql)
);
goto cleanup;
}

mysql_stmt_close(stmt_1);
mysql_stmt_close(stmt_2);

diag("%s: Closing initial connection...", tap_curtime().c_str());
mysql_close(proxy_mysql);

// 2. Open a new connection and prepare the stmts it again in a new connection
proxy_mysql = mysql_init(NULL);

diag("%s: Openning new connection for testing 'Multiplex' disabling", tap_curtime().c_str());
if (!mysql_real_connect(proxy_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(proxy_mysql));
return EXIT_FAILURE;
}

stmt_1 = mysql_stmt_init(proxy_mysql);
stmt_2 = mysql_stmt_init(proxy_mysql);
stmt_3 = mysql_stmt_init(proxy_mysql);

diag("%s: Issuing the prepare for `%s` in new conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_1);
my_err = mysql_stmt_prepare(stmt_1, Q_CALC_FOUND_ROWS_1, strlen(Q_CALC_FOUND_ROWS_1));
if (my_err) {
diag(
"'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'",
Q_CALC_FOUND_ROWS_1, mysql_errno(proxy_mysql), mysql_error(proxy_mysql)
);
goto cleanup;
}

{
diag("%s: Issuing execute for `%s` in new conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_1);
my_err = mysql_stmt_execute(stmt_1);
if (my_err) {
diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_1));
goto cleanup;
}

int64_t f_query_res = 0;
my_err = get_stmt_result(stmt_1, f_query_res);
if (my_err) {
diag("'get_stmt_result' at line %d failed", __LINE__);
goto cleanup;
}
}
{
diag("%s: Issuing the prepare for `%s` in new conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_2);
my_err = mysql_stmt_prepare(stmt_2, Q_CALC_FOUND_ROWS_2, strlen(Q_CALC_FOUND_ROWS_2));
if (my_err) {
diag(
"'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'",
Q_CALC_FOUND_ROWS_2, mysql_errno(proxy_mysql), mysql_error(proxy_mysql)
);
goto cleanup;
}

{
diag("%s: Issuing execute for `%s` in new conn", tap_curtime().c_str(), Q_CALC_FOUND_ROWS_2);
my_err = mysql_stmt_execute(stmt_2);
if (my_err) {
diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_2));
goto cleanup;
}

int64_t s_query_res = 0;
my_err = get_stmt_result(stmt_2, s_query_res);
if (my_err) {
diag("'get_stmt_result' at line %d failed", __LINE__);
goto cleanup;
}
}

diag("%s: Issuing the prepare for `%s` in new conn", tap_curtime().c_str(), Q_FOUND_ROWS);
my_err = mysql_stmt_prepare(stmt_3, Q_FOUND_ROWS, strlen(Q_FOUND_ROWS));
if (my_err) {
diag(
"'mysql_stmt_prepare' failed for query '%s' with error - Err: '%d', ErrMsg: '%s'",
Q_FOUND_ROWS, mysql_errno(proxy_mysql), mysql_error(proxy_mysql)
);
goto cleanup;
}

// 4. Perform execs of both stmt followed by 'found_rows()' stmt and check results
bool exp_results = false;
const int RETRIES = 3;
const int ITERATIONS = 5;

for (int i = 0; i < RETRIES; i++) {
my_err = mysql_stmt_execute(stmt_1);
if (my_err) {
diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_1));
goto cleanup;
}

int64_t f_query_res = 0;
my_err = get_stmt_result(stmt_1, f_query_res);
if (my_err) {
diag("'get_stmt_result' at line %d failed", __LINE__);
goto cleanup;
}

my_err = mysql_stmt_execute(stmt_2);
if (my_err) {
diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_2));
goto cleanup;
}

int64_t s_query_res = 0;
my_err = get_stmt_result(stmt_2, s_query_res);
if (my_err) {
diag("'get_stmt_result' at line %d failed", __LINE__);
goto cleanup;
}

pair<int,int> results {f_query_res, s_query_res};
diag("Results from 'mysql_stmt_execute' were: %s", json {results}.dump().c_str());

ok(
results.first == 1 && results.second == 4,
"'mysql_stmt_execute' returned the expected values - first: %d, second: %d",
results.first, results.second
);

diag("Perform multiple execute for 'FOUND_ROWS()' and check result");

vector<int> found_rows_results {};

for (int i = 0; i < ITERATIONS; i++) {
my_err = mysql_stmt_execute(stmt_3);
if (my_err) {
diag("'mysql_stmt_execute' at line %d failed: %s", __LINE__, mysql_stmt_error(stmt_1));
goto cleanup;
}

int64_t found_rows_res = 0;
my_err = get_stmt_result(stmt_3, found_rows_res);
found_rows_results.push_back(found_rows_res);
}

diag("Results from 'FOUND_ROWS' executions - '%s'", json { found_rows_results }.dump().c_str());

bool correct_found_rows = false;
bool exp_f_val = found_rows_results.front() == 2;
bool exp_tail_val = true;

for (int i = 1; i < ITERATIONS; i++) {
exp_tail_val &= exp_tail_val && found_rows_results[i] == 1;
}

ok(exp_f_val && exp_tail_val, "'FOUND_ROWS' execution returned expected values");
}
}

cleanup:

mysql_stmt_close(stmt_1);
mysql_stmt_close(stmt_2);
mysql_stmt_close(stmt_3);

mysql_close(proxy_mysql);

return exit_status();
}
Loading

0 comments on commit 8c48b37

Please sign in to comment.