diff options
Diffstat (limited to 'sql/sp_head.cc')
-rw-r--r-- | sql/sp_head.cc | 1110 |
1 files changed, 655 insertions, 455 deletions
diff --git a/sql/sp_head.cc b/sql/sp_head.cc index b1bfab40acd..d1f920fd3a5 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -32,6 +32,8 @@ #include <my_user.h> +extern "C" uchar *sp_table_key(const uchar *ptr, size_t *plen, my_bool first); + Item_result sp_map_result_type(enum enum_field_types type) { @@ -78,19 +80,20 @@ sp_map_item_type(enum enum_field_types type) } -/* +/** Return a string representation of the Item value. - NOTE: If the item has a string result type, the string is escaped - according to its character set. + @param thd thread handle + @param str string buffer for representation of the value - SYNOPSIS - item a pointer to the Item - str string buffer for representation of the value + @note + If the item has a string result type, the string is escaped + according to its character set. - RETURN - NULL on error - a pointer to valid a valid string on success + @retval + NULL on error + @retval + non-NULL a pointer to valid a valid string on success */ static String * @@ -136,16 +139,12 @@ sp_get_item_value(THD *thd, Item *item, String *str) } -/* - SYNOPSIS - sp_get_flags_for_command() - - DESCRIPTION - Returns a combination of: - * sp_head::MULTI_RESULTS: added if the 'cmd' is a command that might - result in multiple result sets being sent back. - * sp_head::CONTAINS_DYNAMIC_SQL: added if 'cmd' is one of PREPARE, - EXECUTE, DEALLOCATE. +/** + Returns a combination of: + - sp_head::MULTI_RESULTS: added if the 'cmd' is a command that might + result in multiple result sets being sent back. + - sp_head::CONTAINS_DYNAMIC_SQL: added if 'cmd' is one of PREPARE, + EXECUTE, DEALLOCATE. */ uint @@ -162,34 +161,42 @@ sp_get_flags_for_command(LEX *lex) } /* fallthrough */ case SQLCOM_ANALYZE: + case SQLCOM_BACKUP_TABLE: case SQLCOM_OPTIMIZE: case SQLCOM_PRELOAD_KEYS: case SQLCOM_ASSIGN_TO_KEYCACHE: case SQLCOM_CHECKSUM: case SQLCOM_CHECK: case SQLCOM_HA_READ: + case SQLCOM_SHOW_AUTHORS: case SQLCOM_SHOW_BINLOGS: case SQLCOM_SHOW_BINLOG_EVENTS: case SQLCOM_SHOW_CHARSETS: case SQLCOM_SHOW_COLLATIONS: case SQLCOM_SHOW_COLUMN_TYPES: + case SQLCOM_SHOW_CONTRIBUTORS: case SQLCOM_SHOW_CREATE: case SQLCOM_SHOW_CREATE_DB: case SQLCOM_SHOW_CREATE_FUNC: case SQLCOM_SHOW_CREATE_PROC: + case SQLCOM_SHOW_CREATE_EVENT: + case SQLCOM_SHOW_CREATE_TRIGGER: case SQLCOM_SHOW_DATABASES: case SQLCOM_SHOW_ERRORS: case SQLCOM_SHOW_FIELDS: + case SQLCOM_SHOW_FUNC_CODE: case SQLCOM_SHOW_GRANTS: - case SQLCOM_SHOW_INNODB_STATUS: + case SQLCOM_SHOW_ENGINE_STATUS: + case SQLCOM_SHOW_ENGINE_LOGS: + case SQLCOM_SHOW_ENGINE_MUTEX: + case SQLCOM_SHOW_EVENTS: case SQLCOM_SHOW_KEYS: - case SQLCOM_SHOW_LOGS: case SQLCOM_SHOW_MASTER_STAT: - case SQLCOM_SHOW_MUTEX_STATUS: case SQLCOM_SHOW_NEW_MASTER: case SQLCOM_SHOW_OPEN_TABLES: case SQLCOM_SHOW_PRIVILEGES: case SQLCOM_SHOW_PROCESSLIST: + case SQLCOM_SHOW_PROC_CODE: case SQLCOM_SHOW_SLAVE_HOSTS: case SQLCOM_SHOW_SLAVE_STAT: case SQLCOM_SHOW_STATUS: @@ -199,10 +206,7 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_SHOW_TABLES: case SQLCOM_SHOW_VARIABLES: case SQLCOM_SHOW_WARNS: - case SQLCOM_SHOW_PROC_CODE: - case SQLCOM_SHOW_FUNC_CODE: case SQLCOM_REPAIR: - case SQLCOM_BACKUP_TABLE: case SQLCOM_RESTORE_TABLE: flags= sp_head::MULTI_RESULTS; break; @@ -243,11 +247,14 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_CREATE_TRIGGER: case SQLCOM_CREATE_USER: case SQLCOM_ALTER_TABLE: + case SQLCOM_GRANT: + case SQLCOM_REVOKE: case SQLCOM_BEGIN: case SQLCOM_RENAME_TABLE: case SQLCOM_RENAME_USER: case SQLCOM_DROP_INDEX: case SQLCOM_DROP_DB: + case SQLCOM_REVOKE_ALL: case SQLCOM_DROP_USER: case SQLCOM_DROP_VIEW: case SQLCOM_DROP_TRIGGER: @@ -263,6 +270,11 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_ALTER_FUNCTION: case SQLCOM_DROP_PROCEDURE: case SQLCOM_DROP_FUNCTION: + case SQLCOM_CREATE_EVENT: + case SQLCOM_ALTER_EVENT: + case SQLCOM_DROP_EVENT: + case SQLCOM_INSTALL_PLUGIN: + case SQLCOM_UNINSTALL_PLUGIN: flags= sp_head::HAS_COMMIT_OR_ROLLBACK; break; default: @@ -272,18 +284,16 @@ sp_get_flags_for_command(LEX *lex) return flags; } - -/* +/** Prepare an Item for evaluation (call of fix_fields). - SYNOPSIS - sp_prepare_func_item() - thd thread handler - it_addr pointer on item refernce + @param thd thread handler + @param it_addr pointer on item refernce - RETURN - NULL error - prepared item + @retval + NULL error + @retval + non-NULL prepared item */ Item * @@ -303,17 +313,16 @@ sp_prepare_func_item(THD* thd, Item **it_addr) } -/* +/** Evaluate an expression and store the result in the field. - SYNOPSIS - sp_eval_expr() - thd - current thread object - expr_item - the root item of the expression - result_field - the field to store the result + @param thd current thread object + @param result_field the field to store the result + @param expr_item_ptr the root item of the expression - RETURN VALUES + @retval FALSE on success + @retval TRUE on error */ @@ -324,6 +333,9 @@ sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr) DBUG_ENTER("sp_eval_expr"); + if (!*expr_item_ptr) + DBUG_RETURN(TRUE); + if (!(expr_item= sp_prepare_func_item(thd, expr_item_ptr))) DBUG_RETURN(TRUE); @@ -354,7 +366,7 @@ sp_eval_expr(THD *thd, Field *result_field, Item **expr_item_ptr) thd->abort_on_warning= save_abort_on_warning; thd->transaction.stmt.modified_non_trans_table= save_stmt_modified_non_trans_table; - if (thd->net.report_error) + if (thd->is_error()) { /* Return error status if something went wrong. */ err_status= TRUE; @@ -393,41 +405,58 @@ sp_name::sp_name(THD *thd, char *key, uint key_len) m_explicit_name= false; } + +/** + Init the qualified name from the db and name. +*/ void sp_name::init_qname(THD *thd) { const uint dot= !!m_db.length; /* m_sroutines format: m_type + [database + dot] + name + nul */ m_sroutines_key.length= 1 + m_db.length + dot + m_name.length; - if (!(m_sroutines_key.str= thd->alloc(m_sroutines_key.length + 1))) + if (!(m_sroutines_key.str= (char*) thd->alloc(m_sroutines_key.length + 1))) return; m_qname.length= m_sroutines_key.length - 1; m_qname.str= m_sroutines_key.str + 1; sprintf(m_qname.str, "%.*s%.*s%.*s", - m_db.length, (m_db.length ? m_db.str : ""), - dot, ".", m_name.length, m_name.str); + (int) m_db.length, (m_db.length ? m_db.str : ""), + dot, ".", + (int) m_name.length, m_name.str); } -/* - Check that the name 'ident' is ok. It's assumed to be an 'ident' +/** + Check that the name 'ident' is ok. It's assumed to be an 'ident' from the parser, so we only have to check length and trailing spaces. The former is a standard requirement (and 'show status' assumes a non-empty name), the latter is a mysql:ism as trailing spaces are removed by get_field(). - - RETURN - TRUE - bad name - FALSE - name is ok + + @retval + TRUE bad name + @retval + FALSE name is ok */ bool -check_routine_name(LEX_STRING ident) +check_routine_name(LEX_STRING *ident) { - return (!ident.str || !ident.str[0] || ident.str[ident.length-1] == ' '); -} + if (!ident || !ident->str || !ident->str[0] || + ident->str[ident->length-1] == ' ') + { + my_error(ER_SP_WRONG_NAME, MYF(0), ident->str); + return TRUE; + } + if (check_string_char_length(ident, "", NAME_CHAR_LEN, + system_charset_info, 1)) + { + my_error(ER_TOO_LONG_IDENT, MYF(0), ident->str); + return TRUE; + } -/* ------------------------------------------------------------------ */ + return FALSE; +} /* @@ -478,14 +507,20 @@ sp_head::sp_head() m_flags(0), m_recursion_level(0), m_next_cached_sp(0), m_cont_level(0) { + const LEX_STRING str_reset= { NULL, 0 }; + m_first_instance= this; m_first_free_instance= this; m_last_cached_sp= this; m_return_field_def.charset = NULL; + /* + FIXME: the only use case when name is NULL is events, and it should + be rewritten soon. Remove the else part and replace 'if' with + an assert when this is done. + */ + m_db= m_name= m_qname= str_reset; - extern byte * - sp_table_key(const byte *ptr, uint *plen, my_bool first); DBUG_ENTER("sp_head::sp_head"); m_backpatch.empty(); @@ -493,6 +528,10 @@ sp_head::sp_head() m_lex.empty(); hash_init(&m_sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0); hash_init(&m_sroutines, system_charset_info, 0, 0, 0, sp_sroutine_key, 0, 0); + + m_body_utf8.str= NULL; + m_body_utf8.length= 0; + DBUG_VOID_RETURN; } @@ -572,9 +611,9 @@ sp_head::init_sp_name(THD *thd, sp_name *spname) spname->init_qname(thd); m_sroutines_key.length= spname->m_sroutines_key.length; - m_sroutines_key.str= memdup_root(thd->mem_root, - spname->m_sroutines_key.str, - spname->m_sroutines_key.length + 1); + m_sroutines_key.str= (char*) memdup_root(thd->mem_root, + spname->m_sroutines_key.str, + spname->m_sroutines_key.length + 1); m_sroutines_key.str[0]= static_cast<char>(m_type); m_qname.length= m_sroutines_key.length - 1; @@ -585,43 +624,63 @@ sp_head::init_sp_name(THD *thd, sp_name *spname) void -sp_head::init_strings(THD *thd, LEX *lex) +sp_head::set_body_start(THD *thd, const char *begin_ptr) +{ + m_body_begin= begin_ptr; + thd->m_parser_state->m_lip.body_utf8_start(thd, begin_ptr); +} + + +void +sp_head::set_stmt_end(THD *thd) { - DBUG_ENTER("sp_head::init_strings"); - const char *endp; /* Used to trim the end */ - /* During parsing, we must use thd->mem_root */ - MEM_ROOT *root= thd->mem_root; - Lex_input_stream *lip= & thd->m_parser_state->m_lip; + Lex_input_stream *lip= & thd->m_parser_state->m_lip; /* shortcut */ + const char *end_ptr= lip->get_cpp_ptr(); /* shortcut */ + + /* Make the string of parameters. */ if (m_param_begin && m_param_end) { m_params.length= m_param_end - m_param_begin; - m_params.str= strmake_root(root, - (char *)m_param_begin, m_params.length); + m_params.str= thd->strmake(m_param_begin, m_params.length); } - /* If ptr has overrun end_of_query then end_of_query is the end */ - endp= (lip->ptr > lip->end_of_query ? lip->end_of_query : lip->ptr); + /* Remember end pointer for further dumping of whole statement. */ + + thd->lex->stmt_definition_end= end_ptr; + + /* Make the string of body (in the original character set). */ + + m_body.length= end_ptr - m_body_begin; + m_body.str= thd->strmake(m_body_begin, m_body.length); + trim_whitespace(thd->charset(), & m_body); + + /* Make the string of UTF-body. */ + + lip->body_utf8_append(end_ptr); + + m_body_utf8.length= lip->get_body_utf8_length(); + m_body_utf8.str= thd->strmake(lip->get_body_utf8_str(), m_body_utf8.length); + trim_whitespace(thd->charset(), & m_body_utf8); + /* - Trim "garbage" at the end. This is sometimes needed with the - "/ * ! VERSION... * /" wrapper in dump files. + Make the string of whole stored-program-definition query (in the + original character set). */ - endp= skip_rear_comments(thd->charset(), (char*) m_body_begin, (char*) endp); - m_body.length= endp - m_body_begin; - m_body.str= strmake_root(root, m_body_begin, m_body.length); - m_defstr.length= endp - lip->buf; - m_defstr.str= strmake_root(root, lip->buf, m_defstr.length); - DBUG_VOID_RETURN; + m_defstr.length= end_ptr - lip->get_cpp_buf(); + m_defstr.str= thd->strmake(lip->get_cpp_buf(), m_defstr.length); + trim_whitespace(thd->charset(), & m_defstr); } static TYPELIB * -create_typelib(MEM_ROOT *mem_root, create_field *field_def, List<String> *src) +create_typelib(MEM_ROOT *mem_root, Create_field *field_def, List<String> *src) { TYPELIB *result= NULL; CHARSET_INFO *cs= field_def->charset; DBUG_ENTER("create_typelib"); + if (src->elements) { result= (TYPELIB*) alloc_root(mem_root, sizeof(TYPELIB)); @@ -630,7 +689,7 @@ create_typelib(MEM_ROOT *mem_root, create_field *field_def, List<String> *src) if (!(result->type_names=(const char **) alloc_root(mem_root,(sizeof(char *)+sizeof(int))*(result->count+1)))) DBUG_RETURN(0); - result->type_lengths= (unsigned int *)(result->type_names + result->count+1); + result->type_lengths= (uint*)(result->type_names + result->count+1); List_iterator<String> it(*src); String conv; for (uint i=0; i < result->count; i++) @@ -671,25 +730,20 @@ int sp_head::create(THD *thd) { DBUG_ENTER("sp_head::create"); - int ret; - DBUG_PRINT("info", ("type: %d name: %s params: %s body: %s", m_type, m_name.str, m_params.str, m_body.str)); - if (m_type == TYPE_ENUM_FUNCTION) - ret= sp_create_function(thd, this); - else - ret= sp_create_procedure(thd, this); - - DBUG_RETURN(ret); + DBUG_RETURN(sp_create_routine(thd, m_type, this)); } sp_head::~sp_head() { + DBUG_ENTER("sp_head::~sp_head"); destroy(); delete m_next_cached_sp; if (m_thd) restore_thd_mem_root(m_thd); + DBUG_VOID_RETURN; } void @@ -728,7 +782,7 @@ sp_head::destroy() } -/* +/** This is only used for result fields from functions (both during fix_length_and_dec() and evaluation). */ @@ -745,7 +799,8 @@ sp_head::create_result_field(uint field_max_length, const char *field_name, field_length= !m_return_field_def.length ? field_max_length : m_return_field_def.length; - field= ::make_field((char*) 0, /* field ptr */ + field= ::make_field(table->s, /* TABLE_SHARE ptr */ + (uchar*) 0, /* field ptr */ field_length, /* field [max] length */ (uchar*) "", /* null ptr */ 0, /* null bit */ @@ -755,8 +810,10 @@ sp_head::create_result_field(uint field_max_length, const char *field_name, m_return_field_def.geom_type, Field::NONE, /* unreg check */ m_return_field_def.interval, - field_name ? field_name : (const char *) m_name.str, - table); + field_name ? field_name : (const char *) m_name.str); + + if (field) + field->init(table); DBUG_RETURN(field); } @@ -770,6 +827,9 @@ int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b) /* StoredRoutinesBinlogging + This paragraph applies only to statement-based binlogging. Row-based + binlogging does not need anything special like this. + Top-down overview: 1. Statements @@ -783,7 +843,7 @@ int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b) Statements that have is_update_query(stmt) == FALSE (e.g. SELECTs) are not written into binary log. Instead we catch function calls the statement makes and write it into binary log separately (see #3). - + 2. PROCEDURE calls CALL statements are not written into binary log. Instead @@ -804,7 +864,7 @@ int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b) function execution (grep for start_union_events and stop_union_events) If the answers are No and Yes, we write the function call into the binary - log as "SELECT spfunc(<param1value>, <param2value>, ...)". + log as "SELECT spfunc(<param1value>, <param2value>, ...)" 4. Miscellaneous issues. @@ -817,7 +877,7 @@ int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b) This set is produced by tracking user variable reads during statement execution. - Fo SPs, this has the following implications: + For SPs, this has the following implications: 1) thd->user_var_events may contain events from several SP statements and needs to be valid after exection of these statements was finished. In order to achieve that, we @@ -830,32 +890,34 @@ int cmp_splocal_locations(Item_splocal * const *a, Item_splocal * const *b) reset_dynamic(&thd->user_var_events); calls in several different places. (TODO cosider moving this into mysql_bin_log.write() function) + + 4.2 Auto_increment storage in binlog + + As we may write two statements to binlog from one single logical statement + (case of "SELECT func1(),func2()": it is binlogged as "SELECT func1()" and + then "SELECT func2()"), we need to reset auto_increment binlog variables + after each binlogged SELECT. Otherwise, the auto_increment value of the + first SELECT would be used for the second too. */ -/* - Replace thd->query{_length} with a string that one can write to the binlog - or the query cache. - - SYNOPSIS - subst_spvars() - thd Current thread. - instr Instruction (we look for Item_splocal instances in - instr->free_list) - query_str Original query string - - DESCRIPTION +/** + Replace thd->query{_length} with a string that one can write to + the binlog. The binlog-suitable string is produced by replacing references to SP local - variables with NAME_CONST('sp_var_name', value) calls. To make this string - suitable for the query cache this function allocates some additional space - for the query cache flags. - - RETURN - FALSE on success - thd->query{_length} either has been appropriately replaced or there - is no need for replacements. - TRUE out of memory error. + variables with NAME_CONST('sp_var_name', value) calls. + + @param thd Current thread. + @param instr Instruction (we look for Item_splocal instances in + instr->free_list) + @param query_str Original query string + + @return + - FALSE on success. + thd->query{_length} either has been appropriately replaced or there + is no need for replacements. + - TRUE out of memory error. */ static bool @@ -915,7 +977,7 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) break; val= (*splocal)->this_item(); - DBUG_PRINT("info", ("print %p", val)); + DBUG_PRINT("info", ("print 0x%lx", (long) val)); str_value= sp_get_item_value(thd, val, &str_value_holder); if (str_value) res|= qbuf.append(*str_value); @@ -934,7 +996,7 @@ subst_spvars(THD *thd, sp_instr *instr, LEX_STRING *query_str) query_cache_send_result_to_client function. */ buf_len= qbuf.length() + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE + 1; - if ((pbuf= alloc_root(thd->mem_root, buf_len))) + if ((pbuf= (char *) alloc_root(thd->mem_root, buf_len))) { memcpy(pbuf, qbuf.ptr(), qbuf.length()); pbuf[qbuf.length()]= 0; @@ -974,23 +1036,27 @@ void sp_head::recursion_level_error(THD *thd) } -/* - Execute the routine. The main instruction jump loop is there +/** + Execute the routine. The main instruction jump loop is there. Assume the parameters already set. - - RETURN + @todo + - Will write this SP statement into binlog separately + (TODO: consider changing the condition to "not inside event union") + + @retval FALSE on success + @retval TRUE on error - */ bool sp_head::execute(THD *thd) { DBUG_ENTER("sp_head::execute"); - char old_db_buf[NAME_LEN+1]; - LEX_STRING old_db= { old_db_buf, sizeof(old_db_buf) }; - bool dbchanged; + 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) }; + bool cur_db_changed= FALSE; sp_rcontext *ctx; bool err_status= FALSE; uint ip= 0; @@ -1006,9 +1072,12 @@ sp_head::execute(THD *thd) LEX *old_lex; Item_change_list old_change_list; String old_packet; + Reprepare_observer *save_reprepare_observer= thd->m_reprepare_observer; + + Object_creation_ctx *saved_creation_ctx; /* Use some extra margin for possible SP recursion and functions */ - if (check_stack_overrun(thd, 8 * STACK_MIN_SIZE, (char*)&old_packet)) + if (check_stack_overrun(thd, 8 * STACK_MIN_SIZE, (uchar*)&old_packet)) DBUG_RETURN(TRUE); /* init per-instruction memroot */ @@ -1043,15 +1112,24 @@ sp_head::execute(THD *thd) */ if (m_db.length && - (err_status= sp_use_new_db(thd, m_db, &old_db, 0, &dbchanged))) + (err_status= mysql_opt_change_db(thd, &m_db, &saved_cur_db_name, FALSE, + &cur_db_changed))) + { goto done; + } if ((ctx= thd->spcont)) ctx->clear_handler(); - thd->query_error= 0; + thd->is_slave_error= 0; old_arena= thd->stmt_arena; /* + Switch query context. This has to be done early as this is sometimes + allocated trough sql_alloc + */ + saved_creation_ctx= m_creation_ctx->set_n_backup(thd); + + /* We have to save/restore this info when we are changing call level to be able properly do close_thread_tables() in instructions. */ @@ -1062,6 +1140,25 @@ sp_head::execute(THD *thd) thd->variables.sql_mode= m_sql_mode; save_abort_on_warning= thd->abort_on_warning; thd->abort_on_warning= 0; + /** + When inside a substatement (a stored function or trigger + statement), clear the metadata observer in THD, if any. + Remember the value of the observer here, to be able + to restore it when leaving the substatement. + + We reset the observer to suppress errors when a substatement + uses temporary tables. If a temporary table does not exist + at start of the main statement, it's not prelocked + and thus is not validated with other prelocked tables. + + Later on, when the temporary table is opened, metadata + versions mismatch, expectedly. + + The proper solution for the problem is to re-validate tables + of substatements (Bug#12257, Bug#27011, Bug#32868, Bug#33000), + but it's not implemented yet. + */ + thd->m_reprepare_observer= 0; /* It is also more efficient to save/restore current thd->lex once when @@ -1096,15 +1193,36 @@ sp_head::execute(THD *thd) */ thd->spcont->callers_arena= &backup_arena; +#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) + /* Discard the initial part of executing routines. */ + thd->profiling.discard_current_query(); +#endif do { sp_instr *i; uint hip; // Handler ip +#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) + /* + Treat each "instr" of a routine as discrete unit that could be profiled. + Profiling only records information for segments of code that set the + source of the query, and almost all kinds of instructions in s-p do not. + */ + thd->profiling.finish_current_query(); + thd->profiling.start_new_query("continuing inside routine"); +#endif + i = get_instr(ip); // Returns NULL when we're done. if (i == NULL) + { +#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) + thd->profiling.discard_current_query(); +#endif break; + } + DBUG_PRINT("execute", ("Instruction %u", ip)); + /* Don't change NOW() in FUNCTION or TRIGGER */ if (!thd->in_sub_stmt) thd->set_time(); // Make current_time() et al work @@ -1126,12 +1244,6 @@ sp_head::execute(THD *thd) err_status= i->execute(thd, &ip); - /* - If this SP instruction have sent eof, it has caused no_send_error to be - set. Clear it back to allow the next instruction to send error. (multi- - statement execution code clears no_send_error between statements too) - */ - thd->net.no_send_error= 0; if (i->free_list) cleanup_items(i->free_list); @@ -1174,6 +1286,7 @@ sp_head::execute(THD *thd) ctx->clear_handler(); ctx->enter_handler(hip); thd->clear_error(); + thd->is_fatal_error= 0; thd->killed= THD::NOT_KILLED; thd->mysys_var->abort= 0; continue; @@ -1181,6 +1294,17 @@ sp_head::execute(THD *thd) } } while (!err_status && !thd->killed); +#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) + thd->profiling.finish_current_query(); + thd->profiling.start_new_query("tail end of routine"); +#endif + + /* Restore query context. */ + + m_creation_ctx->restore_env(thd, saved_creation_ctx); + + /* Restore arena. */ + thd->restore_active_arena(&execute_arena, &backup_arena); thd->spcont->pop_all_cursors(); // To avoid memory leaks after an error @@ -1197,13 +1321,15 @@ sp_head::execute(THD *thd) thd->derived_tables= old_derived_tables; thd->variables.sql_mode= save_sql_mode; thd->abort_on_warning= save_abort_on_warning; + thd->m_reprepare_observer= save_reprepare_observer; thd->stmt_arena= old_arena; state= EXECUTED; done: - DBUG_PRINT("info", ("err_status: %d killed: %d query_error: %d", - err_status, thd->killed, thd->query_error)); + DBUG_PRINT("info", ("err_status: %d killed: %d is_slave_error: %d report_error: %d", + err_status, thd->killed, thd->is_slave_error, + thd->is_error())); if (thd->killed) err_status= TRUE; @@ -1211,14 +1337,14 @@ sp_head::execute(THD *thd) If the DB has changed, the pointer has changed too, but the original thd->db will then have been freed */ - if (dbchanged) + if (cur_db_changed && !thd->killed) { /* - No access check when changing back to where we came from. - (It would generate an error from mysql_change_db() when old_db=="") + Force switching back to the saved current database, because it may be + NULL. In this case, mysql_change_db() would generate an error. */ - if (! thd->killed) - err_status|= mysql_change_db(thd, &old_db, TRUE); + + err_status|= mysql_change_db(thd, &saved_cur_db_name, TRUE); } m_flags&= ~IS_INVOKED; DBUG_PRINT("info", @@ -1251,22 +1377,26 @@ sp_head::execute(THD *thd) #ifndef NO_EMBEDDED_ACCESS_CHECKS -/* +/** set_routine_security_ctx() changes routine security context, and checks if there is an EXECUTE privilege in new context. If there is no EXECUTE privilege, it changes the context back and returns a error. - SYNOPSIS - set_routine_security_ctx() - thd thread handle - sp stored routine to change the context for - is_proc TRUE is procedure, FALSE if function - save_ctx pointer to an old security context - - RETURN - TRUE if there was a error, and the context wasn't changed. - FALSE if the context was changed. + @param thd thread handle + @param sp stored routine to change the context for + @param is_proc TRUE is procedure, FALSE if function + @param save_ctx pointer to an old security context + + @todo + - Cache if the definer has the right to use the object on the + first usage and only reset the cache if someone does a GRANT + statement that 'may' affect this. + + @retval + TRUE if there was a error, and the context wasn't changed. + @retval + FALSE if the context was changed. */ bool @@ -1274,7 +1404,11 @@ set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, Security_context **save_ctx) { *save_ctx= 0; - if (sp_change_security_context(thd, sp, save_ctx)) + if (sp->m_chistics->suid != SP_IS_NOT_SUID && + sp->m_security_ctx.change_security_context(thd, &sp->m_definer_user, + &sp->m_definer_host, + &sp->m_db, + save_ctx)) return TRUE; /* @@ -1291,7 +1425,7 @@ set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, check_routine_access(thd, EXECUTE_ACL, sp->m_db.str, sp->m_name.str, is_proc, FALSE)) { - sp_restore_security_context(thd, *save_ctx); + sp->m_security_ctx.restore_security_context(thd, *save_ctx); *save_ctx= 0; return TRUE; } @@ -1301,30 +1435,36 @@ set_routine_security_ctx(THD *thd, sp_head *sp, bool is_proc, #endif // ! NO_EMBEDDED_ACCESS_CHECKS -/* - Execute a trigger: - - changes security context for triggers - - switch to new memroot - - call sp_head::execute - - restore old memroot - - restores security context +/** + Execute trigger stored program. + + - changes security context for triggers + - switch to new memroot + - call sp_head::execute + - restore old memroot + - restores security context + + @param thd Thread handle + @param db database name + @param table table name + @param grant_info GRANT_INFO structure to be filled with + information about definer's privileges + on subject table + + @todo + - TODO: we should create sp_rcontext once per command and reuse it + on subsequent executions of a trigger. - SYNOPSIS - sp_head::execute_trigger() - thd Thread handle - db database name - table table name - grant_info GRANT_INFO structure to be filled with - information about definer's privileges - on subject table - - RETURN + @retval FALSE on success + @retval TRUE on error */ bool -sp_head::execute_trigger(THD *thd, const char *db, const char *table, +sp_head::execute_trigger(THD *thd, + const LEX_STRING *db_name, + const LEX_STRING *table_name, GRANT_INFO *grant_info) { sp_rcontext *octx = thd->spcont; @@ -1338,26 +1478,43 @@ sp_head::execute_trigger(THD *thd, const char *db, const char *table, DBUG_PRINT("info", ("trigger %s", m_name.str)); #ifndef NO_EMBEDDED_ACCESS_CHECKS - Security_context *save_ctx; - if (sp_change_security_context(thd, this, &save_ctx)) + Security_context *save_ctx= NULL; + + + if (m_chistics->suid != SP_IS_NOT_SUID && + m_security_ctx.change_security_context(thd, + &m_definer_user, + &m_definer_host, + &m_db, + &save_ctx)) DBUG_RETURN(TRUE); /* - NOTE: TRIGGER_ACL should be used here. + Fetch information about table-level privileges for subject table into + GRANT_INFO instance. The access check itself will happen in + Item_trigger_field, where this information will be used along with + information about column-level privileges. */ - if (check_global_access(thd, SUPER_ACL)) + + fill_effective_table_privileges(thd, + grant_info, + db_name->str, + table_name->str); + + /* Check that the definer has TRIGGER privilege on the subject table. */ + + if (!(grant_info->privilege & TRIGGER_ACL)) { - sp_restore_security_context(thd, save_ctx); + char priv_desc[128]; + get_privilege_desc(priv_desc, sizeof(priv_desc), TRIGGER_ACL); + + my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), priv_desc, + thd->security_ctx->priv_user, thd->security_ctx->host_or_ip, + table_name->str); + + m_security_ctx.restore_security_context(thd, save_ctx); DBUG_RETURN(TRUE); } - - /* - Fetch information about table-level privileges to GRANT_INFO - structure for subject table. Check of privileges that will use it - and information about column-level privileges will happen in - Item_trigger_field::fix_fields(). - */ - fill_effective_table_privileges(thd, grant_info, db, table); #endif // NO_EMBEDDED_ACCESS_CHECKS /* @@ -1392,9 +1549,11 @@ sp_head::execute_trigger(THD *thd, const char *db, const char *table, err_with_cleanup: thd->restore_active_arena(&call_arena, &backup_arena); + #ifndef NO_EMBEDDED_ACCESS_CHECKS - sp_restore_security_context(thd, save_ctx); + m_security_ctx.restore_security_context(thd, save_ctx); #endif // NO_EMBEDDED_ACCESS_CHECKS + delete nctx; call_arena.free_items(); free_root(&call_mem_root, MYF(0)); @@ -1407,8 +1566,9 @@ err_with_cleanup: } -/* - Execute a function: +/** + Execute a function. + - evaluate parameters - changes security context for SUID routines - switch to new memroot @@ -1417,17 +1577,25 @@ err_with_cleanup: - evaluate the return value - restores security context - SYNOPSIS - sp_head::execute_function() - thd Thread handle - argp Passed arguments (these are items from containing - statement?) - argcount Number of passed arguments. We need to check if this is - correct. - return_value_fld Save result here. - - RETURN + @param thd Thread handle + @param argp Passed arguments (these are items from containing + statement?) + @param argcount Number of passed arguments. We need to check if + this is correct. + @param return_value_fld Save result here. + + @todo + We should create sp_rcontext once per command and reuse + it on subsequent executions of a function/trigger. + + @todo + In future we should associate call arena/mem_root with + sp_rcontext and allocate all these objects (and sp_rcontext + itself) on it directly rather than juggle with arenas. + + @retval FALSE on success + @retval TRUE on error */ @@ -1446,10 +1614,11 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, MEM_ROOT call_mem_root; Query_arena call_arena(&call_mem_root, Query_arena::INITIALIZED_FOR_SP); Query_arena backup_arena; - DBUG_ENTER("sp_head::execute_function"); DBUG_PRINT("info", ("function %s", m_name.str)); + LINT_INIT(binlog_save_options); + /* Check that the function is called with all specified arguments. @@ -1510,7 +1679,12 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, goto err_with_cleanup; } - need_binlog_call= mysql_bin_log.is_open() && (thd->options & OPTION_BIN_LOG); + /* + If row-based binlogging, we don't need to binlog the function's call, let + each substatement be binlogged its way. + */ + need_binlog_call= mysql_bin_log.is_open() && + (thd->options & OPTION_BIN_LOG) && !thd->current_stmt_binlog_row_based; /* Remember the original arguments for unrolled replication of functions @@ -1553,7 +1727,6 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, } #endif - binlog_save_options= thd->options; if (need_binlog_call) { query_id_t q; @@ -1574,6 +1747,8 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, q= global_query_id; VOID(pthread_mutex_unlock(&LOCK_thread_count)); mysql_bin_log.start_union_events(thd, q + 1); + binlog_save_options= thd->options; + thd->options&= ~OPTION_BIN_LOG; } /* @@ -1586,27 +1761,30 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, */ thd->set_n_backup_active_arena(&call_arena, &backup_arena); - thd->options&= ~OPTION_BIN_LOG; err_status= execute(thd); - thd->options= binlog_save_options; thd->restore_active_arena(&call_arena, &backup_arena); if (need_binlog_call) - mysql_bin_log.stop_union_events(thd); - - if (need_binlog_call && thd->binlog_evt_union.unioned_events) { - Query_log_event qinfo(thd, binlog_buf.ptr(), binlog_buf.length(), - thd->binlog_evt_union.unioned_events_trans, FALSE); - if (mysql_bin_log.write(&qinfo) && - thd->binlog_evt_union.unioned_events_trans) + mysql_bin_log.stop_union_events(thd); + thd->options= binlog_save_options; + if (thd->binlog_evt_union.unioned_events) { - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, - "Invoked ROUTINE modified a transactional table but MySQL " - "failed to reflect this change in the binary log"); + Query_log_event qinfo(thd, binlog_buf.ptr(), binlog_buf.length(), + thd->binlog_evt_union.unioned_events_trans, FALSE); + if (mysql_bin_log.write(&qinfo) && + thd->binlog_evt_union.unioned_events_trans) + { + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR, + "Invoked ROUTINE modified a transactional table but MySQL " + "failed to reflect this change in the binary log"); + } + reset_dynamic(&thd->user_var_events); + /* Forget those values, in case more function calls are binlogged: */ + thd->stmt_depends_on_first_successful_insert_id_in_prev_stmt= 0; + thd->auto_inc_intervals_in_cur_stmt_for_binlog.empty(); } - reset_dynamic(&thd->user_var_events); } if (!err_status) @@ -1621,7 +1799,7 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, } #ifndef NO_EMBEDDED_ACCESS_CHECKS - sp_restore_security_context(thd, save_security_ctx); + m_security_ctx.restore_security_context(thd, save_security_ctx); #endif err_with_cleanup: @@ -1634,14 +1812,8 @@ err_with_cleanup: } -/* +/** Execute a procedure. - SYNOPSIS - sp_head::execute_procedure() - thd Thread handle - args List of values passed as arguments. - - DESCRIPTION The function does the following steps: - Set all parameters @@ -1650,8 +1822,12 @@ err_with_cleanup: - copy back values of INOUT and OUT parameters - restores security context - RETURN + @param thd Thread handle + @param args List of values passed as arguments. + + @retval FALSE on success + @retval TRUE on error */ @@ -1662,6 +1838,8 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) uint params = m_pcont->context_var_count(); sp_rcontext *save_spcont, *octx; sp_rcontext *nctx = NULL; + bool save_enable_slow_log= false; + bool save_log_general= false; DBUG_ENTER("sp_head::execute_procedure"); DBUG_PRINT("info", ("procedure %s", m_name.str)); @@ -1706,7 +1884,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) { List_iterator<Item> it_args(*args); - DBUG_PRINT("info",(" %.*s: eval args", m_name.length, m_name.str)); + DBUG_PRINT("info",(" %.*s: eval args", (int) m_name.length, m_name.str)); for (uint i= 0 ; i < params ; i++) { @@ -1762,11 +1940,24 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) we'll leave it here. */ if (!thd->in_sub_stmt) - close_thread_tables(thd, 0, 0); + close_thread_tables(thd); - DBUG_PRINT("info",(" %.*s: eval args done", m_name.length, m_name.str)); + DBUG_PRINT("info",(" %.*s: eval args done", + (int) m_name.length, m_name.str)); + } + if (!(m_flags & LOG_SLOW_STATEMENTS) && thd->enable_slow_log) + { + DBUG_PRINT("info", ("Disabling slow log for the execution")); + save_enable_slow_log= true; + thd->enable_slow_log= FALSE; + } + if (!(m_flags & LOG_GENERAL_LOG) && !(thd->options & OPTION_LOG_OFF)) + { + DBUG_PRINT("info", ("Disabling general log for the execution")); + save_log_general= true; + /* disable this bit */ + thd->options |= OPTION_LOG_OFF; } - thd->spcont= nctx; #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -1778,6 +1969,10 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) if (!err_status) err_status= execute(thd); + if (save_log_general) + thd->options &= ~OPTION_LOG_OFF; + if (save_enable_slow_log) + thd->enable_slow_log= true; /* In the case when we weren't able to employ reuse mechanism for OUT/INOUT paranmeters, we should reallocate memory. This @@ -1821,7 +2016,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) #ifndef NO_EMBEDDED_ACCESS_CHECKS if (save_security_ctx) - sp_restore_security_context(thd, save_security_ctx); + m_security_ctx.restore_security_context(thd, save_security_ctx); #endif if (!save_spcont) @@ -1835,7 +2030,7 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) /** - @brief Reset lex during parsing, before we parse a sub statement. + Reset lex during parsing, before we parse a sub statement. @param thd Thread handler. @@ -1869,8 +2064,6 @@ sp_head::reset_lex(THD *thd) sublex->trg_table_fields.empty(); sublex->sp_lex_in_use= FALSE; - sublex->in_comment= oldlex->in_comment; - /* Reset type info. */ sublex->charset= NULL; @@ -1882,7 +2075,7 @@ sp_head::reset_lex(THD *thd) DBUG_RETURN(FALSE); } -// Restore lex during parsing, after we have parsed a sub statement. +/// Restore lex during parsing, after we have parsed a sub statement. void sp_head::restore_lex(THD *thd) { @@ -1899,6 +2092,14 @@ sp_head::restore_lex(THD *thd) oldlex->trg_table_fields.push_back(&sublex->trg_table_fields); /* + If this substatement needs row-based, the entire routine does too (we + cannot switch from statement-based to row-based only for this + substatement). + */ + if (sublex->is_stmt_unsafe()) + m_flags|= BINLOG_ROW_BASED_IF_MIXED; + + /* Add routines which are used by statement to respective set for this routine. */ @@ -1917,6 +2118,9 @@ sp_head::restore_lex(THD *thd) DBUG_VOID_RETURN; } +/** + Put the instruction on the backpatch list, associated with the label. +*/ void sp_head::push_backpatch(sp_instr *i, sp_label_t *lab) { @@ -1930,6 +2134,10 @@ sp_head::push_backpatch(sp_instr *i, sp_label_t *lab) } } +/** + Update all instruction with this label in the backpatch list to + the current position. +*/ void sp_head::backpatch(sp_label_t *lab) { @@ -1950,39 +2158,35 @@ sp_head::backpatch(sp_label_t *lab) DBUG_VOID_RETURN; } -/* - Prepare an instance of create_field for field creation (fill all necessary +/** + Prepare an instance of Create_field for field creation (fill all necessary attributes). - SYNOPSIS - sp_head::fill_field_definition() - thd [IN] Thread handle - lex [IN] Yacc parsing context - field_type [IN] Field type - field_def [OUT] An instance of create_field to be filled + @param[in] thd Thread handle + @param[in] lex Yacc parsing context + @param[in] field_type Field type + @param[out] field_def An instance of create_field to be filled - RETURN + @retval FALSE on success + @retval TRUE on error */ bool sp_head::fill_field_definition(THD *thd, LEX *lex, enum enum_field_types field_type, - create_field *field_def) + Create_field *field_def) { - HA_CREATE_INFO sp_db_info; LEX_STRING cmt = { 0, 0 }; uint unused1= 0; int unused2= 0; - load_db_opt_by_name(thd, m_db.str, &sp_db_info); - if (field_def->init(thd, (char*) "", field_type, lex->length, lex->dec, lex->type, (Item*) 0, (Item*) 0, &cmt, 0, &lex->interval_list, - (lex->charset ? lex->charset : - sp_db_info.default_table_charset), + lex->charset ? lex->charset : + thd->variables.collation_database, lex->uint_geom_type)) return TRUE; @@ -2057,10 +2261,10 @@ void sp_head::set_definer(const char *definer, uint definerlen) { char user_name_holder[USERNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT user_name(user_name_holder, USERNAME_LENGTH); + LEX_STRING user_name= { user_name_holder, USERNAME_LENGTH }; char host_name_holder[HOSTNAME_LENGTH + 1]; - LEX_STRING_WITH_INIT host_name(host_name_holder, HOSTNAME_LENGTH); + LEX_STRING host_name= { host_name_holder, HOSTNAME_LENGTH }; parse_user(definer, definerlen, user_name.str, &user_name.length, host_name.str, &host_name.length); @@ -2111,18 +2315,17 @@ sp_head::restore_thd_mem_root(THD *thd) } -/* - Check if a user has access right to a routine - - SYNOPSIS - check_show_routine_access() - thd Thread handler - sp SP - full_access Set to 1 if the user has SELECT right to the - 'mysql.proc' able or is the owner of the routine - RETURN - 0 ok - 1 error +/** + Check if a user has access right to a routine. + + @param thd Thread handler + @param sp SP + @param full_access Set to 1 if the user has SELECT right to the + 'mysql.proc' able or is the owner of the routine + @retval + false ok + @retval + true error */ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) @@ -2131,7 +2334,7 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) bzero((char*) &tables,sizeof(tables)); tables.db= (char*) "mysql"; tables.table_name= tables.alias= (char*) "proc"; - *full_access= (!check_table_access(thd, SELECT_ACL, &tables, 1) || + *full_access= (!check_table_access(thd, SELECT_ACL, &tables, 1, TRUE) || (!strcmp(sp->m_definer_user.str, thd->security_ctx->priv_user) && !strcmp(sp->m_definer_host.str, @@ -2143,61 +2346,116 @@ bool check_show_routine_access(THD *thd, sp_head *sp, bool *full_access) } -int -sp_head::show_create_procedure(THD *thd) +/** + Implement SHOW CREATE statement for stored routines. + + @param thd Thread context. + @param type Stored routine type + (TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION) + + @return Error status. + @retval FALSE on success + @retval TRUE on error +*/ + +bool +sp_head::show_create_routine(THD *thd, int type) { + const char *col1_caption= type == TYPE_ENUM_PROCEDURE ? + "Procedure" : "Function"; + + const char *col3_caption= type == TYPE_ENUM_PROCEDURE ? + "Create Procedure" : "Create Function"; + + bool err_status; + Protocol *protocol= thd->protocol; - char buff[2048]; - String buffer(buff, sizeof(buff), system_charset_info); - int res; - List<Item> field_list; - byte *sql_mode_str; - ulong sql_mode_len; + List<Item> fields; + + LEX_STRING sql_mode; + bool full_access; - DBUG_ENTER("sp_head::show_create_procedure"); - DBUG_PRINT("info", ("procedure %s", m_name.str)); - LINT_INIT(sql_mode_str); - LINT_INIT(sql_mode_len); + DBUG_ENTER("sp_head::show_create_routine"); + DBUG_PRINT("info", ("routine %s", m_name.str)); + + DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE || + type == TYPE_ENUM_FUNCTION); if (check_show_routine_access(thd, this, &full_access)) - DBUG_RETURN(1); + DBUG_RETURN(TRUE); - sql_mode_str= - sys_var_thd_sql_mode::symbolic_mode_representation(thd, - m_sql_mode, - &sql_mode_len); - field_list.push_back(new Item_empty_string("Procedure", NAME_LEN)); - field_list.push_back(new Item_empty_string("sql_mode", sql_mode_len)); - // 1024 is for not to confuse old clients - Item_empty_string *definition= - new Item_empty_string("Create Procedure", max(buffer.length(),1024)); - definition->maybe_null= TRUE; - field_list.push_back(definition); + sys_var_thd_sql_mode::symbolic_mode_representation( + thd, m_sql_mode, &sql_mode); + + /* Send header. */ + + fields.push_back(new Item_empty_string(col1_caption, NAME_CHAR_LEN)); + fields.push_back(new Item_empty_string("sql_mode", sql_mode.length)); + + { + /* + NOTE: SQL statement field must be not less than 1024 in order not to + confuse old clients. + */ + + Item_empty_string *stmt_fld= + new Item_empty_string(col3_caption, + max(m_defstr.length, 1024)); + + stmt_fld->maybe_null= TRUE; + + fields.push_back(stmt_fld); + } + + fields.push_back(new Item_empty_string("character_set_client", + MY_CS_NAME_SIZE)); + + fields.push_back(new Item_empty_string("collation_connection", + MY_CS_NAME_SIZE)); + + fields.push_back(new Item_empty_string("Database Collation", + MY_CS_NAME_SIZE)); + + if (protocol->send_fields(&fields, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + { + DBUG_RETURN(TRUE); + } + + /* Send data. */ - if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | - Protocol::SEND_EOF)) - DBUG_RETURN(1); protocol->prepare_for_resend(); + protocol->store(m_name.str, m_name.length, system_charset_info); - protocol->store((char*) sql_mode_str, sql_mode_len, system_charset_info); + protocol->store(sql_mode.str, sql_mode.length, system_charset_info); + if (full_access) - protocol->store(m_defstr.str, m_defstr.length, system_charset_info); + protocol->store(m_defstr.str, m_defstr.length, + m_creation_ctx->get_client_cs()); else protocol->store_null(); - res= protocol->write(); - send_eof(thd); - DBUG_RETURN(res); + + protocol->store(m_creation_ctx->get_client_cs()->csname, system_charset_info); + protocol->store(m_creation_ctx->get_connection_cl()->name, system_charset_info); + protocol->store(m_creation_ctx->get_db_cl()->name, system_charset_info); + + err_status= protocol->write(); + + if (!err_status) + my_eof(thd); + + DBUG_RETURN(err_status); } -/* - Add instruction to SP - SYNOPSIS - sp_head::add_instr() - instr Instruction + +/** + Add instruction to SP. + + @param instr Instruction */ void sp_head::add_instr(sp_instr *instr) @@ -2211,71 +2469,24 @@ void sp_head::add_instr(sp_instr *instr) entire stored procedure, as their life span is equal. */ instr->mem_root= &main_mem_root; - insert_dynamic(&m_instr, (gptr)&instr); -} - - -int -sp_head::show_create_function(THD *thd) -{ - Protocol *protocol= thd->protocol; - char buff[2048]; - String buffer(buff, sizeof(buff), system_charset_info); - int res; - List<Item> field_list; - byte *sql_mode_str; - ulong sql_mode_len; - bool full_access; - DBUG_ENTER("sp_head::show_create_function"); - DBUG_PRINT("info", ("procedure %s", m_name.str)); - LINT_INIT(sql_mode_str); - LINT_INIT(sql_mode_len); - - if (check_show_routine_access(thd, this, &full_access)) - DBUG_RETURN(1); - - sql_mode_str= - sys_var_thd_sql_mode::symbolic_mode_representation(thd, - m_sql_mode, - &sql_mode_len); - field_list.push_back(new Item_empty_string("Function",NAME_LEN)); - field_list.push_back(new Item_empty_string("sql_mode", sql_mode_len)); - Item_empty_string *definition= - new Item_empty_string("Create Function", max(buffer.length(),1024)); - definition->maybe_null= TRUE; - field_list.push_back(definition); - - if (protocol->send_fields(&field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) - DBUG_RETURN(1); - protocol->prepare_for_resend(); - protocol->store(m_name.str, m_name.length, system_charset_info); - protocol->store((char*) sql_mode_str, sql_mode_len, system_charset_info); - if (full_access) - protocol->store(m_defstr.str, m_defstr.length, system_charset_info); - else - protocol->store_null(); - res= protocol->write(); - send_eof(thd); - - DBUG_RETURN(res); + insert_dynamic(&m_instr, (uchar*)&instr); } -/* +/** Do some minimal optimization of the code: - 1) Mark used instructions - 1.1) While doing this, shortcut jumps to jump instructions - 2) Compact the code, removing unused instructions + -# Mark used instructions + -# While doing this, shortcut jumps to jump instructions + -# Compact the code, removing unused instructions. This is the main mark and move loop; it relies on the following methods in sp_instr and its subclasses: - opt_mark() Mark instruction as reachable - opt_shortcut_jump() Shortcut jumps to the final destination; - used by opt_mark(). - opt_move() Update moved instruction - set_destination() Set the new destination (jump instructions only) + - opt_mark() : Mark instruction as reachable + - opt_shortcut_jump(): Shortcut jumps to the final destination; + used by opt_mark(). + - opt_move() : Update moved instruction + - set_destination() : Set the new destination (jump instructions only) */ void sp_head::optimize() @@ -2302,7 +2513,7 @@ void sp_head::optimize() sp_instr *ibp; List_iterator_fast<sp_instr> li(bp); - set_dynamic(&m_instr, (gptr)&i, dst); + set_dynamic(&m_instr, (uchar*)&i, dst); while ((ibp= li++)) { sp_instr_opt_meta *im= static_cast<sp_instr_opt_meta *>(ibp); @@ -2366,9 +2577,10 @@ sp_head::opt_mark() #ifndef DBUG_OFF -/* +/** Return the routine instructions as a result set. - Returns 0 if ok, !=0 on error. + @return + 0 if ok, !=0 on error. */ int sp_head::show_routine_code(THD *thd) @@ -2422,34 +2634,34 @@ sp_head::show_routine_code(THD *thd) if ((res= protocol->write())) break; } - send_eof(thd); + + if (!res) + my_eof(thd); DBUG_RETURN(res); } #endif // ifndef DBUG_OFF -/* +/** Prepare LEX and thread for execution of instruction, if requested open and lock LEX's tables, execute instruction's core function, perform cleanup afterwards. - SYNOPSIS - reset_lex_and_exec_core() - thd - thread context - nextp - out - next instruction - open_tables - if TRUE then check read access to tables in LEX's table - list and open and lock them (used in instructions which - need to calculate some expression and don't execute - complete statement). - sp_instr - instruction for which we prepare context, and which core - function execute by calling its exec_core() method. + @param thd thread context + @param nextp out - next instruction + @param open_tables if TRUE then check read access to tables in LEX's table + list and open and lock them (used in instructions which + need to calculate some expression and don't execute + complete statement). + @param sp_instr instruction for which we prepare context, and which core + function execute by calling its exec_core() method. - NOTE + @note We are not saving/restoring some parts of THD which may need this because we do this once for whole routine execution in sp_head::execute(). - RETURN VALUE + @return 0/non-0 - Success/Failure */ @@ -2458,6 +2670,8 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables, sp_instr* instr) { int res= 0; + DBUG_ENTER("reset_lex_and_exec_core"); + /* The flag is saved at the entry to the following substatement. It's reset further in the common code part. @@ -2503,13 +2717,17 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables); if (!res) + { res= instr->exec_core(thd, nextp); + DBUG_PRINT("info",("exec_core returned: %d", res)); + } m_lex->unit.cleanup(); - thd->proc_info="closing tables"; + thd_proc_info(thd, "closing tables"); + /* Here we also commit or rollback the current statement. */ close_thread_tables(thd); - thd->proc_info= 0; + thd_proc_info(thd, 0); if (m_lex->query_tables_own_last) { @@ -2545,7 +2763,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, cleanup_items() is called in sp_head::execute() */ - return res || thd->net.report_error; + DBUG_RETURN(res || thd->is_error()); } @@ -2561,7 +2779,7 @@ int sp_instr::exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables) Check whenever we have access to tables for this statement and open and lock them before executing instructions core function. */ - if (check_table_access(thd, SELECT_ACL, tables, 0) + if (check_table_access(thd, SELECT_ACL, tables, UINT_MAX, FALSE) || open_and_lock_tables(thd, tables)) result= -1; else @@ -2597,23 +2815,40 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) query= thd->query; query_length= thd->query_length; - if (!(res= alloc_query(thd, m_query.str, m_query.length+1)) && +#if defined(ENABLED_PROFILING) && defined(COMMUNITY_SERVER) + /* This s-p instr is profilable and will be captured. */ + thd->profiling.set_query_source(m_query.str, m_query.length); +#endif + if (!(res= alloc_query(thd, m_query.str, m_query.length)) && !(res=subst_spvars(thd, this, &m_query))) { /* (the order of query cache and subst_spvars calls is irrelevant because queries with SP vars can't be cached) */ + if (unlikely((thd->options & OPTION_LOG_OFF)==0)) + general_log_write(thd, COM_QUERY, thd->query, thd->query_length); + if (query_cache_send_result_to_client(thd, thd->query, thd->query_length) <= 0) { res= m_lex_keeper.reset_lex_and_exec_core(thd, nextp, FALSE, this); + + if (thd->main_da.is_eof()) + net_end_statement(thd); + query_cache_end_of_result(thd); + + if (!res && unlikely(thd->enable_slow_log)) + log_slow_statement(thd); } else *nextp= m_ip+1; thd->query= query; thd->query_length= query_length; + + if (!thd->is_error()) + thd->main_da.reset_diagnostics_area(); } DBUG_RETURN(res); } @@ -2723,7 +2958,7 @@ sp_instr_set::print(String *str) } str->qs_append(m_offset); str->qs_append(' '); - m_value->print(str); + m_value->print(str, QT_ORDINARY); } @@ -2751,9 +2986,9 @@ void sp_instr_set_trigger_field::print(String *str) { str->append(STRING_WITH_LEN("set_trigger_field ")); - trigger_field->print(str); + trigger_field->print(str, QT_ORDINARY); str->append(STRING_WITH_LEN(":=")); - value->print(str); + value->print(str, QT_ORDINARY); } /* @@ -2879,7 +3114,7 @@ sp_instr_jump_if_not::print(String *str) str->qs_append('('); str->qs_append(m_cont_dest); str->qs_append(STRING_WITH_LEN(") ")); - m_expr->print(str); + m_expr->print(str, QT_ORDINARY); } @@ -2967,7 +3202,7 @@ sp_instr_freturn::print(String *str) str->qs_append(STRING_WITH_LEN("freturn ")); str->qs_append((uint)m_type); str->qs_append(' '); - m_value->print(str); + m_value->print(str, QT_ORDINARY); } /* @@ -3193,6 +3428,11 @@ sp_instr_cpop::print(String *str) sp_instr_copen class functions */ +/** + @todo + Assert that we either have an error or a cursor +*/ + int sp_instr_copen::execute(THD *thd, uint *nextp) { @@ -3450,7 +3690,7 @@ sp_instr_set_case_expr::print(String *str) str->qs_append(STRING_WITH_LEN(") ")); str->qs_append(m_case_expr_id); str->qs_append(' '); - m_case_expr->print(str); + m_case_expr->print(str, QT_ORDINARY); } uint @@ -3481,44 +3721,6 @@ sp_instr_set_case_expr::opt_move(uint dst, List<sp_instr> *bp) /* ------------------------------------------------------------------ */ -/* - Security context swapping -*/ - -#ifndef NO_EMBEDDED_ACCESS_CHECKS -bool -sp_change_security_context(THD *thd, sp_head *sp, Security_context **backup) -{ - *backup= 0; - if (sp->m_chistics->suid != SP_IS_NOT_SUID && - (strcmp(sp->m_definer_user.str, - thd->security_ctx->priv_user) || - my_strcasecmp(system_charset_info, sp->m_definer_host.str, - thd->security_ctx->priv_host))) - { - if (acl_getroot_no_password(&sp->m_security_ctx, sp->m_definer_user.str, - sp->m_definer_host.str, - sp->m_definer_host.str, - sp->m_db.str)) - { - my_error(ER_NO_SUCH_USER, MYF(0), sp->m_definer_user.str, - sp->m_definer_host.str); - return TRUE; - } - *backup= thd->security_ctx; - thd->security_ctx= &sp->m_security_ctx; - } - return FALSE; -} - -void -sp_restore_security_context(THD *thd, Security_context *backup) -{ - if (backup) - thd->security_ctx= backup; -} - -#endif /* NO_EMBEDDED_ACCESS_CHECKS */ /* Structure that represent all instances of one table @@ -3543,33 +3745,32 @@ typedef struct st_sp_table uint8 trg_event_map; } SP_TABLE; -byte * -sp_table_key(const byte *ptr, uint *plen, my_bool first) + +uchar *sp_table_key(const uchar *ptr, size_t *plen, my_bool first) { SP_TABLE *tab= (SP_TABLE *)ptr; *plen= tab->qname.length; - return (byte *)tab->qname.str; + return (uchar *)tab->qname.str; } -/* +/** Merge the list of tables used by some query into the multi-set of tables used by routine. - SYNOPSIS - merge_table_list() - thd - thread context - table - table list - lex_for_tmp_check - LEX of the query for which we are merging - table list. + @param thd thread context + @param table table list + @param lex_for_tmp_check LEX of the query for which we are merging + table list. - NOTE + @note This method will use LEX provided to check whenever we are creating temporary table and mark it as such in target multi-set. - RETURN VALUE - TRUE - Success - FALSE - Error + @retval + TRUE Success + @retval + FALSE Error */ bool @@ -3617,8 +3818,8 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) (and therefore should not be prelocked). Otherwise we will erroneously treat table with same name but with different alias as non-temporary. */ - if ((tab= (SP_TABLE *)hash_search(&m_sptabs, (byte *)tname, tlen)) || - ((tab= (SP_TABLE *)hash_search(&m_sptabs, (byte *)tname, + if ((tab= (SP_TABLE *)hash_search(&m_sptabs, (uchar *)tname, tlen)) || + ((tab= (SP_TABLE *)hash_search(&m_sptabs, (uchar *)tname, tlen - alen - 1)) && tab->temp)) { @@ -3650,34 +3851,33 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) tab->lock_type= table->lock_type; tab->lock_count= tab->query_lock_count= 1; tab->trg_event_map= table->trg_event_map; - my_hash_insert(&m_sptabs, (byte *)tab); + my_hash_insert(&m_sptabs, (uchar *)tab); } } return TRUE; } -/* +/** Add tables used by routine to the table list. - SYNOPSIS - add_used_tables_to_table_list() - thd [in] Thread context - query_tables_last_ptr [in/out] Pointer to the next_global member of - last element of the list where tables - will be added (or to its root). - belong_to_view [in] Uppermost view which uses this routine, - 0 if none. - - DESCRIPTION Converts multi-set of tables used by this routine to table list and adds this list to the end of table list specified by 'query_tables_last_ptr'. Elements of list will be allocated in PS memroot, so this list will be persistent between PS executions. - RETURN VALUE - TRUE - if some elements were added, FALSE - otherwise. + @param[in] thd Thread context + @param[in,out] query_tables_last_ptr Pointer to the next_global member of + last element of the list where tables + will be added (or to its root). + @param[in] belong_to_view Uppermost view which uses this routine, + 0 if none. + + @retval + TRUE if some elements were added + @retval + FALSE otherwise. */ bool @@ -3747,7 +3947,7 @@ sp_head::add_used_tables_to_table_list(THD *thd, } -/* +/** Simple function for adding an explicetly named (systems) table to the global table list, e.g. "mysql", "proc". */ @@ -3770,7 +3970,7 @@ sp_add_to_query_tables(THD *thd, LEX *lex, table->table_name= thd->strmake(name, table->table_name_length); table->alias= thd->strdup(name); table->lock_type= locktype; - table->select_lex= lex->current_select; // QQ? + table->select_lex= lex->current_select; table->cacheable_table= 1; lex->add_to_query_tables(table); |