summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorsjaakola <seppo.jaakola@iki.fi>2022-02-22 11:37:43 +0200
committerJan Lindström <jan.lindstrom@mariadb.com>2022-03-18 08:30:18 +0200
commit8e9e1c397902d3c3fdba7311c5889f9ed12a0215 (patch)
tree89d64ad6a3e7f085f9c6a2f09cbb533d29751ce5 /client
parent069139a549a62f26d566c1aea2a49c13e9e8a654 (diff)
downloadmariadb-git-8e9e1c397902d3c3fdba7311c5889f9ed12a0215.tar.gz
MDEV-27649 Crash with PS execute after BF abort
This commit contains a test for reproducing the issue in MDEV-27649, where a transaction, executing a prepared statment, is BF aborted. The scenario, in MDEV-27649 has a transaction which has prepared a PS, but not yet executed it, and this transaction is then BF aborted in this state. When the BF aborted transaction tries to execute the PS, it will receive deadlock error. But, when it tries to execute the PS second time, the node crashes. Mtr test galera.galera_bf_abort_ps_bind, exercises this scenario. However, mtr test platform does not have mechanism to control the execution of PS in required detail. For this purpose, mysqltetst.cc was extended to contain 4 new commands: PS_prepare - to prepare a prepared statement PS_bind - to bind values for parameters for the PS PS_execute - to execute the PS PS_close - to close the PS The support for controlling prepared statments in mtr scripts is quite minimal in this commit. Limitations are: * only one PS can be used by a connection, at a time * only input parameters can be bound for the PS * only varchar, integer or float type of parameters can be bound added the result fixes Reviewed-by: Jan Lindström <jan.lindstrom@mariadb.com>
Diffstat (limited to 'client')
-rw-r--r--client/mysqltest.cc450
1 files changed, 450 insertions, 0 deletions
diff --git a/client/mysqltest.cc b/client/mysqltest.cc
index 85beda907c3..814c07ba60f 100644
--- a/client/mysqltest.cc
+++ b/client/mysqltest.cc
@@ -319,6 +319,7 @@ struct st_connection
char *name;
size_t name_len;
MYSQL_STMT* stmt;
+ MYSQL_BIND *ps_params;
/* Set after send to disallow other queries before reap */
my_bool pending;
@@ -393,6 +394,10 @@ enum enum_commands {
Q_ENABLE_PREPARE_WARNINGS, Q_DISABLE_PREPARE_WARNINGS,
Q_RESET_CONNECTION,
Q_OPTIMIZER_TRACE,
+ Q_PS_PREPARE,
+ Q_PS_BIND,
+ Q_PS_EXECUTE,
+ Q_PS_CLOSE,
Q_UNKNOWN, /* Unknown command. */
Q_COMMENT, /* Comments, ignored. */
Q_COMMENT_WITH_COMMAND,
@@ -506,6 +511,10 @@ const char *command_names[]=
"disable_prepare_warnings",
"reset_connection",
"optimizer_trace",
+ "PS_prepare",
+ "PS_bind",
+ "PS_execute",
+ "PS_close",
0
};
@@ -7848,6 +7857,15 @@ static void handle_no_active_connection(struct st_command *command,
var_set_errno(2006);
}
+/* handler functions to execute prepared statement calls in client C API */
+void run_prepare_stmt(struct st_connection *cn, struct st_command *command, const char *query,
+ size_t query_len, DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings);
+void run_bind_stmt(struct st_connection *cn, struct st_command *command, const char *query,
+ size_t query_len, DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings);
+void run_execute_stmt(struct st_connection *cn, struct st_command *command, const char *query,
+ size_t query_len, DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings);
+void run_close_stmt(struct st_connection *cn, struct st_command *command, const char *query,
+ size_t query_len, DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings);
/*
Run query using MySQL C API
@@ -7879,6 +7897,32 @@ void run_query_normal(struct st_connection *cn, struct st_command *command,
DBUG_VOID_RETURN;
}
+ /* handle prepared statement commands */
+ switch (command->type) {
+ case Q_PS_PREPARE:
+ run_prepare_stmt(cn, command, query, query_len, ds, ds_warnings);
+ flags &= ~QUERY_SEND_FLAG;
+ goto end;
+ break;
+ case Q_PS_BIND:
+ run_bind_stmt(cn, command, query, query_len, ds, ds_warnings);
+ flags &= ~QUERY_SEND_FLAG;
+ goto end;
+ break;
+ case Q_PS_EXECUTE:
+ run_execute_stmt(cn, command, query, query_len, ds, ds_warnings);
+ flags &= ~QUERY_SEND_FLAG;
+ goto end;
+ break;
+ case Q_PS_CLOSE:
+ run_close_stmt(cn, command, query, query_len, ds, ds_warnings);
+ flags &= ~QUERY_SEND_FLAG;
+ goto end;
+ break;
+ default: /* not a prepared statement command */
+ break;
+ }
+
if (flags & QUERY_SEND_FLAG)
{
/*
@@ -8434,6 +8478,408 @@ end:
DBUG_VOID_RETURN;
}
+/*
+ prepare query using prepared statement C API
+
+ SYNPOSIS
+ run_prepare_stmt
+ mysql - mysql handle
+ command - current command pointer
+ query - query string to execute
+ query_len - length query string to execute
+ ds - output buffer where to store result form query
+
+ RETURN VALUE
+ error - function will not return
+*/
+
+void run_prepare_stmt(struct st_connection *cn, struct st_command *command, const char *query, size_t query_len, DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings)
+{
+
+ MYSQL *mysql= cn->mysql;
+ MYSQL_STMT *stmt;
+ DYNAMIC_STRING ds_prepare_warnings;
+ DBUG_ENTER("run_prepare_stmt");
+ DBUG_PRINT("query", ("'%-.60s'", query));
+
+ /*
+ Init a new stmt if it's not already one created for this connection
+ */
+ if(!(stmt= cn->stmt))
+ {
+ if (!(stmt= mysql_stmt_init(mysql)))
+ die("unable to init stmt structure");
+ cn->stmt= stmt;
+ }
+
+ /* Init dynamic strings for warnings */
+ if (!disable_warnings)
+ {
+ init_dynamic_string(&ds_prepare_warnings, NULL, 0, 256);
+ }
+
+ /*
+ Prepare the query
+ */
+ char* PS_query= command->first_argument;
+ size_t PS_query_len= command->end - command->first_argument;
+ if (do_stmt_prepare(cn, PS_query, PS_query_len))
+ {
+ handle_error(command, mysql_stmt_errno(stmt),
+ mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds);
+ goto end;
+ }
+
+ /*
+ Get the warnings from mysql_stmt_prepare and keep them in a
+ separate string
+ */
+ if (!disable_warnings)
+ append_warnings(&ds_prepare_warnings, mysql);
+ end:
+ DBUG_VOID_RETURN;
+}
+
+/*
+ bind parameters for a prepared statement C API
+
+ SYNPOSIS
+ run_bind_stmt
+ mysql - mysql handle
+ command - current command pointer
+ query - query string to execute
+ query_len - length query string to execute
+ ds - output buffer where to store result form query
+
+ RETURN VALUE
+ error - function will not return
+*/
+
+void run_bind_stmt(struct st_connection *cn, struct st_command *command,
+ const char *query, size_t query_len, DYNAMIC_STRING *ds,
+ DYNAMIC_STRING *ds_warnings
+ )
+{
+ MYSQL_STMT *stmt= cn->stmt;
+ DBUG_ENTER("run_bind_stmt");
+ DBUG_PRINT("query", ("'%-.60s'", query));
+ MYSQL_BIND *ps_params= cn->ps_params;
+ if (ps_params)
+ {
+ for (size_t i=0; i<stmt->param_count; i++)
+ {
+ my_free(ps_params[i].buffer);
+ ps_params[i].buffer= NULL;
+ }
+ my_free(ps_params);
+ ps_params= NULL;
+ }
+
+ /* Init PS-parameters. */
+ cn->ps_params= ps_params = (MYSQL_BIND*)my_malloc(sizeof(MYSQL_BIND) * stmt->param_count,
+ MYF(MY_WME));
+ bzero((char *) ps_params, sizeof(MYSQL_BIND) * stmt->param_count);
+
+ int i=0;
+ char *c;
+ long *l;
+ double *d;
+
+ char *p= strtok((char*)command->first_argument, " ");
+ while (p != nullptr)
+ {
+ (void)strtol(p, &c, 10);
+ if (!*c)
+ {
+ ps_params[i].buffer_type= MYSQL_TYPE_LONG;
+ l= (long*)my_malloc(sizeof(long), MYF(MY_WME));
+ *l= strtol(p, &c, 10);
+ ps_params[i].buffer= (void*)l;
+ ps_params[i].buffer_length= 8;
+ }
+ else
+ {
+ (void)strtod(p, &c);
+ if (!*c)
+ {
+ ps_params[i].buffer_type= MYSQL_TYPE_DECIMAL;
+ d= (double*)my_malloc(sizeof(double), MYF(MY_WME));
+ *d= strtod(p, &c);
+ ps_params[i].buffer= (void*)d;
+ ps_params[i].buffer_length= 8;
+ }
+ else
+ {
+ ps_params[i].buffer_type= MYSQL_TYPE_STRING;
+ ps_params[i].buffer= strdup(p);
+ ps_params[i].buffer_length= (unsigned long)strlen(p);
+ }
+ }
+
+ p= strtok(nullptr, " ");
+ i++;
+ }
+
+ int rc= mysql_stmt_bind_param(stmt, ps_params);
+ if (rc)
+ {
+ die("mysql_stmt_bind_param() failed': %d %s",
+ mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+/*
+ execute query using prepared statement C API
+
+ SYNPOSIS
+ run_axecute_stmt
+ mysql - mysql handle
+ command - current command pointer
+ query - query string to execute
+ query_len - length query string to execute
+ ds - output buffer where to store result form query
+
+ RETURN VALUE
+ error - function will not return
+*/
+
+void run_execute_stmt(struct st_connection *cn, struct st_command *command,
+ const char *query, size_t query_len, DYNAMIC_STRING *ds,
+ DYNAMIC_STRING *ds_warnings
+ )
+{
+ MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */
+ MYSQL *mysql= cn->mysql;
+ MYSQL_STMT *stmt= cn->stmt;
+ DYNAMIC_STRING ds_execute_warnings;
+ DBUG_ENTER("run_execute_stmt");
+ DBUG_PRINT("query", ("'%-.60s'", query));
+
+ /* Init dynamic strings for warnings */
+ if (!disable_warnings)
+ {
+ init_dynamic_string(&ds_execute_warnings, NULL, 0, 256);
+ }
+
+#if MYSQL_VERSION_ID >= 50000
+ if (cursor_protocol_enabled)
+ {
+ /*
+ Use cursor when retrieving result
+ */
+ ulong type= CURSOR_TYPE_READ_ONLY;
+ if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type))
+ die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s",
+ mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
+ }
+#endif
+
+ /*
+ Execute the query
+ */
+ if (do_stmt_execute(cn))
+ {
+ handle_error(command, mysql_stmt_errno(stmt),
+ mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds);
+ goto end;
+ }
+
+ /*
+ When running in cursor_protocol get the warnings from execute here
+ and keep them in a separate string for later.
+ */
+ if (cursor_protocol_enabled && !disable_warnings)
+ append_warnings(&ds_execute_warnings, mysql);
+
+ /*
+ We instruct that we want to update the "max_length" field in
+ mysql_stmt_store_result(), this is our only way to know how much
+ buffer to allocate for result data
+ */
+ {
+ my_bool one= 1;
+ if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one))
+ die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s",
+ mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
+ }
+
+ /*
+ If we got here the statement succeeded and was expected to do so,
+ get data. Note that this can still give errors found during execution!
+ Store the result of the query if if will return any fields
+ */
+ if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt))
+ {
+ handle_error(command, mysql_stmt_errno(stmt),
+ mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds);
+ goto end;
+ }
+
+ /* If we got here the statement was both executed and read successfully */
+ handle_no_error(command);
+ if (!disable_result_log)
+ {
+ /*
+ Not all statements creates a result set. If there is one we can
+ now create another normal result set that contains the meta
+ data. This set can be handled almost like any other non prepared
+ statement result set.
+ */
+ if ((res= mysql_stmt_result_metadata(stmt)) != NULL)
+ {
+ /* Take the column count from meta info */
+ MYSQL_FIELD *fields= mysql_fetch_fields(res);
+ uint num_fields= mysql_num_fields(res);
+
+ if (display_metadata)
+ append_metadata(ds, fields, num_fields);
+
+ if (!display_result_vertically)
+ append_table_headings(ds, fields, num_fields);
+
+ append_stmt_result(ds, stmt, fields, num_fields);
+
+ mysql_free_result(res); /* Free normal result set with meta data */
+
+ /*
+ Normally, if there is a result set, we do not show warnings from the
+ prepare phase. This is because some warnings are generated both during
+ prepare and execute; this would generate different warning output
+ between normal and ps-protocol test runs.
+
+ The --enable_prepare_warnings command can be used to change this so
+ that warnings from both the prepare and execute phase are shown.
+ */
+ }
+ else
+ {
+ /*
+ This is a query without resultset
+ */
+ }
+
+ /*
+ Fetch info before fetching warnings, since it will be reset
+ otherwise.
+ */
+ if (!disable_info)
+ append_info(ds, mysql_stmt_affected_rows(stmt), mysql_info(mysql));
+
+ if (display_session_track_info)
+ append_session_track_info(ds, mysql);
+
+
+ if (!disable_warnings)
+ {
+ /* Get the warnings from execute */
+
+ /* Append warnings to ds - if there are any */
+ if (append_warnings(&ds_execute_warnings, mysql) ||
+ ds_execute_warnings.length ||
+ ds_warnings->length)
+ {
+ dynstr_append_mem(ds, "Warnings:\n", 10);
+ if (ds_warnings->length)
+ dynstr_append_mem(ds, ds_warnings->str,
+ ds_warnings->length);
+ if (ds_execute_warnings.length)
+ dynstr_append_mem(ds, ds_execute_warnings.str,
+ ds_execute_warnings.length);
+ }
+ }
+ }
+
+end:
+ if (!disable_warnings)
+ {
+ dynstr_free(&ds_execute_warnings);
+ }
+
+ /*
+ We save the return code (mysql_stmt_errno(stmt)) from the last call sent
+ to the server into the mysqltest builtin variable $mysql_errno. This
+ variable then can be used from the test case itself.
+ */
+
+ var_set_errno(mysql_stmt_errno(stmt));
+
+ revert_properties();
+
+ /* Close the statement if reconnect, need new prepare */
+ {
+#ifndef EMBEDDED_LIBRARY
+ my_bool reconnect;
+ mysql_get_option(mysql, MYSQL_OPT_RECONNECT, &reconnect);
+ if (reconnect)
+#else
+ if (mysql->reconnect)
+#endif
+ {
+ if (cn->ps_params)
+ {
+ for (size_t i=0; i<stmt->param_count; i++)
+ {
+ my_free(cn->ps_params[i].buffer);
+ cn->ps_params[i].buffer= NULL;
+ }
+ my_free(cn->ps_params);
+ }
+ mysql_stmt_close(stmt);
+ cn->stmt= NULL;
+ cn->ps_params= NULL;
+ }
+ }
+ DBUG_VOID_RETURN;
+}
+
+/*
+ close a prepared statement C API
+
+ SYNPOSIS
+ run_close_stmt
+ mysql - mysql handle
+ command - current command pointer
+ query - query string to execute
+ query_len - length query string to execute
+ ds - output buffer where to store result form query
+
+ RETURN VALUE
+ error - function will not return
+*/
+
+void run_close_stmt(struct st_connection *cn, struct st_command *command,
+ const char *query, size_t query_len, DYNAMIC_STRING *ds,
+ DYNAMIC_STRING *ds_warnings
+ )
+{
+ MYSQL_STMT *stmt= cn->stmt;
+ DBUG_ENTER("run_close_stmt");
+ DBUG_PRINT("query", ("'%-.60s'", query));
+
+ if (cn->ps_params)
+ {
+
+ for (size_t i=0; i<stmt->param_count; i++)
+ {
+ my_free(cn->ps_params[i].buffer);
+ cn->ps_params[i].buffer= NULL;
+ }
+ my_free(cn->ps_params);
+ }
+
+ /* Close the statement */
+ if (stmt)
+ {
+ mysql_stmt_close(stmt);
+ cn->stmt= NULL;
+ }
+ cn->ps_params= NULL;
+
+ DBUG_VOID_RETURN;
+}
+
/*
@@ -9474,6 +9920,10 @@ int main(int argc, char **argv)
/* fall through */
case Q_QUERY:
case Q_REAP:
+ case Q_PS_PREPARE:
+ case Q_PS_BIND:
+ case Q_PS_EXECUTE:
+ case Q_PS_CLOSE:
{
my_bool old_display_result_vertically= display_result_vertically;
/* Default is full query, both reap and send */