summaryrefslogtreecommitdiff
path: root/client/mysqltest.cc
diff options
context:
space:
mode:
authorMarko Mäkelä <marko.makela@mariadb.com>2022-03-29 12:59:18 +0300
committerMarko Mäkelä <marko.makela@mariadb.com>2022-03-29 12:59:18 +0300
commitd62b0368ca53cc10b45b703bbeefcf0b674bd39d (patch)
treee65926bf20605d24a87619553374a74e7ef1c2c8 /client/mysqltest.cc
parent9d6d1221230e2acf9fac2ab6fe685c0a2a7845aa (diff)
parent088b37b5eaa8c3198c7f8ea0358d15135833f6bb (diff)
downloadmariadb-git-d62b0368ca53cc10b45b703bbeefcf0b674bd39d.tar.gz
Merge 10.4 into 10.5
Diffstat (limited to 'client/mysqltest.cc')
-rw-r--r--client/mysqltest.cc455
1 files changed, 454 insertions, 1 deletions
diff --git a/client/mysqltest.cc b/client/mysqltest.cc
index d76f3b06efc..e855e1af807 100644
--- a/client/mysqltest.cc
+++ b/client/mysqltest.cc
@@ -1,5 +1,5 @@
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
- Copyright (c) 2009, 2021, MariaDB
+ Copyright (c) 2009, 2022, MariaDB
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -320,6 +320,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;
@@ -394,6 +395,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,
@@ -507,6 +512,10 @@ const char *command_names[]=
"disable_prepare_warnings",
"reset_connection",
"optimizer_trace",
+ "PS_prepare",
+ "PS_bind",
+ "PS_execute",
+ "PS_close",
0
};
@@ -7900,6 +7909,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
@@ -7931,6 +7949,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)
{
/*
@@ -8486,6 +8530,411 @@ 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(PSI_NOT_INSTRUMENTED,
+ 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(PSI_NOT_INSTRUMENTED, 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(PSI_NOT_INSTRUMENTED, 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;
+}
+
/*
@@ -9526,6 +9975,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 */