Commit fcad8201 authored by Konstantin Osipov's avatar Konstantin Osipov

Backport of:

----------------------------------------------------------
revno: 2617.69.24
committer: Konstantin Osipov <kostja@sun.com>
branch nick: 5.4-42546
timestamp: Fri 2009-08-14 19:22:05 +0400
message:
  A pre-requisite for a fix for Bug#42546 "Backup: RESTORE fails, thinking it
  finds an existing table"
  Back-port from WL 148 "Foreign keys" feature tree a patch
  that introduced Prelocking_strategy class -- a way to parameterize
  open_tables() behaviour, implemented by Dmitry Lenev.

(Part of WL#4284).

sql/sql_base.cc:
  Implement different prelocking strategies. Use an instance of
  prelocking_strategy in open_tables().
sql/sql_class.h:
  Add declarations for class Prelocking_strategy.
sql/sql_lex.h:
  Add a helper method to access last table of the global table list
  (lex->query_tables).
sql/sql_parse.cc:
  Use a special prelocking strategy when locking tables for LOCK TABLES.
sql/sql_table.cc:
  Use normal open_and_lock_tables_derived() in ALTER TABLE.
sql/sql_yacc.yy:
  Modify the grammar to not pollute the global table list with tables
  that should not be opened.
parent c8f4e972
...@@ -1474,10 +1474,26 @@ int setup_ftfuncs(SELECT_LEX* select); ...@@ -1474,10 +1474,26 @@ int setup_ftfuncs(SELECT_LEX* select);
int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order); int init_ftfuncs(THD *thd, SELECT_LEX* select, bool no_order);
void wait_for_condition(THD *thd, pthread_mutex_t *mutex, void wait_for_condition(THD *thd, pthread_mutex_t *mutex,
pthread_cond_t *cond); pthread_cond_t *cond);
int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags); int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags,
Prelocking_strategy *prelocking_strategy);
inline int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags)
{
DML_prelocking_strategy prelocking_strategy;
return open_tables(thd, tables, counter, flags, &prelocking_strategy);
}
/* open_and_lock_tables with optional derived handling */ /* open_and_lock_tables with optional derived handling */
int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables,
uint flags); bool derived, uint flags,
Prelocking_strategy *prelocking_strategy);
inline int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables,
bool derived, uint flags)
{
DML_prelocking_strategy prelocking_strategy;
return open_and_lock_tables_derived(thd, tables, derived, flags,
&prelocking_strategy);
}
/* simple open_and_lock_tables without derived handling */ /* simple open_and_lock_tables without derived handling */
inline int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables) inline int simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables)
{ {
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
#include "sp.h" #include "sp.h"
#include "sp_head.h" #include "sp_head.h"
#include "sp_cache.h" #include "sp_cache.h"
#include "sql_trigger.h"
#include <my_user.h> #include <my_user.h>
...@@ -1388,32 +1387,6 @@ sp_routine_exists_in_table(THD *thd, int type, sp_name *name) ...@@ -1388,32 +1387,6 @@ sp_routine_exists_in_table(THD *thd, int type, sp_name *name)
} }
/**
Structure that represents element in the set of stored routines
used by statement or routine.
*/
struct Sroutine_hash_entry;
struct Sroutine_hash_entry
{
/**
Set key consisting of one-byte routine type and quoted routine name.
*/
LEX_STRING key;
/**
Next element in list linking all routines in set. See also comments
for LEX::sroutine/sroutine_list and sp_head::m_sroutines.
*/
Sroutine_hash_entry *next;
/**
Uppermost view which directly or indirectly uses this routine.
0 if routine is not used in view. Note that it also can be 0 if
statement uses routine both via view and directly.
*/
TABLE_LIST *belong_to_view;
};
extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen,
my_bool first) my_bool first)
{ {
...@@ -1423,52 +1396,17 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, ...@@ -1423,52 +1396,17 @@ extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen,
} }
/**
Check if
- current statement (the one in thd->lex) needs table prelocking
- first routine in thd->lex->sroutines_list needs to execute its body in
prelocked mode.
@param thd Current thread, thd->lex is the statement to be
checked.
@param[out] need_prelocking TRUE - prelocked mode should be activated
before executing the statement;
FALSE - Don't activate prelocking
@param[out] first_no_prelocking TRUE - Tables used by first routine in
thd->lex->sroutines_list should be
prelocked. FALSE - Otherwise.
@note
This function assumes that for any "CALL proc(...)" statement routines_list
will have 'proc' as first element (it may have several, consider e.g.
"proc(sp_func(...)))". This property is currently guaranted by the parser.
*/
void sp_get_prelocking_info(THD *thd, bool *need_prelocking,
bool *first_no_prelocking)
{
Sroutine_hash_entry *routine;
routine= (Sroutine_hash_entry*)thd->lex->sroutines_list.first;
DBUG_ASSERT(routine);
bool first_is_procedure= (routine->key.str[0] == TYPE_ENUM_PROCEDURE);
*first_no_prelocking= first_is_procedure;
*need_prelocking= !first_is_procedure || test(routine->next);
}
/** /**
Auxilary function that adds new element to the set of stored routines Auxilary function that adds new element to the set of stored routines
used by statement. used by statement.
In case when statement uses stored routines but does not need In case when statement uses stored routines but does not need
prelocking (i.e. it does not use any tables) we will access the prelocking (i.e. it does not use any tables) we will access the
elements of LEX::sroutines set on prepared statement re-execution. elements of Query_tables_list::sroutines set on prepared statement
Because of this we have to allocate memory for both hash element re-execution. Because of this we have to allocate memory for both
and copy of its key in persistent arena. hash element and copy of its key in persistent arena.
@param lex LEX representing statement @param prelocking_ctx Prelocking context of the statement
@param arena Arena in which memory for new element will be @param arena Arena in which memory for new element will be
allocated allocated
@param key Key for the hash representing set @param key Key for the hash representing set
...@@ -1476,7 +1414,7 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking, ...@@ -1476,7 +1414,7 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking,
(0 if routine is not used by view) (0 if routine is not used by view)
@note @note
Will also add element to end of 'LEX::sroutines_list' list. Will also add element to end of 'Query_tables_list::sroutines_list' list.
@todo @todo
When we will got rid of these accesses on re-executions we will be When we will got rid of these accesses on re-executions we will be
...@@ -1491,15 +1429,15 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking, ...@@ -1491,15 +1429,15 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking,
the set). the set).
*/ */
static bool add_used_routine(LEX *lex, Query_arena *arena, bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
const LEX_STRING *key, const LEX_STRING *key, TABLE_LIST *belong_to_view)
TABLE_LIST *belong_to_view)
{ {
my_hash_init_opt(&lex->sroutines, system_charset_info, my_hash_init_opt(&prelocking_ctx->sroutines, system_charset_info,
Query_tables_list::START_SROUTINES_HASH_SIZE, Query_tables_list::START_SROUTINES_HASH_SIZE,
0, 0, sp_sroutine_key, 0, 0); 0, 0, sp_sroutine_key, 0, 0);
if (!my_hash_search(&lex->sroutines, (uchar *)key->str, key->length)) if (!my_hash_search(&prelocking_ctx->sroutines, (uchar *)key->str,
key->length))
{ {
Sroutine_hash_entry *rn= Sroutine_hash_entry *rn=
(Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry) + (Sroutine_hash_entry *)arena->alloc(sizeof(Sroutine_hash_entry) +
...@@ -1509,8 +1447,8 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, ...@@ -1509,8 +1447,8 @@ static bool add_used_routine(LEX *lex, Query_arena *arena,
rn->key.length= key->length; rn->key.length= key->length;
rn->key.str= (char *)rn + sizeof(Sroutine_hash_entry); rn->key.str= (char *)rn + sizeof(Sroutine_hash_entry);
memcpy(rn->key.str, key->str, key->length + 1); memcpy(rn->key.str, key->str, key->length + 1);
my_hash_insert(&lex->sroutines, (uchar *)rn); my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn);
lex->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next); prelocking_ctx->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next);
rn->belong_to_view= belong_to_view; rn->belong_to_view= belong_to_view;
return TRUE; return TRUE;
} }
...@@ -1525,24 +1463,25 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, ...@@ -1525,24 +1463,25 @@ static bool add_used_routine(LEX *lex, Query_arena *arena,
To be friendly towards prepared statements one should pass To be friendly towards prepared statements one should pass
persistent arena as second argument. persistent arena as second argument.
@param lex LEX representing statement @param prelocking_ctx Prelocking context of the statement
@param arena arena in which memory for new element of the set @param arena Arena in which memory for new element of the set
will be allocated will be allocated
@param rt routine name @param rt Routine name
@param rt_type routine type (one of TYPE_ENUM_PROCEDURE/...) @param rt_type Routine type (one of TYPE_ENUM_PROCEDURE/...)
@note @note
Will also add element to end of 'LEX::sroutines_list' list (and will Will also add element to end of 'Query_tables_list::sroutines_list' list
take into account that this is explicitly used routine). (and will take into account that this is an explicitly used routine).
*/ */
void sp_add_used_routine(LEX *lex, Query_arena *arena, void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
sp_name *rt, char rt_type) sp_name *rt, char rt_type)
{ {
rt->set_routine_type(rt_type); rt->set_routine_type(rt_type);
(void)add_used_routine(lex, arena, &rt->m_sroutines_key, 0); (void)sp_add_used_routine(prelocking_ctx, arena, &rt->m_sroutines_key, 0);
lex->sroutines_list_own_last= lex->sroutines_list.next; prelocking_ctx->sroutines_list_own_last= prelocking_ctx->sroutines_list.next;
lex->sroutines_list_own_elements= lex->sroutines_list.elements; prelocking_ctx->sroutines_list_own_elements=
prelocking_ctx->sroutines_list.elements;
} }
...@@ -1550,13 +1489,14 @@ void sp_add_used_routine(LEX *lex, Query_arena *arena, ...@@ -1550,13 +1489,14 @@ void sp_add_used_routine(LEX *lex, Query_arena *arena,
Remove routines which are only indirectly used by statement from Remove routines which are only indirectly used by statement from
the set of routines used by this statement. the set of routines used by this statement.
@param lex LEX representing statement @param prelocking_ctx Prelocking context of the statement
*/ */
void sp_remove_not_own_routines(LEX *lex) void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx)
{ {
Sroutine_hash_entry *not_own_rt, *next_rt; Sroutine_hash_entry *not_own_rt, *next_rt;
for (not_own_rt= *(Sroutine_hash_entry **)lex->sroutines_list_own_last; for (not_own_rt=
*(Sroutine_hash_entry **)prelocking_ctx->sroutines_list_own_last;
not_own_rt; not_own_rt= next_rt) not_own_rt; not_own_rt= next_rt)
{ {
/* /*
...@@ -1564,12 +1504,13 @@ void sp_remove_not_own_routines(LEX *lex) ...@@ -1564,12 +1504,13 @@ void sp_remove_not_own_routines(LEX *lex)
but we want to be more future-proof. but we want to be more future-proof.
*/ */
next_rt= not_own_rt->next; next_rt= not_own_rt->next;
my_hash_delete(&lex->sroutines, (uchar *)not_own_rt); my_hash_delete(&prelocking_ctx->sroutines, (uchar *)not_own_rt);
} }
*(Sroutine_hash_entry **)lex->sroutines_list_own_last= NULL; *(Sroutine_hash_entry **)prelocking_ctx->sroutines_list_own_last= NULL;
lex->sroutines_list.next= lex->sroutines_list_own_last; prelocking_ctx->sroutines_list.next= prelocking_ctx->sroutines_list_own_last;
lex->sroutines_list.elements= lex->sroutines_list_own_elements; prelocking_ctx->sroutines_list.elements=
prelocking_ctx->sroutines_list_own_elements;
} }
...@@ -1605,23 +1546,24 @@ void sp_update_sp_used_routines(HASH *dst, HASH *src) ...@@ -1605,23 +1546,24 @@ void sp_update_sp_used_routines(HASH *dst, HASH *src)
routines used by statement. routines used by statement.
@param thd Thread context @param thd Thread context
@param lex LEX representing statement @param prelocking_ctx Prelocking context of the statement
@param src Hash representing set from which routines will @param src Hash representing set from which routines will
be added be added
@param belong_to_view Uppermost view which uses these routines, 0 if none @param belong_to_view Uppermost view which uses these routines, 0 if none
@note @note It will also add elements to end of
It will also add elements to end of 'LEX::sroutines_list' list. 'Query_tables_list::sroutines_list' list.
*/ */
static void void
sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src, sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *belong_to_view) HASH *src, TABLE_LIST *belong_to_view)
{ {
for (uint i=0 ; i < src->records ; i++) for (uint i=0 ; i < src->records ; i++)
{ {
Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i); Sroutine_hash_entry *rt= (Sroutine_hash_entry *)my_hash_element(src, i);
(void)add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view); (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &rt->key,
belong_to_view);
} }
} }
...@@ -1631,251 +1573,101 @@ sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src, ...@@ -1631,251 +1573,101 @@ sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src,
routines used by statement. routines used by statement.
@param thd Thread context @param thd Thread context
@param lex LEX representing statement @param prelocking_ctx Prelocking context of the statement
@param src List representing set from which routines will @param src List representing set from which routines will
be added be added
@param belong_to_view Uppermost view which uses these routines, 0 if none @param belong_to_view Uppermost view which uses these routines, 0 if none
@note @note It will also add elements to end of
It will also add elements to end of 'LEX::sroutines_list' list. 'Query_tables_list::sroutines_list' list.
*/ */
static void sp_update_stmt_used_routines(THD *thd, LEX *lex, SQL_LIST *src, void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *belong_to_view) SQL_LIST *src, TABLE_LIST *belong_to_view)
{ {
for (Sroutine_hash_entry *rt= (Sroutine_hash_entry *)src->first; for (Sroutine_hash_entry *rt= (Sroutine_hash_entry *)src->first;
rt; rt= rt->next) rt; rt= rt->next)
(void)add_used_routine(lex, thd->stmt_arena, &rt->key, belong_to_view); (void)sp_add_used_routine(prelocking_ctx, thd->stmt_arena, &rt->key,
belong_to_view);
} }
/** /**
Cache sub-set of routines used by statement, add tables used by these Ensure that routine is present in cache by loading it from the mysql.proc
routines to statement table list. Do the same for all routines used table if needed. Emit an appropriate error if there was a problem during
by these routines. loading.
@param thd thread context @param[in] thd Thread context.
@param lex LEX representing statement @param[in] type Type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE).
@param start first routine from the list of routines to be cached @param[in] name Name of routine.
(this list defines mentioned sub-set). @param[out] sp Pointer to sp_head object for routine, NULL if routine was
@param first_no_prelock If true, don't add tables or cache routines used by not found,
the body of the first routine (i.e. *start)
will be executed in non-prelocked mode. @retval 0 Either routine is found and was succesfully loaded into cache
@param tabs_changed Set to TRUE some tables were added, FALSE otherwise or it does not exist.
@retval non-0 Error while loading routine from mysql,proc table.
@note
If some function is missing this won't be reported here.
Instead this fact will be discovered during query execution.
@retval
0 success
@retval
non-0 failure
*/ */
static int int sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp)
sp_cache_routines_and_add_tables_aux(THD *thd, LEX *lex,
Sroutine_hash_entry *start,
bool first_no_prelock)
{ {
int ret= 0; int ret= 0;
bool first= TRUE;
DBUG_ENTER("sp_cache_routines_and_add_tables_aux");
for (Sroutine_hash_entry *rt= start; rt; rt= rt->next) DBUG_ENTER("sp_cache_routine");
{
sp_name name(thd, rt->key.str, rt->key.length);
int type= rt->key.str[0];
sp_head *sp;
/* DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE);
Triggers can't happen here: their bodies are always processed
in sp_cache_routines_and_add_tables_for_triggers().
*/
DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE);
if (!(sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ? if (!(*sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ?
&thd->sp_func_cache : &thd->sp_proc_cache), &thd->sp_func_cache : &thd->sp_proc_cache),
&name))) name)))
{
switch ((ret= db_find_routine(thd, type, name, sp)))
{ {
switch ((ret= db_find_routine(thd, type, &name, &sp))) case SP_OK:
{ if (type == TYPE_ENUM_FUNCTION)
case SP_OK: sp_cache_insert(&thd->sp_func_cache, *sp);
{ else
if (type == TYPE_ENUM_FUNCTION) sp_cache_insert(&thd->sp_proc_cache, *sp);
sp_cache_insert(&thd->sp_func_cache, sp); break;
else case SP_KEY_NOT_FOUND:
sp_cache_insert(&thd->sp_proc_cache, sp); ret= SP_OK;
} break;
break; default:
case SP_KEY_NOT_FOUND: /* Query might have been killed, don't set error. */
ret= SP_OK; if (thd->killed)
break; break;
default: /*
/* Query might have been killed, don't set error. */ Any error when loading an existing routine is either some problem
if (thd->killed) with the mysql.proc table, or a parse error because the contents
break; has been tampered with (in which case we clear that error).
*/
if (ret == SP_PARSE_ERROR)
thd->clear_error();
/*
If we cleared the parse error, or when db_find_routine() flagged
an error with it's return value without calling my_error(), we
set the generic "mysql.proc table corrupt" error here.
*/
if (! thd->is_error())
{
/* /*
Any error when loading an existing routine is either some problem SP allows full NAME_LEN chars thus he have to allocate enough
with the mysql.proc table, or a parse error because the contents size in bytes. Otherwise there is stack overrun could happen
has been tampered with (in which case we clear that error). if multibyte sequence is `name`. `db` is still safe because the
rest of the server checks agains NAME_LEN bytes and not chars.
Hence, the overrun happens only if the name is in length > 32 and
uses multibyte (cyrillic, greek, etc.)
*/ */
if (ret == SP_PARSE_ERROR) char n[NAME_LEN*2+2];
thd->clear_error();
/*
If we cleared the parse error, or when db_find_routine() flagged
an error with it's return value without calling my_error(), we
set the generic "mysql.proc table corrupt" error here.
*/
if (! thd->is_error())
{
/*
SP allows full NAME_LEN chars thus he have to allocate enough
size in bytes. Otherwise there is stack overrun could happen
if multibyte sequence is `name`. `db` is still safe because the
rest of the server checks agains NAME_LEN bytes and not chars.
Hence, the overrun happens only if the name is in length > 32 and
uses multibyte (cyrillic, greek, etc.)
*/
char n[NAME_LEN*2+2];
/* m_qname.str is not always \0 terminated */
memcpy(n, name.m_qname.str, name.m_qname.length);
n[name.m_qname.length]= '\0';
my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret);
}
break;
}
}
if (sp)
{
if (!(first && first_no_prelock))
{
sp_update_stmt_used_routines(thd, lex, &sp->m_sroutines,
rt->belong_to_view);
(void)sp->add_used_tables_to_table_list(thd, &lex->query_tables_last,
rt->belong_to_view);
}
sp->propagate_attributes(lex);
}
first= FALSE;
}
DBUG_RETURN(ret);
}
/**
Cache all routines from the set of used by statement, add tables used
by those routines to statement table list. Do the same for all routines
used by those routines.
@param thd thread context
@param lex LEX representing statement
@param first_no_prelock If true, don't add tables or cache routines used by
the body of the first routine (i.e. *start)
@retval
0 success
@retval
non-0 failure
*/
int
sp_cache_routines_and_add_tables(THD *thd, LEX *lex, bool first_no_prelock)
{
return sp_cache_routines_and_add_tables_aux(thd, lex,
(Sroutine_hash_entry *)lex->sroutines_list.first,
first_no_prelock);
}
/**
Add all routines used by view to the set of routines used by
statement.
Add tables used by those routines to statement table list. Do the same
for all routines used by these routines.
@param thd Thread context
@param lex LEX representing statement
@param view Table list element representing view
@retval /* m_qname.str is not always \0 terminated */
0 success memcpy(n, name->m_qname.str, name->m_qname.length);
@retval n[name->m_qname.length]= '\0';
non-0 failure my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret);
*/
int
sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex, TABLE_LIST *view)
{
Sroutine_hash_entry **last_cached_routine_ptr=
(Sroutine_hash_entry **)lex->sroutines_list.next;
sp_update_stmt_used_routines(thd, lex, &view->view->sroutines_list,
view->top_table());
return sp_cache_routines_and_add_tables_aux(thd, lex,
*last_cached_routine_ptr, FALSE);
}
/**
Add triggers for table to the set of routines used by statement.
Add tables used by them to statement table list. Do the same for
all implicitly used routines.
@param thd thread context
@param lex LEX respresenting statement
@param table Table list element for table with trigger
@retval
0 success
@retval
non-0 failure
*/
int
sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex,
TABLE_LIST *table)
{
if (static_cast<int>(table->lock_type) >=
static_cast<int>(TL_WRITE_ALLOW_WRITE))
{
for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
{
if (table->trg_event_map &
static_cast<uint8>(1 << static_cast<int>(i)))
{
for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
{
/* We can have only one trigger per action type currently */
sp_head *trigger= table->table->triggers->bodies[i][j];
if (trigger &&
add_used_routine(lex, thd->stmt_arena, &trigger->m_sroutines_key,
table->belong_to_view))
{
int ret;
/* Sic: excludes the trigger key from processing */
Sroutine_hash_entry **last_cached_routine_ptr=
(Sroutine_hash_entry **)lex->sroutines_list.next;
trigger->add_used_tables_to_table_list(thd, &lex->query_tables_last,
table->belong_to_view);
trigger->propagate_attributes(lex);
sp_update_stmt_used_routines(thd, lex,
&trigger->m_sroutines,
table->belong_to_view);
ret= sp_cache_routines_and_add_tables_aux(thd, lex,
*last_cached_routine_ptr,
FALSE);
if (ret)
return ret;
}
}
} }
break;
} }
} }
return 0; DBUG_RETURN(ret);
} }
......
...@@ -42,6 +42,9 @@ sp_head * ...@@ -42,6 +42,9 @@ sp_head *
sp_find_routine(THD *thd, int type, sp_name *name, sp_find_routine(THD *thd, int type, sp_name *name,
sp_cache **cp, bool cache_only); sp_cache **cp, bool cache_only);
int
sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp);
bool bool
sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any); sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any);
...@@ -60,22 +63,45 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics); ...@@ -60,22 +63,45 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics);
int int
sp_drop_routine(THD *thd, int type, sp_name *name); sp_drop_routine(THD *thd, int type, sp_name *name);
/**
Structure that represents element in the set of stored routines
used by statement or routine.
*/
struct Sroutine_hash_entry
{
/**
Set key consisting of one-byte routine type and quoted routine name.
*/
LEX_STRING key;
/**
Next element in list linking all routines in set. See also comments
for LEX::sroutine/sroutine_list and sp_head::m_sroutines.
*/
Sroutine_hash_entry *next;
/**
Uppermost view which directly or indirectly uses this routine.
0 if routine is not used in view. Note that it also can be 0 if
statement uses routine both via view and directly.
*/
TABLE_LIST *belong_to_view;
};
/* /*
Procedures for pre-caching of stored routines and building table list Procedures for handling sets of stored routines used by statement or routine.
for prelocking.
*/ */
void sp_get_prelocking_info(THD *thd, bool *need_prelocking, void sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
bool *first_no_prelocking);
void sp_add_used_routine(LEX *lex, Query_arena *arena,
sp_name *rt, char rt_type); sp_name *rt, char rt_type);
void sp_remove_not_own_routines(LEX *lex); bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
const LEX_STRING *key, TABLE_LIST *belong_to_view);
void sp_remove_not_own_routines(Query_tables_list *prelocking_ctx);
void sp_update_sp_used_routines(HASH *dst, HASH *src); void sp_update_sp_used_routines(HASH *dst, HASH *src);
int sp_cache_routines_and_add_tables(THD *thd, LEX *lex, void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
bool first_no_prelock); HASH *src, TABLE_LIST *belong_to_view);
int sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex, void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *view); SQL_LIST *src, TABLE_LIST *belong_to_view);
int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex,
TABLE_LIST *table);
extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen, extern "C" uchar* sp_sroutine_key(const uchar *ptr, size_t *plen,
my_bool first); my_bool first);
......
...@@ -454,10 +454,10 @@ class sp_head :private Query_arena ...@@ -454,10 +454,10 @@ class sp_head :private Query_arena
/* /*
This method is intended for attributes of a routine which need This method is intended for attributes of a routine which need
to propagate upwards to the LEX of the caller (when a property of a to propagate upwards to the Query_tables_list of the caller (when
sp_head needs to "taint" the caller). a property of a sp_head needs to "taint" the calling statement).
*/ */
void propagate_attributes(LEX *lex) void propagate_attributes(Query_tables_list *prelocking_ctx)
{ {
/* /*
If this routine needs row-based binary logging, the entire top statement If this routine needs row-based binary logging, the entire top statement
...@@ -466,7 +466,7 @@ class sp_head :private Query_arena ...@@ -466,7 +466,7 @@ class sp_head :private Query_arena
the substatements not). the substatements not).
*/ */
if (m_flags & BINLOG_ROW_BASED_IF_MIXED) if (m_flags & BINLOG_ROW_BASED_IF_MIXED)
lex->set_stmt_unsafe(); prelocking_ctx->set_stmt_unsafe();
} }
......
...@@ -3714,34 +3714,99 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table) ...@@ -3714,34 +3714,99 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table)
/* /*
Perform steps of prelocking algorithm for elements of the
prelocking set other than tables. E.g. cache routines and, if
prelocking strategy prescribes so, extend the prelocking set with
tables and routines used by them.
@param[in] thd Thread context.
@param[in] prelocking_ctx Prelocking context.
@param[in] start First element in the list representing
subset of the prelocking set to be
processed.
@param[in] prelocking_strategy Strategy which specifies how the
prelocking set should be extended when
one of its elements is processed.
@param[out] need_prelocking Set to TRUE if it was detected that this
statement will require prelocked mode for
its execution, not touched otherwise.
@retval FALSE Success.
@retval TRUE Failure (Conflicting metadata lock, OOM, other errors).
*/
static bool
open_routines(THD *thd, Query_tables_list *prelocking_ctx,
Sroutine_hash_entry *start,
Prelocking_strategy *prelocking_strategy,
bool *need_prelocking)
{
DBUG_ENTER("open_routines");
for (Sroutine_hash_entry *rt= start; rt; rt= rt->next)
{
int type= rt->key.str[0];
switch (type)
{
case TYPE_ENUM_FUNCTION:
case TYPE_ENUM_PROCEDURE:
{
sp_name name(thd, rt->key.str, rt->key.length);
sp_head *sp;
if (sp_cache_routine(thd, type, &name, &sp))
DBUG_RETURN(TRUE);
if (sp)
{
prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp,
need_prelocking);
}
}
break;
case TYPE_ENUM_TRIGGER:
break;
default:
/* Impossible type value. */
DBUG_ASSERT(0);
}
}
DBUG_RETURN(FALSE);
}
/**
Open all tables in list Open all tables in list
SYNOPSIS @param[in] thd Thread context.
open_tables() @param[in,out] start List of tables to be open (it can be adjusted for
thd - thread handler statement that uses tables only implicitly, e.g.
start - list of tables in/out for "SELECT f1()").
counter - number of opened tables will be return using this parameter @param[out] counter Number of tables which were open.
flags - bitmap of flags to modify how the tables will be open: @param[in] flags Bitmap of flags to modify how the tables will be
MYSQL_LOCK_IGNORE_FLUSH - open table even if someone has open, see open_table() description for details.
done a flush or namelock on it. @param[in] prelocking_strategy Strategy which specifies how prelocking
algorithm should work for this statement.
NOTE @note
Unless we are already in prelocked mode, this function will also precache Unless we are already in prelocked mode and prelocking strategy prescribes
all SP/SFs explicitly or implicitly (via views and triggers) used by the so this function will also precache all SP/SFs explicitly or implicitly
query and add tables needed for their execution to table list. If resulting (via views and triggers) used by the query and add tables needed for their
tables list will be non empty it will mark query as requiring precaching. execution to table list. Statement that uses SFs, invokes triggers or
requires foreign key checks will be marked as requiring prelocking.
Prelocked mode will be enabled for such query during lock_tables() call. 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 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 prelocking it won't do such precaching and will simply reuse table list
which is already built. which is already built.
RETURN @retval 0 OK
0 - OK @retval -1 Error.
-1 - error
*/ */
int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
Prelocking_strategy *prelocking_strategy)
{ {
TABLE_LIST *tables= NULL; TABLE_LIST *tables= NULL;
Open_table_context ot_ctx(thd); Open_table_context ot_ctx(thd);
...@@ -3786,13 +3851,14 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) ...@@ -3786,13 +3851,14 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
!thd->lex->requires_prelocking() && !thd->lex->requires_prelocking() &&
thd->lex->uses_stored_routines()) thd->lex->uses_stored_routines())
{ {
bool first_no_prelocking, need_prelocking; bool need_prelocking= FALSE;
TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last; TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
DBUG_ASSERT(thd->lex->query_tables == *start); DBUG_ASSERT(thd->lex->query_tables == *start);
sp_get_prelocking_info(thd, &need_prelocking, &first_no_prelocking);
if (sp_cache_routines_and_add_tables(thd, thd->lex, first_no_prelocking)) if (open_routines(thd, thd->lex,
(Sroutine_hash_entry *)thd->lex->sroutines_list.first,
prelocking_strategy, &need_prelocking))
{ {
/* /*
Serious error during reading stored routines from mysql.proc table. Serious error during reading stored routines from mysql.proc table.
...@@ -3973,27 +4039,54 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) ...@@ -3973,27 +4039,54 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
/* /*
If we are not already in prelocked mode and extended table list is not 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 yet built we might have to build the prelocking set for this statement.
cache all routines used by its triggers and add their tables to
prelocking list. Since currently no prelocking strategy prescribes doing anything for
If we lock table for reading we won't update it so there is no need to tables which are only read, we do below checks only if table is going
process its triggers since they never will be activated. to be changed.
*/ */
if (thd->locked_tables_mode <= LTM_LOCK_TABLES && if (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
!thd->lex->requires_prelocking() && !thd->lex->requires_prelocking() &&
tables->trg_event_map && tables->table->triggers &&
tables->lock_type >= TL_WRITE_ALLOW_WRITE) tables->lock_type >= TL_WRITE_ALLOW_WRITE)
{ {
if (!query_tables_last_own) bool need_prelocking= FALSE;
query_tables_last_own= thd->lex->query_tables_last; bool not_used;
if (sp_cache_routines_and_add_tables_for_triggers(thd, thd->lex, TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
tables)) Sroutine_hash_entry **sroutines_next=
(Sroutine_hash_entry **)thd->lex->sroutines_list.next;
/*
Extend statement's table list and the prelocking set with
tables and routines according to the current prelocking
strategy.
For example, for DML statements we need to add tables and routines
used by triggers which are going to be invoked for this element of
table list and also add tables required for handling of foreign keys.
*/
error= prelocking_strategy->handle_table(thd, thd->lex, tables,
&need_prelocking);
if (need_prelocking && ! query_tables_last_own)
query_tables_last_own= save_query_tables_last;
if (error)
{
result= -1;
goto err;
}
/*
Process elements of the prelocking set which were added
by the above invocation of Prelocking_strategy method.
For example, if new element is a routine, cache it and then, if
prelocking strategy prescribes so, add tables it uses to the table
list and routines it might invoke to the prelocking set.
*/
if (open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy,
&not_used))
{ {
/*
Serious error during reading stored routines from mysql.proc table.
Something's wrong with the table or its contents, and an error has
been emitted; we must abort.
*/
result= -1; result= -1;
goto err; goto err;
} }
...@@ -4039,13 +4132,27 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) ...@@ -4039,13 +4132,27 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
*/ */
if (tables->view && if (tables->view &&
thd->locked_tables_mode <= LTM_LOCK_TABLES && thd->locked_tables_mode <= LTM_LOCK_TABLES &&
!thd->lex->requires_prelocking() && !thd->lex->requires_prelocking())
tables->view->uses_stored_routines())
{ {
/* We have at least one table in TL here. */ bool need_prelocking= FALSE;
if (!query_tables_last_own) bool not_used;
query_tables_last_own= thd->lex->query_tables_last; TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
if (sp_cache_routines_and_add_tables_for_view(thd, thd->lex, tables)) Sroutine_hash_entry **sroutines_next=
(Sroutine_hash_entry **)thd->lex->sroutines_list.next;
error= prelocking_strategy->handle_view(thd, thd->lex, tables,
&need_prelocking);
if (need_prelocking && ! query_tables_last_own)
query_tables_last_own= save_query_tables_last;
if (error)
{
result= -1;
goto err;
}
if (open_routines(thd, thd->lex, *sroutines_next, prelocking_strategy,
&not_used))
{ {
/* /*
Serious error during reading stored routines from mysql.proc table. Serious error during reading stored routines from mysql.proc table.
...@@ -4097,6 +4204,220 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags) ...@@ -4097,6 +4204,220 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
} }
/**
Defines how prelocking algorithm for DML statements should handle routines:
- For CALL statements we do unrolling (i.e. open and lock tables for each
sub-statement individually). So for such statements prelocking is enabled
only if stored functions are used in parameter list and only for period
during which we calculate values of parameters. Thus in this strategy we
ignore procedure which is directly called by such statement and extend
the prelocking set only with tables/functions used by SF called from the
parameter list.
- For any other statement any routine which is directly or indirectly called
by statement is going to be executed in prelocked mode. So in this case we
simply add all tables and routines used by it to the prelocking set.
@param[in] thd Thread context.
@param[in] prelocking_ctx Prelocking context of the statement.
@param[in] rt Prelocking set element describing routine.
@param[in] sp Routine body.
@param[out] need_prelocking Set to TRUE if method detects that prelocking
required, not changed otherwise.
@retval FALSE Success.
@retval TRUE Failure (OOM).
*/
bool DML_prelocking_strategy::
handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking)
{
/*
We assume that for any "CALL proc(...)" statement sroutines_list will
have 'proc' as first element (it may have several, consider e.g.
"proc(sp_func(...)))". This property is currently guaranted by the
parser.
*/
if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first ||
rt->key.str[0] != TYPE_ENUM_PROCEDURE)
{
*need_prelocking= TRUE;
sp_update_stmt_used_routines(thd, prelocking_ctx, &sp->m_sroutines,
rt->belong_to_view);
(void)sp->add_used_tables_to_table_list(thd,
&prelocking_ctx->query_tables_last,
rt->belong_to_view);
}
sp->propagate_attributes(prelocking_ctx);
return FALSE;
}
/**
Defines how prelocking algorithm for DML statements should handle table list
elements:
- If table has triggers we should add all tables and routines
used by them to the prelocking set.
We do not need to acquire metadata locks on trigger names
in DML statements, since all DDL statements
that change trigger metadata always lock their
subject tables.
@param[in] thd Thread context.
@param[in] prelocking_ctx Prelocking context of the statement.
@param[in] table_list Table list element for table.
@param[in] sp Routine body.
@param[out] need_prelocking Set to TRUE if method detects that prelocking
required, not changed otherwise.
@retval FALSE Success.
@retval TRUE Failure (OOM).
*/
bool DML_prelocking_strategy::
handle_table(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking)
{
/* We rely on a caller to check that table is going to be changed. */
DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE);
if (table_list->trg_event_map)
{
if (table_list->table->triggers)
{
*need_prelocking= TRUE;
if (table_list->table->triggers->
add_tables_and_routines_for_triggers(thd, prelocking_ctx, table_list))
return TRUE;
}
}
return FALSE;
}
/**
Defines how prelocking algorithm for DML statements should handle view -
all view routines should be added to the prelocking set.
@param[in] thd Thread context.
@param[in] prelocking_ctx Prelocking context of the statement.
@param[in] table_list Table list element for view.
@param[in] sp Routine body.
@param[out] need_prelocking Set to TRUE if method detects that prelocking
required, not changed otherwise.
@retval FALSE Success.
@retval TRUE Failure (OOM).
*/
bool DML_prelocking_strategy::
handle_view(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking)
{
if (table_list->view->uses_stored_routines())
{
*need_prelocking= TRUE;
sp_update_stmt_used_routines(thd, prelocking_ctx,
&table_list->view->sroutines_list,
table_list->top_table());
}
return FALSE;
}
/**
Defines how prelocking algorithm for LOCK TABLES statement should handle
table list elements.
@param[in] thd Thread context.
@param[in] prelocking_ctx Prelocking context of the statement.
@param[in] table_list Table list element for table.
@param[in] sp Routine body.
@param[out] need_prelocking Set to TRUE if method detects that prelocking
required, not changed otherwise.
@retval FALSE Success.
@retval TRUE Failure (OOM).
*/
bool Lock_tables_prelocking_strategy::
handle_table(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking)
{
if (DML_prelocking_strategy::handle_table(thd, prelocking_ctx, table_list,
need_prelocking))
return TRUE;
/* We rely on a caller to check that table is going to be changed. */
DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE);
return FALSE;
}
/**
Defines how prelocking algorithm for ALTER TABLE statement should handle
routines - do nothing as this statement is not supposed to call routines.
We still can end up in this method when someone tries
to define a foreign key referencing a view, and not just
a simple view, but one that uses stored routines.
*/
bool Alter_table_prelocking_strategy::
handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
Sroutine_hash_entry *rt, sp_head *sp, bool *need_prelocking)
{
return FALSE;
}
/**
Defines how prelocking algorithm for ALTER TABLE statement should handle
table list elements.
Unlike in DML, we do not process triggers here.
@param[in] thd Thread context.
@param[in] prelocking_ctx Prelocking context of the statement.
@param[in] table_list Table list element for table.
@param[in] sp Routine body.
@param[out] need_prelocking Set to TRUE if method detects that prelocking
required, not changed otherwise.
@retval FALSE Success.
@retval TRUE Failure (OOM).
*/
bool Alter_table_prelocking_strategy::
handle_table(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking)
{
return FALSE;
}
/**
Defines how prelocking algorithm for ALTER TABLE statement
should handle view - do nothing. We don't need to add view
routines to the prelocking set in this case as view is not going
to be materialized.
*/
bool Alter_table_prelocking_strategy::
handle_view(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking)
{
return FALSE;
}
/* /*
Check that lock is ok for tables; Call start stmt if ok Check that lock is ok for tables; Call start stmt if ok
...@@ -4312,34 +4633,35 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, ...@@ -4312,34 +4633,35 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
} }
/* /**
Open all tables in list, locks them and optionally process derived tables. Open all tables in list, locks them and optionally process derived tables.
SYNOPSIS @param thd Thread context.
open_and_lock_tables_derived() @param tables List of tables for open and locking.
thd - thread handler @param derived If to handle derived tables.
tables - list of tables for open&locking @param flags Bitmap of options to be used to open and lock
flags - set of options to be used to open and lock tables (see tables (see open_tables() and mysql_lock_tables()
open_tables() and mysql_lock_tables() for details). for details).
derived - if to handle derived tables @param prelocking_strategy Strategy which specifies how prelocking algorithm
should work for this statement.
RETURN
FALSE - ok
TRUE - error
NOTE @note
The lock will automaticaly be freed by close_thread_tables() The lock will automaticaly be freed by close_thread_tables()
NOTE @note
There are two convenience functions: There are several convenience functions, e.g. :
- simple_open_n_lock_tables(thd, tables) without derived handling - simple_open_n_lock_tables(thd, tables) without derived handling
- open_and_lock_tables(thd, tables) with derived handling - open_and_lock_tables(thd, tables) with derived handling
Both inline functions call open_and_lock_tables_derived() with Both inline functions call open_and_lock_tables_derived() with
the third argument set appropriately. the third argument set appropriately.
@retval FALSE OK.
@retval TRUE Error
*/ */
int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables,
uint flags) bool derived, uint flags,
Prelocking_strategy *prelocking_strategy)
{ {
uint counter; uint counter;
bool need_reopen; bool need_reopen;
...@@ -4349,7 +4671,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived, ...@@ -4349,7 +4671,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived,
for ( ; ; ) for ( ; ; )
{ {
if (open_tables(thd, &tables, &counter, flags)) if (open_tables(thd, &tables, &counter, flags, prelocking_strategy))
DBUG_RETURN(-1); DBUG_RETURN(-1);
DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", { DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", {
......
...@@ -38,6 +38,7 @@ class sp_rcontext; ...@@ -38,6 +38,7 @@ class sp_rcontext;
class sp_cache; class sp_cache;
class Parser_state; class Parser_state;
class Rows_log_event; class Rows_log_event;
class Sroutine_hash_entry;
enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE }; enum enum_enable_or_disable { LEAVE_AS_IS, ENABLE, DISABLE };
enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY, RNEXT_SAME }; enum enum_ha_read_modes { RFIRST, RNEXT, RPREV, RLAST, RKEY, RNEXT_SAME };
...@@ -1181,6 +1182,89 @@ class Drop_table_error_handler : public Internal_error_handler ...@@ -1181,6 +1182,89 @@ class Drop_table_error_handler : public Internal_error_handler
}; };
/**
An abstract class for a strategy specifying how the prelocking
algorithm should extend the prelocking set while processing
already existing elements in the set.
*/
class Prelocking_strategy
{
public:
virtual ~Prelocking_strategy() { }
virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
Sroutine_hash_entry *rt, sp_head *sp,
bool *need_prelocking) = 0;
virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking) = 0;
virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking)= 0;
};
/**
A Strategy for prelocking algorithm suitable for DML statements.
Ensures that all tables used by all statement's SF/SP/triggers and
required for foreign key checks are prelocked and SF/SPs used are
cached.
*/
class DML_prelocking_strategy : public Prelocking_strategy
{
public:
virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
Sroutine_hash_entry *rt, sp_head *sp,
bool *need_prelocking);
virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking);
virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking);
};
/**
A strategy for prelocking algorithm to be used for LOCK TABLES
statement.
*/
class Lock_tables_prelocking_strategy : public DML_prelocking_strategy
{
virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking);
};
/**
Strategy for prelocking algorithm to be used for ALTER TABLE statements.
Unlike DML or LOCK TABLES strategy, it doesn't
prelock triggers, views or stored routines, since they are not
used during ALTER.
*/
class Alter_table_prelocking_strategy : public Prelocking_strategy
{
public:
Alter_table_prelocking_strategy(Alter_info *alter_info)
: m_alter_info(alter_info)
{}
virtual bool handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
Sroutine_hash_entry *rt, sp_head *sp,
bool *need_prelocking);
virtual bool handle_table(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking);
virtual bool handle_view(THD *thd, Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list, bool *need_prelocking);
private:
Alter_info *m_alter_info;
};
/** /**
A context of open_tables() function, used to recover A context of open_tables() function, used to recover
from a failed open_table() attempt. from a failed open_table() attempt.
......
...@@ -1052,6 +1052,17 @@ class Query_tables_list ...@@ -1052,6 +1052,17 @@ class Query_tables_list
} }
} }
/** Return a pointer to the last element in query table list. */
TABLE_LIST *last_table()
{
/* Don't use offsetof() macro in order to avoid warnings. */
return query_tables ?
(TABLE_LIST*) ((char*) query_tables_last -
((char*) &(query_tables->next_global) -
(char*) query_tables)) :
0;
}
/** /**
Has the parser/scanner detected that this statement is unsafe? Has the parser/scanner detected that this statement is unsafe?
*/ */
...@@ -1751,6 +1762,12 @@ struct LEX: public Query_tables_list ...@@ -1751,6 +1762,12 @@ struct LEX: public Query_tables_list
bool subqueries, ignore; bool subqueries, ignore;
st_parsing_options parsing_options; st_parsing_options parsing_options;
Alter_info alter_info; Alter_info alter_info;
/*
For CREATE TABLE statement last element of table list which is not
part of SELECT or LIKE part (i.e. either element for table we are
creating or last of tables referenced by foreign keys).
*/
TABLE_LIST *create_last_non_select_table;
/* Prepared statements SQL syntax:*/ /* Prepared statements SQL syntax:*/
LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */ LEX_STRING prepared_stmt_name; /* Statement name (in all queries) */
/* /*
......
...@@ -3342,9 +3342,14 @@ case SQLCOM_PREPARE: ...@@ -3342,9 +3342,14 @@ case SQLCOM_PREPARE:
thd->options|= OPTION_TABLE_LOCK; thd->options|= OPTION_TABLE_LOCK;
thd->in_lock_tables=1; thd->in_lock_tables=1;
res= (open_and_lock_tables_derived(thd, all_tables, FALSE, {
MYSQL_OPEN_TAKE_UPGRADABLE_MDL) || Lock_tables_prelocking_strategy lock_tables_prelocking_strategy;
thd->locked_tables_list.init_locked_tables(thd));
res= (open_and_lock_tables_derived(thd, all_tables, FALSE,
MYSQL_OPEN_TAKE_UPGRADABLE_MDL,
&lock_tables_prelocking_strategy) ||
thd->locked_tables_list.init_locked_tables(thd));
}
thd->in_lock_tables= 0; thd->in_lock_tables= 0;
......
...@@ -6560,9 +6560,26 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6560,9 +6560,26 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
DBUG_RETURN(error); DBUG_RETURN(error);
} }
if (!(table= open_n_lock_single_table(thd, table_list, TL_WRITE_ALLOW_READ,
MYSQL_OPEN_TAKE_UPGRADABLE_MDL))) /*
Code below can handle only base tables so ensure that we won't open a view.
Note that RENAME TABLE the only ALTER clause which is supported for views
has been already processed.
*/
table_list->required_type= FRMTYPE_TABLE;
Alter_table_prelocking_strategy alter_prelocking_strategy(alter_info);
error= open_and_lock_tables_derived(thd, table_list, FALSE,
MYSQL_OPEN_TAKE_UPGRADABLE_MDL,
&alter_prelocking_strategy);
if (error)
{
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
}
table= table_list->table;
table->use_all_columns(); table->use_all_columns();
mdl_ticket= table->mdl_ticket; mdl_ticket= table->mdl_ticket;
...@@ -6572,7 +6589,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6572,7 +6589,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
set of tables from the old table or to open a new TABLE object for set of tables from the old table or to open a new TABLE object for
an extended list and verify that they belong to locked tables. an extended list and verify that they belong to locked tables.
*/ */
if (thd->locked_tables_mode && if ((thd->locked_tables_mode == LTM_LOCK_TABLES ||
thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
(create_info->used_fields & HA_CREATE_USED_UNION) && (create_info->used_fields & HA_CREATE_USED_UNION) &&
(table->s->tmp_table == NO_TMP_TABLE)) (table->s->tmp_table == NO_TMP_TABLE))
{ {
...@@ -6806,7 +6824,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -6806,7 +6824,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
table_list->table= NULL; // For query cache table_list->table= NULL; // For query cache
query_cache_invalidate3(thd, table_list, 0); query_cache_invalidate3(thd, table_list, 0);
if (thd->locked_tables_mode) if ((thd->locked_tables_mode == LTM_LOCK_TABLES ||
thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES))
{ {
/* /*
Under LOCK TABLES we should adjust meta-data locks before finishing Under LOCK TABLES we should adjust meta-data locks before finishing
...@@ -7290,7 +7309,9 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -7290,7 +7309,9 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
if (table->s->tmp_table != NO_TMP_TABLE) if (table->s->tmp_table != NO_TMP_TABLE)
{ {
/* Close lock if this is a transactional table */ /* Close lock if this is a transactional table */
if (thd->lock && ! thd->locked_tables_mode) if (thd->lock &&
! (thd->locked_tables_mode == LTM_LOCK_TABLES ||
thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES))
{ {
mysql_unlock_tables(thd, thd->lock); mysql_unlock_tables(thd, thd->lock);
thd->lock=0; thd->lock=0;
...@@ -7492,7 +7513,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ...@@ -7492,7 +7513,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
table_list->table=0; // For query cache table_list->table=0; // For query cache
query_cache_invalidate3(thd, table_list, 0); query_cache_invalidate3(thd, table_list, 0);
if (thd->locked_tables_mode) if (thd->locked_tables_mode == LTM_LOCK_TABLES ||
thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
{ {
if ((new_name != table_name || new_db != db)) if ((new_name != table_name || new_db != db))
{ {
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "sp_head.h" #include "sp_head.h"
#include "sql_trigger.h" #include "sql_trigger.h"
#include "parse_file.h" #include "parse_file.h"
#include "sp.h"
/*************************************************************************/ /*************************************************************************/
...@@ -2017,6 +2018,57 @@ bool Table_triggers_list::process_triggers(THD *thd, ...@@ -2017,6 +2018,57 @@ bool Table_triggers_list::process_triggers(THD *thd,
} }
/**
Add triggers for table to the set of routines used by statement.
Add tables used by them to statement table list. Do the same for
routines used by triggers.
@param thd Thread context.
@param prelocking_ctx Prelocking context of the statement.
@param table_list Table list element for table with trigger.
@retval FALSE Success.
@retval TRUE Failure.
*/
bool
Table_triggers_list::
add_tables_and_routines_for_triggers(THD *thd,
Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list)
{
DBUG_ASSERT(static_cast<int>(table_list->lock_type) >=
static_cast<int>(TL_WRITE_ALLOW_WRITE));
for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
{
if (table_list->trg_event_map &
static_cast<uint8>(1 << static_cast<int>(i)))
{
for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
{
/* We can have only one trigger per action type currently */
sp_head *trigger= table_list->table->triggers->bodies[i][j];
if (trigger && sp_add_used_routine(prelocking_ctx, thd->stmt_arena,
&trigger->m_sroutines_key,
table_list->belong_to_view))
{
trigger->add_used_tables_to_table_list(thd,
&prelocking_ctx->query_tables_last,
table_list->belong_to_view);
sp_update_stmt_used_routines(thd, prelocking_ctx,
&trigger->m_sroutines,
table_list->belong_to_view);
trigger->propagate_attributes(prelocking_ctx);
}
}
}
}
return FALSE;
}
/** /**
Mark fields of subject table which we read/set in its triggers Mark fields of subject table which we read/set in its triggers
as such. as such.
......
...@@ -144,8 +144,10 @@ class Table_triggers_list: public Sql_alloc ...@@ -144,8 +144,10 @@ class Table_triggers_list: public Sql_alloc
void mark_fields_used(trg_event_type event); void mark_fields_used(trg_event_type event);
friend class Item_trigger_field; friend class Item_trigger_field;
friend int sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex,
TABLE_LIST *table); bool add_tables_and_routines_for_triggers(THD *thd,
Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list);
private: private:
bool prepare_record1_accessors(TABLE *table); bool prepare_record1_accessors(TABLE *table);
......
...@@ -1766,6 +1766,7 @@ create: ...@@ -1766,6 +1766,7 @@ create:
lex->create_info.default_table_charset= NULL; lex->create_info.default_table_charset= NULL;
lex->name.str= 0; lex->name.str= 0;
lex->name.length= 0; lex->name.length= 0;
lex->create_last_non_select_table= lex->last_table();
} }
create2 create2
{ {
...@@ -1788,7 +1789,8 @@ create: ...@@ -1788,7 +1789,8 @@ create:
lex->sql_command= SQLCOM_CREATE_INDEX; lex->sql_command= SQLCOM_CREATE_INDEX;
if (!lex->current_select->add_table_to_list(lex->thd, $7, if (!lex->current_select->add_table_to_list(lex->thd, $7,
NULL, NULL,
TL_OPTION_UPDATING)) TL_OPTION_UPDATING,
TL_WRITE_ALLOW_READ))
MYSQL_YYABORT; MYSQL_YYABORT;
lex->alter_info.reset(); lex->alter_info.reset();
lex->alter_info.flags= ALTER_ADD_INDEX; lex->alter_info.flags= ALTER_ADD_INDEX;
...@@ -3952,7 +3954,7 @@ create2: ...@@ -3952,7 +3954,7 @@ create2:
; ;
create2a: create2a:
field_list ')' opt_create_table_options create_field_list ')' opt_create_table_options
opt_partitioning opt_partitioning
create3 {} create3 {}
| opt_partitioning | opt_partitioning
...@@ -4802,19 +4804,30 @@ create_table_option: ...@@ -4802,19 +4804,30 @@ create_table_option:
Lex->create_info.row_type= $3; Lex->create_info.row_type= $3;
Lex->create_info.used_fields|= HA_CREATE_USED_ROW_FORMAT; Lex->create_info.used_fields|= HA_CREATE_USED_ROW_FORMAT;
} }
| UNION_SYM opt_equal '(' opt_table_list ')' | UNION_SYM opt_equal
{
Lex->select_lex.table_list.save_and_clear(&Lex->save_list);
}
'(' opt_table_list ')'
{ {
/* Move the union list to the merge_list */ /*
Move the union list to the merge_list and exclude its tables
from the global list.
*/
LEX *lex=Lex; LEX *lex=Lex;
TABLE_LIST *table_list= lex->select_lex.get_table_list();
lex->create_info.merge_list= lex->select_lex.table_list; lex->create_info.merge_list= lex->select_lex.table_list;
lex->create_info.merge_list.elements--; lex->select_lex.table_list= lex->save_list;
lex->create_info.merge_list.first= /*
(uchar*) (table_list->next_local); When excluding union list from the global list we assume that
lex->select_lex.table_list.elements=1; elements of the former immediately follow elements which represent
lex->select_lex.table_list.next= table being created/altered and parent tables.
(uchar**) &(table_list->next_local); */
table_list->next_local= 0; TABLE_LIST *last_non_sel_table= lex->create_last_non_select_table;
DBUG_ASSERT(last_non_sel_table->next_global ==
(TABLE_LIST *)lex->create_info.merge_list.first);
last_non_sel_table->next_global= 0;
Lex->query_tables_last= &last_non_sel_table->next_global;
lex->create_info.used_fields|= HA_CREATE_USED_UNION; lex->create_info.used_fields|= HA_CREATE_USED_UNION;
} }
| default_charset | default_charset
...@@ -4952,6 +4965,14 @@ udf_type: ...@@ -4952,6 +4965,14 @@ udf_type:
| INT_SYM {$$ = (int) INT_RESULT; } | INT_SYM {$$ = (int) INT_RESULT; }
; ;
create_field_list:
field_list
{
Lex->create_last_non_select_table= Lex->last_table();
}
;
field_list: field_list:
field_list_item field_list_item
| field_list ',' field_list_item | field_list ',' field_list_item
...@@ -5743,7 +5764,8 @@ alter: ...@@ -5743,7 +5764,8 @@ alter:
lex->sql_command= SQLCOM_ALTER_TABLE; lex->sql_command= SQLCOM_ALTER_TABLE;
lex->duplicates= DUP_ERROR; lex->duplicates= DUP_ERROR;
if (!lex->select_lex.add_table_to_list(thd, $4, NULL, if (!lex->select_lex.add_table_to_list(thd, $4, NULL,
TL_OPTION_UPDATING)) TL_OPTION_UPDATING,
TL_WRITE_ALLOW_READ))
MYSQL_YYABORT; MYSQL_YYABORT;
lex->col_list.empty(); lex->col_list.empty();
lex->select_lex.init_order(); lex->select_lex.init_order();
...@@ -5756,6 +5778,7 @@ alter: ...@@ -5756,6 +5778,7 @@ alter:
lex->alter_info.reset(); lex->alter_info.reset();
lex->no_write_to_binlog= 0; lex->no_write_to_binlog= 0;
lex->create_info.storage_media= HA_SM_DEFAULT; lex->create_info.storage_media= HA_SM_DEFAULT;
lex->create_last_non_select_table= lex->last_table();
} }
alter_commands alter_commands
{} {}
...@@ -6139,12 +6162,16 @@ add_column: ...@@ -6139,12 +6162,16 @@ add_column:
; ;
alter_list_item: alter_list_item:
add_column column_def opt_place { } add_column column_def opt_place
{
Lex->create_last_non_select_table= Lex->last_table();
}
| ADD key_def | ADD key_def
{ {
Lex->create_last_non_select_table= Lex->last_table();
Lex->alter_info.flags|= ALTER_ADD_INDEX; Lex->alter_info.flags|= ALTER_ADD_INDEX;
} }
| add_column '(' field_list ')' | add_column '(' create_field_list ')'
{ {
Lex->alter_info.flags|= ALTER_ADD_COLUMN | ALTER_ADD_INDEX; Lex->alter_info.flags|= ALTER_ADD_COLUMN | ALTER_ADD_INDEX;
} }
...@@ -6155,6 +6182,9 @@ alter_list_item: ...@@ -6155,6 +6182,9 @@ alter_list_item:
lex->alter_info.flags|= ALTER_CHANGE_COLUMN; lex->alter_info.flags|= ALTER_CHANGE_COLUMN;
} }
field_spec opt_place field_spec opt_place
{
Lex->create_last_non_select_table= Lex->last_table();
}
| MODIFY_SYM opt_column field_ident | MODIFY_SYM opt_column field_ident
{ {
LEX *lex=Lex; LEX *lex=Lex;
...@@ -6177,6 +6207,9 @@ alter_list_item: ...@@ -6177,6 +6207,9 @@ alter_list_item:
MYSQL_YYABORT; MYSQL_YYABORT;
} }
opt_place opt_place
{
Lex->create_last_non_select_table= Lex->last_table();
}
| DROP opt_column field_ident opt_restrict | DROP opt_column field_ident opt_restrict
{ {
LEX *lex=Lex; LEX *lex=Lex;
...@@ -9607,7 +9640,8 @@ drop: ...@@ -9607,7 +9640,8 @@ drop:
lex->alter_info.flags= ALTER_DROP_INDEX; lex->alter_info.flags= ALTER_DROP_INDEX;
lex->alter_info.drop_list.push_back(ad); lex->alter_info.drop_list.push_back(ad);
if (!lex->current_select->add_table_to_list(lex->thd, $5, NULL, if (!lex->current_select->add_table_to_list(lex->thd, $5, NULL,
TL_OPTION_UPDATING)) TL_OPTION_UPDATING,
TL_WRITE_ALLOW_READ))
MYSQL_YYABORT; MYSQL_YYABORT;
} }
| DROP DATABASE if_exists ident | DROP DATABASE if_exists ident
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment