1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

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).
This commit is contained in:
Konstantin Osipov
2009-12-08 17:13:12 +03:00
parent b9895d46c1
commit c20afa6d49
12 changed files with 790 additions and 418 deletions

View File

@ -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
SYNOPSIS
open_tables()
thd - thread handler
start - list of tables in/out
counter - number of opened tables will be return using this parameter
flags - bitmap of flags to modify how the tables will be open:
MYSQL_LOCK_IGNORE_FLUSH - open table even if someone has
done a flush or namelock on it.
@param[in] thd Thread context.
@param[in,out] start List of tables to be open (it can be adjusted for
statement that uses tables only implicitly, e.g.
for "SELECT f1()").
@param[out] counter Number of tables which were open.
@param[in] flags Bitmap of flags to modify how the tables will be
open, see open_table() description for details.
@param[in] prelocking_strategy Strategy which specifies how prelocking
algorithm should work for this statement.
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.
@note
Unless we are already in prelocked mode and prelocking strategy prescribes
so 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. 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.
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
@retval 0 OK
@retval -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;
Open_table_context ot_ctx(thd);
@ -3786,13 +3851,14 @@ int open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags)
!thd->lex->requires_prelocking() &&
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;
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.
@ -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
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.
yet built we might have to build the prelocking set for this statement.
Since currently no prelocking strategy prescribes doing anything for
tables which are only read, we do below checks only if table is going
to be changed.
*/
if (thd->locked_tables_mode <= LTM_LOCK_TABLES &&
!thd->lex->requires_prelocking() &&
tables->trg_event_map && tables->table->triggers &&
tables->lock_type >= TL_WRITE_ALLOW_WRITE)
{
if (!query_tables_last_own)
query_tables_last_own= thd->lex->query_tables_last;
if (sp_cache_routines_and_add_tables_for_triggers(thd, thd->lex,
tables))
bool need_prelocking= FALSE;
bool not_used;
TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
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;
goto err;
}
@ -4039,13 +4132,27 @@ process_view_routines:
*/
if (tables->view &&
thd->locked_tables_mode <= LTM_LOCK_TABLES &&
!thd->lex->requires_prelocking() &&
tables->view->uses_stored_routines())
!thd->lex->requires_prelocking())
{
/* We have at least one table in TL here. */
if (!query_tables_last_own)
query_tables_last_own= thd->lex->query_tables_last;
if (sp_cache_routines_and_add_tables_for_view(thd, thd->lex, tables))
bool need_prelocking= FALSE;
bool not_used;
TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
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.
@ -4097,6 +4204,220 @@ process_view_routines:
}
/**
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
@ -4312,34 +4633,35 @@ end:
}
/*
/**
Open all tables in list, locks them and optionally process derived tables.
SYNOPSIS
open_and_lock_tables_derived()
thd - thread handler
tables - list of tables for open&locking
flags - set of options to be used to open and lock tables (see
open_tables() and mysql_lock_tables() for details).
derived - if to handle derived tables
@param thd Thread context.
@param tables List of tables for open and locking.
@param derived If to handle derived tables.
@param flags Bitmap of options to be used to open and lock
tables (see open_tables() and mysql_lock_tables()
for details).
@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()
NOTE
There are two convenience functions:
@note
There are several convenience functions, e.g. :
- simple_open_n_lock_tables(thd, tables) without derived handling
- open_and_lock_tables(thd, tables) with derived handling
Both inline functions call open_and_lock_tables_derived() with
the third argument set appropriately.
@retval FALSE OK.
@retval TRUE Error
*/
int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived,
uint flags)
int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables,
bool derived, uint flags,
Prelocking_strategy *prelocking_strategy)
{
uint counter;
bool need_reopen;
@ -4349,7 +4671,7 @@ int open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived,
for ( ; ; )
{
if (open_tables(thd, &tables, &counter, flags))
if (open_tables(thd, &tables, &counter, flags, prelocking_strategy))
DBUG_RETURN(-1);
DBUG_EXECUTE_IF("sleep_open_and_lock_after_open", {