summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client/mysqltest.cc450
-rw-r--r--mysql-test/suite/galera/r/galera_bf_abort_ps_bind.result37
-rw-r--r--mysql-test/suite/galera/t/galera_bf_abort_ps_bind.cnf7
-rw-r--r--mysql-test/suite/galera/t/galera_bf_abort_ps_bind.test58
4 files changed, 552 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 */
diff --git a/mysql-test/suite/galera/r/galera_bf_abort_ps_bind.result b/mysql-test/suite/galera/r/galera_bf_abort_ps_bind.result
new file mode 100644
index 00000000000..adc7da58eae
--- /dev/null
+++ b/mysql-test/suite/galera/r/galera_bf_abort_ps_bind.result
@@ -0,0 +1,37 @@
+connection node_2;
+connection node_1;
+CREATE TABLE t (i int primary key auto_increment, j varchar(20) character set utf8);
+connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1;
+connection node_1a;
+SET SESSION wsrep_sync_wait = 0;
+connection node_1;
+insert into t values (1, 'first');
+PS_prepare INSERT INTO t(j) VALUES (?);;
+PS_bind node1;
+PS_execute;
+PS_execute;
+select * from t;
+i j
+1 first
+3 node1
+5 node1
+PS_close;
+PS_prepare INSERT INTO t(j) VALUES (?);;
+PS_bind node1;
+begin;
+update t set j='node1' where i=1;
+connection node_2;
+update t set j='node2' where i=1;
+connection node_1a;
+connection node_1;
+PS_execute;
+ERROR 40001: Deadlock found when trying to get lock; try restarting transaction
+PS_execute;
+commit;
+select * from t;
+i j
+1 node2
+3 node1
+5 node1
+7 node1
+drop table t;
diff --git a/mysql-test/suite/galera/t/galera_bf_abort_ps_bind.cnf b/mysql-test/suite/galera/t/galera_bf_abort_ps_bind.cnf
new file mode 100644
index 00000000000..62cf1854032
--- /dev/null
+++ b/mysql-test/suite/galera/t/galera_bf_abort_ps_bind.cnf
@@ -0,0 +1,7 @@
+!include ../galera_2nodes.cnf
+
+[mysqld.1]
+wsrep-debug=1
+
+[mysqld.2]
+wsrep-debug=1
diff --git a/mysql-test/suite/galera/t/galera_bf_abort_ps_bind.test b/mysql-test/suite/galera/t/galera_bf_abort_ps_bind.test
new file mode 100644
index 00000000000..a840f612a82
--- /dev/null
+++ b/mysql-test/suite/galera/t/galera_bf_abort_ps_bind.test
@@ -0,0 +1,58 @@
+--source include/galera_cluster.inc
+--source include/have_innodb.inc
+
+CREATE TABLE t (i int primary key auto_increment, j varchar(20) character set utf8);
+
+--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1
+--connection node_1a
+SET SESSION wsrep_sync_wait = 0;
+
+--connection node_1
+insert into t values (1, 'first');
+
+# prepare a statement for inserting rows into table t
+--PS_prepare INSERT INTO t(j) VALUES (?);
+
+# bind parameter, to insert with column j having value 'node1'
+--PS_bind node1
+
+# insert two rows with the PS
+# this is for showing that two execute commands can follow a bind command
+--PS_execute
+--PS_execute
+select * from t;
+
+# close the prepared statement, and prepare a new PS,
+# this happens to be same as the first PS
+# also bind parameter for the PS
+--PS_close
+--PS_prepare INSERT INTO t(j) VALUES (?);
+--PS_bind node1
+
+# start a transaction and make one update
+# leaving the transaction open
+begin;
+update t set j='node1' where i=1;
+
+# replicate a transaction from node2, which BF aborts the open
+# transaction in node1
+--connection node_2
+update t set j='node2' where i=1;
+
+# wait until the BF has completed, and update from node_2 has committed
+--connection node_1a
+--let $wait_condition = SELECT COUNT(*) = 1 FROM t WHERE j='node2'
+--source include/wait_condition.inc
+
+# continue the open transaction, trying to insert third row, deadlock is now observed
+--connection node_1
+--error ER_LOCK_DEADLOCK
+--PS_execute
+
+# try to insert one more row
+--PS_execute
+commit;
+
+select * from t;
+
+drop table t;