diff options
author | unknown <dlenev@mysql.com> | 2005-03-04 17:46:45 +0300 |
---|---|---|
committer | unknown <dlenev@mysql.com> | 2005-03-04 17:46:45 +0300 |
commit | b1095fb0cd0b42158f6ca3d8f157df5d23bec423 (patch) | |
tree | bff7a84c42deaf288f2aee7fdb2011a47b92ba6c /sql | |
parent | d6949b3e9f8164e28bb24b7c1d948cdce12db005 (diff) | |
parent | ac9f68b9fac716ebc09b9f31b4348b0db135519b (diff) | |
download | mariadb-git-b1095fb0cd0b42158f6ca3d8f157df5d23bec423.tar.gz |
Manual merge SP-locking improvements patch with current tree.
mysql-test/r/mysqldump.result:
Auto merged
mysql-test/r/sp.result:
Auto merged
mysql-test/t/mysqldump.test:
Auto merged
mysql-test/t/sp.test:
Auto merged
sql/item_func.cc:
Auto merged
sql/mysql_priv.h:
Auto merged
sql/sp.cc:
Auto merged
sql/sp_rcontext.cc:
Auto merged
sql/sql_acl.cc:
Auto merged
sql/sql_base.cc:
Auto merged
sql/sql_class.h:
Auto merged
sql/sql_lex.cc:
Auto merged
sql/sql_lex.h:
Auto merged
sql/sql_prepare.cc:
Auto merged
sql/sql_update.cc:
Auto merged
sql/sql_view.cc:
Auto merged
sql/table.h:
Auto merged
sql/sp_head.cc:
Manual merge.
sql/sql_class.cc:
Manual merge.
sql/sql_parse.cc:
Manual merge.
sql/sql_yacc.yy:
Manual merge.
Diffstat (limited to 'sql')
-rw-r--r-- | sql/item_func.cc | 5 | ||||
-rw-r--r-- | sql/mysql_priv.h | 2 | ||||
-rw-r--r-- | sql/sp.cc | 189 | ||||
-rw-r--r-- | sql/sp.h | 8 | ||||
-rw-r--r-- | sql/sp_head.cc | 674 | ||||
-rw-r--r-- | sql/sp_head.h | 221 | ||||
-rw-r--r-- | sql/sp_rcontext.cc | 8 | ||||
-rw-r--r-- | sql/sp_rcontext.h | 11 | ||||
-rw-r--r-- | sql/sql_acl.cc | 29 | ||||
-rw-r--r-- | sql/sql_base.cc | 371 | ||||
-rw-r--r-- | sql/sql_class.cc | 5 | ||||
-rw-r--r-- | sql/sql_class.h | 44 | ||||
-rw-r--r-- | sql/sql_handler.cc | 2 | ||||
-rw-r--r-- | sql/sql_lex.cc | 7 | ||||
-rw-r--r-- | sql/sql_lex.h | 36 | ||||
-rw-r--r-- | sql/sql_parse.cc | 168 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 25 | ||||
-rw-r--r-- | sql/sql_trigger.h | 13 | ||||
-rw-r--r-- | sql/sql_update.cc | 4 | ||||
-rw-r--r-- | sql/sql_view.cc | 72 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 230 | ||||
-rw-r--r-- | sql/table.h | 5 | ||||
-rw-r--r-- | sql/tztime.cc | 4 |
23 files changed, 1340 insertions, 793 deletions
diff --git a/sql/item_func.cc b/sql/item_func.cc index 37e59fa89d3..0d88c689d9f 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -4414,11 +4414,6 @@ Item_func_sp::execute(Item **itp) } #endif - /* - We don't need to suppress sending of OK packet here (by setting - thd->net.no_send_ok to true), because we are not allowing statements - in functions now. - */ res= m_sp->execute_function(thd, args, arg_count, itp); #ifndef NO_EMBEDDED_ACCESS_CHECKS diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 428f3e51801..aef1cd1efec 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -870,7 +870,7 @@ int setup_conds(THD *thd, TABLE_LIST *tables, TABLE_LIST *leaves, int setup_ftfuncs(SELECT_LEX* select); int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order); void wait_for_refresh(THD *thd); -int open_tables(THD *thd, TABLE_LIST *tables, uint *counter); +int open_tables(THD *thd, TABLE_LIST **tables, uint *counter); int simple_open_n_lock_tables(THD *thd,TABLE_LIST *tables); bool open_and_lock_tables(THD *thd,TABLE_LIST *tables); bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables); diff --git a/sql/sp.cc b/sql/sp.cc index a57d90dbf7a..8b8e8897180 100644 --- a/sql/sp.cc +++ b/sql/sp.cc @@ -720,8 +720,29 @@ sp_drop_db_routines(THD *thd, char *db) PROCEDURE ******************************************************************************/ +/* + Obtain object representing stored procedure by its name from + stored procedures cache and looking into mysql.proc if needed. + + SYNOPSIS + sp_find_procedure() + thd - thread context + name - name of procedure + cache_only - if true perform cache-only lookup + (Don't look in mysql.proc). + + TODO + We should consider merging of sp_find_procedure() and + sp_find_function() into one sp_find_routine() function + (the same applies to other similarly paired functions). + + RETURN VALUE + Non-0 pointer to sp_head object for the procedure, or + 0 - in case of error. +*/ + sp_head * -sp_find_procedure(THD *thd, sp_name *name) +sp_find_procedure(THD *thd, sp_name *name, bool cache_only) { sp_head *sp; DBUG_ENTER("sp_find_procedure"); @@ -729,7 +750,7 @@ sp_find_procedure(THD *thd, sp_name *name) name->m_db.length, name->m_db.str, name->m_name.length, name->m_name.str)); - if (!(sp= sp_cache_lookup(&thd->sp_proc_cache, name))) + if (!(sp= sp_cache_lookup(&thd->sp_proc_cache, name)) && !cache_only) { if (db_find_routine(thd, TYPE_ENUM_PROCEDURE, name, &sp) == SP_OK) sp_cache_insert(&thd->sp_proc_cache, sp); @@ -855,6 +876,25 @@ sp_show_status_procedure(THD *thd, const char *wild) FUNCTION ******************************************************************************/ +/* + Obtain object representing stored function by its name from + stored functions cache and looking into mysql.proc if needed. + + SYNOPSIS + sp_find_function() + thd - thread context + name - name of function + cache_only - if true perform cache-only lookup + (Don't look in mysql.proc). + + NOTE + See TODO section for sp_find_procedure(). + + RETURN VALUE + Non-0 pointer to sp_head object for the function, or + 0 - in case of error. +*/ + sp_head * sp_find_function(THD *thd, sp_name *name, bool cache_only) { @@ -988,79 +1028,120 @@ sp_add_to_hash(HASH *h, sp_name *fun) } -void +/* + Merge contents of two hashes containing LEX_STRING's + + SYNOPSIS + sp_merge_hash() + dst - hash to which elements should be added + src - hash from which elements merged + + RETURN VALUE + TRUE - if we have added some new elements to destination hash. + FALSE - there were no new elements in src. +*/ + +bool sp_merge_hash(HASH *dst, HASH *src) { + bool res= FALSE; for (uint i=0 ; i < src->records ; i++) { LEX_STRING *ls= (LEX_STRING *)hash_element(src, i); if (! hash_search(dst, (byte *)ls->str, ls->length)) + { my_hash_insert(dst, (byte *)ls); + res= TRUE; + } } + return res; } -int -sp_cache_routines(THD *thd, LEX *lex, int type) +/* + Cache all routines implicitly or explicitly used by query + (or whatever object is represented by LEX). + + SYNOPSIS + sp_cache_routines() + thd - thread context + lex - LEX representing query + + NOTE + If some function is missing this won't be reported here. + Instead this fact will be discovered during query execution. + + TODO + Currently if after passing through routine hashes we discover + that we have added something to them, we do one more pass to + process all routines which were missed on previous pass because + of these additions. We can avoid this if along with hashes + we use lists holding routine names and iterate other these + lists instead of hashes (since addition to the end of list + does not reorder elements in it). +*/ + +void +sp_cache_routines(THD *thd, LEX *lex) { - HASH *h= (type == TYPE_ENUM_FUNCTION ? &lex->spfuns : &lex->spprocs); - int ret= 0; + bool routines_added= TRUE; - for (uint i=0 ; i < h->records ; i++) + DBUG_ENTER("sp_cache_routines"); + + while (routines_added) { - LEX_STRING *ls= (LEX_STRING *)hash_element(h, i); - sp_name name(*ls); + routines_added= FALSE; - name.m_qname= *ls; - if (! sp_cache_lookup((type == TYPE_ENUM_FUNCTION ? - &thd->sp_func_cache : &thd->sp_proc_cache), - &name)) + for (int type= TYPE_ENUM_FUNCTION; type < TYPE_ENUM_TRIGGER; type++) { - sp_head *sp; - LEX *oldlex= thd->lex; - LEX *newlex= new st_lex; - - thd->lex= newlex; - newlex->proc_table= oldlex->proc_table; // hint if mysql.oper is opened - newlex->current_select= NULL; - name.m_name.str= strchr(name.m_qname.str, '.'); - name.m_db.length= name.m_name.str - name.m_qname.str; - name.m_db.str= strmake_root(thd->mem_root, - name.m_qname.str, name.m_db.length); - name.m_name.str+= 1; - name.m_name.length= name.m_qname.length - name.m_db.length - 1; - - if (db_find_routine(thd, type, &name, &sp) == SP_OK) - { - if (type == TYPE_ENUM_FUNCTION) - sp_cache_insert(&thd->sp_func_cache, sp); - else - sp_cache_insert(&thd->sp_proc_cache, sp); - ret= sp_cache_routines(thd, newlex, TYPE_ENUM_FUNCTION); - if (!ret) - { - sp_merge_hash(&lex->spfuns, &newlex->spfuns); - ret= sp_cache_routines(thd, newlex, TYPE_ENUM_PROCEDURE); - } - if (!ret) - { - sp_merge_hash(&lex->spprocs, &newlex->spprocs); - sp_merge_table_hash(&lex->sptabs, &sp->m_sptabs); - } - delete newlex; - thd->lex= oldlex; - if (ret) - break; - } - else + HASH *h= (type == TYPE_ENUM_FUNCTION ? &lex->spfuns : &lex->spprocs); + + for (uint i=0 ; i < h->records ; i++) { - delete newlex; - thd->lex= oldlex; + LEX_STRING *ls= (LEX_STRING *)hash_element(h, i); + sp_name name(*ls); + sp_head *sp; + + name.m_qname= *ls; + if (!(sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ? + &thd->sp_func_cache : &thd->sp_proc_cache), + &name))) + { + LEX *oldlex= thd->lex; + LEX *newlex= new st_lex; + + thd->lex= newlex; + /* Pass hint pointer to mysql.proc table */ + newlex->proc_table= oldlex->proc_table; + newlex->current_select= NULL; + name.m_name.str= strchr(name.m_qname.str, '.'); + name.m_db.length= name.m_name.str - name.m_qname.str; + name.m_db.str= strmake_root(thd->mem_root, name.m_qname.str, + name.m_db.length); + name.m_name.str+= 1; + name.m_name.length= name.m_qname.length - name.m_db.length - 1; + + if (db_find_routine(thd, type, &name, &sp) == SP_OK) + { + if (type == TYPE_ENUM_FUNCTION) + sp_cache_insert(&thd->sp_func_cache, sp); + else + sp_cache_insert(&thd->sp_proc_cache, sp); + } + delete newlex; + thd->lex= oldlex; + } + + if (sp) + { + routines_added|= sp_merge_hash(&lex->spfuns, &sp->m_spfuns); + routines_added|= sp_merge_hash(&lex->spprocs, &sp->m_spprocs); + } } } } - return ret; + DBUG_VOID_RETURN; } /* @@ -34,7 +34,7 @@ int sp_drop_db_routines(THD *thd, char *db); sp_head * -sp_find_procedure(THD *thd, sp_name *name); +sp_find_procedure(THD *thd, sp_name *name, bool cache_only = 0); int sp_exists_routine(THD *thd, TABLE_LIST *procs, bool any, bool no_error); @@ -82,10 +82,10 @@ sp_function_exists(THD *thd, sp_name *name); */ void sp_add_to_hash(HASH *h, sp_name *fun); -void +bool sp_merge_hash(HASH *dst, HASH *src); -int -sp_cache_routines(THD *thd, LEX *lex, int type); +void +sp_cache_routines(THD *thd, LEX *lex); // diff --git a/sql/sp_head.cc b/sql/sp_head.cc index ad527d39d21..59cdac1b153 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -279,12 +279,16 @@ sp_head::sp_head() { extern byte * sp_table_key(const byte *ptr, uint *plen, my_bool first); + extern byte + *sp_lex_sp_key(const byte *ptr, uint *plen, my_bool first); DBUG_ENTER("sp_head::sp_head"); state= INITIALIZED; m_backpatch.empty(); m_lex.empty(); hash_init(&m_sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0); + hash_init(&m_spfuns, system_charset_info, 0, 0, 0, sp_lex_sp_key, 0, 0); + hash_init(&m_spprocs, system_charset_info, 0, 0, 0, sp_lex_sp_key, 0, 0); DBUG_VOID_RETURN; } @@ -460,8 +464,9 @@ sp_head::destroy() if (lex != &m_thd->main_lex) // We got interrupted and have lex'es left delete lex; } - if (m_sptabs.array.buffer) - hash_free(&m_sptabs); + hash_free(&m_sptabs); + hash_free(&m_spfuns); + hash_free(&m_spprocs); DBUG_VOID_RETURN; } @@ -475,6 +480,11 @@ sp_head::execute(THD *thd) int ret= 0; uint ip= 0; Item_arena *old_arena; + query_id_t old_query_id; + TABLE *old_derived_tables; + LEX *old_lex; + Item_change_list old_change_list; + String old_packet; #ifndef EMBEDDED_LIBRARY @@ -495,6 +505,34 @@ sp_head::execute(THD *thd) old_arena= thd->current_arena; thd->current_arena= this; + /* + We have to save/restore this info when we are changing call level to + be able properly do close_thread_tables() in instructions. + */ + old_query_id= thd->query_id; + old_derived_tables= thd->derived_tables; + thd->derived_tables= 0; + /* + It is also more efficient to save/restore current thd->lex once when + do it in each instruction + */ + old_lex= thd->lex; + /* + We should also save Item tree change list to avoid rollback something + too early in the calling query. + */ + old_change_list= thd->change_list; + thd->change_list.empty(); + /* + Cursors will use thd->packet, so they may corrupt data which was prepared + for sending by upper level. OTOH cursors in the same routine can share this + buffer safely so let use use routine-local packet instead of having own + packet buffer for each cursor. + + It is probably safe to use same thd->convert_buff everywhere. + */ + old_packet.swap(thd->packet); + do { sp_instr *i; @@ -506,7 +544,6 @@ sp_head::execute(THD *thd) DBUG_PRINT("execute", ("Instruction %u", ip)); thd->set_time(); // Make current_time() et al work ret= i->execute(thd, &ip); - thd->rollback_item_tree_changes(); if (i->free_list) cleanup_items(i->free_list); // Check if an exception has occurred and a handler has been found @@ -535,6 +572,17 @@ sp_head::execute(THD *thd) } } while (ret == 0 && !thd->killed); + /* Restore all saved */ + old_packet.swap(thd->packet); + DBUG_ASSERT(thd->change_list.is_empty()); + thd->change_list= old_change_list; + /* To avoid wiping out thd->change_list on old_change_list destruction */ + old_change_list.empty(); + thd->lex= old_lex; + thd->query_id= old_query_id; + DBUG_ASSERT(!thd->derived_tables); + thd->derived_tables= old_derived_tables; + cleanup_items(thd->current_arena->free_list); thd->current_arena= old_arena; @@ -592,14 +640,6 @@ sp_head::execute_function(THD *thd, Item **argp, uint argcount, Item **resp) DBUG_RETURN(-1); } } -#ifdef NOT_WORKING - /* - Close tables opened for subselect in argument list - This can't be done as this will close all other tables used - by the query. - */ - close_thread_tables(thd); -#endif // The rest of the frame are local variables which are all IN. // Default all variables to null (those with default clauses will // be set by an set instruction). @@ -705,10 +745,6 @@ sp_head::execute_procedure(THD *thd, List<Item> *args) nctx->set_oindex(i, static_cast<Item_splocal *>(it)->get_offset()); } } - // Clean up the joins before closing the tables. - thd->lex->unit.cleanup(); - // Close tables opened for subselect in argument list - close_thread_tables(thd); // The rest of the frame are local variables which are all IN. // Default all variables to null (those with default clauses will @@ -828,11 +864,17 @@ sp_head::restore_lex(THD *thd) oldlex->next_state= sublex->next_state; oldlex->trg_table_fields.push_back(&sublex->trg_table_fields); - // Collect some data from the sub statement lex. - sp_merge_hash(&oldlex->spfuns, &sublex->spfuns); - sp_merge_hash(&oldlex->spprocs, &sublex->spprocs); - // Merge used tables - sp_merge_table_list(thd, &m_sptabs, sublex->query_tables, sublex); + /* + Add routines which are used by statement to respective sets for + this routine + */ + sp_merge_hash(&m_spfuns, &sublex->spfuns); + sp_merge_hash(&m_spprocs, &sublex->spprocs); + /* + Merge tables used by this statement (but not by its functions or + procedures) to multiset of tables used by this routine. + */ + merge_table_list(thd, sublex->query_tables, sublex); if (! sublex->sp_lex_in_use) delete sublex; thd->lex= oldlex; @@ -1134,22 +1176,121 @@ sp_head::opt_mark(uint ip) // ------------------------------------------------------------------ + +/* + 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. + + 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 + 0/non-0 - Success/Failure +*/ + +int +sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, + bool open_tables, sp_instr* instr) +{ + int res= 0; + + DBUG_ASSERT(!thd->derived_tables); + DBUG_ASSERT(thd->change_list.is_empty()); + /* + Use our own lex. + We should not save old value since it is saved/restored in + sp_head::execute() when we are entering/leaving routine. + */ + thd->lex= m_lex; + + VOID(pthread_mutex_lock(&LOCK_thread_count)); + thd->query_id= next_query_id(); + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + + /* + FIXME. Resetting statement (and using it) is not reentrant, thus recursive + functions which try to use the same LEX twice will crash server. + We should prevent such situations by tracking if LEX is already + in use and throwing error about unallowed recursion if needed. + OTOH it is nice to allow recursion in cases when LEX is not really + used (e.g. in mathematical functions), so such tracking should be + implemented at the same time as ability not to store LEX for + instruction if it is not really used. + */ + reset_stmt_for_execute(thd, m_lex); + + /* + If requested check whenever we have access to tables in LEX's table list + and open and lock them before executing instructtions core function. + */ + if (open_tables && + (check_table_access(thd, SELECT_ACL, m_lex->query_tables, 0) || + open_and_lock_tables(thd, m_lex->query_tables))) + res= -1; + + if (!res) + res= instr->exec_core(thd, nextp); + + m_lex->unit.cleanup(); + + thd->proc_info="closing tables"; + close_thread_tables(thd); + + thd->rollback_item_tree_changes(); + + /* + Unlike for PS we should not call Item's destructors for newly created + items after execution of each instruction in stored routine. This is + because SP often create Item (like Item_int, Item_string etc...) when + they want to store some value in local variable, pass return value and + etc... So their life time should be longer than one instruction. + + Probably we can call destructors for most of them then we are leaving + routine. But this won't help much as they are allocated in main query + MEM_ROOT anyway. So they all go to global thd->free_list. + + May be we can use some other MEM_ROOT for this purprose ??? + + What else should we do for cleanup ? + cleanup_items() is called in sp_head::execute() + */ + return res; +} + + // -// sp_instr_stmt +// sp_instr // -sp_instr_stmt::~sp_instr_stmt() +int sp_instr::exec_core(THD *thd, uint *nextp) { - if (m_lex) - delete m_lex; + DBUG_ASSERT(0); + return 0; } + +// +// sp_instr_stmt +// int sp_instr_stmt::execute(THD *thd, uint *nextp) { char *query; uint32 query_length; DBUG_ENTER("sp_instr_stmt::execute"); - DBUG_PRINT("info", ("command: %d", m_lex->sql_command)); + DBUG_PRINT("info", ("command: %d", m_lex_keeper.sql_command())); int res; query= thd->query; @@ -1159,13 +1300,14 @@ sp_instr_stmt::execute(THD *thd, uint *nextp) if (query_cache_send_result_to_client(thd, thd->query, thd->query_length) <= 0) { - res= exec_stmt(thd, m_lex); + res= m_lex_keeper.reset_lex_and_exec_core(thd, nextp, FALSE, this); query_cache_end_of_result(thd); } + else + *nextp= m_ip+1; thd->query= query; thd->query_length= query_length; } - *nextp = m_ip+1; DBUG_RETURN(res); } @@ -1174,39 +1316,15 @@ sp_instr_stmt::print(String *str) { str->reserve(12); str->append("stmt "); - str->qs_append((uint)m_lex->sql_command); + str->qs_append((uint)m_lex_keeper.sql_command()); } int -sp_instr_stmt::exec_stmt(THD *thd, LEX *lex) +sp_instr_stmt::exec_core(THD *thd, uint *nextp) { - LEX *olex; // The other lex - int res; - - olex= thd->lex; // Save the other lex - thd->lex= lex; // Use my own lex - thd->lex->thd = thd; // QQ Not reentrant! - thd->lex->unit.thd= thd; // QQ Not reentrant - thd->free_list= NULL; - - VOID(pthread_mutex_lock(&LOCK_thread_count)); - thd->query_id= next_query_id(); - VOID(pthread_mutex_unlock(&LOCK_thread_count)); - - reset_stmt_for_execute(thd, lex); - - res= mysql_execute_command(thd); - - lex->unit.cleanup(); - if (thd->lock || thd->open_tables || thd->derived_tables) - { - thd->proc_info="closing tables"; - close_thread_tables(thd); /* Free tables */ - } - - thd->lex= olex; // Restore the other lex - + int res= mysql_execute_command(thd); + *nextp= m_ip+1; return res; } @@ -1218,14 +1336,16 @@ sp_instr_set::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_set::execute"); DBUG_PRINT("info", ("offset: %u", m_offset)); + + DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this)); +} + +int +sp_instr_set::exec_core(THD *thd, uint *nextp) +{ Item *it; int res; - if (tables && - ((res= check_table_access(thd, SELECT_ACL, tables, 0)) || - (res= open_and_lock_tables(thd, tables)))) - DBUG_RETURN(res); - it= sp_eval_func_item(thd, m_value, m_type); if (! it) res= -1; @@ -1235,9 +1355,8 @@ sp_instr_set::execute(THD *thd, uint *nextp) thd->spcont->set_item(m_offset, it); } *nextp = m_ip+1; - if (tables && (thd->lock || thd->open_tables || thd->derived_tables)) - close_thread_tables(thd); - DBUG_RETURN(res); + + return res; } void @@ -1250,32 +1369,6 @@ sp_instr_set::print(String *str) m_value->print(str); } -// -// sp_instr_set_user_var -// -int -sp_instr_set_user_var::execute(THD *thd, uint *nextp) -{ - int res= 0; - - DBUG_ENTER("sp_instr_set_user_var::execute"); - /* - It is ok to pass 0 as 3rd argument to fix_fields() since - Item_func_set_user_var::fix_fields() won't use it. - QQ: Still unsure what should we return in case of error 1 or -1 ? - */ - if (!m_set_var_item.fixed && m_set_var_item.fix_fields(thd, 0, 0) || - m_set_var_item.check() || m_set_var_item.update()) - res= -1; - *nextp= m_ip + 1; - DBUG_RETURN(res); -} - -void -sp_instr_set_user_var::print(String *str) -{ - m_set_var_item.print_as_stmt(str); -} // // sp_instr_set_trigger_field @@ -1368,19 +1461,21 @@ sp_instr_jump::opt_move(uint dst, List<sp_instr> *bp) // // sp_instr_jump_if // + int sp_instr_jump_if::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_jump_if::execute"); DBUG_PRINT("info", ("destination: %u", m_dest)); + DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this)); +} + +int +sp_instr_jump_if::exec_core(THD *thd, uint *nextp) +{ Item *it; int res; - if (tables && - ((res= check_table_access(thd, SELECT_ACL, tables, 0)) || - (res= open_and_lock_tables(thd, tables)))) - DBUG_RETURN(res); - it= sp_eval_func_item(thd, m_expr, MYSQL_TYPE_TINY); if (!it) res= -1; @@ -1392,9 +1487,8 @@ sp_instr_jump_if::execute(THD *thd, uint *nextp) else *nextp = m_ip+1; } - if (tables && (thd->lock || thd->open_tables || thd->derived_tables)) - close_thread_tables(thd); - DBUG_RETURN(res); + + return res; } void @@ -1430,14 +1524,16 @@ sp_instr_jump_if_not::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_jump_if_not::execute"); DBUG_PRINT("info", ("destination: %u", m_dest)); + DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this)); +} + + +int +sp_instr_jump_if_not::exec_core(THD *thd, uint *nextp) +{ Item *it; int res; - if (tables && - ((res= check_table_access(thd, SELECT_ACL, tables, 0)) || - (res= open_and_lock_tables(thd, tables)))) - DBUG_RETURN(res); - it= sp_eval_func_item(thd, m_expr, MYSQL_TYPE_TINY); if (! it) res= -1; @@ -1449,9 +1545,8 @@ sp_instr_jump_if_not::execute(THD *thd, uint *nextp) else *nextp = m_ip+1; } - if (tables && (thd->lock || thd->open_tables || thd->derived_tables)) - close_thread_tables(thd); - DBUG_RETURN(res); + + return res; } void @@ -1482,18 +1577,21 @@ sp_instr_jump_if_not::opt_mark(sp_head *sp) // // sp_instr_freturn // + int sp_instr_freturn::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_freturn::execute"); + DBUG_RETURN(m_lex_keeper.reset_lex_and_exec_core(thd, nextp, TRUE, this)); +} + + +int +sp_instr_freturn::exec_core(THD *thd, uint *nextp) +{ Item *it; int res; - if (tables && - ((res= check_table_access(thd, SELECT_ACL, tables, 0)) || - (res= open_and_lock_tables(thd, tables)))) - DBUG_RETURN(res); - it= sp_eval_func_item(thd, m_value, m_type); if (! it) res= -1; @@ -1503,7 +1601,8 @@ sp_instr_freturn::execute(THD *thd, uint *nextp) thd->spcont->set_result(it); } *nextp= UINT_MAX; - DBUG_RETURN(res); + + return res; } void @@ -1637,17 +1736,11 @@ int sp_instr_cpush::execute(THD *thd, uint *nextp) { DBUG_ENTER("sp_instr_cpush::execute"); - thd->spcont->push_cursor(m_lex); + thd->spcont->push_cursor(&m_lex_keeper); *nextp= m_ip+1; DBUG_RETURN(0); } -sp_instr_cpush::~sp_instr_cpush() -{ - if (m_lex) - delete m_lex; -} - void sp_instr_cpush::print(String *str) { @@ -1694,19 +1787,30 @@ sp_instr_copen::execute(THD *thd, uint *nextp) res= -1; else { - LEX *lex= c->pre_open(thd); + sp_lex_keeper *lex_keeper= c->pre_open(thd); - if (! lex) + if (!lex_keeper) + { res= -1; + *nextp= m_ip+1; + } else - res= exec_stmt(thd, lex); - c->post_open(thd, (lex ? TRUE : FALSE)); + res= lex_keeper->reset_lex_and_exec_core(thd, nextp, FALSE, this); + + c->post_open(thd, (lex_keeper ? TRUE : FALSE)); } - *nextp= m_ip+1; DBUG_RETURN(res); } +int +sp_instr_copen::exec_core(THD *thd, uint *nextp) +{ + int res= mysql_execute_command(thd); + *nextp= m_ip+1; + return res; +} + void sp_instr_copen::print(String *str) { @@ -1858,14 +1962,17 @@ sp_restore_security_context(THD *thd, sp_head *sp, st_sp_security_context *ctxp) #endif /* NO_EMBEDDED_ACCESS_CHECKS */ /* - * Table merge hash table - * - */ + Structure that represent all instances of one table + in optimized multi-set of tables used by routine. +*/ + typedef struct st_sp_table { LEX_STRING qname; bool temp; TABLE_LIST *table; + uint lock_count; + uint query_lock_count; } SP_TABLE; byte * @@ -1876,23 +1983,47 @@ sp_table_key(const byte *ptr, uint *plen, my_bool first) return (byte *)tab->qname.str; } + /* - * Merge the table list into the hash table. - * If the optional lex is provided, it's used to check and set - * the flag for creation of a temporary table. - */ + 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. + + 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 +*/ + bool -sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table, - LEX *lex_for_tmp_check) +sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check) { + SP_TABLE *tab; + + if (lex_for_tmp_check->sql_command == SQLCOM_DROP_TABLE && + lex_for_tmp_check->drop_temporary) + return TRUE; + + for (uint i= 0 ; i < m_sptabs.records ; i++) + { + tab= (SP_TABLE *)hash_element(&m_sptabs, i); + tab->query_lock_count= 0; + } + for (; table ; table= table->next_global) - if (!table->derived && - (!table->select_lex || - !(table->select_lex->options & OPTION_SCHEMA_TABLE))) + if (!table->derived && !table->schema_table) { char tname[64+1+64+1+64+1]; // db.table.alias\0 uint tlen, alen; - SP_TABLE *tab; tlen= table->db_length; memcpy(tname, table->db, tlen); @@ -1905,10 +2036,17 @@ sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table, tlen+= alen; tname[tlen]= '\0'; - if ((tab= (SP_TABLE *)hash_search(h, (byte *)tname, tlen))) + /* + It is safe to store pointer to table list elements in hash, + since they are supposed to have the same lifetime. + */ + if ((tab= (SP_TABLE *)hash_search(&m_sptabs, (byte *)tname, tlen))) { if (tab->table->lock_type < table->lock_type) tab->table= table; // Use the table with the highest lock type + tab->query_lock_count++; + if (tab->query_lock_count > tab->lock_count) + tab->lock_count++; } else { @@ -1918,152 +2056,102 @@ sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table, tab->qname.str= (char *)thd->strmake(tname, tab->qname.length); if (!tab->qname.str) return FALSE; - if (lex_for_tmp_check && - lex_for_tmp_check->sql_command == SQLCOM_CREATE_TABLE && + if (lex_for_tmp_check->sql_command == SQLCOM_CREATE_TABLE && lex_for_tmp_check->query_tables == table && lex_for_tmp_check->create_info.options & HA_LEX_CREATE_TMP_TABLE) tab->temp= TRUE; tab->table= table; - my_hash_insert(h, (byte *)tab); + tab->lock_count= tab->query_lock_count= 1; + my_hash_insert(&m_sptabs, (byte *)tab); } } return TRUE; } -void -sp_merge_routine_tables(THD *thd, LEX *lex) -{ - uint i; - for (i= 0 ; i < lex->spfuns.records ; i++) - { - sp_head *sp; - LEX_STRING *ls= (LEX_STRING *)hash_element(&lex->spfuns, i); - sp_name name(*ls); +/* + Add tables used by routine to the table list. - name.m_qname= *ls; - if ((sp= sp_cache_lookup(&thd->sp_func_cache, &name))) - sp_merge_table_hash(&lex->sptabs, &sp->m_sptabs); - } - for (i= 0 ; i < lex->spprocs.records ; i++) - { - sp_head *sp; - LEX_STRING *ls= (LEX_STRING *)hash_element(&lex->spprocs, i); - sp_name name(*ls); + SYNOPSIS + add_used_tables_to_table_list() + thd - thread context + query_tables_last_ptr - (in/out) pointer the next_global member of last + element of the list where tables will be added + (or to its root). - name.m_qname= *ls; - if ((sp= sp_cache_lookup(&thd->sp_proc_cache, &name))) - sp_merge_table_hash(&lex->sptabs, &sp->m_sptabs); - } -} + 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'. -void -sp_merge_table_hash(HASH *hdst, HASH *hsrc) -{ - for (uint i=0 ; i < hsrc->records ; i++) - { - SP_TABLE *tabdst; - SP_TABLE *tabsrc= (SP_TABLE *)hash_element(hsrc, i); + Elements of list will be allocated in PS memroot, so this list will be + persistent between PS executions. - if (! (tabdst= (SP_TABLE *)hash_search(hdst, - (byte *) tabsrc->qname.str, - tabsrc->qname.length))) - { - my_hash_insert(hdst, (byte *)tabsrc); - } - else - { - if (tabdst->table->lock_type < tabsrc->table->lock_type) - tabdst->table= tabsrc->table; // Use the highest lock type - } - } -} + RETURN VALUE + TRUE - if some elements were added, FALSE - otherwise. +*/ -TABLE_LIST * -sp_hash_to_table_list(THD *thd, HASH *h) +bool +sp_head::add_used_tables_to_table_list(THD *thd, + TABLE_LIST ***query_tables_last_ptr) { uint i; - TABLE_LIST *tables= NULL; - DBUG_ENTER("sp_hash_to_table_list"); + Item_arena *arena, backup; + bool result= FALSE; + DBUG_ENTER("sp_head::add_used_tables_to_table_list"); + + /* + Use persistent arena for table list allocation to be PS friendly. + */ + arena= thd->change_arena_if_needed(&backup); - for (i=0 ; i < h->records ; i++) + for (i=0 ; i < m_sptabs.records ; i++) { - SP_TABLE *stab= (SP_TABLE *)hash_element(h, i); + char *tab_buff; + TABLE_LIST *table, *otable; + SP_TABLE *stab= (SP_TABLE *)hash_element(&m_sptabs, i); if (stab->temp) continue; - TABLE_LIST *table, *otable= stab->table; - - if (! (table= (TABLE_LIST *)thd->calloc(sizeof(TABLE_LIST)))) - return NULL; - table->db= otable->db; - table->db_length= otable->db_length; - table->alias= otable->alias; - table->table_name= otable->table_name; - table->table_name_length= otable->table_name_length; - table->lock_type= otable->lock_type; - table->updating= otable->updating; - table->force_index= otable->force_index; - table->ignore_leaves= otable->ignore_leaves; - table->derived= otable->derived; - table->schema_table= otable->schema_table; - table->select_lex= otable->select_lex; - table->cacheable_table= otable->cacheable_table; - table->use_index= otable->use_index; - table->ignore_index= otable->ignore_index; - table->option= otable->option; - - table->next_global= tables; - tables= table; - } - DBUG_RETURN(tables); -} -bool -sp_open_and_lock_tables(THD *thd, TABLE_LIST *tables) -{ - DBUG_ENTER("sp_open_and_lock_tables"); - bool ret; + otable= stab->table; - thd->in_lock_tables= 1; - thd->options|= OPTION_TABLE_LOCK; - if (simple_open_n_lock_tables(thd, tables)) - { - thd->options&= ~(ulong)(OPTION_TABLE_LOCK); - ret= FALSE; - } - else - { -#if 0 - // QQ What about this? -#ifdef HAVE_QUERY_CACHE - if (thd->variables.query_cache_wlock_invalidate) - query_cache.invalidate_locked_for_write(first_table); // QQ first_table? -#endif /* HAVE_QUERY_CACHE */ -#endif - thd->locked_tables= thd->lock; - thd->lock= 0; - ret= TRUE; - } - thd->in_lock_tables= 0; - DBUG_RETURN(ret); -} + if (!(tab_buff= (char *)thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST)) * + stab->lock_count))) + DBUG_RETURN(FALSE); -void -sp_unlock_tables(THD *thd) -{ - thd->lock= thd->locked_tables; - thd->locked_tables= 0; - close_thread_tables(thd); // Free tables - if (thd->options & OPTION_TABLE_LOCK) - { -#if 0 - // QQ What about this? - end_active_trans(thd); -#endif - thd->options&= ~(ulong)(OPTION_TABLE_LOCK); + for (uint j= 0; j < stab->lock_count; j++) + { + table= (TABLE_LIST *)tab_buff; + + /* + It's enough to just copy the pointers as the data will not change + during the lifetime of the SP. If the SP is used by PS, we assume + that the PS will be invalidated if the functions is deleted or + changed. + */ + table->db= otable->db; + table->db_length= otable->db_length; + table->alias= otable->alias; + table->table_name= otable->table_name; + table->table_name_length= otable->table_name_length; + table->lock_type= otable->lock_type; + table->cacheable_table= 1; + table->prelocking_placeholder= 1; + + /* Everyting else should be zeroed */ + + **query_tables_last_ptr= table; + table->prev_global= *query_tables_last_ptr; + *query_tables_last_ptr= &table->next_global; + + tab_buff+= ALIGN_SIZE(sizeof(TABLE_LIST)); + result= TRUE; + } } - if (thd->global_read_lock) - unlock_global_read_lock(thd); + + if (arena) + thd->restore_backup_item_arena(arena, &backup); + + DBUG_RETURN(result); } /* @@ -2095,3 +2183,73 @@ sp_add_to_query_tables(THD *thd, LEX *lex, lex->add_to_query_tables(table); return table; } + + +/* + Auxilary function for adding tables used by routines used in query + to table lists. + + SYNOPSIS + sp_add_sp_tables_to_table_list_aux() + thd - thread context + lex - LEX to which table list tables will be added + func_hash - routines for which tables should be added + func_cache- SP cache in which this routines should be looked up + + NOTE + See sp_add_sp_tables_to_table_list() for more info. + + RETURN VALUE + TRUE - some tables were added + FALSE - no tables were added. +*/ + +static bool +sp_add_sp_tables_to_table_list_aux(THD *thd, LEX *lex, HASH *func_hash, + sp_cache **func_cache) +{ + uint i; + bool result= FALSE; + + for (i= 0 ; i < func_hash->records ; i++) + { + sp_head *sp; + LEX_STRING *ls= (LEX_STRING *)hash_element(func_hash, i); + sp_name name(*ls); + + name.m_qname= *ls; + if ((sp= sp_cache_lookup(func_cache, &name))) + result|= sp->add_used_tables_to_table_list(thd, &lex->query_tables_last); + } + + return result; +} + + +/* + Add tables used by routines used in query to table list. + + SYNOPSIS + sp_add_sp_tables_to_table_list() + thd - thread context + lex - LEX to which table list tables will be added + func_lex - LEX for which functions we get tables + (useful for adding tables used by view routines) + + NOTE + Elements of list will be allocated in PS memroot, so this + list will be persistent between PS execetutions. + + RETURN VALUE + TRUE - some tables were added + FALSE - no tables were added. +*/ + +bool +sp_add_sp_tables_to_table_list(THD *thd, LEX *lex, LEX *func_lex) +{ + return (sp_add_sp_tables_to_table_list_aux(thd, lex, &func_lex->spfuns, + &thd->sp_func_cache) | + sp_add_sp_tables_to_table_list_aux(thd, lex, &func_lex->spprocs, + &thd->sp_proc_cache)); +} diff --git a/sql/sp_head.h b/sql/sp_head.h index 5df9c753048..60979a438cb 100644 --- a/sql/sp_head.h +++ b/sql/sp_head.h @@ -103,7 +103,14 @@ public: LEX_STRING m_definer_host; longlong m_created; longlong m_modified; - HASH m_sptabs; /* Merged table lists */ + /* + Sets containing names of SP and SF used by this routine. + + TODO Probably we should combine these two hashes in one. It will + decrease memory overhead ans simplify algorithms using them. The + same applies to similar hashes in LEX. + */ + HASH m_spfuns, m_spprocs; // Pointers set during parsing uchar *m_param_begin, *m_param_end, *m_returns_begin, *m_returns_end, *m_body_begin; @@ -225,6 +232,10 @@ public: return ip; } + /* Add tables used by routine to the table list. */ + bool add_used_tables_to_table_list(THD *thd, + TABLE_LIST ***query_tables_last_ptr); + private: MEM_ROOT *m_thd_root; // Temp. store for thd's mem_root @@ -240,10 +251,20 @@ private: sp_instr *instr; } bp_t; List<bp_t> m_backpatch; // Instructions needing backpatching + /* + Multi-set representing optimized list of tables to be locked by this + routine. Does not include tables which are used by invoked routines. + */ + HASH m_sptabs; int execute(THD *thd); + /* + Merge the list of tables used by query into the multi-set of tables used + by routine. + */ + bool merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check); }; // class sp_head : public Sql_alloc @@ -277,6 +298,17 @@ public: // Returns 0 on success, non-zero if some error occured. virtual int execute(THD *thd, uint *nextp) = 0; + /* + Execute core function of instruction after all preparations (e.g. + setting of proper LEX, saving part of the thread context have been + done). + + Should be implemented for instructions using expressions or whole + statements (thus having to have own LEX). Used in concert with + sp_lex_keeper class and its descendants. + */ + virtual int exec_core(THD *thd, uint *nextp); + virtual void print(String *str) = 0; virtual void backpatch(uint dest, sp_pcontext *dst_ctx) @@ -301,6 +333,60 @@ public: }; // class sp_instr : public Sql_alloc +/* + Auxilary class to which instructions delegate responsibility + for handling LEX and preparations before executing statement + or calculating complex expression. + + Exist mainly to avoid having double hierarchy between instruction + classes. + + TODO: Add ability to not store LEX and do any preparations if + expression used is simple. +*/ + +class sp_lex_keeper +{ + /* Prevent use of these */ + sp_lex_keeper(const sp_lex_keeper &); + void operator=(sp_lex_keeper &); +public: + + sp_lex_keeper(LEX *lex, bool lex_resp) + : m_lex(lex), m_lex_resp(lex_resp) + { + lex->sp_lex_in_use= TRUE; + } + virtual ~sp_lex_keeper() + { + if (m_lex_resp) + delete m_lex; + } + + /* + Prepare execution of instruction using LEX, if requested check whenever + we have read access to tables used and open/lock them, call instruction's + exec_core() method, perform cleanup afterwards. + */ + int reset_lex_and_exec_core(THD *thd, uint *nextp, bool open_tables, + sp_instr* instr); + + inline uint sql_command() const + { + return (uint)m_lex->sql_command; + } + +private: + + LEX *m_lex; + /* + Indicates whenever this sp_lex_keeper instance responsible + for LEX deletion. + */ + bool m_lex_resp; +}; + + // // Call out to some prepared SQL statement. // @@ -313,38 +399,25 @@ public: LEX_STRING m_query; // For thd->query - sp_instr_stmt(uint ip, sp_pcontext *ctx) - : sp_instr(ip, ctx), m_lex(NULL) + sp_instr_stmt(uint ip, sp_pcontext *ctx, LEX *lex) + : sp_instr(ip, ctx), m_lex_keeper(lex, TRUE) { m_query.str= 0; m_query.length= 0; } - virtual ~sp_instr_stmt(); + virtual ~sp_instr_stmt() + {}; virtual int execute(THD *thd, uint *nextp); - virtual void print(String *str); - - inline void - set_lex(LEX *lex) - { - m_lex= lex; - } - - inline LEX * - get_lex() - { - return m_lex; - } + virtual int exec_core(THD *thd, uint *nextp); -protected: - - int exec_stmt(THD *thd, LEX *lex); // Execute a statement + virtual void print(String *str); private: - LEX *m_lex; // My own lex + sp_lex_keeper m_lex_keeper; }; // class sp_instr_stmt : public sp_instr @@ -356,12 +429,11 @@ class sp_instr_set : public sp_instr public: - TABLE_LIST *tables; - sp_instr_set(uint ip, sp_pcontext *ctx, - uint offset, Item *val, enum enum_field_types type) - : sp_instr(ip, ctx), - tables(NULL), m_offset(offset), m_value(val), m_type(type) + uint offset, Item *val, enum enum_field_types type, + LEX *lex, bool lex_resp) + : sp_instr(ip, ctx), m_offset(offset), m_value(val), m_type(type), + m_lex_keeper(lex, lex_resp) {} virtual ~sp_instr_set() @@ -369,6 +441,8 @@ public: virtual int execute(THD *thd, uint *nextp); + virtual int exec_core(THD *thd, uint *nextp); + virtual void print(String *str); private: @@ -376,42 +450,12 @@ private: uint m_offset; // Frame offset Item *m_value; enum enum_field_types m_type; // The declared type + sp_lex_keeper m_lex_keeper; }; // class sp_instr_set : public sp_instr /* - Set user variable instruction. - Used in functions and triggers to set user variables because we don't - want use sp_instr_stmt + "SET @a:=..." statement in this case since - latter will close all tables and thus will ruin execution of statement - calling/invoking this function/trigger. -*/ -class sp_instr_set_user_var : public sp_instr -{ - sp_instr_set_user_var(const sp_instr_set_user_var &); - void operator=(sp_instr_set_user_var &); - -public: - - sp_instr_set_user_var(uint ip, sp_pcontext *ctx, LEX_STRING var, Item *val) - : sp_instr(ip, ctx), m_set_var_item(var, val) - {} - - virtual ~sp_instr_set_user_var() - {} - - virtual int execute(THD *thd, uint *nextp); - - virtual void print(String *str); - -private: - - Item_func_set_user_var m_set_var_item; -}; // class sp_instr_set_user_var : public sp_instr - - -/* Set NEW/OLD row field value instruction. Used in triggers. */ class sp_instr_set_trigger_field : public sp_instr @@ -492,14 +536,12 @@ class sp_instr_jump_if : public sp_instr_jump public: - TABLE_LIST *tables; - - sp_instr_jump_if(uint ip, sp_pcontext *ctx, Item *i) - : sp_instr_jump(ip, ctx), tables(NULL), m_expr(i) + sp_instr_jump_if(uint ip, sp_pcontext *ctx, Item *i, LEX *lex) + : sp_instr_jump(ip, ctx), m_expr(i), m_lex_keeper(lex, TRUE) {} - sp_instr_jump_if(uint ip, sp_pcontext *ctx, Item *i, uint dest) - : sp_instr_jump(ip, ctx, dest), tables(NULL), m_expr(i) + sp_instr_jump_if(uint ip, sp_pcontext *ctx, Item *i, uint dest, LEX *lex) + : sp_instr_jump(ip, ctx, dest), m_expr(i), m_lex_keeper(lex, TRUE) {} virtual ~sp_instr_jump_if() @@ -507,6 +549,8 @@ public: virtual int execute(THD *thd, uint *nextp); + virtual int exec_core(THD *thd, uint *nextp); + virtual void print(String *str); virtual uint opt_mark(sp_head *sp); @@ -519,6 +563,7 @@ public: private: Item *m_expr; // The condition + sp_lex_keeper m_lex_keeper; }; // class sp_instr_jump_if : public sp_instr_jump @@ -530,14 +575,12 @@ class sp_instr_jump_if_not : public sp_instr_jump public: - TABLE_LIST *tables; - - sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i) - : sp_instr_jump(ip, ctx), tables(NULL), m_expr(i) + sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i, LEX *lex) + : sp_instr_jump(ip, ctx), m_expr(i), m_lex_keeper(lex, TRUE) {} - sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i, uint dest) - : sp_instr_jump(ip, ctx, dest), tables(NULL), m_expr(i) + sp_instr_jump_if_not(uint ip, sp_pcontext *ctx, Item *i, uint dest, LEX *lex) + : sp_instr_jump(ip, ctx, dest), m_expr(i), m_lex_keeper(lex, TRUE) {} virtual ~sp_instr_jump_if_not() @@ -545,6 +588,8 @@ public: virtual int execute(THD *thd, uint *nextp); + virtual int exec_core(THD *thd, uint *nextp); + virtual void print(String *str); virtual uint opt_mark(sp_head *sp); @@ -557,6 +602,7 @@ public: private: Item *m_expr; // The condition + sp_lex_keeper m_lex_keeper; }; // class sp_instr_jump_if_not : public sp_instr_jump @@ -568,11 +614,9 @@ class sp_instr_freturn : public sp_instr public: - TABLE_LIST *tables; - sp_instr_freturn(uint ip, sp_pcontext *ctx, - Item *val, enum enum_field_types type) - : sp_instr(ip, ctx), tables(NULL), m_value(val), m_type(type) + Item *val, enum enum_field_types type, LEX *lex) + : sp_instr(ip, ctx), m_value(val), m_type(type), m_lex_keeper(lex, TRUE) {} virtual ~sp_instr_freturn() @@ -580,6 +624,8 @@ public: virtual int execute(THD *thd, uint *nextp); + virtual int exec_core(THD *thd, uint *nextp); + virtual void print(String *str); virtual uint opt_mark(sp_head *sp) @@ -592,6 +638,7 @@ protected: Item *m_value; enum enum_field_types m_type; + sp_lex_keeper m_lex_keeper; }; // class sp_instr_freturn : public sp_instr @@ -710,10 +757,11 @@ class sp_instr_cpush : public sp_instr public: sp_instr_cpush(uint ip, sp_pcontext *ctx, LEX *lex) - : sp_instr(ip, ctx), m_lex(lex) + : sp_instr(ip, ctx), m_lex_keeper(lex, TRUE) {} - virtual ~sp_instr_cpush(); + virtual ~sp_instr_cpush() + {} virtual int execute(THD *thd, uint *nextp); @@ -721,7 +769,7 @@ public: private: - LEX *m_lex; + sp_lex_keeper m_lex_keeper; }; // class sp_instr_cpush : public sp_instr @@ -760,7 +808,7 @@ private: }; // class sp_instr_cpop : public sp_instr -class sp_instr_copen : public sp_instr_stmt +class sp_instr_copen : public sp_instr { sp_instr_copen(const sp_instr_copen &); /* Prevent use of these */ void operator=(sp_instr_copen &); @@ -768,7 +816,7 @@ class sp_instr_copen : public sp_instr_stmt public: sp_instr_copen(uint ip, sp_pcontext *ctx, uint c) - : sp_instr_stmt(ip, ctx), m_cursor(c) + : sp_instr(ip, ctx), m_cursor(c) {} virtual ~sp_instr_copen() @@ -776,6 +824,8 @@ public: virtual int execute(THD *thd, uint *nextp); + virtual int exec_core(THD *thd, uint *nextp); + virtual void print(String *str); private: @@ -893,22 +943,11 @@ void sp_restore_security_context(THD *thd, sp_head *sp,st_sp_security_context *ctxp); #endif /* NO_EMBEDDED_ACCESS_CHECKS */ -bool -sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table, - LEX *lex_for_tmp_check = 0); -void -sp_merge_routine_tables(THD *thd, LEX *lex); -void -sp_merge_table_hash(HASH *hdst, HASH *hsrc); -TABLE_LIST * -sp_hash_to_table_list(THD *thd, HASH *h); -bool -sp_open_and_lock_tables(THD *thd, TABLE_LIST *tables); -void -sp_unlock_tables(THD *thd); TABLE_LIST * sp_add_to_query_tables(THD *thd, LEX *lex, const char *db, const char *name, thr_lock_type locktype); +bool +sp_add_sp_tables_to_table_list(THD *thd, LEX *lex, LEX *func_lex); #endif /* _SP_HEAD_H_ */ diff --git a/sql/sp_rcontext.cc b/sql/sp_rcontext.cc index 5b177650726..9b29c173856 100644 --- a/sql/sp_rcontext.cc +++ b/sql/sp_rcontext.cc @@ -125,9 +125,9 @@ sp_rcontext::restore_variables(uint fp) } void -sp_rcontext::push_cursor(LEX *lex) +sp_rcontext::push_cursor(sp_lex_keeper *lex_keeper) { - m_cstack[m_ccount++]= new sp_cursor(lex); + m_cstack[m_ccount++]= new sp_cursor(lex_keeper); } void @@ -148,7 +148,7 @@ sp_rcontext::pop_cursors(uint count) // We have split this in two to make it easy for sp_instr_copen // to reuse the sp_instr::exec_stmt() code. -LEX * +sp_lex_keeper* sp_cursor::pre_open(THD *thd) { if (m_isopen) @@ -168,7 +168,7 @@ sp_cursor::pre_open(THD *thd) m_nseof= thd->net.no_send_eof; thd->net.no_send_eof= TRUE; - return m_lex; + return m_lex_keeper; } void diff --git a/sql/sp_rcontext.h b/sql/sp_rcontext.h index 8e818ab76d1..37d718048a0 100644 --- a/sql/sp_rcontext.h +++ b/sql/sp_rcontext.h @@ -25,6 +25,7 @@ struct sp_cond_type; class sp_cursor; struct sp_pvar; +class sp_lex_keeper; #define SP_HANDLER_NONE 0 #define SP_HANDLER_EXIT 1 @@ -164,7 +165,7 @@ class sp_rcontext : public Sql_alloc restore_variables(uint fp); void - push_cursor(LEX *lex); + push_cursor(sp_lex_keeper *lex_keeper); void pop_cursors(uint count); @@ -207,8 +208,8 @@ class sp_cursor : public Sql_alloc { public: - sp_cursor(LEX *lex) - : m_lex(lex), m_prot(NULL), m_isopen(0), m_current_row(NULL) + sp_cursor(sp_lex_keeper *lex_keeper) + : m_lex_keeper(lex_keeper), m_prot(NULL), m_isopen(0), m_current_row(NULL) { /* Empty */ } @@ -220,7 +221,7 @@ public: // We have split this in two to make it easy for sp_instr_copen // to reuse the sp_instr::exec_stmt() code. - LEX * + sp_lex_keeper * pre_open(THD *thd); void post_open(THD *thd, my_bool was_opened); @@ -240,7 +241,7 @@ public: private: MEM_ROOT m_mem_root; // My own mem_root - LEX *m_lex; + sp_lex_keeper *m_lex_keeper; Protocol_cursor *m_prot; my_bool m_isopen; my_bool m_nseof; // Original no_send_eof diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 87c83771ec8..041154c96cc 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -138,7 +138,6 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) TABLE_LIST tables[3]; TABLE *table; READ_RECORD read_record_info; - MYSQL_LOCK *lock; my_bool return_val=1; bool check_no_resolve= specialflag & SPECIAL_NO_RESOLVE; char tmp_name[NAME_LEN+1]; @@ -176,20 +175,9 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ; tables[0].db=tables[1].db=tables[2].db=thd->db; - uint counter; - if (open_tables(thd, tables, &counter)) - { - sql_print_error("Fatal error: Can't open privilege tables: %s", - thd->net.last_error); - goto end; - } - TABLE *ptr[3]; // Lock tables for quick update - ptr[0]= tables[0].table; - ptr[1]= tables[1].table; - ptr[2]= tables[2].table; - if (!(lock=mysql_lock_tables(thd,ptr,3))) + if (simple_open_n_lock_tables(thd, tables)) { - sql_print_error("Fatal error: Can't lock privilege tables: %s", + sql_print_error("Fatal error: Can't open and lock privilege tables: %s", thd->net.last_error); goto end; } @@ -459,7 +447,6 @@ my_bool acl_init(THD *org_thd, bool dont_read_acl_tables) freeze_size(&acl_dbs); init_check_host(); - mysql_unlock_tables(thd, lock); initialized=1; thd->version--; // Force close to free memory return_val=0; @@ -3112,7 +3099,6 @@ my_bool grant_init(THD *org_thd) { THD *thd; TABLE_LIST tables[3]; - MYSQL_LOCK *lock; MEM_ROOT *memex_ptr; my_bool return_val= 1; TABLE *t_table, *c_table, *p_table; @@ -3146,15 +3132,7 @@ my_bool grant_init(THD *org_thd) tables[0].lock_type=tables[1].lock_type=tables[2].lock_type=TL_READ; tables[0].db=tables[1].db=tables[2].db=thd->db; - uint counter; - if (open_tables(thd, tables, &counter)) - goto end; - - TABLE *ptr[3]; // Lock tables for quick update - ptr[0]= tables[0].table; - ptr[1]= tables[1].table; - ptr[2]= tables[2].table; - if (!(lock=mysql_lock_tables(thd,ptr,3))) + if (simple_open_n_lock_tables(thd, tables)) goto end; t_table = tables[0].table; c_table = tables[1].table; @@ -3244,7 +3222,6 @@ my_bool grant_init(THD *org_thd) end_unlock: t_table->file->ha_index_end(); p_table->file->ha_index_end(); - mysql_unlock_tables(thd, lock); thd->version--; // Force close to free memory end: diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 530ef567aaa..4fd943c08f4 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -20,6 +20,7 @@ #include "mysql_priv.h" #include "sql_select.h" #include "sp_head.h" +#include "sp.h" #include "sql_trigger.h" #include <m_ctype.h> #include <my_dir.h> @@ -359,7 +360,30 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, /* - Close all tables used by thread + Mark all tables in the list which were used by current substatement + as free for reuse. + + SYNOPSIS + mark_used_tables_as_free_for_reuse() + thd - thread context + table - head of the list of tables + + DESCRIPTION + Marks all tables in the list which were used by current substatement + (they are marked by its query_id) as free for reuse. +*/ + +static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table) +{ + for (; table ; table= table->next) + if (table->query_id == thd->query_id) + table->query_id= 0; +} + + +/* + Close all tables used by the current substatement, or all tables + used by this thread if we are on the upper level. SYNOPSIS close_thread_tables() @@ -372,14 +396,31 @@ bool close_cached_tables(THD *thd, bool if_wait_for_refresh, IMPLEMENTATION Unlocks tables and frees derived tables. Put all normal tables used by thread in free list. + + When in prelocked mode it will only close/mark as free for reuse + tables opened by this substatement, it will also check if we are + closing tables after execution of complete query (i.e. we are on + upper level) and will leave prelocked mode if needed. */ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived, TABLE *stopper) { bool found_old_table; + prelocked_mode_type prelocked_mode= thd->prelocked_mode; DBUG_ENTER("close_thread_tables"); + /* + We are assuming here that thd->derived_tables contains ONLY derived + tables for this substatement. i.e. instead of approach which uses + query_id matching for determining which of the derived tables belong + to this substatement we rely on the ability of substatements to + save/restore thd->derived_tables during their execution. + + TODO: Probably even better approach is to simply associate list of + derived tables with (sub-)statement instead of thread and destroy + them at the end of its execution. + */ if (thd->derived_tables && !skip_derived) { TABLE *table, *next; @@ -394,10 +435,50 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived, } thd->derived_tables= 0; } - if (thd->locked_tables) + + if (prelocked_mode) { - ha_commit_stmt(thd); // If select statement - DBUG_VOID_RETURN; // LOCK TABLES in use + /* + Mark all temporary tables used by this substatement as free for reuse. + */ + mark_used_tables_as_free_for_reuse(thd, thd->temporary_tables); + } + + if (thd->locked_tables || prelocked_mode) + { + /* + TODO: It is not 100% clear whenever we should do ha_commit_stmt() for + sub-statements. This issue needs additional investigation. + */ + ha_commit_stmt(thd); + + /* We are under simple LOCK TABLES so should not do anything else. */ + if (!prelocked_mode) + DBUG_VOID_RETURN; + + if (!thd->lex->requires_prelocking()) + { + /* + If we are executing one of substatements we have to mark + all tables which it used as free for reuse. + */ + mark_used_tables_as_free_for_reuse(thd, thd->open_tables); + DBUG_VOID_RETURN; + } + + DBUG_ASSERT(prelocked_mode); + /* + We are in prelocked mode, so we have to leave it now with doing + implicit UNLOCK TABLES if need. + */ + thd->prelocked_mode= NON_PRELOCKED; + + if (prelocked_mode == PRELOCKED_UNDER_LOCK_TABLES) + DBUG_VOID_RETURN; + + thd->lock= thd->locked_tables; + thd->locked_tables= 0; + /* Fallthrough */ } if (thd->lock) @@ -441,6 +522,17 @@ void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived, if (!lock_in_use) VOID(pthread_mutex_unlock(&LOCK_open)); /* VOID(pthread_sigmask(SIG_SETMASK,&thd->signals,NULL)); */ + + if (prelocked_mode == PRELOCKED) + { + /* + If we are here then we are leaving normal prelocked mode, so it is + good idea to turn off OPTION_TABLE_LOCK flag. + */ + DBUG_ASSERT(thd->lex->requires_prelocking()); + thd->options&= ~(ulong) (OPTION_TABLE_LOCK); + } + DBUG_VOID_RETURN; } @@ -910,7 +1002,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, !memcmp(table->s->table_cache_key, key, key_length + TMP_TABLE_KEY_EXTRA)) { - if (table->query_id == thd->query_id) + if (table->query_id == thd->query_id || + thd->prelocked_mode && table->query_id) { my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias); DBUG_RETURN(0); @@ -924,16 +1017,17 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, } } - if (thd->locked_tables) + if (thd->locked_tables || thd->prelocked_mode) { // Using table locks for (table=thd->open_tables; table ; table=table->next) { if (table->s->key_length == key_length && - !memcmp(table->s->table_cache_key,key,key_length) && - !my_strcasecmp(system_charset_info, table->alias, alias)) + !memcmp(table->s->table_cache_key, key, key_length) && + !my_strcasecmp(system_charset_info, table->alias, alias) && + table->query_id != thd->query_id && /* skip tables already used by this query */ + !(thd->prelocked_mode && table->query_id)) { - if (table->query_id != thd->query_id) - table->query_id=thd->query_id; + table->query_id= thd->query_id; DBUG_PRINT("info",("Using locked table")); goto reset; } @@ -1625,21 +1719,34 @@ err: SYNOPSIS open_tables() thd - thread handler - start - list of tables + start - list of tables in/out counter - number of opened tables will be return using this parameter + NOTE + Unless we are already in prelocked mode, this function will also precache + all SP/SFs explicitly or implicitly (via views and triggers) used by the + query and add tables needed for their execution to table list. If resulting + tables list will be non empty it will mark query as requiring precaching. + Prelocked mode will be enabled for such query during lock_tables() call. + + If query for which we are opening tables is already marked as requiring + prelocking it won't do such precaching and will simply reuse table list + which is already built. + RETURN 0 - OK -1 - error */ -int open_tables(THD *thd, TABLE_LIST *start, uint *counter) +int open_tables(THD *thd, TABLE_LIST **start, uint *counter) { TABLE_LIST *tables; bool refresh; int result=0; - DBUG_ENTER("open_tables"); MEM_ROOT new_frm_mem; + /* Also used for indicating that prelocking is need */ + TABLE_LIST **query_tables_last_own; + DBUG_ENTER("open_tables"); /* temporary mem_root for new .frm parsing. TODO: variables for size @@ -1649,8 +1756,51 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter) thd->current_tablenr= 0; restart: *counter= 0; + query_tables_last_own= 0; thd->proc_info="Opening tables"; - for (tables= start; tables ;tables= tables->next_global) + + /* + If we are not already executing prelocked statement and don't have + statement for which table list for prelocking is already built, let + us cache routines and try to build such table list. + + NOTE: If we want queries with functions to work under explicit + LOCK TABLES we have to additionaly lock mysql.proc table in it. + At least until Monty will fix SP loading :) + + NOTE: We can't delay prelocking until we will met some sub-statement + which really uses tables, since this will imply that we have to restore + its table list to be able execute it in some other context. + And current views implementation assumes that view tables are added to + global table list only once during PS preparing/first SP execution. + Also locking at earlier stage is probably faster altough may decrease + concurrency a bit. + + NOTE: We will mark statement as requiring prelocking only if we will + have non empty table list. But this does not guarantee that in prelocked + mode we will have some locked tables, because queries which use only + derived/information schema tables and views possible. Thus "counter" + may be still zero for prelocked statement... + */ + if (!thd->prelocked_mode && !thd->lex->requires_prelocking() && + (thd->lex->spfuns.records || thd->lex->spprocs.records)) + { + TABLE_LIST **save_query_tables_last; + + sp_cache_routines(thd, thd->lex); + save_query_tables_last= thd->lex->query_tables_last; + + DBUG_ASSERT(thd->lex->query_tables == *start); + + if (sp_add_sp_tables_to_table_list(thd, thd->lex, thd->lex) || + *start) + { + query_tables_last_own= save_query_tables_last; + *start= thd->lex->query_tables; + } + } + + for (tables= *start; tables ;tables= tables->next_global) { /* Ignore placeholders for derived tables. After derived tables @@ -1671,8 +1821,27 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter) free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); if (tables->view) { + /* VIEW placeholder */ (*counter)--; - continue; //VIEW placeholder + /* + Again if needed we have to get cache all routines used by this view + and add tables used by them to table list. + */ + if (!thd->prelocked_mode && !thd->lex->requires_prelocking() && + (tables->view->spfuns.records || tables->view->spprocs.records)) + { + // FIXME We should catch recursion for both views and funcs here + sp_cache_routines(thd, tables->view); + + /* We have at least one table in TL here */ + if (!query_tables_last_own) + query_tables_last_own= thd->lex->query_tables_last; + sp_add_sp_tables_to_table_list(thd, thd->lex, tables->view); + } + /* Cleanup hashes because destructo for this LEX is never called */ + hash_free(&tables->view->spfuns); + hash_free(&tables->view->spprocs); + continue; } if (refresh) // Refresh in progress @@ -1684,7 +1853,12 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter) thd->version=refresh_version; TABLE **prev_table= &thd->open_tables; bool found=0; - for (TABLE_LIST *tmp= start; tmp; tmp= tmp->next_global) + /* + QQ: What we should do if we have started building of table list + for prelocking ??? Probably throw it away ? But before we should + mark all temporary tables as free? How about locked ? + */ + for (TABLE_LIST *tmp= *start; tmp; tmp= tmp->next_global) { /* Close normal (not temporary) changed tables */ if (tmp->table && ! tmp->table->s->tmp_table) @@ -1713,7 +1887,27 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter) break; } else + { + /* + If we are not already in prelocked mode and extended table list is not + yet built and we have trigger for table being opened then we should + cache all routines used by its triggers and add their tables to + prelocking list. + If we lock table for reading we won't update it so there is no need to + process its triggers since they never will be activated. + + FIXME Now we are simply turning on prelocking. Proper integration + and testing is to be done later. + */ + if (!thd->prelocked_mode && !thd->lex->requires_prelocking() && + tables->table->triggers && + tables->lock_type >= TL_WRITE_ALLOW_WRITE) + { + if (!query_tables_last_own) + query_tables_last_own= thd->lex->query_tables_last; + } free_root(&new_frm_mem, MYF(MY_KEEP_PREALLOC)); + } if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables) tables->table->reginfo.lock_type=tables->lock_type; @@ -1721,6 +1915,10 @@ int open_tables(THD *thd, TABLE_LIST *start, uint *counter) } thd->proc_info=0; free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block + + if (query_tables_last_own) + thd->lex->mark_as_requiring_prelocking(query_tables_last_own); + DBUG_RETURN(result); } @@ -1769,6 +1967,11 @@ static bool check_lock_and_start_stmt(THD *thd, TABLE *table, table_list Table to open is first table in this list lock_type Lock to use for open + NOTE + This function don't do anything like SP/SF/views/triggers analysis done + in open_tables(). It is intended for opening of only one concrete table. + And used only in special contexts. + RETURN VALUES table Opened table 0 Error @@ -1843,7 +2046,7 @@ int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables) { DBUG_ENTER("simple_open_n_lock_tables"); uint counter; - if (open_tables(thd, tables, &counter) || lock_tables(thd, tables, counter)) + if (open_tables(thd, &tables, &counter) || lock_tables(thd, tables, counter)) DBUG_RETURN(-1); /* purecov: inspected */ DBUG_RETURN(0); } @@ -1870,7 +2073,7 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables) { uint counter; DBUG_ENTER("open_and_lock_tables"); - if (open_tables(thd, tables, &counter) || + if (open_tables(thd, &tables, &counter) || lock_tables(thd, tables, counter) || mysql_handle_derived(thd->lex, &mysql_derived_prepare) || (thd->fill_derived_tables() && @@ -1903,7 +2106,7 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables) uint counter; DBUG_ENTER("open_normal_and_derived_tables"); DBUG_ASSERT(!thd->fill_derived_tables()); - if (open_tables(thd, tables, &counter) || + if (open_tables(thd, &tables, &counter) || mysql_handle_derived(thd->lex, &mysql_derived_prepare)) DBUG_RETURN(TRUE); /* purecov: inspected */ relink_tables_for_multidelete(thd); // Not really needed, but @@ -1937,6 +2140,27 @@ static void relink_tables_for_multidelete(THD *thd) /* + Mark all real tables in the list as free for reuse. + + SYNOPSIS + mark_real_tables_as_free_for_reuse() + thd - thread context + table - head of the list of tables + + DESCRIPTION + Marks all real tables in the list (i.e. not views, derived + or schema tables) as free for reuse. +*/ + +static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table) +{ + for (; table; table= table->next_global) + if (!table->placeholder() && !table->schema_table) + table->table->query_id= 0; +} + + +/* Lock all tables in list SYNOPSIS @@ -1950,6 +2174,10 @@ static void relink_tables_for_multidelete(THD *thd) handling thr_lock gives us. You most always get all needed locks at once. + If query for which we are calling this function marked as requring + prelocking, this function will do implicit LOCK TABLES and change + thd::prelocked_mode accordingly. + RETURN VALUES 0 ok -1 Error @@ -1958,36 +2186,125 @@ static void relink_tables_for_multidelete(THD *thd) int lock_tables(THD *thd, TABLE_LIST *tables, uint count) { TABLE_LIST *table; + + DBUG_ENTER("lock_tables"); + /* + We can't meet statement requiring prelocking if we already + in prelocked mode. + */ + DBUG_ASSERT(!thd->prelocked_mode || !thd->lex->requires_prelocking()); + /* + If statement requires prelocking then it has non-empty table list. + So it is safe to shortcut. + */ + DBUG_ASSERT(!thd->lex->requires_prelocking() || tables); + if (!tables) - return 0; + DBUG_RETURN(0); - if (!thd->locked_tables) + /* + We need this extra check for thd->prelocked_mode because we want to avoid + attempts to lock tables in substatements. Checking for thd->locked_tables + is not enough in some situations. For example for SP containing + "drop table t3; create temporary t3 ..; insert into t3 ...;" + thd->locked_tables may be 0 after drop tables, and without this extra + check insert will try to lock temporary table t3, that will lead + to memory leak... + */ + if (!thd->locked_tables && !thd->prelocked_mode) { DBUG_ASSERT(thd->lock == 0); // You must lock everything at once TABLE **start,**ptr; + if (!(ptr=start=(TABLE**) thd->alloc(sizeof(TABLE*)*count))) - return -1; + DBUG_RETURN(-1); for (table= tables; table; table= table->next_global) { if (!table->placeholder() && !table->schema_table) *(ptr++)= table->table; } + + /* We have to emulate LOCK TABLES if we are statement needs prelocking. */ + if (thd->lex->requires_prelocking()) + { + thd->in_lock_tables=1; + thd->options|= OPTION_TABLE_LOCK; + } + if (!(thd->lock=mysql_lock_tables(thd,start, (uint) (ptr - start)))) - return -1; /* purecov: inspected */ + { + if (thd->lex->requires_prelocking()) + { + thd->options&= ~(ulong) (OPTION_TABLE_LOCK); + thd->in_lock_tables=0; + } + DBUG_RETURN(-1); + } + if (thd->lex->requires_prelocking() && + thd->lex->sql_command != SQLCOM_LOCK_TABLES) + { + TABLE_LIST *first_not_own= thd->lex->first_not_own_table(); + /* + We just have done implicit LOCK TABLES, and now we have + to emulate first open_and_lock_tables() after it. + + Note that "LOCK TABLES" can also be marked as requiring prelocking + (e.g. if one locks view which uses functions). We should not emulate + such open_and_lock_tables() in this case. We also should not set + THD::prelocked_mode or first close_thread_tables() call will do + "UNLOCK TABLES". + */ + thd->locked_tables= thd->lock; + thd->lock= 0; + thd->in_lock_tables=0; + + for (table= tables; table != first_not_own; table= table->next_global) + { + if (!table->placeholder() && !table->schema_table) + { + table->table->query_id= thd->query_id; + if (check_lock_and_start_stmt(thd, table->table, table->lock_type)) + { + ha_rollback_stmt(thd); + mysql_unlock_tables(thd, thd->locked_tables); + thd->locked_tables= 0; + thd->options&= ~(ulong) (OPTION_TABLE_LOCK); + DBUG_RETURN(-1); + } + } + } + /* + Let us mark all tables which don't belong to the statement itself, + and was marked as occupied during open_tables() as free for reuse. + */ + mark_real_tables_as_free_for_reuse(first_not_own); + thd->prelocked_mode= PRELOCKED; + } } else { - for (table= tables; table; table= table->next_global) + TABLE_LIST *first_not_own= thd->lex->first_not_own_table(); + for (table= tables; table != first_not_own; table= table->next_global) { - if (!table->placeholder() && + if (!table->placeholder() && !table->schema_table && check_lock_and_start_stmt(thd, table->table, table->lock_type)) { ha_rollback_stmt(thd); - return -1; + DBUG_RETURN(-1); } } + /* + If we are under explicit LOCK TABLES and our statement requires + prelocking, we should mark all "additional" tables as free for use + and enter prelocked mode. + */ + if (thd->lex->requires_prelocking()) + { + mark_real_tables_as_free_for_reuse(first_not_own); + thd->prelocked_mode= PRELOCKED_UNDER_LOCK_TABLES; + } } - return 0; + DBUG_RETURN(0); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 527f349038c..bac3e42ed62 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -251,8 +251,9 @@ THD::THD() protocol_prep.init(this); tablespace_op=FALSE; - ulong tmp=sql_rnd_with_mutex(); - randominit(&rand, tmp + (ulong) &rand, tmp + (ulong) ::query_id); + ulong tmp=sql_rnd_with_mutex(); + randominit(&rand, tmp + (ulong) &rand, tmp + (ulong) ::query_id); + prelocked_mode= NON_PRELOCKED; } diff --git a/sql/sql_class.h b/sql/sql_class.h index e88205568c2..fa2bcc4d478 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -926,6 +926,15 @@ typedef I_List<Item_change_record> Item_change_list; /* + Type of prelocked mode. + See comment for THD::prelocked_mode for complete description. +*/ + +enum prelocked_mode_type {NON_PRELOCKED= 0, PRELOCKED= 1, + PRELOCKED_UNDER_LOCK_TABLES= 2}; + + +/* For each client connection we create a separate thread with THD serving as a thread/connection descriptor */ @@ -1025,7 +1034,13 @@ public: See also lock_tables() for details. */ MYSQL_LOCK *lock; /* Current locks */ - MYSQL_LOCK *locked_tables; /* Tables locked with LOCK */ + /* + Tables that were locked with explicit or implicit LOCK TABLES. + (Implicit LOCK TABLES happens when we are prelocking tables for + execution of statement which uses stored routines. See description + THD::prelocked_mode for more info.) + */ + MYSQL_LOCK *locked_tables; HASH handler_tables_hash; /* One thread can hold up to one named user-level lock. This variable @@ -1192,8 +1207,6 @@ public: sp_rcontext *spcont; // SP runtime context sp_cache *sp_proc_cache; sp_cache *sp_func_cache; - bool shortcut_make_view; /* Don't do full mysql_make_view() - during pre-opening of tables. */ /* If we do a purge of binary logs, log index info of the threads @@ -1209,6 +1222,31 @@ public: long long_value; } sys_var_tmp; + /* + prelocked_mode_type enum and prelocked_mode member are used for + indicating whenever "prelocked mode" is on, and what type of + "prelocked mode" is it. + + Prelocked mode is used for execution of queries which explicitly + or implicitly (via views or triggers) use functions, thus may need + some additional tables (mentioned in query table list) for their + execution. + + First open_tables() call for such query will analyse all functions + used by it and add all additional tables to table its list. It will + also mark this query as requiring prelocking. After that lock_tables() + will issue implicit LOCK TABLES for the whole table list and change + thd::prelocked_mode to non-0. All queries called in functions invoked + by the main query will use prelocked tables. Non-0 prelocked_mode + will also surpress mentioned analysys in those queries thus saving + cycles. Prelocked mode will be turned off once close_thread_tables() + for the main query will be called. + + Note: Since not all "tables" present in table list are really locked + thd::relocked_mode does not imply thd::locked_tables. + */ + prelocked_mode_type prelocked_mode; + THD(); ~THD(); diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index dd2ac3c013b..bb48b7ada77 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -187,7 +187,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, bool reopen) /* for now HANDLER can be used only for real TABLES */ tables->required_type= FRMTYPE_TABLE; - error= open_tables(thd, tables, &counter); + error= open_tables(thd, &tables, &counter); HANDLER_TABLES_HACK(thd); if (error) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 738a6e0dbbd..91c4dc40c01 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -169,13 +169,12 @@ void lex_start(THD *thd, uchar *buf,uint length) lex->sphead= NULL; lex->spcont= NULL; lex->proc_list.first= 0; + lex->query_tables_own_last= 0; if (lex->spfuns.records) my_hash_reset(&lex->spfuns); if (lex->spprocs.records) my_hash_reset(&lex->spprocs); - if (lex->sptabs.records) - my_hash_reset(&lex->sptabs); DBUG_VOID_RETURN; } @@ -1868,6 +1867,8 @@ TABLE_LIST *st_lex::unlink_first_table(bool *link_to_local) */ if ((query_tables= query_tables->next_global)) query_tables->prev_global= &query_tables; + else + query_tables_last= &query_tables; first->next_global= 0; /* @@ -1973,6 +1974,8 @@ void st_lex::link_first_table_back(TABLE_LIST *first, { if ((first->next_global= query_tables)) query_tables->prev_global= &first->next_global; + else + query_tables_last= &first->next_global; query_tables= first; if (link_to_local) diff --git a/sql/sql_lex.h b/sql/sql_lex.h index a0145dcaccf..e50c4edabb4 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -784,7 +784,6 @@ typedef struct st_lex sp_pcontext *spcont; HASH spfuns; /* Called functions */ HASH spprocs; /* Called procedures */ - HASH sptabs; /* Merged table lists */ st_sp_chistics sp_chistics; bool only_view; /* used for SHOW CREATE TABLE/VIEW */ /* @@ -803,23 +802,25 @@ typedef struct st_lex */ SQL_LIST trg_table_fields; - st_lex() :result(0), sql_command(SQLCOM_END) + /* + If non-0 then indicates that query requires prelocking and points to + next_global member of last own element in query table list (i.e. last + table which was not added to it as part of preparation to prelocking). + 0 - indicates that this query does not need prelocking. + */ + TABLE_LIST **query_tables_own_last; + + st_lex() :result(0), sql_command(SQLCOM_END), query_tables_own_last(0) { extern byte *sp_lex_sp_key(const byte *ptr, uint *plen, my_bool first); - extern byte *sp_table_key(const byte *ptr, uint *plen, my_bool first); hash_init(&spfuns, system_charset_info, 0, 0, 0, sp_lex_sp_key, 0, 0); hash_init(&spprocs, system_charset_info, 0, 0, 0, sp_lex_sp_key, 0, 0); - hash_init(&sptabs, system_charset_info, 0, 0, 0, sp_table_key, 0, 0); } ~st_lex() { - if (spfuns.array.buffer) - hash_free(&spfuns); - if (spprocs.array.buffer) - hash_free(&spprocs); - if (sptabs.array.buffer) - hash_free(&sptabs); + hash_free(&spfuns); + hash_free(&spprocs); } inline void uncacheable(uint8 cause) @@ -856,6 +857,21 @@ typedef struct st_lex bool can_not_use_merged(); bool only_view_structure(); bool need_correct_ident(); + + inline bool requires_prelocking() + { + return query_tables_own_last; + } + inline void mark_as_requiring_prelocking(TABLE_LIST **tables_own_last) + { + query_tables_own_last= tables_own_last; + } + /* Return pointer to first not-own table in query-tables or 0 */ + TABLE_LIST* first_not_own_table() + { + return ( query_tables_own_last ? *query_tables_own_last : 0); + } + } LEX; struct st_lex_local: public st_lex diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 2f38f96c976..e5c29f56305 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1453,105 +1453,6 @@ bool do_command(THD *thd) } #endif /* EMBEDDED_LIBRARY */ -static void release_local_lock(THD *thd, TABLE_LIST *locked_tables, - bool old_innodb_table_locks) -{ - if (locked_tables) - { -#ifdef HAVE_INNOBASE_DB - thd->variables.innodb_table_locks= old_innodb_table_locks; -#endif - if (thd->locked_tables) - sp_unlock_tables(thd); - } - -} - -static bool process_nested_sp(THD *thd, LEX *lex, TABLE_LIST** locked_tables) -{ - DBUG_ENTER("process_nested_sp"); - while (1) - { - if (sp_cache_routines(thd, lex, TYPE_ENUM_FUNCTION)) - DBUG_RETURN(TRUE); - if (sp_cache_routines(thd, lex, TYPE_ENUM_PROCEDURE)) - DBUG_RETURN(TRUE); - if (!thd->locked_tables && - lex->sql_command != SQLCOM_CREATE_TABLE && - lex->sql_command != SQLCOM_CREATE_VIEW) - { - MEM_ROOT *thdmemroot= NULL; - - sp_merge_routine_tables(thd, lex); - // QQ Preopen tables to find views and triggers. - // This means we open, close and open again, which sucks, but - // right now it's the easiest way to get it to work. A better - // solution will hopefully be found soon... - if (lex->sptabs.records || lex->query_tables) - { - uint procs, funs, tabs; - - if (thd->mem_root != thd->current_arena->mem_root) - { - thdmemroot= thd->mem_root; - thd->mem_root= thd->current_arena->mem_root; - } - if (!sp_merge_table_list(thd, &lex->sptabs, lex->query_tables)) - DBUG_RETURN(TRUE); - procs= lex->spprocs.records; - funs= lex->spfuns.records; - tabs= lex->sptabs.records; - - if (((*locked_tables)= sp_hash_to_table_list(thd, &lex->sptabs))) - { - // We don't want these updated now - uint ctmpdtabs= thd->status_var.created_tmp_disk_tables; - uint ctmptabs= thd->status_var.created_tmp_tables; - uint count; - - thd->shortcut_make_view= TRUE; - open_tables(thd, *locked_tables, &count); - thd->shortcut_make_view= FALSE; - close_thread_tables(thd); - thd->status_var.created_tmp_disk_tables= ctmpdtabs; - thd->status_var.created_tmp_tables= ctmptabs; - thd->clear_error(); - mysql_reset_errors(thd); - (*locked_tables)= NULL; - } - // A kludge: Decrease all temp. table's query ids to allow a - // second opening. - for (TABLE *table= thd->temporary_tables; table ; table=table->next) - table->query_id-= 1; - if (procs < lex->spprocs.records || - funs < lex->spfuns.records || - tabs < lex->sptabs.records) - { - if (thdmemroot) - thd->mem_root= thdmemroot; - continue; // Found more SPs or tabs, try again - } - } - if (lex->sptabs.records && - (lex->spfuns.records || lex->spprocs.records) && - sp_merge_table_list(thd, &lex->sptabs, lex->query_tables)) - { - if (((*locked_tables)= sp_hash_to_table_list(thd, &lex->sptabs))) - { -#ifdef HAVE_INNOBASE_DB - thd->variables.innodb_table_locks= FALSE; -#endif - sp_open_and_lock_tables(thd, *locked_tables); - } - } - if (thdmemroot) - thd->mem_root= thdmemroot; - } - break; - } // while (1) - DBUG_RETURN(FALSE); -} - /* Perform one connection-level (COM_XXXX) command. @@ -1752,7 +1653,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, in embedded server - just store them to be executed later */ #ifndef EMBEDDED_LIBRARY - if (thd->lock || thd->open_tables || thd->derived_tables) + if (thd->lock || thd->open_tables || thd->derived_tables || + thd->prelocked_mode) close_thread_tables(thd); #endif ulong length= (ulong)(packet_end-packet); @@ -1855,19 +1757,12 @@ bool dispatch_command(enum enum_server_command command, THD *thd, select_lex.table_list.link_in_list((byte*) &table_list, (byte**) &table_list.next_local); thd->lex->query_tables= &table_list; - thd->shortcut_make_view= 0; - process_nested_sp(thd, thd->lex, &locked_tables); /* switch on VIEW optimisation: do not fill temporary tables */ thd->lex->sql_command= SQLCOM_SHOW_FIELDS; mysqld_list_fields(thd,&table_list,fields); thd->lex->unit.cleanup(); thd->cleanup_after_query(); -#ifdef HAVE_INNOBASE_DB - release_local_lock(thd, locked_tables, old_innodb_table_locks); -#else - release_local_lock(thd, locked_tables, false); -#endif break; } #endif @@ -2089,7 +1984,8 @@ bool dispatch_command(enum enum_server_command command, THD *thd, my_message(ER_UNKNOWN_COM_ERROR, ER(ER_UNKNOWN_COM_ERROR), MYF(0)); break; } - if (thd->lock || thd->open_tables || thd->derived_tables) + if (thd->lock || thd->open_tables || thd->derived_tables || + thd->prelocked_mode) { thd->proc_info="closing tables"; close_thread_tables(thd); /* Free tables */ @@ -2334,11 +2230,7 @@ mysql_execute_command(THD *thd) TABLE_LIST *all_tables; /* most outer SELECT_LEX_UNIT of query */ SELECT_LEX_UNIT *unit= &lex->unit; - /* Locked closure of all tables */ - TABLE_LIST *locked_tables= NULL; /* Saved variable value */ - my_bool old_innodb_table_locks= - IF_INNOBASE_DB(thd->variables.innodb_table_locks, FALSE); DBUG_ENTER("mysql_execute_command"); thd->net.no_send_error= 0; @@ -2361,26 +2253,14 @@ mysql_execute_command(THD *thd) /* should be assigned after making first tables same */ all_tables= lex->query_tables; - thd->shortcut_make_view= 0; - if (lex->sql_command != SQLCOM_CREATE_PROCEDURE && - lex->sql_command != SQLCOM_CREATE_SPFUNCTION && - lex->sql_command != SQLCOM_LOCK_TABLES && - lex->sql_command != SQLCOM_UNLOCK_TABLES) - { - thd->no_warnings_for_error= 1; - res= process_nested_sp(thd, lex, &locked_tables); - thd->no_warnings_for_error= 0; - if (res) - DBUG_RETURN(TRUE); - } - /* Reset warning count for each query that uses tables A better approach would be to reset this for any commands that is not a SHOW command or a select that only access local variables, but for now this is probably good enough. */ - if (all_tables || &lex->select_lex != lex->all_selects_list) + if (all_tables || &lex->select_lex != lex->all_selects_list || + lex->spfuns.records || lex->spprocs.records) mysql_reset_errors(thd); #ifdef HAVE_REPLICATION @@ -2614,9 +2494,8 @@ mysql_execute_command(THD *thd) break; } case SQLCOM_DO: - if (all_tables && - (check_table_access(thd, SELECT_ACL, all_tables, 0) || - open_and_lock_tables(thd, all_tables))) + if (check_table_access(thd, SELECT_ACL, all_tables, 0) || + open_and_lock_tables(thd, all_tables)) goto error; res= mysql_do(thd, *lex->insert_list); @@ -3485,8 +3364,7 @@ unsent_create_error: case SQLCOM_SET_OPTION: { List<set_var_base> *lex_var_list= &lex->var_list; - if (all_tables && - (check_table_access(thd, SELECT_ACL, all_tables, 0) || + if ((check_table_access(thd, SELECT_ACL, all_tables, 0) || open_and_lock_tables(thd, all_tables))) goto error; if (lex->one_shot_set && not_all_support_one_shot(lex_var_list)) @@ -3532,7 +3410,7 @@ unsent_create_error: thd->in_lock_tables=1; thd->options|= OPTION_TABLE_LOCK; - if (!(res= open_and_lock_tables(thd, all_tables))) + if (!(res= simple_open_n_lock_tables(thd, all_tables))) { #ifdef HAVE_QUERY_CACHE if (thd->variables.query_cache_wlock_invalidate) @@ -4115,7 +3993,20 @@ unsent_create_error: { sp_head *sp; - if (!(sp= sp_find_procedure(thd, lex->spname))) + /* + This will cache all SP and SF and open and lock all tables + required for execution. + */ + if (check_table_access(thd, SELECT_ACL, all_tables, 0) || + open_and_lock_tables(thd, all_tables)) + goto error; + + /* + By this moment all needed SPs should be in cache so no need + to look into DB. Moreover we may be unable to do it becuase + we may don't have read lock on mysql.proc + */ + if (!(sp= sp_find_procedure(thd, lex->spname, TRUE))) { my_error(ER_SP_DOES_NOT_EXIST, MYF(0), "PROCEDURE", lex->spname->m_qname.str); @@ -4130,12 +4021,6 @@ unsent_create_error: /* bits that should be cleared in thd->server_status */ uint bits_to_be_cleared= 0; - /* In case the arguments are subselects... */ - if (all_tables && - (check_table_access(thd, SELECT_ACL, all_tables, 0) || - open_and_lock_tables(thd, all_tables))) - goto error; - #ifndef EMBEDDED_LIBRARY my_bool nsok= thd->net.no_send_ok; thd->net.no_send_ok= TRUE; @@ -4606,11 +4491,6 @@ cleanup: if (thd->lock == thd->locked_tables) thd->lock= 0; } -#ifdef HAVE_INNOBASE_DB - release_local_lock(thd, locked_tables, old_innodb_table_locks); -#else - release_local_lock(thd, locked_tables, false); -#endif DBUG_RETURN(res || thd->net.report_error); } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index d1594edc3f9..cf988654b22 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1004,7 +1004,7 @@ static int mysql_test_update(Prepared_statement *stmt, if (update_precheck(thd, table_list)) DBUG_RETURN(1); - if (!open_tables(thd, table_list, &table_count)) + if (!open_tables(thd, &table_list, &table_count)) { if (table_list->ancestor && table_list->ancestor->next_local) { @@ -1545,22 +1545,6 @@ static int check_prepared_statement(Prepared_statement *stmt, lex->first_lists_tables_same(); tables= lex->query_tables; - /* - Preopen 'proc' system table and cache all functions used in this - statement. We must do that before we open ordinary tables to avoid - deadlocks. We can't open and lock any table once query tables were - opened. - */ - if (lex->sql_command != SQLCOM_CREATE_PROCEDURE && - lex->sql_command != SQLCOM_CREATE_SPFUNCTION) - { - /* The error is printed inside */ - if (sp_cache_routines(thd, lex, TYPE_ENUM_FUNCTION)) - DBUG_RETURN(-1); - if (sp_cache_routines(thd, lex, TYPE_ENUM_PROCEDURE)) - DBUG_RETURN(-1); - } - switch (sql_command) { case SQLCOM_REPLACE: case SQLCOM_INSERT: @@ -1793,9 +1777,9 @@ bool mysql_stmt_prepare(THD *thd, char *packet, uint packet_length, thd->lex->sphead= NULL; } lex_end(lex); + close_thread_tables(thd); thd->restore_backup_statement(stmt, &thd->stmt_backup); cleanup_items(stmt->free_list); - close_thread_tables(thd); thd->rollback_item_tree_changes(); thd->cleanup_after_query(); thd->current_arena= thd; @@ -1885,6 +1869,11 @@ void reset_stmt_for_execute(THD *thd, LEX *lex) to indicate the table is altered, and re-do the setup_* and open the tables back. */ + /* + NOTE: We should reset whole table list here including all tables added + by prelocking algorithm (it is not a problem for substatements since + they have their own table list). + */ for (TABLE_LIST *tables= lex->query_tables; tables; tables= tables->next_global) diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h index 82e7c1ce023..7dd6734eb89 100644 --- a/sql/sql_trigger.h +++ b/sql/sql_trigger.h @@ -42,14 +42,21 @@ public: if (bodies[event][time_type]) { - /* - Similar to function invocation we don't need to surpress sending of - ok packets here because don't allow execute statements from trigger. +#ifndef EMBEDDED_LIBRARY + /* Surpress OK packets in case if we will execute statements */ + my_bool nsok= thd->net.no_send_ok; + thd->net.no_send_ok= TRUE; +#endif + /* FIXME: We should juggle with security context here (because trigger should be invoked with creator rights). */ res= bodies[event][time_type]->execute_function(thd, 0, 0, 0); + +#ifndef EMBEDDED_LIBRARY + thd->net.no_send_ok= nsok; +#endif } return res; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 54a976fe2b0..477a283448a 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -136,7 +136,7 @@ int mysql_update(THD *thd, LINT_INIT(timestamp_query_id); - if (open_tables(thd, table_list, &table_count)) + if (open_tables(thd, &table_list, &table_count)) DBUG_RETURN(1); if (table_list->ancestor && table_list->ancestor->next_local) @@ -635,7 +635,7 @@ bool mysql_multi_update_prepare(THD *thd) thd->lex->sql_command= SQLCOM_UPDATE_MULTI; /* open tables and create derived ones, but do not lock and fill them */ - if ((original_multiupdate && open_tables(thd, table_list, & table_count)) || + if ((original_multiupdate && open_tables(thd, &table_list, & table_count)) || mysql_handle_derived(lex, &mysql_derived_prepare)) DBUG_RETURN(TRUE); /* diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 96a0dc2f636..0c7bf2632ec 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -19,6 +19,7 @@ #include "sql_select.h" #include "parse_file.h" #include "sp.h" +#include "sp_head.h" #define MD5_BUFF_LENGTH 33 @@ -615,10 +616,7 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table) table->view= lex= thd->lex= (LEX*) new(thd->mem_root) st_lex_local; lex_start(thd, (uchar*)table->query.str, table->query.length); view_select= &lex->select_lex; - /* Only if we're not in the pre-open phase */ - if (!thd->shortcut_make_view) - view_select->select_number= ++thd->select_number; - old_lex->derived_tables|= DERIVED_VIEW; + view_select->select_number= ++thd->select_number; { ulong options= thd->options; /* switch off modes which can prevent normal parsing of VIEW @@ -662,35 +660,13 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table) TABLE_LIST *view_tables_tail= 0; TABLE_LIST *tbl; - /* move SP to main LEX */ - if (lex->spfuns.records) - sp_merge_hash(&old_lex->spfuns, &lex->spfuns); - - /* cleanup LEX */ - if (lex->spfuns.array.buffer) - hash_free(&lex->spfuns); - if (lex->spprocs.array.buffer) - hash_free(&lex->spprocs); - if (lex->sptabs.array.buffer) - hash_free(&lex->sptabs); - - /* If we're pre-opening tables to find SPs and tables we need - not go any further; doing so will cause an infinite loop. */ - if (thd->shortcut_make_view) - { - extern bool - sp_merge_table_list(THD *thd, HASH *h, TABLE_LIST *table, - LEX *lex_for_tmp_check = 0); - - sp_merge_table_list(thd, &old_lex->sptabs, view_tables); - goto ok; - } - /* - check rights to run commands (EXPLAIN SELECT & SHOW CREATE) which show - underlying tables + Check rights to run commands (EXPLAIN SELECT & SHOW CREATE) which show + underlying tables. + Skip this step if we are opening view for prelocking only. */ - if ((old_lex->sql_command == SQLCOM_SELECT && old_lex->describe)) + if (!table->prelocking_placeholder && + (old_lex->sql_command == SQLCOM_SELECT && old_lex->describe)) { if (check_table_access(thd, SELECT_ACL, view_tables, 1) && check_table_access(thd, SHOW_VIEW_ACL, table, 1)) @@ -699,7 +675,8 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table) goto err; } } - else if (old_lex->sql_command == SQLCOM_SHOW_CREATE) + else if (!table->prelocking_placeholder && + old_lex->sql_command == SQLCOM_SHOW_CREATE) { if (check_table_access(thd, SHOW_VIEW_ACL, table, 0)) goto err; @@ -717,13 +694,6 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table) tbl->belong_to_view= top_view; } - /* move SQL_NO_CACHE & Co to whole query */ - old_lex->safe_to_cache_query= (old_lex->safe_to_cache_query && - lex->safe_to_cache_query); - /* move SQL_CACHE to whole query */ - if (view_select->options & OPTION_TO_QUERY_CACHE) - old_lex->select_lex.options|= OPTION_TO_QUERY_CACHE; - /* Put tables of VIEW after VIEW TABLE_LIST @@ -740,13 +710,30 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table) } else { - lex->query_tables_last= &view_tables_tail->next_global; + old_lex->query_tables_last= &view_tables_tail->next_global; } view_tables->prev_global= &table->next_global; table->next_global= view_tables; } /* + If we are opening this view as part of implicit LOCK TABLES, then + this view serves as simple placeholder and we should not continue + further processing. + */ + if (table->prelocking_placeholder) + goto ok2; + + old_lex->derived_tables|= DERIVED_VIEW; + + /* move SQL_NO_CACHE & Co to whole query */ + old_lex->safe_to_cache_query= (old_lex->safe_to_cache_query && + lex->safe_to_cache_query); + /* move SQL_CACHE to whole query */ + if (view_select->options & OPTION_TO_QUERY_CACHE) + old_lex->select_lex.options|= OPTION_TO_QUERY_CACHE; + + /* check MERGE algorithm ability - algorithm is not explicit TEMPORARY TABLE - VIEW SELECT allow merging @@ -850,8 +837,6 @@ mysql_make_view(File_parser *parser, TABLE_LIST *table) goto err; ok: - if (arena) - thd->restore_backup_item_arena(arena, &backup); /* global SELECT list linking */ end= view_select; // primary SELECT_LEX is always last end->link_next= old_lex->all_selects_list; @@ -860,6 +845,9 @@ ok: lex->all_selects_list->link_prev= (st_select_lex_node**)&old_lex->all_selects_list; +ok2: + if (arena) + thd->restore_backup_item_arena(arena, &backup); thd->lex= old_lex; DBUG_RETURN(0); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index a69b6a96982..fae01502a8d 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1548,12 +1548,12 @@ sp_opt_inout: sp_proc_stmts: /* Empty */ {} - | sp_proc_stmts { Lex->query_tables= 0; } sp_proc_stmt ';' + | sp_proc_stmts sp_proc_stmt ';' ; sp_proc_stmts1: sp_proc_stmt ';' {} - | sp_proc_stmts1 { Lex->query_tables= 0; } sp_proc_stmt ';' + | sp_proc_stmts1 sp_proc_stmt ';' ; sp_decls: @@ -1587,13 +1587,15 @@ sp_decls: ; sp_decl: - DECLARE_SYM sp_decl_idents type sp_opt_default + DECLARE_SYM sp_decl_idents type + { Lex->sphead->reset_lex(YYTHD); } + sp_opt_default { LEX *lex= Lex; sp_pcontext *ctx= lex->spcont; uint max= ctx->context_pvars(); enum enum_field_types type= (enum enum_field_types)$3; - Item *it= $4; + Item *it= $5; for (uint i = max-$2 ; i < max ; i++) { @@ -1605,15 +1607,19 @@ sp_decl: sp_instr_set *in= new sp_instr_set(lex->sphead->instructions(), ctx, ctx->pvar_context2index(i), - it, type); + it, type, lex, + (i == max - 1)); - in->tables= lex->query_tables; - lex->query_tables= 0; + /* + The last instruction is assigned to be responsible for + freeing LEX. + */ lex->sphead->add_instr(in); ctx->set_isset(i, TRUE); ctx->set_default(i, it); } } + lex->sphead->restore_lex(YYTHD); $$.vars= $2; $$.conds= $$.hndlrs= $$.curs= 0; } @@ -1865,36 +1871,39 @@ sp_proc_stmt: my_message(ER_SP_NO_USE, ER(ER_SP_NO_USE), MYF(0)); YYABORT; } - /* Don't add an instruction for empty SET statements. - ** (This happens if the SET only contained local variables, - ** which get their set instructions generated separately.) + /* + Don't add an instruction for SET statements, since all + instructions for them were already added during processing + of "set" rule. */ - if (lex->sql_command != SQLCOM_SET_OPTION || - ! lex->var_list.is_empty()) + DBUG_ASSERT(lex->sql_command != SQLCOM_SET_OPTION || + lex->var_list.is_empty()); + if (lex->sql_command != SQLCOM_SET_OPTION) { - sp_instr_stmt *i=new sp_instr_stmt(sp->instructions(), - lex->spcont); - - /* Extract the query statement from the tokenizer: - The end is either lex->tok_end or tok->ptr. */ - if (lex->ptr - lex->tok_end > 1) - i->m_query.length= lex->ptr - sp->m_tmp_query; - else - i->m_query.length= lex->tok_end - sp->m_tmp_query; - i->m_query.str= strmake_root(YYTHD->mem_root, - (char *)sp->m_tmp_query, - i->m_query.length); - i->set_lex(lex); - sp->add_instr(i); - lex->sp_lex_in_use= TRUE; - } + sp_instr_stmt *i=new sp_instr_stmt(sp->instructions(), + lex->spcont, lex); + + /* Extract the query statement from the tokenizer: + The end is either lex->tok_end or tok->ptr. */ + if (lex->ptr - lex->tok_end > 1) + i->m_query.length= lex->ptr - sp->m_tmp_query; + else + i->m_query.length= lex->tok_end - sp->m_tmp_query; + i->m_query.str= strmake_root(YYTHD->mem_root, + (char *)sp->m_tmp_query, + i->m_query.length); + sp->add_instr(i); + } sp->restore_lex(YYTHD); } - | RETURN_SYM expr + | RETURN_SYM + { Lex->sphead->reset_lex(YYTHD); } + expr { LEX *lex= Lex; + sp_head *sp= lex->sphead; - if (lex->sphead->m_type == TYPE_ENUM_PROCEDURE) + if (sp->m_type == TYPE_ENUM_PROCEDURE) { my_message(ER_SP_BADRETURN, ER(ER_SP_BADRETURN), MYF(0)); YYABORT; @@ -1903,12 +1912,12 @@ sp_proc_stmt: { sp_instr_freturn *i; - i= new sp_instr_freturn(lex->sphead->instructions(), - lex->spcont, - $2, lex->sphead->m_returns); - lex->sphead->add_instr(i); - lex->sphead->m_has_return= TRUE; + i= new sp_instr_freturn(sp->instructions(), lex->spcont, + $3, sp->m_returns, lex); + sp->add_instr(i); + sp->m_has_return= TRUE; } + sp->restore_lex(YYTHD); } | IF sp_if END IF {} | CASE_SYM WHEN_SYM @@ -1916,7 +1925,9 @@ sp_proc_stmt: Lex->sphead->m_simple_case= FALSE; } sp_case END CASE_SYM {} - | CASE_SYM expr WHEN_SYM + | CASE_SYM + { Lex->sphead->reset_lex(YYTHD); } + expr WHEN_SYM { /* We "fake" this by using an anonymous variable which we set to the expression. Note that all WHENs are evaluate @@ -1925,15 +1936,14 @@ sp_proc_stmt: LEX *lex= Lex; uint offset= lex->spcont->current_pvars(); sp_instr_set *i = new sp_instr_set(lex->sphead->instructions(), - lex->spcont, - offset, $2, MYSQL_TYPE_STRING); + lex->spcont, offset, $3, + MYSQL_TYPE_STRING, lex, TRUE); LEX_STRING dummy={(char*)"", 0}; lex->spcont->push_pvar(&dummy, MYSQL_TYPE_STRING, sp_param_in); - i->tables= lex->query_tables; - lex->query_tables= 0; lex->sphead->add_instr(i); lex->sphead->m_simple_case= TRUE; + lex->sphead->restore_lex(YYTHD); } sp_case END CASE_SYM { @@ -2187,18 +2197,19 @@ sp_fetch_list: ; sp_if: - expr THEN_SYM + { Lex->sphead->reset_lex(YYTHD); } + expr THEN_SYM { LEX *lex= Lex; sp_head *sp= lex->sphead; sp_pcontext *ctx= lex->spcont; uint ip= sp->instructions(); - sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, ctx, $1); + sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, ctx, + $2, lex); - i->tables= lex->query_tables; - lex->query_tables= 0; sp->push_backpatch(i, ctx->push_label((char *)"", 0)); sp->add_instr(i); + sp->restore_lex(YYTHD); } sp_proc_stmts1 { @@ -2226,7 +2237,8 @@ sp_elseifs: ; sp_case: - expr THEN_SYM + { Lex->sphead->reset_lex(YYTHD); } + expr THEN_SYM { LEX *lex= Lex; sp_head *sp= lex->sphead; @@ -2235,7 +2247,7 @@ sp_case: sp_instr_jump_if_not *i; if (! sp->m_simple_case) - i= new sp_instr_jump_if_not(ip, ctx, $1); + i= new sp_instr_jump_if_not(ip, ctx, $2, lex); else { /* Simple case: <caseval> = <whenval> */ LEX_STRING ivar; @@ -2244,15 +2256,14 @@ sp_case: ivar.length= 5; Item *var= (Item*) new Item_splocal(ivar, ctx->current_pvars()-1); - Item *expr= new Item_func_eq(var, $1); + Item *expr= new Item_func_eq(var, $2); - i= new sp_instr_jump_if_not(ip, ctx, expr); + i= new sp_instr_jump_if_not(ip, ctx, expr, lex); lex->variables_used= 1; } sp->push_backpatch(i, ctx->push_label((char *)"", 0)); - i->tables= lex->query_tables; - lex->query_tables= 0; sp->add_instr(i); + sp->restore_lex(YYTHD); } sp_proc_stmts1 { @@ -2368,19 +2379,20 @@ sp_unlabeled_control: lex->sphead->add_instr(i); } - | WHILE_SYM expr DO_SYM + | WHILE_SYM + { Lex->sphead->reset_lex(YYTHD); } + expr DO_SYM { LEX *lex= Lex; sp_head *sp= lex->sphead; uint ip= sp->instructions(); sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont, - $2); + $3, lex); /* Jumping forward */ sp->push_backpatch(i, lex->spcont->last_label()); - i->tables= lex->query_tables; - lex->query_tables= 0; sp->add_instr(i); + sp->restore_lex(YYTHD); } sp_proc_stmts1 END WHILE_SYM { @@ -2391,17 +2403,18 @@ sp_unlabeled_control: lex->sphead->add_instr(i); } - | REPEAT_SYM sp_proc_stmts1 UNTIL_SYM expr END REPEAT_SYM + | REPEAT_SYM sp_proc_stmts1 UNTIL_SYM + { Lex->sphead->reset_lex(YYTHD); } + expr END REPEAT_SYM { LEX *lex= Lex; uint ip= lex->sphead->instructions(); sp_label_t *lab= lex->spcont->last_label(); /* Jumping back */ sp_instr_jump_if_not *i = new sp_instr_jump_if_not(ip, lex->spcont, - $4, lab->ip); - - i->tables= lex->query_tables; - lex->query_tables= 0; + $5, lab->ip, + lex); lex->sphead->add_instr(i); + lex->sphead->restore_lex(YYTHD); } ; @@ -7168,8 +7181,75 @@ opt_option: | OPTION {}; option_value_list: + option_type_value + | option_value_list ',' option_type_value; + +option_type_value: + { + if (Lex->sphead) + { + /* + If we are in SP we want have own LEX for each assignment. + This is mostly because it is hard for several sp_instr_set + and sp_instr_set_trigger instructions share one LEX. + (Well, it is theoretically possible but adds some extra + overhead on preparation for execution stage and IMO less + robust). + + QQ: May be we should simply prohibit group assignments in SP? + */ + LEX *lex; + Lex->sphead->reset_lex(YYTHD); + lex= Lex; + + /* Set new LEX as if we at start of set rule. */ + lex->sql_command= SQLCOM_SET_OPTION; + mysql_init_select(lex); + lex->option_type=OPT_SESSION; + lex->var_list.empty(); + lex->one_shot_set= 0; + lex->sphead->m_tmp_query= lex->tok_start; + } + } option_type option_value - | option_value_list ',' option_type option_value; + { + LEX *lex= Lex; + + if (lex->sphead) + { + sp_head *sp= lex->sphead; + + if (!lex->var_list.is_empty()) + { + /* + We have assignment to user or system variable or + option setting, so we should construct sp_instr_stmt + for it. + */ + LEX_STRING qbuff; + sp_instr_stmt *i; + + if (!(i= new sp_instr_stmt(sp->instructions(), lex->spcont, + lex))) + YYABORT; + + if (lex->ptr - lex->tok_end > 1) + qbuff.length= lex->ptr - sp->m_tmp_query; + else + qbuff.length= lex->tok_end - sp->m_tmp_query; + + if (!(qbuff.str= alloc_root(YYTHD->mem_root, qbuff.length + 5))) + YYABORT; + + strmake(strmake(qbuff.str, "SET ", 4), (char *)sp->m_tmp_query, + qbuff.length); + qbuff.length+= 4; + i->m_query= qbuff; + sp->add_instr(i); + } + lex->sphead->restore_lex(YYTHD); + } + }; option_type: /* empty */ {} @@ -7196,31 +7276,7 @@ opt_var_ident_type: option_value: '@' ident_or_text equal expr { - LEX *lex= Lex; - - if (lex->sphead && lex->sphead->m_type != TYPE_ENUM_PROCEDURE) - { - /* - We have to use special instruction in functions and triggers - because sp_instr_stmt will close all tables and thus ruin - execution of statement invoking function or trigger. - - We also do not want to allow expression with subselects in - this case. - */ - if (lex->query_tables) - { - my_message(ER_SP_SUBSELECT_NYI, ER(ER_SP_SUBSELECT_NYI), - MYF(0)); - YYABORT; - } - sp_instr_set_user_var *i= - new sp_instr_set_user_var(lex->sphead->instructions(), - lex->spcont, $2, $4); - lex->sphead->add_instr(i); - } - else - lex->var_list.push_back(new set_var_user(new Item_func_set_user_var($2,$4))); + Lex->var_list.push_back(new set_var_user(new Item_func_set_user_var($2,$4))); } | internal_variable_name equal set_expr_or_default { @@ -7281,9 +7337,7 @@ option_value: else it= new Item_null(); i= new sp_instr_set(lex->sphead->instructions(), ctx, - spv->offset, it, spv->type); - i->tables= lex->query_tables; - lex->query_tables= 0; + spv->offset, it, spv->type, lex, TRUE); lex->sphead->add_instr(i); spv->isset= TRUE; } diff --git a/sql/table.h b/sql/table.h index 4306d3733c4..49ead2cb0b7 100644 --- a/sql/table.h +++ b/sql/table.h @@ -429,6 +429,11 @@ typedef struct st_table_list /* FRMTYPE_ERROR if any type is acceptable */ enum frm_type_enum required_type; char timestamp_buffer[20]; /* buffer for timestamp (19+1) */ + /* + This TABLE_LIST object is just placeholder for prelocking, it will be + used for implicit LOCK TABLES only and won't be used in real statement. + */ + bool prelocking_placeholder; void calc_md5(char *buffer); void set_ancestor(); diff --git a/sql/tztime.cc b/sql/tztime.cc index b2b3576e221..bd9d49f0ab0 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -1524,7 +1524,6 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) TZ_NAMES_ENTRY *tmp_tzname; my_bool return_val= 1; int res; - uint counter; DBUG_ENTER("my_tz_init"); /* @@ -1593,8 +1592,7 @@ my_tz_init(THD *org_thd, const char *default_tzname, my_bool bootstrap) last_global_next_ptr= &(tables_buff[0].next_global); tz_init_table_list(tables_buff + 1, &last_global_next_ptr); - if (open_tables(thd, tables_buff, &counter) || - lock_tables(thd, tables_buff, counter)) + if (simple_open_n_lock_tables(thd, tables_buff)) { sql_print_warning("Can't open and lock time zone table: %s " "trying to live without them", thd->net.last_error); |