diff options
Diffstat (limited to 'sql/sql_prepare.cc')
-rw-r--r-- | sql/sql_prepare.cc | 638 |
1 files changed, 518 insertions, 120 deletions
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 091cc18114b..3036b36f8fa 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -155,21 +155,28 @@ public: virtual void cleanup_stmt(); bool set_name(LEX_STRING *name); inline void close_cursor() { delete cursor; cursor= 0; } - + inline bool is_in_use() { return flags & (uint) IS_IN_USE; } + inline bool is_protocol_text() const { return protocol == &thd->protocol_text; } bool prepare(const char *packet, uint packet_length); - bool execute(String *expanded_query, bool open_cursor); + bool execute_loop(String *expanded_query, + bool open_cursor, + uchar *packet_arg, uchar *packet_end_arg); /* Destroy this statement */ - bool deallocate(); + void deallocate(); private: /** - Store the parsed tree of a prepared statement here. - */ - LEX main_lex; - /** The memory root to allocate parsed tree elements (instances of Item, SELECT_LEX and other classes). */ MEM_ROOT main_mem_root; +private: + bool set_db(const char *db, uint db_length); + bool set_parameters(String *expanded_query, + uchar *packet, uchar *packet_end); + bool execute(String *expanded_query, bool open_cursor); + bool reprepare(); + bool validate_metadata(Prepared_statement *copy); + void swap_prepared_statement(Prepared_statement *copy); }; @@ -198,7 +205,7 @@ inline bool is_param_null(const uchar *pos, ulong param_no) */ static Prepared_statement * -find_prepared_statement(THD *thd, ulong id, const char *where) +find_prepared_statement(THD *thd, ulong id) { /* To strictly separate namespaces of SQL prepared statements and C API @@ -208,12 +215,8 @@ find_prepared_statement(THD *thd, ulong id, const char *where) Statement *stmt= thd->stmt_map.find(id); if (stmt == 0 || stmt->type() != Query_arena::PREPARED_STATEMENT) - { - char llbuf[22]; - my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf), llstr(id, llbuf), - where); - return 0; - } + return NULL; + return (Prepared_statement *) stmt; } @@ -945,6 +948,55 @@ static bool emb_insert_params_with_log(Prepared_statement *stmt, #endif /*!EMBEDDED_LIBRARY*/ +/** + Setup data conversion routines using an array of parameter + markers from the original prepared statement. + Swap the parameter data of the original prepared + statement to the new one. + + Used only when we re-prepare a prepared statement. + There are two reasons for this function to exist: + + 1) In the binary client/server protocol, parameter metadata + is sent only at first execute. Consequently, if we need to + reprepare a prepared statement at a subsequent execution, + we may not have metadata information in the packet. + In that case we use the parameter array of the original + prepared statement to setup parameter types of the new + prepared statement. + + 2) In the binary client/server protocol, we may supply + long data in pieces. When the last piece is supplied, + we assemble the pieces and convert them from client + character set to the connection character set. After + that the parameter value is only available inside + the parameter, the original pieces are lost, and thus + we can only assign the corresponding parameter of the + reprepared statement from the original value. + + @param[out] param_array_dst parameter markers of the new statement + @param[in] param_array_src parameter markers of the original + statement + @param[in] param_count total number of parameters. Is the + same in src and dst arrays, since + the statement query is the same + + @return this function never fails +*/ + +static void +swap_parameter_array(Item_param **param_array_dst, + Item_param **param_array_src, + uint param_count) +{ + Item_param **dst= param_array_dst; + Item_param **src= param_array_src; + Item_param **end= param_array_dst + param_count; + + for (; dst < end; ++src, ++dst) + (*dst)->set_param_type_and_swap_value(*src); +} + /** Assign prepared statement parameters from user variables. @@ -1268,7 +1320,7 @@ error: */ static int mysql_test_select(Prepared_statement *stmt, - TABLE_LIST *tables, bool text_protocol) + TABLE_LIST *tables) { THD *thd= stmt->thd; LEX *lex= stmt->lex; @@ -1304,7 +1356,7 @@ static int mysql_test_select(Prepared_statement *stmt, */ if (unit->prepare(thd, 0, 0)) goto error; - if (!lex->describe && !text_protocol) + if (!lex->describe && !stmt->is_protocol_text()) { /* Make copy of item list, as change_columns may change it */ List<Item> fields(lex->select_lex.item_list); @@ -1396,6 +1448,43 @@ error: /** + Validate and prepare for execution CALL statement expressions. + + @param stmt prepared statement + @param tables list of tables used in this query + @param value_list list of expressions + + @retval FALSE success + @retval TRUE error, error message is set in THD +*/ + +static bool mysql_test_call_fields(Prepared_statement *stmt, + TABLE_LIST *tables, + List<Item> *value_list) +{ + DBUG_ENTER("mysql_test_call_fields"); + + List_iterator<Item> it(*value_list); + THD *thd= stmt->thd; + Item *item; + + if (tables && check_table_access(thd, SELECT_ACL, tables, UINT_MAX, FALSE) || + open_normal_and_derived_tables(thd, tables, 0)) + goto err; + + while ((item= it++)) + { + if (!item->fixed && item->fix_fields(thd, it.ref()) || + item->check_cols(1)) + goto err; + } + DBUG_RETURN(FALSE); +err: + DBUG_RETURN(TRUE); +} + + +/** Check internal SELECT of the prepared command. @param stmt prepared statement @@ -1516,6 +1605,17 @@ static bool mysql_test_create_table(Prepared_statement *stmt) res= select_like_stmt_test(stmt, 0, 0); } + else if (lex->create_info.options & HA_LEX_CREATE_TABLE_LIKE) + { + /* + Check that the source table exist, and also record + its metadata version. Even though not strictly necessary, + we validate metadata of all CREATE TABLE statements, + which keeps metadata validation code simple. + */ + if (open_normal_and_derived_tables(stmt->thd, lex->query_tables, 0)) + DBUG_RETURN(TRUE); + } /* put tables back for PS rexecuting */ lex->link_first_table_back(create_table, link_to_local); @@ -1712,8 +1812,7 @@ static bool mysql_test_insert_select(Prepared_statement *stmt, TRUE error, error message is set in THD (but not sent) */ -static bool check_prepared_statement(Prepared_statement *stmt, - bool text_protocol) +static bool check_prepared_statement(Prepared_statement *stmt) { THD *thd= stmt->thd; LEX *lex= stmt->lex; @@ -1754,9 +1853,23 @@ static bool check_prepared_statement(Prepared_statement *stmt, case SQLCOM_DELETE: res= mysql_test_delete(stmt, tables); break; - + /* The following allow WHERE clause, so they must be tested like SELECT */ + case SQLCOM_SHOW_DATABASES: + case SQLCOM_SHOW_TABLES: + case SQLCOM_SHOW_TRIGGERS: + case SQLCOM_SHOW_EVENTS: + case SQLCOM_SHOW_OPEN_TABLES: + case SQLCOM_SHOW_FIELDS: + case SQLCOM_SHOW_KEYS: + case SQLCOM_SHOW_COLLATIONS: + case SQLCOM_SHOW_CHARSETS: + case SQLCOM_SHOW_VARIABLES: + case SQLCOM_SHOW_STATUS: + case SQLCOM_SHOW_TABLE_STATUS: + case SQLCOM_SHOW_STATUS_PROC: + case SQLCOM_SHOW_STATUS_FUNC: case SQLCOM_SELECT: - res= mysql_test_select(stmt, tables, text_protocol); + res= mysql_test_select(stmt, tables); if (res == 2) { /* Statement and field info has already been sent */ @@ -1779,6 +1892,9 @@ static bool check_prepared_statement(Prepared_statement *stmt, res= mysql_test_do_fields(stmt, tables, lex->insert_list); break; + case SQLCOM_CALL: + res= mysql_test_call_fields(stmt, tables, &lex->value_list); + break; case SQLCOM_SET_OPTION: res= mysql_test_set_fields(stmt, tables, &lex->var_list); break; @@ -1828,7 +1944,6 @@ static bool check_prepared_statement(Prepared_statement *stmt, case SQLCOM_DROP_INDEX: case SQLCOM_ROLLBACK: case SQLCOM_TRUNCATE: - case SQLCOM_CALL: case SQLCOM_DROP_VIEW: case SQLCOM_REPAIR: case SQLCOM_ANALYZE: @@ -1871,8 +1986,8 @@ static bool check_prepared_statement(Prepared_statement *stmt, break; } if (res == 0) - DBUG_RETURN(text_protocol? FALSE : (send_prep_stmt(stmt, 0) || - thd->protocol->flush())); + DBUG_RETURN(stmt->is_protocol_text() ? + FALSE : (send_prep_stmt(stmt, 0) || thd->protocol->flush())); error: DBUG_RETURN(TRUE); } @@ -2121,8 +2236,13 @@ void mysql_sql_stmt_prepare(THD *thd) If there is a statement with the same name, remove it. It is ok to remove old and fail to insert a new one at the same time. */ - if (stmt->deallocate()) + if (stmt->is_in_use()) + { + my_error(ER_PS_NO_RECURSION, MYF(0)); DBUG_VOID_RETURN; + } + + stmt->deallocate(); } if (! (query= get_dynamic_sql_string(lex, &query_len)) || @@ -2308,11 +2428,9 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length) ulong flags= (ulong) packet[4]; /* Query text for binary, general or slow log, if any of them is open */ String expanded_query; -#ifndef EMBEDDED_LIBRARY uchar *packet_end= packet + packet_length; -#endif Prepared_statement *stmt; - bool error; + bool open_cursor; DBUG_ENTER("mysql_stmt_execute"); packet+= 9; /* stmt_id + 5 bytes of flags */ @@ -2320,8 +2438,13 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length) /* First of all clear possible warnings from the previous command */ mysql_reset_thd_for_next_command(thd); - if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_execute"))) + if (!(stmt= find_prepared_statement(thd, stmt_id))) + { + char llbuf[22]; + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf), + llstr(stmt_id, llbuf), "mysql_stmt_execute"); DBUG_VOID_RETURN; + } #if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) thd->profiling.set_query_source(stmt->query, stmt->query_length); @@ -2332,44 +2455,12 @@ void mysql_stmt_execute(THD *thd, char *packet_arg, uint packet_length) sp_cache_flush_obsolete(&thd->sp_proc_cache); sp_cache_flush_obsolete(&thd->sp_func_cache); -#ifndef EMBEDDED_LIBRARY - if (stmt->param_count) - { - uchar *null_array= packet; - if (setup_conversion_functions(stmt, &packet, packet_end) || - stmt->set_params(stmt, null_array, packet, packet_end, - &expanded_query)) - goto set_params_data_err; - } -#else - /* - In embedded library we re-install conversion routines each time - we set params, and also we don't need to parse packet. - So we do it in one function. - */ - if (stmt->param_count && stmt->set_params_data(stmt, &expanded_query)) - goto set_params_data_err; -#endif - if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(),QUERY_PRIOR); + open_cursor= test(flags & (ulong) CURSOR_TYPE_READ_ONLY); - /* - 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 == NULL); + stmt->execute_loop(&expanded_query, open_cursor, packet, packet_end); - error= stmt->execute(&expanded_query, - test(flags & (ulong) CURSOR_TYPE_READ_ONLY)); - if (!(specialflag & SPECIAL_NO_PRIOR)) - my_pthread_setprio(pthread_self(), WAIT_PRIOR); DBUG_VOID_RETURN; -set_params_data_err: - my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_execute"); - reset_stmt_params(stmt); - DBUG_VOID_RETURN; } @@ -2415,24 +2506,8 @@ void mysql_sql_stmt_execute(THD *thd) DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt)); - /* - 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 == NULL); - - if (stmt->set_params_from_vars(stmt, lex->prepared_stmt_params, - &expanded_query)) - goto set_params_data_err; - - (void) stmt->execute(&expanded_query, FALSE); - - DBUG_VOID_RETURN; + (void) stmt->execute_loop(&expanded_query, FALSE, NULL, NULL); -set_params_data_err: - my_error(ER_WRONG_ARGUMENTS, MYF(0), "EXECUTE"); - reset_stmt_params(stmt); DBUG_VOID_RETURN; } @@ -2458,8 +2533,13 @@ void mysql_stmt_fetch(THD *thd, char *packet, uint packet_length) /* First of all clear possible warnings from the previous command */ mysql_reset_thd_for_next_command(thd); status_var_increment(thd->status_var.com_stmt_fetch); - if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_fetch"))) + if (!(stmt= find_prepared_statement(thd, stmt_id))) + { + char llbuf[22]; + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf), + llstr(stmt_id, llbuf), "mysql_stmt_fetch"); DBUG_VOID_RETURN; + } cursor= stmt->cursor; if (!cursor) @@ -2520,8 +2600,13 @@ void mysql_stmt_reset(THD *thd, char *packet) mysql_reset_thd_for_next_command(thd); status_var_increment(thd->status_var.com_stmt_reset); - if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_reset"))) + if (!(stmt= find_prepared_statement(thd, stmt_id))) + { + char llbuf[22]; + my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), sizeof(llbuf), + llstr(stmt_id, llbuf), "mysql_stmt_reset"); DBUG_VOID_RETURN; + } stmt->close_cursor(); @@ -2557,15 +2642,15 @@ void mysql_stmt_close(THD *thd, char *packet) thd->main_da.disable_status(); - if (!(stmt= find_prepared_statement(thd, stmt_id, "mysql_stmt_close"))) + if (!(stmt= find_prepared_statement(thd, stmt_id))) DBUG_VOID_RETURN; /* The only way currently a statement can be deallocated when it's in use is from within Dynamic SQL. */ - DBUG_ASSERT(! (stmt->flags & (uint) Prepared_statement::IS_IN_USE)); - (void) stmt->deallocate(); + DBUG_ASSERT(! stmt->is_in_use()); + stmt->deallocate(); general_log_print(thd, thd->command, NullS); DBUG_VOID_RETURN; @@ -2592,14 +2677,15 @@ void mysql_sql_stmt_close(THD *thd) name->str)); if (! (stmt= (Prepared_statement*) thd->stmt_map.find_by_name(name))) - { my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), name->length, name->str, "DEALLOCATE PREPARE"); - return; - } - - if (stmt->deallocate() == 0) + else if (stmt->is_in_use()) + my_error(ER_PS_NO_RECURSION, MYF(0)); + else + { + stmt->deallocate(); my_ok(thd); + } } /** @@ -2633,17 +2719,13 @@ void mysql_stmt_get_longdata(THD *thd, char *packet, ulong packet_length) #ifndef EMBEDDED_LIBRARY /* Minimal size of long data packet is 6 bytes */ if (packet_length < MYSQL_LONG_DATA_HEADER) - { - my_error(ER_WRONG_ARGUMENTS, MYF(0), "mysql_stmt_send_long_data"); DBUG_VOID_RETURN; - } #endif stmt_id= uint4korr(packet); packet+= 4; - if (!(stmt=find_prepared_statement(thd, stmt_id, - "mysql_stmt_send_long_data"))) + if (!(stmt=find_prepared_statement(thd, stmt_id))) DBUG_VOID_RETURN; param_number= uint2korr(packet); @@ -2729,7 +2811,7 @@ Select_fetch_protocol_binary::send_data(List<Item> &fields) ****************************************************************************/ Prepared_statement::Prepared_statement(THD *thd_arg, Protocol *protocol_arg) - :Statement(&main_lex, &main_mem_root, + :Statement(NULL, &main_mem_root, INITIALIZED, ++thd_arg->statement_id_counter), thd(thd_arg), result(thd_arg), @@ -2800,7 +2882,11 @@ Prepared_statement::~Prepared_statement() like Item_param, don't free everything until free_items() */ free_items(); - delete lex->result; + if (lex) + { + delete lex->result; + delete (st_lex_local *) lex; + } free_root(&main_mem_root, MYF(0)); DBUG_VOID_RETURN; } @@ -2836,6 +2922,34 @@ bool Prepared_statement::set_name(LEX_STRING *name_arg) return name.str == 0; } + +/** + Remember the current database. + + We must reset/restore the current database during execution of + a prepared statement since it affects execution environment: + privileges, @@character_set_database, and other. + + @return Returns an error if out of memory. +*/ + +bool +Prepared_statement::set_db(const char *db_arg, uint db_length_arg) +{ + /* Remember the current database. */ + if (db_arg && db_length_arg) + { + db= this->strmake(db_arg, db_length_arg); + db_length= db_length_arg; + } + else + { + db= NULL; + db_length= 0; + } + return db_arg != NULL && db == NULL; +} + /************************************************************************** Common parts of mysql_[sql]_stmt_prepare, mysql_[sql]_stmt_execute. Essentially, these functions do all the magic of preparing/executing @@ -2877,6 +2991,12 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) */ status_var_increment(thd->status_var.com_stmt_prepare); + if (! (lex= new (mem_root) st_lex_local)) + DBUG_RETURN(TRUE); + + if (set_db(thd->db, thd->db_length)) + DBUG_RETURN(TRUE); + /* alloc_query() uses thd->memroot && thd->query, so we should call both of backup_statement() and backup_query_arena() here. @@ -2904,19 +3024,6 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) lex->set_trg_event_type_for_tables(); - /* Remember the current database. */ - - if (thd->db && thd->db_length) - { - db= this->strmake(thd->db, thd->db_length); - db_length= thd->db_length; - } - else - { - db= NULL; - db_length= 0; - } - /* While doing context analysis of the query (in check_prepared_statement) we allocate a lot of additional memory: for open tables, JOINs, derived @@ -2940,7 +3047,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) */ if (error == 0) - error= check_prepared_statement(this, name.str != 0); + error= check_prepared_statement(this); /* Currently CREATE PROCEDURE/TRIGGER/EVENT are prohibited in prepared @@ -2987,6 +3094,306 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len) DBUG_RETURN(error); } + +/** + Assign parameter values either from variables, in case of SQL PS + or from the execute packet. + + @param expanded_query a container with the original SQL statement. + '?' placeholders will be replaced with + their values in case of success. + The result is used for logging and replication + @param packet pointer to execute packet. + NULL in case of SQL PS + @param packet_end end of the packet. NULL in case of SQL PS + + @todo Use a paremeter source class family instead of 'if's, and + support stored procedure variables. + + @retval TRUE an error occurred when assigning a parameter (likely + a conversion error or out of memory, or malformed packet) + @retval FALSE success +*/ + +bool +Prepared_statement::set_parameters(String *expanded_query, + uchar *packet, uchar *packet_end) +{ + bool is_sql_ps= packet == NULL; + bool res= FALSE; + + if (is_sql_ps) + { + /* SQL prepared statement */ + res= set_params_from_vars(this, thd->lex->prepared_stmt_params, + expanded_query); + } + else if (param_count) + { +#ifndef EMBEDDED_LIBRARY + uchar *null_array= packet; + res= (setup_conversion_functions(this, &packet, packet_end) || + set_params(this, null_array, packet, packet_end, expanded_query)); +#else + /* + In embedded library we re-install conversion routines each time + we set parameters, and also we don't need to parse packet. + So we do it in one function. + */ + res= set_params_data(this, expanded_query); +#endif + } + if (res) + { + my_error(ER_WRONG_ARGUMENTS, MYF(0), + is_sql_ps ? "EXECUTE" : "mysql_stmt_execute"); + reset_stmt_params(this); + } + return res; +} + + +/** + Execute a prepared statement. Re-prepare it a limited number + of times if necessary. + + Try to execute a prepared statement. If there is a metadata + validation error, prepare a new copy of the prepared statement, + swap the old and the new statements, and try again. + If there is a validation error again, repeat the above, but + perform no more than MAX_REPREPARE_ATTEMPTS. + + @note We have to try several times in a loop since we + release metadata locks on tables after prepared statement + prepare. Therefore, a DDL statement may sneak in between prepare + and execute of a new statement. If this happens repeatedly + more than MAX_REPREPARE_ATTEMPTS times, we give up. + + In future we need to be able to keep the metadata locks between + prepare and execute, but right now open_and_lock_tables(), as + well as close_thread_tables() are buried deep inside + execution code (mysql_execute_command()). + + @return TRUE if an error, FALSE if success + @retval TRUE either MAX_REPREPARE_ATTEMPTS has been reached, + or some general error + @retval FALSE successfully executed the statement, perhaps + after having reprepared it a few times. +*/ + +bool +Prepared_statement::execute_loop(String *expanded_query, + bool open_cursor, + uchar *packet, + uchar *packet_end) +{ + const int MAX_REPREPARE_ATTEMPTS= 3; + Reprepare_observer reprepare_observer; + bool error; + int reprepare_attempt= 0; + + if (set_parameters(expanded_query, packet, packet_end)) + return TRUE; + +reexecute: + reprepare_observer.reset_reprepare_observer(); + + /* + 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 == NULL); + + /* + 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) + { + DBUG_ASSERT(thd->m_reprepare_observer == NULL); + thd->m_reprepare_observer = &reprepare_observer; + } + + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(),QUERY_PRIOR); + + error= execute(expanded_query, open_cursor) || thd->is_error(); + + if (!(specialflag & SPECIAL_NO_PRIOR)) + my_pthread_setprio(pthread_self(), WAIT_PRIOR); + + thd->m_reprepare_observer= NULL; + + if (error && !thd->is_fatal_error && !thd->killed && + reprepare_observer.is_invalidated() && + reprepare_attempt++ < MAX_REPREPARE_ATTEMPTS) + { + DBUG_ASSERT(thd->main_da.sql_errno() == ER_NEED_REPREPARE); + thd->clear_error(); + + error= reprepare(); + + if (! error) /* Success */ + goto reexecute; + } + reset_stmt_params(this); + + return error; +} + + +/** + Reprepare this prepared statement. + + Currently this is implemented by creating a new prepared + statement, preparing it with the original query and then + swapping the new statement and the original one. + + @retval TRUE an error occurred. Possible errors include + incompatibility of new and old result set + metadata + @retval FALSE success, the statement has been reprepared +*/ + +bool +Prepared_statement::reprepare() +{ + char saved_cur_db_name_buf[NAME_LEN+1]; + LEX_STRING saved_cur_db_name= + { saved_cur_db_name_buf, sizeof(saved_cur_db_name_buf) }; + LEX_STRING stmt_db_name= { db, db_length }; + bool cur_db_changed; + bool error; + + Prepared_statement copy(thd, &thd->protocol_text); + + status_var_increment(thd->status_var.com_stmt_reprepare); + + if (mysql_opt_change_db(thd, &stmt_db_name, &saved_cur_db_name, TRUE, + &cur_db_changed)) + return TRUE; + + error= (name.str && copy.set_name(&name) || + copy.prepare(query, query_length) || + validate_metadata(©)); + + if (cur_db_changed) + mysql_change_db(thd, &saved_cur_db_name, TRUE); + + if (! error) + { + swap_prepared_statement(©); + swap_parameter_array(param_array, copy.param_array, param_count); +#ifndef DBUG_OFF + is_reprepared= TRUE; +#endif + /* + Clear possible warnings during reprepare, it has to be completely + transparent to the user. We use mysql_reset_errors() since + there were no separate query id issued for re-prepare. + Sic: we can't simply silence warnings during reprepare, because if + it's failed, we need to return all the warnings to the user. + */ + mysql_reset_errors(thd, TRUE); + } + return error; +} + + +/** + Validate statement result set metadata (if the statement returns + a result set). + + Currently we only check that the number of columns of the result + set did not change. + This is a helper method used during re-prepare. + + @param[in] copy the re-prepared prepared statement to verify + the metadata of + + @retval TRUE error, ER_PS_NEED_REBIND is reported + @retval FALSE statement return no or compatible metadata +*/ + + +bool Prepared_statement::validate_metadata(Prepared_statement *copy) +{ + /** + If this is an SQL prepared statement or EXPLAIN, + return FALSE -- the metadata of the original SELECT, + if any, has not been sent to the client. + */ + if (is_protocol_text() || lex->describe) + return FALSE; + + if (lex->select_lex.item_list.elements != + copy->lex->select_lex.item_list.elements) + { + /** Column counts mismatch. */ + my_error(ER_PS_REBIND, MYF(0)); + return TRUE; + } + + return FALSE; +} + + +/** + Replace the original prepared statement with a prepared copy. + + This is a private helper that is used as part of statement + reprepare + + @return This function does not return any errors. +*/ + +void +Prepared_statement::swap_prepared_statement(Prepared_statement *copy) +{ + Statement tmp_stmt; + + /* Swap memory roots. */ + swap_variables(MEM_ROOT, main_mem_root, copy->main_mem_root); + + /* Swap the arenas */ + tmp_stmt.set_query_arena(this); + set_query_arena(copy); + copy->set_query_arena(&tmp_stmt); + + /* Swap the statement parent classes */ + tmp_stmt.set_statement(this); + set_statement(copy); + copy->set_statement(&tmp_stmt); + + /* Swap ids back, we need the original id */ + swap_variables(ulong, id, copy->id); + /* Swap mem_roots back, they must continue pointing at the main_mem_roots */ + swap_variables(MEM_ROOT *, mem_root, copy->mem_root); + /* + Swap the old and the new parameters array. The old array + is allocated in the old arena. + */ + swap_variables(Item_param **, param_array, copy->param_array); + /* Swap flags: this is perhaps unnecessary */ + swap_variables(uint, flags, copy->flags); + /* Swap names, the old name is allocated in the wrong memory root */ + swap_variables(LEX_STRING, name, copy->name); + /* Ditto */ + swap_variables(char *, db, copy->db); + + DBUG_ASSERT(db_length == copy->db_length); + DBUG_ASSERT(param_count == copy->param_count); + DBUG_ASSERT(thd == copy->thd); + last_error[0]= '\0'; + last_errno= 0; + /* Do not swap protocols, the copy always has protocol_text */ +} + + /** Execute a prepared statement. @@ -3149,10 +3556,7 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) DBUG_ASSERT(! (error && cursor)); if (! cursor) - { cleanup_stmt(); - reset_stmt_params(this); - } thd->set_statement(&stmt_backup); thd->stmt_arena= old_stmt_arena; @@ -3186,16 +3590,10 @@ error: /** Common part of DEALLOCATE PREPARE and mysql_stmt_close. */ -bool Prepared_statement::deallocate() +void Prepared_statement::deallocate() { /* We account deallocate in the same manner as mysql_stmt_close */ status_var_increment(thd->status_var.com_stmt_close); - if (flags & (uint) IS_IN_USE) - { - my_error(ER_PS_NO_RECURSION, MYF(0)); - return TRUE; - } /* Statement map calls delete stmt on erase */ thd->stmt_map.erase(this); - return FALSE; } |