diff options
Diffstat (limited to 'sql/sql_prepare.cc')
| -rw-r--r-- | sql/sql_prepare.cc | 704 |
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> ¶ms, + 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> ¶ms, + 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 |
