summaryrefslogtreecommitdiff
path: root/sql/sql_prepare.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_prepare.cc')
-rw-r--r--sql/sql_prepare.cc704
1 files changed, 546 insertions, 158 deletions
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index d3a5d0aeef6..853079dd36a 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -1,5 +1,5 @@
/* Copyright (c) 2002, 2015, Oracle and/or its affiliates.
- Copyright (c) 2008, 2015, MariaDB
+ Copyright (c) 2008, 2016, 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
@@ -102,6 +102,7 @@ When one supplies long data for a placeholder:
#include "sql_acl.h" // *_ACL
#include "sql_derived.h" // mysql_derived_prepare,
// mysql_handle_derived
+#include "sql_cte.h"
#include "sql_cursor.h"
#include "sql_show.h"
#include "sql_repl.h"
@@ -161,6 +162,9 @@ public:
Select_fetch_protocol_binary result;
Item_param **param_array;
Server_side_cursor *cursor;
+ uchar *packet;
+ uchar *packet_end;
+ ulong iterations;
uint param_count;
uint last_errno;
uint flags;
@@ -179,15 +183,19 @@ public:
*/
uint select_number_after_prepare;
char last_error[MYSQL_ERRMSG_SIZE];
+ my_bool start_param;
#ifndef EMBEDDED_LIBRARY
bool (*set_params)(Prepared_statement *st, uchar *data, uchar *data_end,
uchar *read_pos, String *expanded_query);
+ bool (*set_bulk_params)(Prepared_statement *st,
+ uchar **read_pos, uchar *data_end, bool reset);
#else
bool (*set_params_data)(Prepared_statement *st, String *expanded_query);
+ /*TODO: add bulk support for builtin server */
#endif
- bool (*set_params_from_vars)(Prepared_statement *stmt,
- List<LEX_STRING>& varnames,
- String *expanded_query);
+ bool (*set_params_from_actual_params)(Prepared_statement *stmt,
+ List<Item> &list,
+ String *expanded_query);
public:
Prepared_statement(THD *thd_arg);
virtual ~Prepared_statement();
@@ -203,9 +211,16 @@ public:
bool execute_loop(String *expanded_query,
bool open_cursor,
uchar *packet_arg, uchar *packet_end_arg);
+ bool execute_bulk_loop(String *expanded_query,
+ bool open_cursor,
+ uchar *packet_arg, uchar *packet_end_arg,
+ ulong iterations);
bool execute_server_runnable(Server_runnable *server_runnable);
+ my_bool set_bulk_parameters(bool reset);
+ ulong bulk_iterations();
/* Destroy this statement */
void deallocate();
+ bool execute_immediate(const char *query, uint query_length);
private:
/**
The memory root to allocate parsed tree elements (instances of Item,
@@ -217,6 +232,7 @@ private:
bool set_parameters(String *expanded_query,
uchar *packet, uchar *packet_end);
bool execute(String *expanded_query, bool open_cursor);
+ void deallocate_immediate();
bool reprepare();
bool validate_metadata(Prepared_statement *copy);
void swap_prepared_statement(Prepared_statement *copy);
@@ -276,7 +292,7 @@ protected:
virtual bool send_ok(uint server_status, uint statement_warn_count,
ulonglong affected_rows, ulonglong last_insert_id,
- const char *message);
+ const char *message, bool skip_flush);
virtual bool send_eof(uint server_status, uint statement_warn_count);
virtual bool send_error(uint sql_errno, const char *err_msg, const char* sqlstate);
@@ -326,8 +342,14 @@ find_prepared_statement(THD *thd, ulong id)
To strictly separate namespaces of SQL prepared statements and C API
prepared statements find() will return 0 if there is a named prepared
statement with such id.
+
+ LAST_STMT_ID is special value which mean last prepared statement ID
+ (it was made for COM_MULTI to allow prepare and execute a statement
+ in the same command but usage is not limited by COM_MULTI only).
*/
- Statement *stmt= thd->stmt_map.find(id);
+ Statement *stmt= ((id == LAST_STMT_ID) ?
+ thd->last_stmt :
+ thd->stmt_map.find(id));
if (stmt == 0 || stmt->type() != Query_arena::PREPARED_STATEMENT)
return NULL;
@@ -721,54 +743,44 @@ static void setup_one_conversion_function(THD *thd, Item_param *param,
case MYSQL_TYPE_TINY:
param->set_param_func= set_param_tiny;
param->item_type= Item::INT_ITEM;
- param->item_result_type= INT_RESULT;
break;
case MYSQL_TYPE_SHORT:
param->set_param_func= set_param_short;
param->item_type= Item::INT_ITEM;
- param->item_result_type= INT_RESULT;
break;
case MYSQL_TYPE_LONG:
param->set_param_func= set_param_int32;
param->item_type= Item::INT_ITEM;
- param->item_result_type= INT_RESULT;
break;
case MYSQL_TYPE_LONGLONG:
param->set_param_func= set_param_int64;
param->item_type= Item::INT_ITEM;
- param->item_result_type= INT_RESULT;
break;
case MYSQL_TYPE_FLOAT:
param->set_param_func= set_param_float;
param->item_type= Item::REAL_ITEM;
- param->item_result_type= REAL_RESULT;
break;
case MYSQL_TYPE_DOUBLE:
param->set_param_func= set_param_double;
param->item_type= Item::REAL_ITEM;
- param->item_result_type= REAL_RESULT;
break;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
param->set_param_func= set_param_decimal;
param->item_type= Item::DECIMAL_ITEM;
- param->item_result_type= DECIMAL_RESULT;
break;
case MYSQL_TYPE_TIME:
param->set_param_func= set_param_time;
param->item_type= Item::STRING_ITEM;
- param->item_result_type= STRING_RESULT;
break;
case MYSQL_TYPE_DATE:
param->set_param_func= set_param_date;
param->item_type= Item::STRING_ITEM;
- param->item_result_type= STRING_RESULT;
break;
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
param->set_param_func= set_param_datetime;
param->item_type= Item::STRING_ITEM;
- param->item_result_type= STRING_RESULT;
break;
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
@@ -781,7 +793,6 @@ static void setup_one_conversion_function(THD *thd, Item_param *param,
DBUG_ASSERT(thd->variables.character_set_client);
param->value.cs_info.final_character_set_of_str_value= &my_charset_bin;
param->item_type= Item::STRING_ITEM;
- param->item_result_type= STRING_RESULT;
break;
default:
/*
@@ -811,10 +822,9 @@ static void setup_one_conversion_function(THD *thd, Item_param *param,
charset of connection, so we have to set it later.
*/
param->item_type= Item::STRING_ITEM;
- param->item_result_type= STRING_RESULT;
}
}
- param->param_type= (enum enum_field_types) param_type;
+ param->set_handler_by_field_type((enum enum_field_types) param_type);
}
#ifndef EMBEDDED_LIBRARY
@@ -826,8 +836,8 @@ static void setup_one_conversion_function(THD *thd, Item_param *param,
*/
inline bool is_param_long_data_type(Item_param *param)
{
- return ((param->param_type >= MYSQL_TYPE_TINY_BLOB) &&
- (param->param_type <= MYSQL_TYPE_STRING));
+ return ((param->field_type() >= MYSQL_TYPE_TINY_BLOB) &&
+ (param->field_type() <= MYSQL_TYPE_STRING));
}
@@ -965,11 +975,62 @@ static bool insert_params(Prepared_statement *stmt, uchar *null_array,
}
+static bool insert_bulk_params(Prepared_statement *stmt,
+ uchar **read_pos, uchar *data_end,
+ bool reset)
+{
+ Item_param **begin= stmt->param_array;
+ Item_param **end= begin + stmt->param_count;
+
+ DBUG_ENTER("insert_params");
+
+ for (Item_param **it= begin; it < end; ++it)
+ {
+ Item_param *param= *it;
+ if (reset)
+ param->reset();
+ if (param->state != Item_param::LONG_DATA_VALUE)
+ {
+ if (param->indicators)
+ param->indicator= (enum_indicator_type) *((*read_pos)++);
+ else
+ param->indicator= STMT_INDICATOR_NONE;
+ if ((*read_pos) > data_end)
+ DBUG_RETURN(1);
+ switch (param->indicator)
+ {
+ case STMT_INDICATOR_NONE:
+ if ((*read_pos) >= data_end)
+ DBUG_RETURN(1);
+ param->set_param_func(param, read_pos, (uint) (data_end - (*read_pos)));
+ if (param->state == Item_param::NO_VALUE)
+ DBUG_RETURN(1);
+ break;
+ case STMT_INDICATOR_NULL:
+ param->set_null();
+ break;
+ case STMT_INDICATOR_DEFAULT:
+ param->set_default();
+ break;
+ case STMT_INDICATOR_IGNORE:
+ param->set_ignore();
+ break;
+ }
+ }
+ else
+ DBUG_RETURN(1); // long is not supported here
+ }
+ DBUG_RETURN(0);
+}
+
static bool setup_conversion_functions(Prepared_statement *stmt,
- uchar **data, uchar *data_end)
+ uchar **data, uchar *data_end,
+ bool bulk_protocol= 0)
{
/* skip null bits */
- uchar *read_pos= *data + (stmt->param_count+7) / 8;
+ uchar *read_pos= *data;
+ if (!bulk_protocol)
+ read_pos+= (stmt->param_count+7) / 8;
DBUG_ENTER("setup_conversion_functions");
@@ -986,6 +1047,7 @@ static bool setup_conversion_functions(Prepared_statement *stmt,
{
ushort typecode;
const uint signed_bit= 1 << 15;
+ const uint indicators_bit= 1 << 14;
if (read_pos >= data_end)
DBUG_RETURN(1);
@@ -993,7 +1055,10 @@ static bool setup_conversion_functions(Prepared_statement *stmt,
typecode= sint2korr(read_pos);
read_pos+= 2;
(**it).unsigned_flag= MY_TEST(typecode & signed_bit);
- setup_one_conversion_function(thd, *it, (uchar) (typecode & ~signed_bit));
+ if (bulk_protocol)
+ (**it).indicators= MY_TEST(typecode & indicators_bit);
+ setup_one_conversion_function(thd, *it,
+ (uchar) (typecode & 0xff));
}
}
*data= read_pos;
@@ -1002,6 +1067,8 @@ static bool setup_conversion_functions(Prepared_statement *stmt,
#else
+//TODO: support bulk parameters
+
/**
Embedded counterparts of parameter assignment routines.
@@ -1146,31 +1213,27 @@ swap_parameter_array(Item_param **param_array_dst,
Assign prepared statement parameters from user variables.
@param stmt Statement
- @param varnames List of variables. Caller must ensure that number
- of variables in the list is equal to number of statement
+ @param params A list of parameters. Caller must ensure that number
+ of parameters in the list is equal to number of statement
parameters
@param query Ignored
*/
-static bool insert_params_from_vars(Prepared_statement *stmt,
- List<LEX_STRING>& varnames,
- String *query __attribute__((unused)))
+static bool
+insert_params_from_actual_params(Prepared_statement *stmt,
+ List<Item> &params,
+ String *query __attribute__((unused)))
{
Item_param **begin= stmt->param_array;
Item_param **end= begin + stmt->param_count;
- user_var_entry *entry;
- LEX_STRING *varname;
- List_iterator<LEX_STRING> var_it(varnames);
- DBUG_ENTER("insert_params_from_vars");
+ List_iterator<Item> param_it(params);
+ DBUG_ENTER("insert_params_from_actual_params");
for (Item_param **it= begin; it < end; ++it)
{
Item_param *param= *it;
- varname= var_it++;
- entry= (user_var_entry*)my_hash_search(&stmt->thd->user_vars,
- (uchar*) varname->str,
- varname->length);
- if (param->set_from_user_var(stmt->thd, entry) ||
+ Item *ps_param= param_it++;
+ if (ps_param->save_in_param(stmt->thd, param) ||
param->convert_str_value(stmt->thd))
DBUG_RETURN(1);
}
@@ -1179,45 +1242,41 @@ static bool insert_params_from_vars(Prepared_statement *stmt,
/**
- Do the same as insert_params_from_vars but also construct query text for
- binary log.
+ Do the same as insert_params_from_actual_params
+ but also construct query text for binary log.
@param stmt Prepared statement
- @param varnames List of variables. Caller must ensure that number of
- variables in the list is equal to number of statement
+ @param params A list of parameters. Caller must ensure that number of
+ parameters in the list is equal to number of statement
parameters
@param query The query with parameter markers replaced with corresponding
user variables that were used to execute the query.
*/
-static bool insert_params_from_vars_with_log(Prepared_statement *stmt,
- List<LEX_STRING>& varnames,
- String *query)
+static bool
+insert_params_from_actual_params_with_log(Prepared_statement *stmt,
+ List<Item> &params,
+ String *query)
{
Item_param **begin= stmt->param_array;
Item_param **end= begin + stmt->param_count;
- user_var_entry *entry;
- LEX_STRING *varname;
- List_iterator<LEX_STRING> var_it(varnames);
+ List_iterator<Item> param_it(params);
THD *thd= stmt->thd;
Copy_query_with_rewrite acc(thd, stmt->query(), stmt->query_length(), query);
- DBUG_ENTER("insert_params_from_vars_with_log");
+ DBUG_ENTER("insert_params_from_actual_params_with_log");
for (Item_param **it= begin; it < end; ++it)
{
Item_param *param= *it;
- varname= var_it++;
-
- entry= (user_var_entry *) my_hash_search(&thd->user_vars, (uchar*)
- varname->str, varname->length);
+ Item *ps_param= param_it++;
/*
We have to call the setup_one_conversion_function() here to set
the parameter's members that might be needed further
(e.g. value.cs_info.character_set_client is used in the query_val_str()).
*/
- setup_one_conversion_function(thd, param, param->param_type);
- if (param->set_from_user_var(thd, entry))
+ setup_one_conversion_function(thd, param, param->field_type());
+ if (ps_param->save_in_param(thd, param))
DBUG_RETURN(1);
if (acc.append(param))
@@ -1264,7 +1323,7 @@ static bool mysql_test_insert(Prepared_statement *stmt,
*/
if (table_list->lock_type != TL_WRITE_DELAYED)
{
- if (open_temporary_tables(thd, table_list))
+ if (thd->open_temporary_tables(table_list))
goto error;
}
@@ -1321,7 +1380,7 @@ static bool mysql_test_insert(Prepared_statement *stmt,
my_error(ER_WRONG_VALUE_COUNT_ON_ROW, MYF(0), counter);
goto error;
}
- if (setup_fields(thd, 0, *values, MARK_COLUMNS_NONE, 0, 0))
+ if (setup_fields(thd, Ref_ptr_array(), *values, MARK_COLUMNS_NONE, 0, 0))
goto error;
}
}
@@ -1411,7 +1470,8 @@ static int mysql_test_update(Prepared_statement *stmt,
table_list->register_want_access(want_privilege);
#endif
thd->lex->select_lex.no_wrap_view_item= TRUE;
- res= setup_fields(thd, 0, select->item_list, MARK_COLUMNS_READ, 0, 0);
+ res= setup_fields(thd, Ref_ptr_array(),
+ select->item_list, MARK_COLUMNS_READ, 0, 0);
thd->lex->select_lex.no_wrap_view_item= FALSE;
if (res)
goto error;
@@ -1422,7 +1482,8 @@ static int mysql_test_update(Prepared_statement *stmt,
(SELECT_ACL & ~table_list->table->grant.privilege);
table_list->register_want_access(SELECT_ACL);
#endif
- if (setup_fields(thd, 0, stmt->lex->value_list, MARK_COLUMNS_NONE, 0, 0) ||
+ if (setup_fields(thd, Ref_ptr_array(),
+ stmt->lex->value_list, MARK_COLUMNS_NONE, 0, 0) ||
check_unique_table(thd, table_list))
goto error;
/* TODO: here we should send types of placeholders to the client. */
@@ -1468,7 +1529,7 @@ static bool mysql_test_delete(Prepared_statement *stmt,
my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "DELETE");
goto error;
}
- if (!table_list->table || !table_list->table->created)
+ if (!table_list->table || !table_list->table->is_created())
{
my_error(ER_VIEW_DELETE_MERGE_VIEW, MYF(0),
table_list->view_db.str, table_list->view_name.str);
@@ -1512,6 +1573,8 @@ static int mysql_test_select(Prepared_statement *stmt,
lex->select_lex.context.resolve_in_select_list= TRUE;
ulong privilege= lex->exchange ? SELECT_ACL | FILE_ACL : SELECT_ACL;
+ if (check_dependencies_in_with_clauses(lex->with_clauses_list))
+ goto error;
if (tables)
{
if (check_table_access(thd, privilege, tables, FALSE, UINT_MAX, FALSE))
@@ -1592,7 +1655,8 @@ static bool mysql_test_do_fields(Prepared_statement *stmt,
if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL,
DT_PREPARE | DT_CREATE))
DBUG_RETURN(TRUE);
- DBUG_RETURN(setup_fields(thd, 0, *values, MARK_COLUMNS_NONE, 0, 0));
+ DBUG_RETURN(setup_fields(thd, Ref_ptr_array(),
+ *values, MARK_COLUMNS_NONE, 0, 0));
}
@@ -1777,6 +1841,9 @@ static bool mysql_test_create_table(Prepared_statement *stmt)
if (create_table_precheck(thd, tables, create_table))
DBUG_RETURN(TRUE);
+ if (check_dependencies_in_with_clauses(lex->with_clauses_list))
+ DBUG_RETURN(TRUE);
+
if (select_lex->item_list.elements)
{
/* Base table and temporary table are not in the same name space. */
@@ -2028,7 +2095,7 @@ static bool mysql_test_create_view(Prepared_statement *stmt)
Since we can't pre-open temporary tables for SQLCOM_CREATE_VIEW,
(see mysql_create_view) we have to do it here instead.
*/
- if (open_temporary_tables(thd, tables))
+ if (thd->open_temporary_tables(tables))
goto err;
if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL,
@@ -2167,6 +2234,9 @@ static bool mysql_test_insert_select(Prepared_statement *stmt,
if (insert_precheck(stmt->thd, tables))
return 1;
+ if (check_dependencies_in_with_clauses(lex->with_clauses_list))
+ return 1;
+
/* store it, because mysql_insert_select_prepare_tester change it */
first_local_table= lex->select_lex.table_list.first;
DBUG_ASSERT(first_local_table != 0);
@@ -2280,7 +2350,7 @@ static bool check_prepared_statement(Prepared_statement *stmt)
*/
if (sql_command_flags[sql_command] & CF_PREOPEN_TMP_TABLES)
{
- if (open_temporary_tables(thd, tables))
+ if (thd->open_temporary_tables(tables))
goto error;
}
@@ -2585,7 +2655,10 @@ void mysqld_stmt_prepare(THD *thd, const char *packet, uint packet_length)
{
/* Statement map deletes statement on erase */
thd->stmt_map.erase(stmt);
+ thd->clear_last_stmt();
}
+ else
+ thd->set_last_stmt(stmt);
thd->protocol= save_protocol;
@@ -2598,95 +2671,99 @@ end:
}
/**
- Get an SQL statement text from a user variable or from plain text.
+ Get an SQL statement from an item in lex->prepared_stmt_code.
- If the statement is plain text, just assign the
- pointers, otherwise allocate memory in thd->mem_root and copy
- the contents of the variable, possibly with character
- set conversion.
+ This function can return pointers to very different memory classes:
+ - a static string "NULL", if the item returned NULL
+ - the result of prepare_stmt_code->val_str(), if no conversion was needed
+ - a thd->mem_root allocated string with the result of
+ prepare_stmt_code->val_str() converted to @@collation_connection,
+ if conversion was needed
- @param[in] lex main lex
- @param[out] query_len length of the SQL statement (is set only
- in case of success)
+ The caller must dispose the result before the life cycle of "buffer" ends.
+ As soon as buffer's destructor is called, the value is not valid any more!
- @retval
- non-zero success
- @retval
- 0 in case of error (out of memory)
+ mysql_sql_stmt_prepare() and mysql_sql_stmt_execute_immediate()
+ call get_dynamic_sql_string() and then call respectively
+ Prepare_statement::prepare() and Prepare_statment::execute_immediate(),
+ who store the returned result into its permanent location using
+ alloc_query(). "buffer" is still not destructed at that time.
+
+ @param[out] dst the result is stored here
+ @param[inout] buffer
+
+ @retval false on success
+ @retval true on error (out of memory)
*/
-static const char *get_dynamic_sql_string(LEX *lex, uint *query_len)
+bool LEX::get_dynamic_sql_string(LEX_CSTRING *dst, String *buffer)
{
- THD *thd= lex->thd;
- char *query_str= 0;
+ if (prepared_stmt_code->fix_fields(thd, NULL) ||
+ prepared_stmt_code->check_cols(1))
+ return true;
- if (lex->prepared_stmt_code_is_varref)
+ const String *str= prepared_stmt_code->val_str(buffer);
+ if (prepared_stmt_code->null_value)
{
- /* This is PREPARE stmt FROM or EXECUTE IMMEDIATE @var. */
- String str;
- CHARSET_INFO *to_cs= thd->variables.collation_connection;
- bool needs_conversion;
- user_var_entry *entry;
- String *var_value= &str;
- uint32 unused, len;
/*
- Convert @var contents to string in connection character set. Although
- it is known that int/real/NULL value cannot be a valid query we still
- convert it for error messages to be uniform.
+ Prepare source was NULL, so we need to set "str" to
+ something reasonable to get a readable error message during parsing
*/
- if ((entry=
- (user_var_entry*)my_hash_search(&thd->user_vars,
- (uchar*)lex->prepared_stmt_code.str,
- lex->prepared_stmt_code.length))
- && entry->value)
- {
- bool is_var_null;
- var_value= entry->val_str(&is_var_null, &str, NOT_FIXED_DEC);
- /*
- NULL value of variable checked early as entry->value so here
- we can't get NULL in normal conditions
- */
- DBUG_ASSERT(!is_var_null);
- if (!var_value)
- goto end;
- }
- else
- {
- /*
- variable absent or equal to NULL, so we need to set variable to
- something reasonable to get a readable error message during parsing
- */
- str.set(STRING_WITH_LEN("NULL"), &my_charset_latin1);
- }
-
- needs_conversion= String::needs_conversion(var_value->length(),
- var_value->charset(), to_cs,
- &unused);
+ dst->str= "NULL";
+ dst->length= 4;
+ return false;
+ }
- len= (needs_conversion ? var_value->length() * to_cs->mbmaxlen :
- var_value->length());
- if (!(query_str= (char*) alloc_root(thd->mem_root, len+1)))
- goto end;
+ /*
+ Character set conversion notes:
+
+ 1) When PREPARE or EXECUTE IMMEDIATE are used with string literals:
+ PREPARE stmt FROM 'SELECT ''str''';
+ EXECUTE IMMEDIATE 'SELECT ''str''';
+ it's very unlikely that any conversion will happen below, because
+ @@character_set_client and @@collation_connection are normally
+ set to the same CHARSET_INFO pointer.
+
+ In tricky environments when @@collation_connection is set to something
+ different from @@character_set_client, double conversion may happen:
+ - When the parser scans the string literal
+ (sql_yacc.yy rules "prepare_src" -> "expr" -> ... -> "text_literal")
+ it will convert 'str' from @@character_set_client to
+ @@collation_connection.
+ - Then in the code below will convert 'str' from @@collation_connection
+ back to @@character_set_client.
+
+ 2) When PREPARE or EXECUTE IMMEDIATE is used with a user variable,
+ it should work about the same way, because user variables are usually
+ assigned like this:
+ SET @str='str';
+ and thus have the same character set with string literals.
+
+ 3) When PREPARE or EXECUTE IMMEDIATE is used with some
+ more complex expression, conversion will depend on this expression.
+ For example, a concatenation of string literals:
+ EXECUTE IMMEDIATE 'SELECT * FROM'||'t1';
+ should work the same way with just a single literal,
+ so no conversion normally.
+ */
+ CHARSET_INFO *to_cs= thd->variables.character_set_client;
- if (needs_conversion)
+ uint32 unused;
+ if (String::needs_conversion(str->length(), str->charset(), to_cs, &unused))
+ {
+ if (!(dst->str= sql_strmake_with_convert(thd, str->ptr(), str->length(),
+ str->charset(), UINT_MAX32,
+ to_cs, &dst->length)))
{
- uint dummy_errors;
- len= copy_and_convert(query_str, len, to_cs, var_value->ptr(),
- var_value->length(), var_value->charset(),
- &dummy_errors);
+ dst->length= 0;
+ return true;
}
- else
- memcpy(query_str, var_value->ptr(), var_value->length());
- query_str[len]= '\0'; // Safety (mostly for debug)
- *query_len= len;
- }
- else
- {
- query_str= lex->prepared_stmt_code.str;
- *query_len= lex->prepared_stmt_code.length;
+ DBUG_ASSERT(dst->length <= UINT_MAX32);
+ return false;
}
-end:
- return query_str;
+ dst->str= str->ptr();
+ dst->length= str->length();
+ return false;
}
@@ -2709,8 +2786,7 @@ void mysql_sql_stmt_prepare(THD *thd)
LEX *lex= thd->lex;
LEX_STRING *name= &lex->prepared_stmt_name;
Prepared_statement *stmt;
- const char *query;
- uint query_len= 0;
+ LEX_CSTRING query;
DBUG_ENTER("mysql_sql_stmt_prepare");
if ((stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name)))
@@ -2728,7 +2804,12 @@ void mysql_sql_stmt_prepare(THD *thd)
stmt->deallocate();
}
- if (! (query= get_dynamic_sql_string(lex, &query_len)) ||
+ /*
+ It's important for "buffer" not to be destructed before stmt->prepare()!
+ See comments in get_dynamic_sql_string().
+ */
+ StringBuffer<256> buffer;
+ if (lex->get_dynamic_sql_string(&query, &buffer) ||
! (stmt= new Prepared_statement(thd)))
{
DBUG_VOID_RETURN; /* out of memory */
@@ -2749,17 +2830,57 @@ void mysql_sql_stmt_prepare(THD *thd)
DBUG_VOID_RETURN;
}
- if (stmt->prepare(query, query_len))
+ if (stmt->prepare(query.str, (uint) query.length))
{
/* Statement map deletes the statement on erase */
thd->stmt_map.erase(stmt);
}
else
+ {
+ SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL);
my_ok(thd, 0L, 0L, "Statement prepared");
+ }
DBUG_VOID_RETURN;
}
+
+void mysql_sql_stmt_execute_immediate(THD *thd)
+{
+ LEX *lex= thd->lex;
+ Prepared_statement *stmt;
+ LEX_CSTRING query;
+ DBUG_ENTER("mysql_sql_stmt_execute_immediate");
+
+ if (lex->prepared_stmt_params_fix_fields(thd))
+ DBUG_VOID_RETURN;
+
+ /*
+ Prepared_statement is quite large,
+ let's allocate it on the heap rather than on the stack.
+
+ It's important for "buffer" not to be destructed
+ before stmt->execute_immediate().
+ See comments in get_dynamic_sql_string().
+ */
+ StringBuffer<256> buffer;
+ if (lex->get_dynamic_sql_string(&query, &buffer) ||
+ !(stmt= new Prepared_statement(thd)))
+ DBUG_VOID_RETURN; // out of memory
+
+ // See comments on thd->free_list in mysql_sql_stmt_execute()
+ Item *free_list_backup= thd->free_list;
+ thd->free_list= NULL;
+ (void) stmt->execute_immediate(query.str, (uint) query.length);
+ thd->free_items();
+ thd->free_list= free_list_backup;
+
+ stmt->lex->restore_set_statement_var();
+ delete stmt;
+ DBUG_VOID_RETURN;
+}
+
+
/**
Reinit prepared statement/stored procedure before execution.
@@ -2945,6 +3066,11 @@ void mysqld_stmt_execute(THD *thd, char *packet_arg, uint packet_length)
uchar *packet= (uchar*)packet_arg; // GCC 4.0.1 workaround
ulong stmt_id= uint4korr(packet);
ulong flags= (ulong) packet[4];
+#ifndef EMBEDDED_LIBRARY
+ ulong iterations= uint4korr(packet + 5);
+#else
+ ulong iterations= 0; // no support
+#endif
/* Query text for binary, general or slow log, if any of them is open */
String expanded_query;
uchar *packet_end= packet + packet_length;
@@ -2970,12 +3096,16 @@ void mysqld_stmt_execute(THD *thd, char *packet_arg, uint packet_length)
thd->profiling.set_query_source(stmt->query(), stmt->query_length());
#endif
DBUG_PRINT("exec_query", ("%s", stmt->query()));
- DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt));
+ DBUG_PRINT("info",("stmt: %p iterations: %lu", stmt, iterations));
open_cursor= MY_TEST(flags & (ulong) CURSOR_TYPE_READ_ONLY);
thd->protocol= &thd->protocol_binary;
- stmt->execute_loop(&expanded_query, open_cursor, packet, packet_end);
+ if (iterations <= 1)
+ stmt->execute_loop(&expanded_query, open_cursor, packet, packet_end);
+ else
+ stmt->execute_bulk_loop(&expanded_query, open_cursor, packet, packet_end,
+ iterations);
thd->protocol= save_protocol;
sp_cache_enforce_limit(thd->sp_proc_cache, stored_program_cache_size);
@@ -3030,9 +3160,17 @@ void mysql_sql_stmt_execute(THD *thd)
DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt));
+ if (lex->prepared_stmt_params_fix_fields(thd))
+ DBUG_VOID_RETURN;
+
/*
- thd->free_list can already have some Items,
- e.g. for a query like this:
+ thd->free_list can already have some Items.
+
+ Example queries:
+ - SET STATEMENT var=expr FOR EXECUTE stmt;
+ - EXECUTE stmt USING expr;
+
+ E.g. for a query like this:
PREPARE stmt FROM 'INSERT INTO t1 VALUES (@@max_sort_length)';
SET STATEMENT max_sort_length=2048 FOR EXECUTE stmt;
thd->free_list contains a pointer to Item_int corresponding to 2048.
@@ -3046,7 +3184,7 @@ void mysql_sql_stmt_execute(THD *thd)
which calls Query_arena::free_items().
We hide "external" Items, e.g. those created while parsing the
- "SET STATEMENT" part of the query,
+ "SET STATEMENT" or "USING" parts of the query,
so they don't get freed in case of re-prepare.
See MDEV-10702 Crash in SET STATEMENT FOR EXECUTE
*/
@@ -3200,6 +3338,9 @@ void mysqld_stmt_close(THD *thd, char *packet)
stmt->deallocate();
general_log_print(thd, thd->get_command(), NullS);
+ if (thd->last_stmt == stmt)
+ thd->clear_last_stmt();
+
DBUG_VOID_RETURN;
}
@@ -3231,6 +3372,7 @@ void mysql_sql_stmt_close(THD *thd)
else
{
stmt->deallocate();
+ SESSION_TRACKER_CHANGED(thd, SESSION_STATE_CHANGE_TRACKER, NULL);
my_ok(thd);
}
}
@@ -3462,14 +3604,19 @@ end:
Prepared_statement::Prepared_statement(THD *thd_arg)
:Statement(NULL, &main_mem_root,
- STMT_INITIALIZED, ++thd_arg->statement_id_counter),
+ STMT_INITIALIZED,
+ ((++thd_arg->statement_id_counter) & STMT_ID_MASK)),
thd(thd_arg),
result(thd_arg),
param_array(0),
cursor(0),
+ packet(0),
+ packet_end(0),
+ iterations(0),
param_count(0),
last_errno(0),
- flags((uint) IS_IN_USE)
+ flags((uint) IS_IN_USE),
+ start_param(0)
{
init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size,
thd_arg->variables.query_prealloc_size, MYF(MY_THREAD_SPECIFIC));
@@ -3502,19 +3649,23 @@ void Prepared_statement::setup_set_params()
if (replace_params_with_values)
{
- set_params_from_vars= insert_params_from_vars_with_log;
+ set_params_from_actual_params= insert_params_from_actual_params_with_log;
#ifndef EMBEDDED_LIBRARY
set_params= insert_params_with_log;
+ set_bulk_params= insert_bulk_params; // TODO: add binlog support
#else
+ //TODO: add bulk support for bulk parameters
set_params_data= emb_insert_params_with_log;
#endif
}
else
{
- set_params_from_vars= insert_params_from_vars;
+ set_params_from_actual_params= insert_params_from_actual_params;
#ifndef EMBEDDED_LIBRARY
set_params= insert_params;
+ set_bulk_params= insert_bulk_params;
#else
+ //TODO: add bulk support for bulk parameters
set_params_data= emb_insert_params;
#endif
}
@@ -3821,8 +3972,8 @@ Prepared_statement::set_parameters(String *expanded_query,
if (is_sql_ps)
{
/* SQL prepared statement */
- res= set_params_from_vars(this, thd->lex->prepared_stmt_params,
- expanded_query);
+ res= set_params_from_actual_params(this, thd->lex->prepared_stmt_params,
+ expanded_query);
}
else if (param_count)
{
@@ -3871,6 +4022,7 @@ Prepared_statement::set_parameters(String *expanded_query,
@retval FALSE successfully executed the statement, perhaps
after having reprepared it a few times.
*/
+const static int MAX_REPREPARE_ATTEMPTS= 3;
bool
Prepared_statement::execute_loop(String *expanded_query,
@@ -3878,10 +4030,10 @@ Prepared_statement::execute_loop(String *expanded_query,
uchar *packet,
uchar *packet_end)
{
- const int MAX_REPREPARE_ATTEMPTS= 3;
Reprepare_observer reprepare_observer;
bool error;
int reprepare_attempt= 0;
+ iterations= 0;
/*
- In mysql_sql_stmt_execute() we hide all "external" Items
@@ -3939,8 +4091,9 @@ reexecute:
switch (thd->wsrep_conflict_state)
{
case CERT_FAILURE:
- WSREP_DEBUG("PS execute fail for CERT_FAILURE: thd: %ld err: %d",
- thd->thread_id, thd->get_stmt_da()->sql_errno() );
+ WSREP_DEBUG("PS execute fail for CERT_FAILURE: thd: %lld err: %d",
+ (longlong) thd->thread_id,
+ thd->get_stmt_da()->sql_errno() );
thd->wsrep_conflict_state = NO_CONFLICT;
break;
@@ -3973,6 +4126,199 @@ reexecute:
return error;
}
+my_bool bulk_parameters_set(THD *thd)
+{
+ DBUG_ENTER("bulk_parameters_set");
+ Prepared_statement *stmt= (Prepared_statement *) thd->bulk_param;
+
+ if (stmt && stmt->set_bulk_parameters(FALSE))
+ DBUG_RETURN(TRUE);
+ DBUG_RETURN(FALSE);
+}
+
+ulong bulk_parameters_iterations(THD *thd)
+{
+ Prepared_statement *stmt= (Prepared_statement *) thd->bulk_param;
+ if (!stmt)
+ return 1;
+ return stmt->bulk_iterations();
+}
+
+
+my_bool Prepared_statement::set_bulk_parameters(bool reset)
+{
+ DBUG_ENTER("Prepared_statement::set_bulk_parameters");
+ DBUG_PRINT("info", ("iteration: %lu", iterations));
+ if (iterations)
+ {
+#ifndef EMBEDDED_LIBRARY
+ if ((*set_bulk_params)(this, &packet, packet_end, reset))
+#else
+ // bulk parameters are not supported for embedded, so it will an error
+#endif
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0),
+ "mysqld_stmt_bulk_execute");
+ reset_stmt_params(this);
+ DBUG_RETURN(true);
+ }
+ iterations--;
+ }
+ start_param= 0;
+ DBUG_RETURN(false);
+}
+
+ulong Prepared_statement::bulk_iterations()
+{
+ if (iterations)
+ return iterations;
+ return start_param ? 1 : 0;
+}
+
+bool
+Prepared_statement::execute_bulk_loop(String *expanded_query,
+ bool open_cursor,
+ uchar *packet_arg,
+ uchar *packet_end_arg,
+ ulong iterations_arg)
+{
+ Reprepare_observer reprepare_observer;
+ bool error= 0;
+ packet= packet_arg;
+ packet_end= packet_end_arg;
+ iterations= iterations_arg;
+ start_param= true;
+#ifndef DBUG_OFF
+ Item *free_list_state= thd->free_list;
+#endif
+ thd->select_number= select_number_after_prepare;
+ thd->set_bulk_execution((void *)this);
+ /* Check if we got an error when sending long data */
+ if (state == Query_arena::STMT_ERROR)
+ {
+ my_message(last_errno, last_error, MYF(0));
+ thd->set_bulk_execution(0);
+ return TRUE;
+ }
+
+ if (!(sql_command_flags[lex->sql_command] & CF_SP_BULK_SAFE))
+ {
+ my_error(ER_UNSUPPORTED_PS, MYF(0));
+ thd->set_bulk_execution(0);
+ return TRUE;
+ }
+
+#ifndef EMBEDDED_LIBRARY
+ if (setup_conversion_functions(this, &packet, packet_end, TRUE))
+#else
+ // bulk parameters are not supported for embedded, so it will an error
+#endif
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0),
+ "mysqld_stmt_bulk_execute");
+ reset_stmt_params(this);
+ thd->set_bulk_execution(0);
+ return true;
+ }
+
+#ifdef NOT_YET_FROM_MYSQL_5_6
+ if (unlikely(thd->security_ctx->password_expired &&
+ !lex->is_change_password))
+ {
+ my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
+ thd->set_bulk_execution(0);
+ return true;
+ }
+#endif
+
+ // iterations changed by set_bulk_parameters
+ while ((iterations || start_param) && !error && !thd->is_error())
+ {
+ int reprepare_attempt= 0;
+
+ /*
+ Here we set parameters for not optimized commands,
+ optimized commands do it inside thier internal loop.
+ */
+ if (!(sql_command_flags[lex->sql_command] & CF_SP_BULK_OPTIMIZED))
+ {
+ if (set_bulk_parameters(TRUE))
+ {
+ thd->set_bulk_execution(0);
+ return true;
+ }
+ }
+
+reexecute:
+ /*
+ If the free_list is not empty, we'll wrongly free some externally
+ allocated items when cleaning up after validation of the prepared
+ statement.
+ */
+ DBUG_ASSERT(thd->free_list == free_list_state);
+
+ /*
+ Install the metadata observer. If some metadata version is
+ different from prepare time and an observer is installed,
+ the observer method will be invoked to push an error into
+ the error stack.
+ */
+
+ if (sql_command_flags[lex->sql_command] & CF_REEXECUTION_FRAGILE)
+ {
+ reprepare_observer.reset_reprepare_observer();
+ DBUG_ASSERT(thd->m_reprepare_observer == NULL);
+ thd->m_reprepare_observer= &reprepare_observer;
+ }
+
+ error= execute(expanded_query, open_cursor) || thd->is_error();
+
+ thd->m_reprepare_observer= NULL;
+#ifdef WITH_WSREP
+
+ if (WSREP_ON)
+ {
+ mysql_mutex_lock(&thd->LOCK_wsrep_thd);
+ switch (thd->wsrep_conflict_state)
+ {
+ case CERT_FAILURE:
+ WSREP_DEBUG("PS execute fail for CERT_FAILURE: thd: %lld err: %d",
+ (longlong) thd->thread_id,
+ thd->get_stmt_da()->sql_errno() );
+ thd->wsrep_conflict_state = NO_CONFLICT;
+ break;
+
+ case MUST_REPLAY:
+ (void) wsrep_replay_transaction(thd);
+ break;
+
+ default:
+ break;
+ }
+ mysql_mutex_unlock(&thd->LOCK_wsrep_thd);
+ }
+#endif /* WITH_WSREP */
+
+ if ((sql_command_flags[lex->sql_command] & CF_REEXECUTION_FRAGILE) &&
+ error && !thd->is_fatal_error && !thd->killed &&
+ reprepare_observer.is_invalidated() &&
+ reprepare_attempt++ < MAX_REPREPARE_ATTEMPTS)
+ {
+ DBUG_ASSERT(thd->get_stmt_da()->sql_errno() == ER_NEED_REPREPARE);
+ thd->clear_error();
+
+ error= reprepare();
+
+ if (! error) /* Success */
+ goto reexecute;
+ }
+ }
+ reset_stmt_params(this);
+ thd->set_bulk_execution(0);
+
+ return error;
+}
+
bool
Prepared_statement::execute_server_runnable(Server_runnable *server_runnable)
@@ -4379,16 +4725,58 @@ error:
}
-/** Common part of DEALLOCATE PREPARE and mysqld_stmt_close. */
+/**
+ Prepare, execute and clean-up a statement.
+ @param query - query text
+ @param length - query text length
+ @retval true - the query was not executed (parse error, wrong parameters)
+ @retval false - the query was prepared and executed
-void Prepared_statement::deallocate()
+ Note, if some error happened during execution, it still returns "false".
+*/
+bool Prepared_statement::execute_immediate(const char *query, uint query_len)
+{
+ DBUG_ENTER("Prepared_statement::execute_immediate");
+ String expanded_query;
+ static LEX_STRING execute_immediate_stmt_name=
+ {(char*) STRING_WITH_LEN("(immediate)") };
+
+ set_sql_prepare();
+ name= execute_immediate_stmt_name; // for DBUG_PRINT etc
+ if (prepare(query, query_len))
+ DBUG_RETURN(true);
+
+ if (param_count != thd->lex->prepared_stmt_params.elements)
+ {
+ my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE");
+ deallocate_immediate();
+ DBUG_RETURN(true);
+ }
+
+ (void) execute_loop(&expanded_query, FALSE, NULL, NULL);
+ deallocate_immediate();
+ DBUG_RETURN(false);
+}
+
+
+/**
+ Common part of DEALLOCATE PREPARE, EXECUTE IMMEDIATE, mysqld_stmt_close.
+*/
+void Prepared_statement::deallocate_immediate()
{
/* We account deallocate in the same manner as mysqld_stmt_close */
status_var_increment(thd->status_var.com_stmt_close);
/* It should now be safe to reset CHANGE MASTER parameters */
lex_end_stage2(lex);
+}
+
+/** Common part of DEALLOCATE PREPARE and mysqld_stmt_close. */
+
+void Prepared_statement::deallocate()
+{
+ deallocate_immediate();
/* Statement map calls delete stmt on erase */
thd->stmt_map.erase(this);
}
@@ -4899,7 +5287,7 @@ bool Protocol_local::send_out_parameters(List<Item_param> *sp_params)
bool
Protocol_local::send_ok(uint server_status, uint statement_warn_count,
ulonglong affected_rows, ulonglong last_insert_id,
- const char *message)
+ const char *message, bool skip_flush)
{
/*
Just make sure nothing is sent to the client, we have grabbed