summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
Diffstat (limited to 'sql')
-rw-r--r--sql/lock.cc51
-rw-r--r--sql/mysql_priv.h3
-rw-r--r--sql/sp.cc168
-rw-r--r--sql/sp.h18
-rw-r--r--sql/sp_cache.cc67
-rw-r--r--sql/sp_cache.h4
-rw-r--r--sql/sp_head.cc15
-rw-r--r--sql/sp_head.h30
-rw-r--r--sql/sql_base.cc235
-rw-r--r--sql/sql_class.h7
-rw-r--r--sql/sql_parse.cc162
-rw-r--r--sql/sql_prepare.cc50
-rw-r--r--sql/sql_show.cc1
-rw-r--r--sql/sql_table.cc18
-rw-r--r--sql/sql_update.cc4
15 files changed, 548 insertions, 285 deletions
diff --git a/sql/lock.cc b/sql/lock.cc
index 31773585bff..6cdf2e4a202 100644
--- a/sql/lock.cc
+++ b/sql/lock.cc
@@ -980,6 +980,57 @@ void unlock_table_names(THD *thd)
}
+/**
+ Obtain an exclusive metadata lock on the stored routine name.
+
+ @param thd Thread handle.
+ @param is_function Stored routine type (only functions or procedures
+ are name-locked.
+ @param db The schema the routine belongs to.
+ @param name Routine name.
+
+ This function assumes that no metadata locks were acquired
+ before calling it. Additionally, it cannot be called while
+ holding LOCK_open mutex. Both these invariants are enforced by
+ asserts in MDL_context::acquire_exclusive_locks().
+ To avoid deadlocks, we do not try to obtain exclusive metadata
+ locks in LOCK TABLES mode, since in this mode there may be
+ other metadata locks already taken by the current connection,
+ and we must not wait for MDL locks while holding locks.
+
+ @retval FALSE Success.
+ @retval TRUE Failure: we're in LOCK TABLES mode, or out of memory,
+ or this connection was killed.
+*/
+
+bool lock_routine_name(THD *thd, bool is_function,
+ const char *db, const char *name)
+{
+ MDL_key::enum_mdl_namespace mdl_type= (is_function ?
+ MDL_key::FUNCTION :
+ MDL_key::PROCEDURE);
+ MDL_request mdl_request;
+
+ if (thd->locked_tables_mode)
+ {
+ my_message(ER_LOCK_OR_ACTIVE_TRANSACTION,
+ ER(ER_LOCK_OR_ACTIVE_TRANSACTION), MYF(0));
+ return TRUE;
+ }
+
+ DBUG_ASSERT(name);
+ DEBUG_SYNC(thd, "before_wait_locked_pname");
+
+ mdl_request.init(mdl_type, db, name, MDL_EXCLUSIVE);
+
+ if (thd->mdl_context.acquire_exclusive_lock(&mdl_request))
+ return TRUE;
+
+ DEBUG_SYNC(thd, "after_wait_locked_pname");
+ return FALSE;
+}
+
+
static void print_lock_error(int error, const char *table)
{
int textno;
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index caf3130c517..124392f4c63 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -2111,6 +2111,9 @@ void broadcast_refresh(void);
bool lock_table_names(THD *thd, TABLE_LIST *table_list);
void unlock_table_names(THD *thd);
+/* Lock based on stored routine name */
+bool lock_routine_name(THD *thd, bool is_function, const char *db,
+ const char *name);
/* old unireg functions */
diff --git a/sql/sp.cc b/sql/sp.cc
index 279f0d44890..1375d44cb9b 100644
--- a/sql/sp.cc
+++ b/sql/sp.cc
@@ -753,6 +753,11 @@ sp_create_routine(THD *thd, int type, sp_head *sp)
*/
thd->clear_current_stmt_binlog_row_based();
+ /* Grab an exclusive MDL lock. */
+ if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION,
+ sp->m_db.str, sp->m_name.str))
+ DBUG_RETURN(SP_OPEN_TABLE_FAILED);
+
saved_count_cuted_fields= thd->count_cuted_fields;
thd->count_cuted_fields= CHECK_FIELD_WARN;
@@ -919,7 +924,10 @@ sp_create_routine(THD *thd, int type, sp_head *sp)
ret= SP_OK;
if (table->file->ha_write_row(table->record[0]))
ret= SP_WRITE_ROW_FAILED;
- else if (mysql_bin_log.is_open())
+ if (ret == SP_OK)
+ sp_cache_invalidate();
+
+ if (ret == SP_OK && mysql_bin_log.is_open())
{
thd->clear_error();
@@ -948,7 +956,6 @@ sp_create_routine(THD *thd, int type, sp_head *sp)
FALSE, FALSE, 0);
thd->variables.sql_mode= 0;
}
-
}
done:
@@ -994,6 +1001,11 @@ sp_drop_routine(THD *thd, int type, sp_name *name)
*/
thd->clear_current_stmt_binlog_row_based();
+ /* Grab an exclusive MDL lock. */
+ if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION,
+ name->m_db.str, name->m_name.str))
+ DBUG_RETURN(SP_DELETE_ROW_FAILED);
+
if (!(table= open_proc_table_for_update(thd)))
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK)
@@ -1006,6 +1018,20 @@ sp_drop_routine(THD *thd, int type, sp_name *name)
{
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
sp_cache_invalidate();
+
+ /*
+ A lame workaround for lack of cache flush:
+ make sure the routine is at least gone from the
+ local cache.
+ */
+ {
+ sp_head *sp;
+ sp_cache **spc= (type == TYPE_ENUM_FUNCTION ?
+ &thd->sp_func_cache : &thd->sp_proc_cache);
+ sp= sp_cache_lookup(spc, name);
+ if (sp)
+ sp_cache_flush_obsolete(spc, &sp);
+ }
}
close_thread_tables(thd);
@@ -1041,6 +1067,12 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics)
DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
type == TYPE_ENUM_FUNCTION);
+
+ /* Grab an exclusive MDL lock. */
+ if (lock_routine_name(thd, type == TYPE_ENUM_FUNCTION,
+ name->m_db.str, name->m_name.str))
+ DBUG_RETURN(SP_OPEN_TABLE_FAILED);
+
/*
This statement will be replicated as a statement, even when using
row-based replication. The flag will be reset at the end of the
@@ -1052,6 +1084,30 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics)
DBUG_RETURN(SP_OPEN_TABLE_FAILED);
if ((ret= db_find_routine_aux(thd, type, name, table)) == SP_OK)
{
+ if (type == TYPE_ENUM_FUNCTION && ! trust_function_creators &&
+ mysql_bin_log.is_open() &&
+ (chistics->daccess == SP_CONTAINS_SQL ||
+ chistics->daccess == SP_MODIFIES_SQL_DATA))
+ {
+ char *ptr;
+ bool is_deterministic;
+ ptr= get_field(thd->mem_root,
+ table->field[MYSQL_PROC_FIELD_DETERMINISTIC]);
+ if (ptr == NULL)
+ {
+ ret= SP_INTERNAL_ERROR;
+ goto err;
+ }
+ is_deterministic= ptr[0] == 'N' ? FALSE : TRUE;
+ if (!is_deterministic)
+ {
+ my_message(ER_BINLOG_UNSAFE_ROUTINE,
+ ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0));
+ ret= SP_INTERNAL_ERROR;
+ goto err;
+ }
+ }
+
store_record(table,record[1]);
table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;
((Field_timestamp *)table->field[MYSQL_PROC_FIELD_MODIFIED])->set_time();
@@ -1077,7 +1133,7 @@ sp_update_routine(THD *thd, int type, sp_name *name, st_sp_chistics *chistics)
write_bin_log(thd, TRUE, thd->query(), thd->query_length());
sp_cache_invalidate();
}
-
+err:
close_thread_tables(thd);
DBUG_RETURN(ret);
}
@@ -1161,10 +1217,7 @@ err:
bool
sp_show_create_routine(THD *thd, int type, sp_name *name)
{
- bool err_status= TRUE;
sp_head *sp;
- sp_cache **cache = type == TYPE_ENUM_PROCEDURE ?
- &thd->sp_proc_cache : &thd->sp_func_cache;
DBUG_ENTER("sp_show_create_routine");
DBUG_PRINT("enter", ("name: %.*s",
@@ -1174,28 +1227,29 @@ sp_show_create_routine(THD *thd, int type, sp_name *name)
DBUG_ASSERT(type == TYPE_ENUM_PROCEDURE ||
type == TYPE_ENUM_FUNCTION);
- if (type == TYPE_ENUM_PROCEDURE)
+ /*
+ @todo: Consider using prelocking for this code as well. Currently
+ SHOW CREATE PROCEDURE/FUNCTION is a dirty read of the data
+ dictionary, i.e. takes no metadata locks.
+ It is "safe" to do as long as it doesn't affect the results
+ of the binary log or the query cache, which currently it does not.
+ */
+ if (sp_cache_routine(thd, type, name, FALSE, &sp))
+ DBUG_RETURN(TRUE);
+
+ if (sp == NULL || sp->show_create_routine(thd, type))
{
/*
- SHOW CREATE PROCEDURE may require two instances of one sp_head
- object when SHOW CREATE PROCEDURE is called for the procedure that
- is being executed. Basically, there is no actual recursion, so we
- increase the recursion limit for this statement (kind of hack).
-
- SHOW CREATE FUNCTION does not require this because SHOW CREATE
- statements are prohibitted within stored functions.
- */
-
- thd->variables.max_sp_recursion_depth++;
+ If we have insufficient privileges, pretend the routine
+ does not exist.
+ */
+ my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
+ type == TYPE_ENUM_FUNCTION ? "FUNCTION" : "PROCEDURE",
+ name->m_name.str);
+ DBUG_RETURN(TRUE);
}
- if ((sp= sp_find_routine(thd, type, name, cache, FALSE)))
- err_status= sp->show_create_routine(thd, type);
-
- if (type == TYPE_ENUM_PROCEDURE)
- thd->variables.max_sp_recursion_depth--;
-
- DBUG_RETURN(err_status);
+ DBUG_RETURN(FALSE);
}
@@ -1451,6 +1505,7 @@ bool sp_add_used_routine(Query_tables_list *prelocking_ctx, Query_arena *arena,
my_hash_insert(&prelocking_ctx->sroutines, (uchar *)rn);
prelocking_ctx->sroutines_list.link_in_list((uchar *)rn, (uchar **)&rn->next);
rn->belong_to_view= belong_to_view;
+ rn->m_sp_cache_version= 0;
return TRUE;
}
return FALSE;
@@ -1597,40 +1652,80 @@ void sp_update_stmt_used_routines(THD *thd, Query_tables_list *prelocking_ctx,
/**
+ A helper wrapper around sp_cache_routine() to use from
+ prelocking until 'sp_name' is eradicated as a class.
+*/
+
+int sp_cache_routine(THD *thd, Sroutine_hash_entry *rt,
+ bool lookup_only, sp_head **sp)
+{
+ char qname_buff[NAME_LEN*2+1+1];
+ sp_name name(&rt->mdl_request.key, qname_buff);
+ MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace();
+ int type= ((mdl_type == MDL_key::FUNCTION) ?
+ TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE);
+
+ /*
+ Check that we have an MDL lock on this routine, unless it's a top-level
+ CALL. The assert below should be unambiguous: the first element
+ in sroutines_list has an MDL lock unless it's a top-level call, or a
+ trigger, but triggers can't occur here (see the preceding assert).
+ */
+ DBUG_ASSERT(rt->mdl_request.ticket ||
+ rt == (Sroutine_hash_entry*) thd->lex->sroutines_list.first);
+
+ return sp_cache_routine(thd, type, &name, lookup_only, sp);
+}
+
+
+/**
Ensure that routine is present in cache by loading it from the mysql.proc
- table if needed. Emit an appropriate error if there was a problem during
+ table if needed. If the routine is present but old, reload it.
+ Emit an appropriate error if there was a problem during
loading.
@param[in] thd Thread context.
@param[in] type Type of object (TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE).
@param[in] name Name of routine.
+ @param[in] lookup_only Only check that the routine is in the cache.
+ If it's not, don't try to load. If it is present,
+ but old, don't try to reload.
@param[out] sp Pointer to sp_head object for routine, NULL if routine was
- not found,
+ not found.
@retval 0 Either routine is found and was succesfully loaded into cache
or it does not exist.
@retval non-0 Error while loading routine from mysql,proc table.
*/
-int sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp)
+int sp_cache_routine(THD *thd, int type, sp_name *name,
+ bool lookup_only, sp_head **sp)
{
int ret= 0;
+ sp_cache **spc= (type == TYPE_ENUM_FUNCTION ?
+ &thd->sp_func_cache : &thd->sp_proc_cache);
DBUG_ENTER("sp_cache_routine");
DBUG_ASSERT(type == TYPE_ENUM_FUNCTION || type == TYPE_ENUM_PROCEDURE);
- if (!(*sp= sp_cache_lookup((type == TYPE_ENUM_FUNCTION ?
- &thd->sp_func_cache : &thd->sp_proc_cache),
- name)))
+
+ *sp= sp_cache_lookup(spc, name);
+
+ if (lookup_only)
+ DBUG_RETURN(SP_OK);
+
+ if (*sp)
+ {
+ sp_cache_flush_obsolete(spc, sp);
+ if (*sp)
+ DBUG_RETURN(SP_OK);
+ }
+
+ 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)
- sp_cache_insert(&thd->sp_func_cache, *sp);
- else
- sp_cache_insert(&thd->sp_proc_cache, *sp);
+ sp_cache_insert(spc, *sp);
break;
case SP_KEY_NOT_FOUND:
ret= SP_OK;
@@ -1669,7 +1764,6 @@ int sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp)
my_error(ER_SP_PROC_TABLE_CORRUPT, MYF(0), n, ret);
}
break;
- }
}
DBUG_RETURN(ret);
}
diff --git a/sql/sp.h b/sql/sp.h
index a66d72d0e9e..cab4d7dbeb5 100644
--- a/sql/sp.h
+++ b/sql/sp.h
@@ -43,7 +43,13 @@ sp_find_routine(THD *thd, int type, sp_name *name,
sp_cache **cp, bool cache_only);
int
-sp_cache_routine(THD *thd, int type, sp_name *name, sp_head **sp);
+sp_cache_routine(THD *thd, Sroutine_hash_entry *rt,
+ bool lookup_only, sp_head **sp);
+
+
+int
+sp_cache_routine(THD *thd, int type, sp_name *name,
+ bool lookup_only, sp_head **sp);
bool
sp_exist_routines(THD *thd, TABLE_LIST *procs, bool any);
@@ -88,6 +94,16 @@ public:
statement uses routine both via view and directly.
*/
TABLE_LIST *belong_to_view;
+ /**
+ This is for prepared statement validation purposes.
+ A statement looks up and pre-loads all its stored functions
+ at prepare. Later on, if a function is gone from the cache,
+ execute may fail.
+ Remember the version of sp_head at prepare to be able to
+ invalidate the prepared statement at execute if it
+ changes.
+ */
+ ulong m_sp_cache_version;
};
diff --git a/sql/sp_cache.cc b/sql/sp_cache.cc
index d9a23d2be4e..bb962ad0300 100644
--- a/sql/sp_cache.cc
+++ b/sql/sp_cache.cc
@@ -31,8 +31,6 @@ static ulong volatile Cversion= 0;
class sp_cache
{
public:
- ulong version;
-
sp_cache();
~sp_cache();
@@ -48,25 +46,10 @@ public:
namelen);
}
-#ifdef NOT_USED
- inline bool remove(char *name, uint namelen)
- {
- sp_head *sp= lookup(name, namelen);
- if (sp)
- {
- hash_delete(&m_hashtable, (uchar *)sp);
- return TRUE;
- }
- return FALSE;
- }
-#endif
-
- inline void remove_all()
+ inline void remove(sp_head *sp)
{
- cleanup();
- init();
+ my_hash_delete(&m_hashtable, (uchar *)sp);
}
-
private:
void init();
void cleanup();
@@ -129,8 +112,9 @@ void sp_cache_insert(sp_cache **cp, sp_head *sp)
{
if (!(c= new sp_cache()))
return; // End of memory error
- c->version= Cversion; // No need to lock when reading long variable
}
+ /* Reading a ulong variable with no lock. */
+ sp->set_sp_cache_version(Cversion);
DBUG_PRINT("info",("sp_cache: inserting: %.*s", (int) sp->m_qname.length,
sp->m_qname.str));
c->insert(sp);
@@ -181,46 +165,34 @@ void sp_cache_invalidate()
}
-/*
- Remove out-of-date SPs from the cache.
-
- SYNOPSIS
- sp_cache_flush_obsolete()
- cp Cache to flush
+/**
+ Remove an out-of-date SP from the cache.
- NOTE
- This invalidates pointers to sp_head objects this thread uses.
- In practice that means 'dont call this function when inside SP'.
+ @param[in] cp Cache to flush
+ @param[in] sp SP to remove.
+
+ @note This invalidates pointers to sp_head objects this thread
+ uses. In practice that means 'dont call this function when
+ inside SP'.
*/
-void sp_cache_flush_obsolete(sp_cache **cp)
+void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp)
{
- sp_cache *c= *cp;
- if (c)
+ if ((*sp)->sp_cache_version() < Cversion && !(*sp)->is_invoked())
{
- ulong v;
- v= Cversion; // No need to lock when reading long variable
- if (c->version < v)
- {
- DBUG_PRINT("info",("sp_cache: deleting all functions"));
- /* We need to delete all elements. */
- c->remove_all();
- c->version= v;
- }
+ (*cp)->remove(*sp);
+ *sp= NULL;
}
}
/**
- Return the current version of the cache.
+ Return the current global version of the cache.
*/
-ulong sp_cache_version(sp_cache **cp)
+ulong sp_cache_version()
{
- sp_cache *c= *cp;
- if (c)
- return c->version;
- return 0;
+ return Cversion;
}
@@ -265,7 +237,6 @@ sp_cache::init()
{
my_hash_init(&m_hashtable, system_charset_info, 0, 0, 0,
hash_get_key_for_sp_head, hash_free_sp_head, 0);
- version= 0;
}
diff --git a/sql/sp_cache.h b/sql/sp_cache.h
index f4d44a1f29f..7dbb0d5429c 100644
--- a/sql/sp_cache.h
+++ b/sql/sp_cache.h
@@ -57,7 +57,7 @@ void sp_cache_clear(sp_cache **cp);
void sp_cache_insert(sp_cache **cp, sp_head *sp);
sp_head *sp_cache_lookup(sp_cache **cp, sp_name *name);
void sp_cache_invalidate();
-void sp_cache_flush_obsolete(sp_cache **cp);
-ulong sp_cache_version(sp_cache **cp);
+void sp_cache_flush_obsolete(sp_cache **cp, sp_head **sp);
+ulong sp_cache_version();
#endif /* _SP_CACHE_H_ */
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index da99d9e0f6a..4f5ca1fff04 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -511,7 +511,10 @@ sp_head::operator delete(void *ptr, size_t size) throw()
sp_head::sp_head()
:Query_arena(&main_mem_root, INITIALIZED_FOR_SP),
- m_flags(0), m_recursion_level(0), m_next_cached_sp(0),
+ m_flags(0),
+ m_sp_cache_version(0),
+ m_recursion_level(0),
+ m_next_cached_sp(0),
m_cont_level(0)
{
const LEX_STRING str_reset= { NULL, 0 };
@@ -727,16 +730,6 @@ create_typelib(MEM_ROOT *mem_root, Create_field *field_def, List<String> *src)
}
-int
-sp_head::create(THD *thd)
-{
- DBUG_ENTER("sp_head::create");
- DBUG_PRINT("info", ("type: %d name: %s params: %s body: %s",
- m_type, m_name.str, m_params.str, m_body.str));
-
- DBUG_RETURN(sp_create_routine(thd, m_type, this));
-}
-
sp_head::~sp_head()
{
DBUG_ENTER("sp_head::~sp_head");
diff --git a/sql/sp_head.h b/sql/sp_head.h
index 74fcd03180e..bac97a1fd77 100644
--- a/sql/sp_head.h
+++ b/sql/sp_head.h
@@ -175,7 +175,34 @@ public:
LEX_STRING m_definer_user;
LEX_STRING m_definer_host;
+ /**
+ Is this routine being executed?
+ */
+ bool is_invoked() const { return m_flags & IS_INVOKED; }
+
+ /**
+ Get the value of the SP cache version, as remembered
+ when the routine was inserted into the cache.
+ */
+ ulong sp_cache_version() const { return m_sp_cache_version; }
+
+ /** Set the value of the SP cache version. */
+ void set_sp_cache_version(ulong version_arg)
+ {
+ m_sp_cache_version= version_arg;
+ }
private:
+ /**
+ Version of the stored routine cache at the moment when the
+ routine was added to it. Is used only for functions and
+ procedures, not used for triggers or events. When sp_head is
+ created, its version is 0. When it's added to the cache, the
+ version is assigned the global value 'Cversion'.
+ If later on Cversion is incremented, we know that the routine
+ is obsolete and should not be used --
+ sp_cache_flush_obsolete() will purge it.
+ */
+ ulong m_sp_cache_version;
Stored_program_creation_ctx *m_creation_ctx;
public:
@@ -263,9 +290,6 @@ public:
void
set_stmt_end(THD *thd);
- int
- create(THD *thd);
-
virtual ~sp_head();
/// Free memory
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 459ca646d8c..78bb9f9bad7 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -21,6 +21,7 @@
#include "sql_select.h"
#include "sp_head.h"
#include "sp.h"
+#include "sp_cache.h"
#include "sql_trigger.h"
#include "transaction.h"
#include "sql_prepare.h"
@@ -3480,6 +3481,66 @@ check_and_update_table_version(THD *thd,
/**
+ Compares versions of a stored routine obtained from the sp cache
+ and the version used at prepare.
+
+ @details If the new and the old values mismatch, invoke
+ Metadata_version_observer.
+ At prepared statement prepare, all Sroutine_hash_entry version values
+ are NULL and we always have a mismatch. But there is no observer set
+ in THD, and therefore no error is reported. Instead, we update
+ the value in Sroutine_hash_entry, effectively recording the original
+ version.
+ At prepared statement execute, an observer may be installed. If
+ there is a version mismatch, we push an error and return TRUE.
+
+ For conventional execution (no prepared statements), the
+ observer is never installed.
+
+ @param[in] thd used to report errors
+ @param[in/out] rt pointer to stored routine entry in the
+ parse tree
+ @param[in] sp pointer to stored routine cache entry.
+ Can be NULL if there is no such routine.
+ @retval TRUE an error, which has been reported
+ @retval FALSE success, version in Sroutine_hash_entry has been updated
+*/
+
+static bool
+check_and_update_routine_version(THD *thd, Sroutine_hash_entry *rt,
+ sp_head *sp)
+{
+ ulong spc_version= sp_cache_version();
+ /* sp is NULL if there is no such routine. */
+ ulong version= sp ? sp->sp_cache_version() : spc_version;
+ /*
+ If the version in the parse tree is stale,
+ or the version in the cache is stale and sp is not used,
+ we need to reprepare.
+ Sic: version != spc_version <--> sp is not NULL.
+ */
+ if (rt->m_sp_cache_version != version ||
+ (version != spc_version && !sp->is_invoked()))
+ {
+ if (thd->m_reprepare_observer &&
+ thd->m_reprepare_observer->report_error(thd))
+ {
+ /*
+ Version of the sp cache is different from the
+ previous execution of the prepared statement, and it is
+ unacceptable for this SQLCOM. Error has been reported.
+ */
+ DBUG_ASSERT(thd->is_error());
+ return TRUE;
+ }
+ /* Always maintain the latest cache version. */
+ rt->m_sp_cache_version= version;
+ }
+ return FALSE;
+}
+
+
+/**
Open view by getting its definition from disk (and table cache in future).
@param thd Thread handle
@@ -3696,13 +3757,16 @@ request_backoff_action(enum_open_table_action action_arg)
/**
- Recover from failed attempt ot open table by performing requested action.
+ Recover from failed attempt of open table by performing requested action.
@param thd Thread context
- @param table Table list element for table that caused problem
- @param action Type of action requested by failed open_table() call
+ @param mdl_request MDL_request of the object that caused the problem.
+ @param table Optional (can be NULL). Used only if action is OT_REPAIR.
+ In that case a TABLE_LIST for the table to be repaired.
+ @todo: It's unnecessary and should be removed.
- @pre This function should be called only with "action" != OT_NO_ACTION.
+ @pre This function should be called only with "action" != OT_NO_ACTION
+ and after having called @sa close_tables_for_reopen().
@retval FALSE - Success. One should try to open tables once again.
@retval TRUE - Error
@@ -3710,7 +3774,8 @@ request_backoff_action(enum_open_table_action action_arg)
bool
Open_table_context::
-recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table)
+recover_from_failed_open(THD *thd, MDL_request *mdl_request,
+ TABLE_LIST *table)
{
bool result= FALSE;
/* Execute the action. */
@@ -3723,14 +3788,20 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table)
break;
case OT_DISCOVER:
{
- MDL_request mdl_xlock_request(&table->mdl_request);
+ MDL_request mdl_xlock_request(mdl_request);
mdl_xlock_request.set_type(MDL_EXCLUSIVE);
if ((result=
thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request)))
break;
+
+ DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE);
pthread_mutex_lock(&LOCK_open);
- tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name);
- ha_create_table_from_engine(thd, table->db, table->table_name);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
+ mdl_request->key.db_name(),
+ mdl_request->key.name());
+ ha_create_table_from_engine(thd,
+ mdl_request->key.db_name(),
+ mdl_request->key.name());
pthread_mutex_unlock(&LOCK_open);
thd->warning_info->clear_warning_info(thd->query_id);
@@ -3740,14 +3811,17 @@ recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *table)
}
case OT_REPAIR:
{
- MDL_request mdl_xlock_request(&table->mdl_request);
+ MDL_request mdl_xlock_request(mdl_request);
mdl_xlock_request.set_type(MDL_EXCLUSIVE);
if ((result=
thd->mdl_context.acquire_exclusive_lock(&mdl_xlock_request)))
break;
+ DBUG_ASSERT(mdl_request->key.mdl_namespace() == MDL_key::TABLE);
pthread_mutex_lock(&LOCK_open);
- tdc_remove_table(thd, TDC_RT_REMOVE_ALL, table->db, table->table_name);
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
+ mdl_request->key.db_name(),
+ mdl_request->key.name());
pthread_mutex_unlock(&LOCK_open);
result= auto_repair_table(thd, table);
@@ -3808,7 +3882,11 @@ thr_lock_type read_lock_type_for_table(THD *thd, TABLE *table)
@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
+ @param[in] has_prelocking_list Indicates that prelocking set/list for
+ this statement has already been built.
+ @param[in] ot_ctx Context of open_table used to recover from
+ locking failures.
+ @param[out] need_prelocking Set to TRUE if it was detected that this
statement will require prelocked mode for
its execution, not touched otherwise.
@@ -3820,32 +3898,99 @@ static bool
open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
Sroutine_hash_entry *rt,
Prelocking_strategy *prelocking_strategy,
+ bool has_prelocking_list,
+ Open_table_context *ot_ctx,
bool *need_prelocking)
{
+ MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace();
DBUG_ENTER("open_and_process_routine");
- switch (rt->mdl_request.key.mdl_namespace())
+ switch (mdl_type)
{
case MDL_key::FUNCTION:
case MDL_key::PROCEDURE:
{
- char qname_buff[NAME_LEN*2+1+1];
- sp_name name(&rt->mdl_request.key, qname_buff);
sp_head *sp;
- int type= (rt->mdl_request.key.mdl_namespace() == MDL_key::FUNCTION) ?
- TYPE_ENUM_FUNCTION : TYPE_ENUM_PROCEDURE;
+ /*
+ Try to get MDL lock on the routine.
+ Note that we do not take locks on top-level CALLs as this can
+ lead to a deadlock. Not locking top-level CALLs does not break
+ the binlog as only the statements in the called procedure show
+ up there, not the CALL itself.
+ */
+ if (rt != (Sroutine_hash_entry*)prelocking_ctx->sroutines_list.first ||
+ mdl_type != MDL_key::PROCEDURE)
+ {
+ ot_ctx->add_request(&rt->mdl_request);
+ if (thd->mdl_context.try_acquire_shared_lock(&rt->mdl_request))
+ DBUG_RETURN(TRUE);
- if (sp_cache_routine(thd, type, &name, &sp))
- DBUG_RETURN(TRUE);
+ if (rt->mdl_request.ticket == NULL)
+ {
+ /* A lock conflict. Someone's trying to modify SP metadata. */
+ ot_ctx->request_backoff_action(Open_table_context::OT_WAIT);
+ DBUG_RETURN(TRUE);
+ }
+ DEBUG_SYNC(thd, "after_shared_lock_pname");
+
+ /* Ensures the routine is up-to-date and cached, if exists. */
+ if (sp_cache_routine(thd, rt, has_prelocking_list, &sp))
+ DBUG_RETURN(TRUE);
- if (sp)
+ /* Remember the version of the routine in the parse tree. */
+ if (check_and_update_routine_version(thd, rt, sp))
+ DBUG_RETURN(TRUE);
+
+ /* 'sp' is NULL when there is no such routine. */
+ if (sp && !has_prelocking_list)
+ {
+ prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp,
+ need_prelocking);
+ }
+ }
+ else
{
- prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp,
- need_prelocking);
+ /*
+ If it's a top level call, just make sure we have a recent
+ version of the routine, if it exists.
+ Validating routine version is unnecessary, since CALL
+ does not affect the prepared statement prelocked list.
+ */
+ sp_cache_routine(thd, rt, FALSE, &sp);
}
}
break;
case MDL_key::TRIGGER:
+ /**
+ We add trigger entries to lex->sroutines_list, but we don't
+ load them here. The trigger entry is only used when building
+ a transitive closure of objects used in a statement, to avoid
+ adding to this closure objects that are used in the trigger more
+ than once.
+ E.g. if a trigger trg refers to table t2, and the trigger table t1
+ is used multiple times in the statement (say, because it's used in
+ function f1() twice), we will only add t2 once to the list of
+ tables to prelock.
+
+ We don't take metadata locks on triggers either: they are protected
+ by a respective lock on the table, on which the trigger is defined.
+
+ The only two cases which give "trouble" are SHOW CREATE TRIGGER
+ and DROP TRIGGER statements. For these, statement syntax doesn't
+ specify the table on which this trigger is defined, so we have
+ to make a "dirty" read in the data dictionary to find out the
+ table name. Once we discover the table name, we take a metadata
+ lock on it, and this protects all trigger operations.
+ Of course the table, in theory, may disappear between the dirty
+ read and metadata lock acquisition, but in that case we just return
+ a run-time error.
+
+ Grammar of other trigger DDL statements (CREATE, DROP) requires
+ the table to be specified explicitly, so we use the table metadata
+ lock to protect trigger metadata in these statements. Similarly, in
+ DML we always use triggers together with their tables, and thus don't
+ need to take separate metadata locks on them.
+ */
break;
default:
/* Impossible type value. */
@@ -3965,7 +4110,7 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables,
if (error)
{
- if (! ot_ctx->can_recover_from_failed_open_table() && safe_to_ignore_table)
+ if (! ot_ctx->can_recover_from_failed_open() && safe_to_ignore_table)
{
DBUG_PRINT("info", ("open_table: ignoring table '%s'.'%s'",
tables->db, tables->alias));
@@ -4150,7 +4295,7 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
Open_table_context ot_ctx(thd);
bool error= FALSE;
MEM_ROOT new_frm_mem;
- bool has_prelocking_list= thd->lex->requires_prelocking();
+ bool has_prelocking_list;
DBUG_ENTER("open_tables");
/*
@@ -4172,7 +4317,8 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
init_sql_alloc(&new_frm_mem, 8024, 8024);
thd->current_tablenr= 0;
- restart:
+restart:
+ has_prelocking_list= thd->lex->requires_prelocking();
table_to_open= start;
sroutine_to_open= (Sroutine_hash_entry**) &thd->lex->sroutines_list.first;
*counter= 0;
@@ -4184,7 +4330,6 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
*/
while (*table_to_open ||
(thd->locked_tables_mode <= LTM_LOCK_TABLES &&
- ! has_prelocking_list &&
*sroutine_to_open))
{
/*
@@ -4201,7 +4346,7 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
if (error)
{
- if (ot_ctx.can_recover_from_failed_open_table())
+ if (ot_ctx.can_recover_from_failed_open())
{
/*
We have met exclusive metadata lock or old version of table.
@@ -4220,12 +4365,14 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
*/
TABLE_LIST *failed_table= *table_to_open;
close_tables_for_reopen(thd, start);
+
/*
Here we rely on the fact that 'tables' still points to the valid
TABLE_LIST element. Altough currently this assumption is valid
it may change in future.
*/
- if (ot_ctx.recover_from_failed_open_table_attempt(thd, failed_table))
+ if (ot_ctx.recover_from_failed_open(thd, &failed_table->mdl_request,
+ failed_table))
goto err;
error= FALSE;
@@ -4239,8 +4386,11 @@ bool 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 for our statement we need to cache routines it uses
and build the prelocking list for it.
+ If we are not in prelocked mode but have built the extended table
+ list, we still need to call open_and_process_routine() to take
+ MDL locks on the routines.
*/
- if (thd->locked_tables_mode <= LTM_LOCK_TABLES && ! has_prelocking_list)
+ if (thd->locked_tables_mode <= LTM_LOCK_TABLES)
{
bool need_prelocking= FALSE;
TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
@@ -4256,12 +4406,21 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
for (Sroutine_hash_entry *rt= *sroutine_to_open; rt;
sroutine_to_open= &rt->next, rt= rt->next)
{
- error= open_and_process_routine(thd, thd->lex, rt,
- prelocking_strategy,
+ error= open_and_process_routine(thd, thd->lex, rt, prelocking_strategy,
+ has_prelocking_list, &ot_ctx,
&need_prelocking);
if (error)
{
+ if (ot_ctx.can_recover_from_failed_open())
+ {
+ close_tables_for_reopen(thd, start);
+ if (ot_ctx.recover_from_failed_open(thd, &rt->mdl_request, NULL))
+ goto err;
+
+ error= FALSE;
+ goto restart;
+ }
/*
Serious error during reading stored routines from mysql.proc table.
Something is wrong with the table or its contents, and an error has
@@ -4666,7 +4825,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
retry:
while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx, 0)) &&
- ot_ctx.can_recover_from_failed_open_table())
+ ot_ctx.can_recover_from_failed_open())
{
/* We can't back off with an open HANDLER, we don't wait with locks. */
DBUG_ASSERT(thd->mdl_context.lt_or_ha_sentinel() == NULL);
@@ -4677,7 +4836,8 @@ retry:
*/
thd->mdl_context.release_transactional_locks();
table_list->mdl_request.ticket= 0;
- if (ot_ctx.recover_from_failed_open_table_attempt(thd, table_list))
+ if (ot_ctx.recover_from_failed_open(thd, &table_list->mdl_request,
+ table_list))
break;
}
@@ -5242,11 +5402,18 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables)
if (first_not_own_table == *tables)
*tables= 0;
thd->lex->chop_off_not_own_tables();
+ /* Reset MDL tickets for procedures/functions */
+ for (Sroutine_hash_entry *rt=
+ (Sroutine_hash_entry*)thd->lex->sroutines_list.first;
+ rt; rt= rt->next)
+ rt->mdl_request.ticket= NULL;
sp_remove_not_own_routines(thd->lex);
for (tmp= *tables; tmp; tmp= tmp->next_global)
{
tmp->table= 0;
tmp->mdl_request.ticket= NULL;
+ /* We have to cleanup translation tables of views. */
+ tmp->cleanup_items();
}
/*
Metadata lock requests for tables from extended part of prelocking set
@@ -8363,6 +8530,10 @@ tdc_wait_for_old_versions(THD *thd, MDL_request_list *mdl_requests)
MDL_request_list::Iterator it(*mdl_requests);
while ((mdl_request= it++))
{
+ /* Skip requests on non-TDC objects. */
+ if (mdl_request->key.mdl_namespace() != MDL_key::TABLE)
+ continue;
+
if ((share= get_cached_table_share(mdl_request->key.db_name(),
mdl_request->key.name())) &&
share->version != refresh_version)
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 11e0010d85b..ff1b51e7e87 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -1271,7 +1271,7 @@ private:
/**
A context of open_tables() function, used to recover
- from a failed open_table() attempt.
+ from a failed open_table() or open_routine() attempt.
Implemented in sql_base.cc.
*/
@@ -1288,13 +1288,14 @@ public:
};
Open_table_context(THD *thd);
- bool recover_from_failed_open_table_attempt(THD *thd, TABLE_LIST *tables);
+ bool recover_from_failed_open(THD *thd, MDL_request *mdl_request,
+ TABLE_LIST *table);
bool request_backoff_action(enum_open_table_action action_arg);
void add_request(MDL_request *request)
{ m_mdl_requests.push_front(request); }
- bool can_recover_from_failed_open_table() const
+ bool can_recover_from_failed_open() const
{ return m_action != OT_NO_ACTION; }
bool can_deadlock() const { return m_can_deadlock; }
private:
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 79f10120268..689b2cec270 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -284,14 +284,14 @@ void init_update_queries(void)
sql_command_flags[SQLCOM_GRANT]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_REVOKE]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_REVOKE_ALL]= CF_PROTECT_AGAINST_GRL;
- sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA;
- sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
sql_command_flags[SQLCOM_OPTIMIZE]= CF_CHANGES_DATA;
- sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
- sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
- sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
- sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
- sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL;
+ sql_command_flags[SQLCOM_CREATE_FUNCTION]= CF_CHANGES_DATA;
+ sql_command_flags[SQLCOM_CREATE_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_CREATE_SPFUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_DROP_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_ALTER_PROCEDURE]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
+ sql_command_flags[SQLCOM_ALTER_FUNCTION]= CF_CHANGES_DATA | CF_PROTECT_AGAINST_GRL | CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_INSTALL_PLUGIN]= CF_CHANGES_DATA;
sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]= CF_CHANGES_DATA;
@@ -319,10 +319,6 @@ void init_update_queries(void)
sql_command_flags[SQLCOM_REVOKE]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_GRANT]|= CF_AUTO_COMMIT_TRANS;
- sql_command_flags[SQLCOM_CREATE_PROCEDURE]|= CF_AUTO_COMMIT_TRANS;
- sql_command_flags[SQLCOM_CREATE_SPFUNCTION]|= CF_AUTO_COMMIT_TRANS;
- sql_command_flags[SQLCOM_ALTER_PROCEDURE]|= CF_AUTO_COMMIT_TRANS;
- sql_command_flags[SQLCOM_ALTER_FUNCTION]|= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_ASSIGN_TO_KEYCACHE]= CF_AUTO_COMMIT_TRANS;
sql_command_flags[SQLCOM_PRELOAD_KEYS]= CF_AUTO_COMMIT_TRANS;
@@ -1988,9 +1984,10 @@ mysql_execute_command(THD *thd)
#endif
case SQLCOM_SHOW_STATUS_PROC:
case SQLCOM_SHOW_STATUS_FUNC:
- if (!(res= check_table_access(thd, SELECT_ACL, all_tables, FALSE,
+ if ((res= check_table_access(thd, SELECT_ACL, all_tables, FALSE,
UINT_MAX, FALSE)))
- res= execute_sqlcom_select(thd, all_tables);
+ goto error;
+ res= execute_sqlcom_select(thd, all_tables);
break;
case SQLCOM_SHOW_STATUS:
{
@@ -3939,7 +3936,7 @@ end_with_restore_list:
if (sp_process_definer(thd))
goto create_sp_error;
- res= (sp_result= lex->sphead->create(thd));
+ res= (sp_result= sp_create_routine(thd, lex->sphead->m_type, lex->sphead));
switch (sp_result) {
case SP_OK: {
#ifndef NO_EMBEDDED_ACCESS_CHECKS
@@ -3950,6 +3947,16 @@ end_with_restore_list:
Security_context *backup= NULL;
LEX_USER *definer= thd->lex->definer;
/*
+ We're going to issue an implicit GRANT statement.
+ It takes metadata locks and updates system tables.
+ Make sure that sp_create_routine() did not leave any
+ locks in the MDL context, so there is no risk to
+ deadlock.
+ */
+ trans_commit_implicit(thd);
+ close_thread_tables(thd);
+ thd->mdl_context.release_transactional_locks();
+ /*
Check if the definer exists on slave,
then use definer privilege to insert routine privileges to mysql.procs_priv.
@@ -4021,7 +4028,6 @@ create_sp_error:
case SQLCOM_CALL:
{
sp_head *sp;
-
/*
This will cache all SP and SF and open and lock all tables
required for execution.
@@ -4117,65 +4123,22 @@ create_sp_error:
case SQLCOM_ALTER_FUNCTION:
{
int sp_result;
- sp_head *sp;
- st_sp_chistics chistics;
-
- memcpy(&chistics, &lex->sp_chistics, sizeof(chistics));
- if (lex->sql_command == SQLCOM_ALTER_PROCEDURE)
- sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname,
- &thd->sp_proc_cache, FALSE);
- else
- sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname,
- &thd->sp_func_cache, FALSE);
- thd->warning_info->opt_clear_warning_info(thd->query_id);
- if (! sp)
- {
- if (lex->spname->m_db.str)
- sp_result= SP_KEY_NOT_FOUND;
- else
- {
- my_message(ER_NO_DB_ERROR, ER(ER_NO_DB_ERROR), MYF(0));
- goto error;
- }
- }
- else
- {
- if (check_routine_access(thd, ALTER_PROC_ACL, sp->m_db.str,
- sp->m_name.str,
- lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0))
- goto error;
-
- memcpy(&lex->sp_chistics, &chistics, sizeof(lex->sp_chistics));
- if ((sp->m_type == TYPE_ENUM_FUNCTION) &&
- !trust_function_creators && mysql_bin_log.is_open() &&
- !sp->m_chistics->detistic &&
- (chistics.daccess == SP_CONTAINS_SQL ||
- chistics.daccess == SP_MODIFIES_SQL_DATA))
- {
- my_message(ER_BINLOG_UNSAFE_ROUTINE,
- ER(ER_BINLOG_UNSAFE_ROUTINE), MYF(0));
- sp_result= SP_INTERNAL_ERROR;
- }
- else
- {
- /*
- Note that if you implement the capability of ALTER FUNCTION to
- alter the body of the function, this command should be made to
- follow the restrictions that log-bin-trust-function-creators=0
- already puts on CREATE FUNCTION.
- */
- /* Conditionally writes to binlog */
+ int type= (lex->sql_command == SQLCOM_ALTER_PROCEDURE ?
+ TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION);
- int type= lex->sql_command == SQLCOM_ALTER_PROCEDURE ?
- TYPE_ENUM_PROCEDURE :
- TYPE_ENUM_FUNCTION;
+ if (check_routine_access(thd, ALTER_PROC_ACL, lex->spname->m_db.str,
+ lex->spname->m_name.str,
+ lex->sql_command == SQLCOM_ALTER_PROCEDURE, 0))
+ goto error;
- sp_result= sp_update_routine(thd,
- type,
- lex->spname,
- &lex->sp_chistics);
- }
- }
+ /*
+ Note that if you implement the capability of ALTER FUNCTION to
+ alter the body of the function, this command should be made to
+ follow the restrictions that log-bin-trust-function-creators=0
+ already puts on CREATE FUNCTION.
+ */
+ /* Conditionally writes to binlog */
+ sp_result= sp_update_routine(thd, type, lex->spname, &lex->sp_chistics);
switch (sp_result)
{
case SP_OK:
@@ -4199,6 +4162,12 @@ create_sp_error:
int type= (lex->sql_command == SQLCOM_DROP_PROCEDURE ?
TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION);
+ /*
+ @todo: here we break the metadata locking protocol by
+ looking up the information about the routine without
+ a metadata lock. Rewrite this piece to make sp_drop_routine
+ return whether the routine existed or not.
+ */
sp_result= sp_routine_exists_in_table(thd, type, lex->spname);
thd->warning_info->opt_clear_warning_info(thd->query_id);
if (sp_result == SP_OK)
@@ -4210,30 +4179,30 @@ create_sp_error:
lex->sql_command == SQLCOM_DROP_PROCEDURE, 0))
goto error;
- if (trans_commit_implicit(thd))
- goto error;
+ /* Conditionally writes to binlog */
+ sp_result= sp_drop_routine(thd, type, lex->spname);
+#ifndef NO_EMBEDDED_ACCESS_CHECKS
+ /*
+ We're going to issue an implicit REVOKE statement.
+ It takes metadata locks and updates system tables.
+ Make sure that sp_create_routine() did not leave any
+ locks in the MDL context, so there is no risk to
+ deadlock.
+ */
+ trans_commit_implicit(thd);
close_thread_tables(thd);
-
thd->mdl_context.release_transactional_locks();
-#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (sp_automatic_privileges && !opt_noacl &&
- sp_revoke_privileges(thd, db, name,
+ sp_revoke_privileges(thd, db, name,
lex->sql_command == SQLCOM_DROP_PROCEDURE))
{
- push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
ER_PROC_AUTO_REVOKE_FAIL,
ER(ER_PROC_AUTO_REVOKE_FAIL));
}
#endif
- /* Conditionally writes to binlog */
-
- int type= lex->sql_command == SQLCOM_DROP_PROCEDURE ?
- TYPE_ENUM_PROCEDURE :
- TYPE_ENUM_FUNCTION;
-
- sp_result= sp_drop_routine(thd, type, lex->spname);
}
else
{
@@ -4292,21 +4261,13 @@ create_sp_error:
case SQLCOM_SHOW_CREATE_PROC:
{
if (sp_show_create_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname))
- {
- my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
- SP_COM_STRING(lex), lex->spname->m_name.str);
- goto error;
- }
+ goto error;
break;
}
case SQLCOM_SHOW_CREATE_FUNC:
{
if (sp_show_create_routine(thd, TYPE_ENUM_FUNCTION, lex->spname))
- {
- my_error(ER_SP_DOES_NOT_EXIST, MYF(0),
- SP_COM_STRING(lex), lex->spname->m_name.str);
goto error;
- }
break;
}
case SQLCOM_SHOW_PROC_CODE:
@@ -4314,13 +4275,11 @@ create_sp_error:
{
#ifndef DBUG_OFF
sp_head *sp;
+ int type= (lex->sql_command == SQLCOM_SHOW_PROC_CODE ?
+ TYPE_ENUM_PROCEDURE : TYPE_ENUM_FUNCTION);
- if (lex->sql_command == SQLCOM_SHOW_PROC_CODE)
- sp= sp_find_routine(thd, TYPE_ENUM_PROCEDURE, lex->spname,
- &thd->sp_proc_cache, FALSE);
- else
- sp= sp_find_routine(thd, TYPE_ENUM_FUNCTION, lex->spname,
- &thd->sp_func_cache, FALSE);
+ if (sp_cache_routine(thd, type, lex->spname, FALSE, &sp))
+ goto error;
if (!sp || sp->show_routine_code(thd))
{
/* We don't distinguish between errors for now */
@@ -5579,9 +5538,6 @@ void mysql_parse(THD *thd, const char *inBuf, uint length,
{
LEX *lex= thd->lex;
- sp_cache_flush_obsolete(&thd->sp_proc_cache);
- sp_cache_flush_obsolete(&thd->sp_func_cache);
-
Parser_state parser_state(thd, inBuf, length);
bool err= parse_sql(thd, & parser_state, NULL);
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index 700017f2b3e..838f25320ec 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -173,8 +173,6 @@ private:
SELECT_LEX and other classes).
*/
MEM_ROOT main_mem_root;
- /* Version of the stored functions cache at the time of prepare. */
- ulong m_sp_cache_version;
private:
bool set_db(const char *db, uint db_length);
bool set_parameters(String *expanded_query,
@@ -2138,9 +2136,6 @@ void mysqld_stmt_prepare(THD *thd, const char *packet, uint packet_length)
DBUG_VOID_RETURN;
}
- sp_cache_flush_obsolete(&thd->sp_proc_cache);
- sp_cache_flush_obsolete(&thd->sp_func_cache);
-
thd->protocol= &thd->protocol_binary;
if (stmt->prepare(packet, packet_length))
@@ -2419,6 +2414,13 @@ void reinit_stmt_before_use(THD *thd, LEX *lex)
{
tables->reinit_before_use(thd);
}
+
+ /* Reset MDL tickets for procedures/functions */
+ for (Sroutine_hash_entry *rt=
+ (Sroutine_hash_entry*)thd->lex->sroutines_list.first;
+ rt; rt= rt->next)
+ rt->mdl_request.ticket= NULL;
+
/*
Cleanup of the special case of DELETE t1, t2 FROM t1, t2, t3 ...
(multi-delete). We do a full clean up, although at the moment all we
@@ -2512,9 +2514,6 @@ void mysqld_stmt_execute(THD *thd, char *packet_arg, uint packet_length)
DBUG_PRINT("exec_query", ("%s", stmt->query()));
DBUG_PRINT("info",("stmt: 0x%lx", (long) stmt));
- sp_cache_flush_obsolete(&thd->sp_proc_cache);
- sp_cache_flush_obsolete(&thd->sp_func_cache);
-
open_cursor= test(flags & (ulong) CURSOR_TYPE_READ_ONLY);
thd->protocol= &thd->protocol_binary;
@@ -2964,8 +2963,7 @@ Prepared_statement::Prepared_statement(THD *thd_arg)
param_array(0),
param_count(0),
last_errno(0),
- flags((uint) IS_IN_USE),
- m_sp_cache_version(0)
+ flags((uint) IS_IN_USE)
{
init_sql_alloc(&main_mem_root, thd_arg->variables.query_alloc_block_size,
thd_arg->variables.query_prealloc_size);
@@ -3234,20 +3232,6 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
init_stmt_after_parse(lex);
state= Query_arena::PREPARED;
flags&= ~ (uint) IS_IN_USE;
- /*
- This is for prepared statement validation purposes.
- A statement looks up and pre-loads all its stored functions
- at prepare. Later on, if a function is gone from the cache,
- execute may fail.
- Remember the cache version to be able to invalidate the prepared
- statement at execute if it changes.
- We only need to care about version of the stored functions cache:
- if a prepared statement uses a stored procedure, it's indirect,
- via a stored function. The only exception is SQLCOM_CALL,
- but the latter one looks up the stored procedure each time
- it's invoked, rather than once at prepare.
- */
- m_sp_cache_version= sp_cache_version(&thd->sp_func_cache);
/*
Log COM_EXECUTE to the general log. Note, that in case of SQL
@@ -3588,13 +3572,12 @@ Prepared_statement::swap_prepared_statement(Prepared_statement *copy)
is allocated in the old arena.
*/
swap_variables(Item_param **, param_array, copy->param_array);
- /* Swap flags: this is perhaps unnecessary */
- swap_variables(uint, flags, copy->flags);
+ /* Don't swap flags: the copy has IS_SQL_PREPARE always set. */
+ /* swap_variables(uint, flags, copy->flags); */
/* Swap names, the old name is allocated in the wrong memory root */
swap_variables(LEX_STRING, name, copy->name);
/* Ditto */
swap_variables(char *, db, copy->db);
- swap_variables(ulong, m_sp_cache_version, copy->m_sp_cache_version);
DBUG_ASSERT(db_length == copy->db_length);
DBUG_ASSERT(param_count == copy->param_count);
@@ -3654,19 +3637,6 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor)
}
/*
- Reprepare the statement if we're using stored functions
- and the version of the stored routines cache has changed.
- */
- if (lex->uses_stored_routines() &&
- m_sp_cache_version != sp_cache_version(&thd->sp_func_cache) &&
- thd->m_reprepare_observer &&
- thd->m_reprepare_observer->report_error(thd))
- {
- return TRUE;
- }
-
-
- /*
For SHOW VARIABLES lex->result is NULL, as it's a non-SELECT
command. For such queries we don't return an error and don't
open a cursor -- the client library will recognize this case and
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index 492bb46ebad..72fb49cf38c 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -3237,7 +3237,6 @@ end_share:
end_unlock:
pthread_mutex_unlock(&LOCK_open);
-end:
thd->mdl_context.release_lock(table_list.mdl_request.ticket);
thd->clear_error();
return res;
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 3b87a4dd6e8..30d6efff7ec 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -21,6 +21,7 @@
#include <myisam.h>
#include <my_dir.h>
#include "sp_head.h"
+#include "sp.h"
#include "sql_trigger.h"
#include "sql_show.h"
#include "transaction.h"
@@ -5010,6 +5011,23 @@ send_result_message:
trans_commit_implicit(thd);
close_thread_tables(thd);
table->table=0; // For query cache
+
+ /*
+ If it is CHECK TABLE v1, v2, v3, and v1, v2, v3 are views, we will run
+ separate open_tables() for each CHECK TABLE argument.
+ Right now we do not have a separate method to reset the prelocking
+ state in the lex to the state after parsing, so each open will pollute
+ this state: add elements to lex->srotuines_list, TABLE_LISTs to
+ lex->query_tables. Below is a lame attempt to recover from this
+ pollution.
+ @todo: have a method to reset a prelocking context, or use separate
+ contexts for each open.
+ */
+ for (Sroutine_hash_entry *rt=
+ (Sroutine_hash_entry*)thd->lex->sroutines_list.first;
+ rt; rt= rt->next)
+ rt->mdl_request.ticket= NULL;
+
if (protocol->write())
goto err;
}
diff --git a/sql/sql_update.cc b/sql/sql_update.cc
index b81fb30ec27..603ab1b9682 100644
--- a/sql/sql_update.cc
+++ b/sql/sql_update.cc
@@ -1117,10 +1117,6 @@ reopen_tables:
while ((item= it++))
item->cleanup();
- /* We have to cleanup translation tables of views. */
- for (TABLE_LIST *tbl= table_list; tbl; tbl= tbl->next_global)
- tbl->cleanup_items();
-
/*
To not to hog memory (as a result of the
unit->reinit_exec_mechanism() call below):