summaryrefslogtreecommitdiff
path: root/sql/sql_base.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_base.cc')
-rw-r--r--sql/sql_base.cc3162
1 files changed, 1367 insertions, 1795 deletions
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index 27ee8957b25..05cd4709151 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -17,8 +17,8 @@
/* Basic functions needed by many modules */
+#include <my_global.h>
#include "sql_base.h" // setup_table_map
-#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */
#include "sql_priv.h"
#include "unireg.h"
#include "debug_sync.h"
@@ -49,13 +49,15 @@
#include "sql_trigger.h"
#include "transaction.h"
#include "sql_prepare.h"
+#include "sql_statistics.h"
#include <m_ctype.h>
#include <my_dir.h>
#include <hash.h>
#include "rpl_filter.h"
#include "sql_table.h" // build_table_filename
-#include "datadict.h" // dd_frm_type()
+#include "datadict.h" // dd_frm_is_view()
#include "sql_hset.h" // Hash_set
+#include "rpl_rli.h" // rpl_group_info
#ifdef __WIN__
#include <io.h>
#endif
@@ -65,9 +67,9 @@ bool
No_such_table_error_handler::handle_condition(THD *,
uint sql_errno,
const char*,
- MYSQL_ERROR::enum_warning_level level,
+ Sql_condition::enum_warning_level level,
const char*,
- MYSQL_ERROR ** cond_hdl)
+ Sql_condition ** cond_hdl)
{
*cond_hdl= NULL;
if (sql_errno == ER_NO_SUCH_TABLE || sql_errno == ER_NO_SUCH_TABLE_IN_ENGINE)
@@ -76,7 +78,7 @@ No_such_table_error_handler::handle_condition(THD *,
return TRUE;
}
- if (level == MYSQL_ERROR::WARN_LEVEL_ERROR)
+ if (level == Sql_condition::WARN_LEVEL_ERROR)
m_unhandled_errors++;
return FALSE;
}
@@ -109,9 +111,9 @@ public:
bool handle_condition(THD *thd,
uint sql_errno,
const char* sqlstate,
- MYSQL_ERROR::enum_warning_level level,
+ Sql_condition::enum_warning_level level,
const char* msg,
- MYSQL_ERROR ** cond_hdl);
+ Sql_condition ** cond_hdl);
/**
Returns TRUE if there were ER_NO_SUCH_/WRONG_MRG_TABLE and there
@@ -139,9 +141,9 @@ bool
Repair_mrg_table_error_handler::handle_condition(THD *,
uint sql_errno,
const char*,
- MYSQL_ERROR::enum_warning_level level,
+ Sql_condition::enum_warning_level level,
const char*,
- MYSQL_ERROR ** cond_hdl)
+ Sql_condition ** cond_hdl)
{
*cond_hdl= NULL;
if (sql_errno == ER_NO_SUCH_TABLE ||
@@ -162,679 +164,87 @@ Repair_mrg_table_error_handler::handle_condition(THD *,
@{
*/
-/**
- Protects table_def_hash, used and unused lists in the
- TABLE_SHARE object, LRU lists of used TABLEs and used
- TABLE_SHAREs, refresh_version and the table id counter.
-*/
-mysql_mutex_t LOCK_open;
-
-#ifdef HAVE_PSI_INTERFACE
-static PSI_mutex_key key_LOCK_open;
-static PSI_mutex_info all_tdc_mutexes[]= {
- { &key_LOCK_open, "LOCK_open", PSI_FLAG_GLOBAL }
-};
-
-/**
- Initialize performance schema instrumentation points
- used by the table cache.
-*/
-
-static void init_tdc_psi_keys(void)
-{
- const char *category= "sql";
- int count;
-
- if (PSI_server == NULL)
- return;
-
- count= array_elements(all_tdc_mutexes);
- PSI_server->register_mutex(category, all_tdc_mutexes, count);
-}
-#endif /* HAVE_PSI_INTERFACE */
-
-
-/**
- Total number of TABLE instances for tables in the table definition cache
- (both in use by threads and not in use). This value is accessible to user
- as "Open_tables" status variable.
-*/
-uint table_cache_count= 0;
-/**
- List that contains all TABLE instances for tables in the table definition
- cache that are not in use by any thread. Recently used TABLE instances are
- appended to the end of the list. Thus the beginning of the list contains
- tables which have been least recently used.
-*/
-TABLE *unused_tables;
-HASH table_def_cache;
-static TABLE_SHARE *oldest_unused_share, end_of_unused_share;
-static bool table_def_inited= 0;
-static bool table_def_shutdown_in_progress= 0;
-
static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables,
TABLE_SHARE *table_share);
static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry);
static bool auto_repair_table(THD *thd, TABLE_LIST *table_list);
-static void free_cache_entry(TABLE *entry);
-
-uint cached_open_tables(void)
-{
- return table_cache_count;
-}
-
-
-#ifdef EXTRA_DEBUG
-static void check_unused(THD *thd)
-{
- uint count= 0, open_files= 0, idx= 0;
- TABLE *cur_link, *start_link, *entry;
- TABLE_SHARE *share;
- DBUG_ENTER("check_unused");
-
- if ((start_link=cur_link=unused_tables))
- {
- do
- {
- if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next)
- {
- DBUG_PRINT("error",("Unused_links aren't linked properly")); /* purecov: inspected */
- DBUG_VOID_RETURN; /* purecov: inspected */
- }
- } while (count++ < table_cache_count &&
- (cur_link=cur_link->next) != start_link);
- if (cur_link != start_link)
- {
- DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */
- }
- }
- for (idx=0 ; idx < table_def_cache.records ; idx++)
- {
- share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx);
-
- I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
- while ((entry= it++))
- {
- /*
- We must not have TABLEs in the free list that have their file closed.
- */
- DBUG_ASSERT(entry->db_stat && entry->file);
- /* Merge children should be detached from a merge parent */
- if (entry->in_use)
- {
- DBUG_PRINT("error",("Used table is in share's list of unused tables")); /* purecov: inspected */
- }
- /* extra() may assume that in_use is set */
- entry->in_use= thd;
- DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
- entry->in_use= 0;
-
- count--;
- open_files++;
- }
- it.init(share->used_tables);
- while ((entry= it++))
- {
- if (!entry->in_use)
- {
- DBUG_PRINT("error",("Unused table is in share's list of used tables")); /* purecov: inspected */
- }
- open_files++;
- }
- }
- if (count != 0)
- {
- DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d", /* purecov: inspected */
- count)); /* purecov: inspected */
- }
- DBUG_VOID_RETURN;
-}
-#else
-#define check_unused(A)
-#endif
-/*
- Create a table cache key
+/**
+ Create a table cache/table definition cache key
- SYNOPSIS
- create_table_def_key()
- thd Thread handler
- key Create key here (must be of size MAX_DBKEY_LENGTH)
- table_list Table definition
- tmp_table Set if table is a tmp table
+ @param thd Thread context
+ @param key Buffer for the key to be created (must be of
+ size MAX_DBKEY_LENGTH).
+ @param db_name Database name.
+ @param table_name Table name.
- IMPLEMENTATION
+ @note
The table cache_key is created from:
db_name + \0
table_name + \0
- if the table is a tmp table, we add the following to make each tmp table
+ additionally we add the following to make each tmp table
unique on the slave:
4 bytes for master thread id
4 bytes pseudo thread id
- RETURN
- Length of key
+ @return Length of key.
*/
-uint create_table_def_key(THD *thd, char *key,
- const TABLE_LIST *table_list,
- bool tmp_table)
+uint create_tmp_table_def_key(THD *thd, char *key,
+ const char *db, const char *table_name)
{
- uint key_length= create_table_def_key(key, table_list->db,
- table_list->table_name);
-
- if (tmp_table)
- {
- int4store(key + key_length, thd->server_id);
- int4store(key + key_length + 4, thd->variables.pseudo_thread_id);
- key_length+= TMP_TABLE_KEY_EXTRA;
- }
+ uint key_length= tdc_create_key(key, db, table_name);
+ int4store(key + key_length, thd->variables.server_id);
+ int4store(key + key_length + 4, thd->variables.pseudo_thread_id);
+ key_length+= TMP_TABLE_KEY_EXTRA;
return key_length;
}
-
-/*****************************************************************************
- Functions to handle table definition cach (TABLE_SHARE)
-*****************************************************************************/
-
-extern "C" uchar *table_def_key(const uchar *record, size_t *length,
- my_bool not_used __attribute__((unused)))
-{
- TABLE_SHARE *entry=(TABLE_SHARE*) record;
- *length= entry->table_cache_key.length;
- return (uchar*) entry->table_cache_key.str;
-}
-
-
-static void table_def_free_entry(TABLE_SHARE *share)
-{
- DBUG_ENTER("table_def_free_entry");
- mysql_mutex_assert_owner(&LOCK_open);
- if (share->prev)
- {
- /* remove from old_unused_share list */
- *share->prev= share->next;
- share->next->prev= share->prev;
- }
- free_table_share(share);
- DBUG_VOID_RETURN;
-}
-
-
-bool table_def_init(void)
-{
- table_def_inited= 1;
-#ifdef HAVE_PSI_INTERFACE
- init_tdc_psi_keys();
-#endif
- mysql_mutex_init(key_LOCK_open, &LOCK_open, MY_MUTEX_INIT_FAST);
- oldest_unused_share= &end_of_unused_share;
- end_of_unused_share.prev= &oldest_unused_share;
-
-
- return my_hash_init(&table_def_cache, &my_charset_bin, table_def_size,
- 0, 0, table_def_key,
- (my_hash_free_key) table_def_free_entry, 0) != 0;
-}
-
-
-/**
- Notify table definition cache that process of shutting down server
- has started so it has to keep number of TABLE and TABLE_SHARE objects
- minimal in order to reduce number of references to pluggable engines.
-*/
-
-void table_def_start_shutdown(void)
-{
- if (table_def_inited)
- {
- mysql_mutex_lock(&LOCK_open);
- /*
- Ensure that TABLE and TABLE_SHARE objects which are created for
- tables that are open during process of plugins' shutdown are
- immediately released. This keeps number of references to engine
- plugins minimal and allows shutdown to proceed smoothly.
- */
- table_def_shutdown_in_progress= TRUE;
- mysql_mutex_unlock(&LOCK_open);
- /* Free all cached but unused TABLEs and TABLE_SHAREs. */
- close_cached_tables(NULL, NULL, FALSE, LONG_TIMEOUT);
- }
-}
-
-
-void table_def_free(void)
-{
- DBUG_ENTER("table_def_free");
- if (table_def_inited)
- {
- table_def_inited= 0;
- /* Free table definitions. */
- my_hash_free(&table_def_cache);
- mysql_mutex_destroy(&LOCK_open);
- }
- DBUG_VOID_RETURN;
-}
-
-
-uint cached_table_definitions(void)
-{
- return table_def_cache.records;
-}
-
-
-/*
- Auxiliary routines for manipulating with per-share used/unused and
- global unused lists of TABLE objects and table_cache_count counter.
- Responsible for preserving invariants between those lists, counter
- and TABLE::in_use member.
- In fact those routines implement sort of implicit table cache as
- part of table definition cache.
-*/
-
-
-/**
- Add newly created TABLE object for table share which is going
- to be used right away.
-*/
-
-static void table_def_add_used_table(THD *thd, TABLE *table)
-{
- DBUG_ASSERT(table->in_use == thd);
- table->s->used_tables.push_front(table);
- table_cache_count++;
-}
-
-
-/**
- Prepare used or unused TABLE instance for destruction by removing
- it from share's and global list.
-*/
-
-static void table_def_remove_table(TABLE *table)
-{
- if (table->in_use)
- {
- /* Remove from per-share chain of used TABLE objects. */
- table->s->used_tables.remove(table);
- }
- else
- {
- /* Remove from per-share chain of unused TABLE objects. */
- table->s->free_tables.remove(table);
-
- /* And global unused chain. */
- table->next->prev=table->prev;
- table->prev->next=table->next;
- if (table == unused_tables)
- {
- unused_tables=unused_tables->next;
- if (table == unused_tables)
- unused_tables=0;
- }
- check_unused(current_thd);
- }
- table_cache_count--;
-}
-
-
-/**
- Mark already existing TABLE instance as used.
-*/
-
-static void table_def_use_table(THD *thd, TABLE *table)
-{
- DBUG_ASSERT(!table->in_use);
-
- /* Unlink table from list of unused tables for this share. */
- table->s->free_tables.remove(table);
- /* Unlink able from global unused tables list. */
- if (table == unused_tables)
- { // First unused
- unused_tables=unused_tables->next; // Remove from link
- if (table == unused_tables)
- unused_tables=0;
- }
- table->prev->next=table->next; /* Remove from unused list */
- table->next->prev=table->prev;
- check_unused(thd);
- /* Add table to list of used tables for this share. */
- table->s->used_tables.push_front(table);
- table->in_use= thd;
- /* The ex-unused table must be fully functional. */
- DBUG_ASSERT(table->db_stat && table->file);
- /* The children must be detached from the table. */
- DBUG_ASSERT(! table->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
-}
-
-
/**
- Mark already existing used TABLE instance as unused.
-*/
-
-static void table_def_unuse_table(TABLE *table)
-{
- THD *thd __attribute__((unused))= table->in_use;
- DBUG_ASSERT(table->in_use);
-
- /* We shouldn't put the table to 'unused' list if the share is old. */
- DBUG_ASSERT(! table->s->has_old_version());
-
- table->in_use= 0;
- /* Remove table from the list of tables used in this share. */
- table->s->used_tables.remove(table);
- /* Add table to the list of unused TABLE objects for this share. */
- table->s->free_tables.push_front(table);
- /* Also link it last in the global list of unused TABLE objects. */
- if (unused_tables)
- {
- table->next=unused_tables;
- table->prev=unused_tables->prev;
- unused_tables->prev=table;
- table->prev->next=table;
- }
- else
- unused_tables=table->next=table->prev=table;
- check_unused(thd);
-}
-
-
-/*
- Get TABLE_SHARE for a table.
-
- get_table_share()
- thd Thread handle
- table_list Table that should be opened
- key Table cache key
- key_length Length of key
- db_flags Flags to open_table_def():
- OPEN_VIEW
- error out: Error code from open_table_def()
-
- IMPLEMENTATION
- Get a table definition from the table definition cache.
- If it doesn't exist, create a new from the table definition file.
-
- NOTES
- We must have wrlock on LOCK_open when we come here
- (To be changed later)
-
- RETURN
- 0 Error
- # Share for table
+ Get table cache key for a table list element.
+
+ @param table_list[in] Table list element.
+ @param key[out] On return points to table cache key for the table.
+
+ @note Unlike create_table_def_key() call this function doesn't construct
+ key in a buffer provider by caller. Instead it relies on the fact
+ that table list element for which key is requested has properly
+ initialized MDL_request object and the fact that table definition
+ cache key is suffix of key used in MDL subsystem. So to get table
+ definition key it simply needs to return pointer to appropriate
+ part of MDL_key object nested in this table list element.
+ Indeed, this means that lifetime of key produced by this call is
+ limited by the lifetime of table list element which it got as
+ parameter.
+
+ @return Length of key.
*/
-TABLE_SHARE *get_table_share(THD *thd, TABLE_LIST *table_list, char *key,
- uint key_length, uint db_flags, int *error,
- my_hash_value_type hash_value)
+uint get_table_def_key(const TABLE_LIST *table_list, const char **key)
{
- TABLE_SHARE *share;
- DBUG_ENTER("get_table_share");
-
- *error= 0;
-
/*
- To be able perform any operation on table we should own
- some kind of metadata lock on it.
+ This call relies on the fact that TABLE_LIST::mdl_request::key object
+ is properly initialized, so table definition cache can be produced
+ from key used by MDL subsystem.
*/
- DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE,
- table_list->db,
- table_list->table_name,
- MDL_SHARED));
-
- /* Read table definition from cache */
- if ((share= (TABLE_SHARE*) my_hash_search_using_hash_value(&table_def_cache,
- hash_value, (uchar*) key, key_length)))
- goto found;
-
- if (!(share= alloc_table_share(table_list, key, key_length)))
- {
- DBUG_RETURN(0);
- }
-
- /*
- We assign a new table id under the protection of LOCK_open.
- We do this instead of creating a new mutex
- and using it for the sole purpose of serializing accesses to a
- static variable, we assign the table id here. We assign it to the
- share before inserting it into the table_def_cache to be really
- sure that it cannot be read from the cache without having a table
- id assigned.
-
- CAVEAT. This means that the table cannot be used for
- binlogging/replication purposes, unless get_table_share() has been
- called directly or indirectly.
- */
- assign_new_table_id(share);
-
- if (my_hash_insert(&table_def_cache, (uchar*) share))
- {
- free_table_share(share);
- DBUG_RETURN(0); // return error
- }
- if (open_table_def(thd, share, db_flags))
- {
- *error= share->error;
- (void) my_hash_delete(&table_def_cache, (uchar*) share);
- DBUG_RETURN(0);
- }
- share->ref_count++; // Mark in use
- DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u",
- (ulong) share, share->ref_count));
- DBUG_RETURN(share);
+ DBUG_ASSERT(!strcmp(table_list->get_db_name(),
+ table_list->mdl_request.key.db_name()) &&
+ !strcmp(table_list->get_table_name(),
+ table_list->mdl_request.key.name()));
-found:
- /*
- We found an existing table definition. Return it if we didn't get
- an error when reading the table definition from file.
- */
- if (share->error)
- {
- /* Table definition contained an error */
- open_table_error(share, share->error, share->open_errno, share->errarg);
- DBUG_RETURN(0);
- }
- if ((share->is_view && !(db_flags & OPEN_VIEW)) ||
- (!share->is_view && (db_flags & OPEN_VIEW_ONLY)))
- {
- open_table_error(share, 1, ENOENT, 0);
- DBUG_RETURN(0);
- }
-
- ++share->ref_count;
-
- if (share->ref_count == 1 && share->prev)
- {
- /*
- Share was not used before and it was in the old_unused_share list
- Unlink share from this list
- */
- DBUG_PRINT("info", ("Unlinking from not used list"));
- *share->prev= share->next;
- share->next->prev= share->prev;
- share->next= 0;
- share->prev= 0;
- }
-
- /* Free cache if too big */
- while (table_def_cache.records > table_def_size &&
- oldest_unused_share->next)
- my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
-
- DBUG_PRINT("exit", ("share: 0x%lx ref_count: %u",
- (ulong) share, share->ref_count));
- DBUG_RETURN(share);
+ *key= (const char*)table_list->mdl_request.key.ptr() + 1;
+ return table_list->mdl_request.key.length() - 1;
}
-/**
- Get a table share. If it didn't exist, try creating it from engine
-
- For arguments and return values, see get_table_share()
-*/
-
-static TABLE_SHARE *
-get_table_share_with_discover(THD *thd, TABLE_LIST *table_list,
- char *key, uint key_length,
- uint db_flags, int *error,
- my_hash_value_type hash_value)
-
-{
- TABLE_SHARE *share;
- bool exists;
- DBUG_ENTER("get_table_share_with_discover");
-
- share= get_table_share(thd, table_list, key, key_length, db_flags, error,
- hash_value);
- /*
- If share is not NULL, we found an existing share.
-
- If share is NULL, and there is no error, we're inside
- pre-locking, which silences 'ER_NO_SUCH_TABLE' errors
- with the intention to silently drop non-existing tables
- from the pre-locking list. In this case we still need to try
- auto-discover before returning a NULL share.
-
- Or, we're inside SHOW CREATE VIEW, which
- also installs a silencer for ER_NO_SUCH_TABLE error.
-
- If share is NULL and the error is ER_NO_SUCH_TABLE, this is
- the same as above, only that the error was not silenced by
- pre-locking or SHOW CREATE VIEW.
-
- In both these cases it won't harm to try to discover the
- table.
-
- Finally, if share is still NULL, it's a real error and we need
- to abort.
-
- @todo Rework alternative ways to deal with ER_NO_SUCH TABLE.
- */
- if (share ||
- (thd->is_error() && thd->stmt_da->sql_errno() != ER_NO_SUCH_TABLE &&
- thd->stmt_da->sql_errno() != ER_NO_SUCH_TABLE_IN_ENGINE))
- DBUG_RETURN(share);
-
- *error= 0;
-
- /* Table didn't exist. Check if some engine can provide it */
- if (ha_check_if_table_exists(thd, table_list->db, table_list->table_name,
- &exists))
- {
- thd->clear_error();
- /* Conventionally, the storage engine API does not report errors. */
- my_error(ER_OUT_OF_RESOURCES, MYF(0));
- }
- else if (! exists)
- {
- /*
- No such table in any engine.
- Hide "Table doesn't exist" errors if the table belongs to a view.
- The check for thd->is_error() is necessary to not push an
- unwanted error in case the error was already silenced.
- @todo Rework the alternative ways to deal with ER_NO_SUCH TABLE.
- */
- if (thd->is_error())
- {
- if (table_list->parent_l)
- {
- thd->clear_error();
- my_error(ER_WRONG_MRG_TABLE, MYF(0));
- }
- else if (table_list->belong_to_view)
- {
- TABLE_LIST *view= table_list->belong_to_view;
- thd->clear_error();
- my_error(ER_VIEW_INVALID, MYF(0),
- view->view_db.str, view->view_name.str);
- }
- }
- }
- else
- {
- thd->clear_error();
- *error= 7; /* Run auto-discover. */
- }
- DBUG_RETURN(NULL);
-}
-
-
-/**
- Mark that we are not using table share anymore.
-
- @param share Table share
-
- If the share has no open tables and (we have done a refresh or
- if we have already too many open table shares) then delete the
- definition.
-*/
-
-void release_table_share(TABLE_SHARE *share)
-{
- DBUG_ENTER("release_table_share");
- DBUG_PRINT("enter",
- ("share: 0x%lx table: %s.%s ref_count: %u version: %lu",
- (ulong) share, share->db.str, share->table_name.str,
- share->ref_count, share->version));
-
- mysql_mutex_assert_owner(&LOCK_open);
-
- DBUG_ASSERT(share->ref_count);
- if (!--share->ref_count)
- {
- if (share->has_old_version() || table_def_shutdown_in_progress)
- my_hash_delete(&table_def_cache, (uchar*) share);
- else
- {
- /* Link share last in used_table_share list */
- DBUG_PRINT("info",("moving share to unused list"));
-
- DBUG_ASSERT(share->next == 0);
- share->prev= end_of_unused_share.prev;
- *end_of_unused_share.prev= share;
- end_of_unused_share.prev= &share->next;
- share->next= &end_of_unused_share;
-
- if (table_def_cache.records > table_def_size)
- {
- /* Delete the least used share to preserve LRU order. */
- my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
- }
- }
- }
-
- DBUG_VOID_RETURN;
-}
-
-
-/*
- Check if table definition exits in cache
-
- SYNOPSIS
- get_cached_table_share()
- db Database name
- table_name Table name
-
- RETURN
- 0 Not cached
- # TABLE_SHARE for table
-*/
-
-TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name)
-{
- char key[SAFE_NAME_LEN*2+2];
- uint key_length;
- mysql_mutex_assert_owner(&LOCK_open);
-
- key_length= create_table_def_key(key, db, table_name);
- return (TABLE_SHARE*) my_hash_search(&table_def_cache,
- (uchar*) key, key_length);
-}
+/*****************************************************************************
+ Functions to handle table definition cache (TABLE_SHARE)
+*****************************************************************************/
/*
Create a list for all open tables matching SQL expression
@@ -847,7 +257,7 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name)
NOTES
One gets only a list of tables for which one has any kind of privilege.
db and table names are allocated in result struct, so one doesn't need
- a lock on LOCK_open when traversing the return list.
+ a lock when traversing the return list.
RETURN VALUES
NULL Error (Probably OOM)
@@ -856,20 +266,19 @@ TABLE_SHARE *get_cached_table_share(const char *db, const char *table_name)
OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
{
- int result = 0;
OPEN_TABLE_LIST **start_list, *open_list;
TABLE_LIST table_list;
+ TABLE_SHARE *share;
+ TDC_iterator tdc_it;
DBUG_ENTER("list_open_tables");
- mysql_mutex_lock(&LOCK_open);
bzero((char*) &table_list,sizeof(table_list));
start_list= &open_list;
open_list=0;
- for (uint idx=0 ; result == 0 && idx < table_def_cache.records; idx++)
+ tdc_it.init();
+ while ((share= tdc_it.next()))
{
- TABLE_SHARE *share= (TABLE_SHARE *)my_hash_element(&table_def_cache, idx);
-
if (db && my_strcasecmp(system_charset_info, db, share->db.str))
continue;
if (wild && wild_compare(share->table_name.str, wild, 0))
@@ -894,14 +303,18 @@ OPEN_TABLE_LIST *list_open_tables(THD *thd, const char *db, const char *wild)
share->db.str)+1,
share->table_name.str);
(*start_list)->in_use= 0;
- I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
- while (it++)
- ++(*start_list)->in_use;
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables);
+ TABLE *table;
+ while ((table= it++))
+ if (table->in_use)
+ ++(*start_list)->in_use;
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
(*start_list)->locked= 0; /* Obsolete. */
start_list= &(*start_list)->next;
*start_list=0;
}
- mysql_mutex_unlock(&LOCK_open);
+ tdc_it.deinit();
DBUG_RETURN(open_list);
}
@@ -923,33 +336,11 @@ void intern_close_table(TABLE *table)
if (table->file) // Not true if placeholder
(void) closefrm(table, 1); // close file
table->alias.free();
- DBUG_VOID_RETURN;
-}
-
-/*
- Remove table from the open table cache
-
- SYNOPSIS
- free_cache_entry()
- table Table to remove
-
- NOTE
- We need to have a lock on LOCK_open when calling this
-*/
-
-static void free_cache_entry(TABLE *table)
-{
- DBUG_ENTER("free_cache_entry");
-
- /* This should be done before releasing table share. */
- table_def_remove_table(table);
-
- intern_close_table(table);
-
my_free(table);
DBUG_VOID_RETURN;
}
+
/* Free resources allocated by filesort() and read_record() */
void free_io_cache(TABLE *table)
@@ -971,20 +362,24 @@ void free_io_cache(TABLE *table)
@param share Table share.
- @pre Caller should have LOCK_open mutex.
+ @pre Caller should have TABLE_SHARE::tdc.LOCK_table_share mutex.
*/
-static void kill_delayed_threads_for_table(TABLE_SHARE *share)
+void kill_delayed_threads_for_table(TABLE_SHARE *share)
{
- I_P_List_iterator<TABLE, TABLE_share> it(share->used_tables);
+ TABLE_SHARE::All_share_tables_list::Iterator it(share->tdc.all_tables);
TABLE *tab;
- mysql_mutex_assert_owner(&LOCK_open);
+ mysql_mutex_assert_owner(&share->tdc.LOCK_table_share);
+
+ if (!delayed_insert_threads)
+ return;
while ((tab= it++))
{
THD *in_use= tab->in_use;
+ DBUG_ASSERT(in_use && tab->s->tdc.flushed);
if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
! in_use->killed)
{
@@ -1025,64 +420,50 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables,
bool wait_for_refresh, ulong timeout)
{
bool result= FALSE;
- bool found= TRUE;
struct timespec abstime;
+ ulong refresh_version;
DBUG_ENTER("close_cached_tables");
DBUG_ASSERT(thd || (!wait_for_refresh && !tables));
- mysql_mutex_lock(&LOCK_open);
+ refresh_version= tdc_increment_refresh_version();
+
if (!tables)
{
/*
Force close of all open tables.
Note that code in TABLE_SHARE::wait_for_old_version() assumes that
- incrementing of refresh_version and removal of unused tables and
- shares from TDC happens atomically under protection of LOCK_open,
- or putting it another way that TDC does not contain old shares
- which don't have any tables used.
+ incrementing of refresh_version is followed by purge of unused table
+ shares.
*/
- refresh_version++;
- DBUG_PRINT("tcache", ("incremented global refresh_version to: %lu",
- refresh_version));
kill_delayed_threads();
/*
Get rid of all unused TABLE and TABLE_SHARE instances. By doing
this we automatically close all tables which were marked as "old".
*/
- while (unused_tables)
- free_cache_entry(unused_tables);
+ tc_purge(true);
/* Free table shares which were not freed implicitly by loop above. */
- while (oldest_unused_share->next)
- (void) my_hash_delete(&table_def_cache, (uchar*) oldest_unused_share);
+ tdc_purge(true);
}
else
{
bool found=0;
for (TABLE_LIST *table= tables; table; table= table->next_local)
{
- TABLE_SHARE *share= get_cached_table_share(table->db, table->table_name);
-
- if (share)
- {
- kill_delayed_threads_for_table(share);
- /* tdc_remove_table() calls share->remove_from_cache_at_close() */
- tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db,
- table->table_name, TRUE);
- found=1;
- }
+ /* tdc_remove_table() also sets TABLE_SHARE::version to 0. */
+ found|= tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED, table->db,
+ table->table_name, TRUE);
}
if (!found)
wait_for_refresh=0; // Nothing to wait for
}
- mysql_mutex_unlock(&LOCK_open);
+ DBUG_PRINT("info", ("open table definitions: %d",
+ (int) tdc_records()));
if (!wait_for_refresh)
DBUG_RETURN(result);
- set_timespec(abstime, timeout);
-
if (thd->locked_tables_mode)
{
/*
@@ -1114,67 +495,69 @@ bool close_cached_tables(THD *thd, TABLE_LIST *tables,
result= TRUE;
goto err_with_reopen;
}
- close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED);
+ close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL);
}
}
/* Wait until all threads have closed all the tables we are flushing. */
DBUG_PRINT("info", ("Waiting for other threads to close their open tables"));
- while (found && ! thd->killed)
- {
- TABLE_SHARE *share;
- found= FALSE;
- /*
- To a self-deadlock or deadlocks with other FLUSH threads
- waiting on our open HANDLERs, we have to flush them.
- */
- mysql_ha_flush(thd);
- DEBUG_SYNC(thd, "after_flush_unlock");
-
- mysql_mutex_lock(&LOCK_open);
+ /*
+ To a self-deadlock or deadlocks with other FLUSH threads
+ waiting on our open HANDLERs, we have to flush them.
+ */
+ mysql_ha_flush(thd);
+ DEBUG_SYNC(thd, "after_flush_unlock");
- if (!tables)
+ if (!tables)
+ {
+ bool found= true;
+ set_timespec(abstime, timeout);
+ while (found && !thd->killed)
{
- for (uint idx=0 ; idx < table_def_cache.records ; idx++)
+ TABLE_SHARE *share;
+ TDC_iterator tdc_it;
+ found= false;
+
+ tdc_it.init();
+ while ((share= tdc_it.next()))
{
- share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx);
- if (share->has_old_version())
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
+ if (share->tdc.flushed && share->tdc.version < refresh_version)
{
- found= TRUE;
+ /* wait_for_old_version() will unlock mutex and free share */
+ found= true;
break;
}
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
}
- }
- else
- {
- for (TABLE_LIST *table= tables; table; table= table->next_local)
+ tdc_it.deinit();
+
+ if (found)
{
- share= get_cached_table_share(table->db, table->table_name);
- if (share && share->has_old_version())
+ if (share->wait_for_old_version(thd, &abstime,
+ MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))
{
- found= TRUE;
+ result= TRUE;
break;
}
}
}
-
- if (found)
+ }
+ else
+ {
+ for (TABLE_LIST *table= tables; table; table= table->next_local)
{
- /*
- The method below temporarily unlocks LOCK_open and frees
- share's memory.
- */
- if (share->wait_for_old_version(thd, &abstime,
- MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))
+ if (thd->killed)
+ break;
+ if (tdc_wait_for_old_version(thd, table->db, table->table_name, timeout,
+ MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL,
+ refresh_version))
{
- mysql_mutex_unlock(&LOCK_open);
result= TRUE;
- goto err_with_reopen;
+ break;
}
}
-
- mysql_mutex_unlock(&LOCK_open);
}
err_with_reopen:
@@ -1185,14 +568,14 @@ err_with_reopen:
old locks. This should always succeed (unless some external process
has removed the tables)
*/
- thd->locked_tables_list.reopen_tables(thd);
+ thd->locked_tables_list.reopen_tables(thd, false);
/*
- Since downgrade_exclusive_lock() won't do anything with shared
+ Since downgrade_lock() won't do anything with shared
metadata lock it is much simpler to go through all open tables rather
than picking only those tables that were flushed.
*/
for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
- tab->mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE);
+ tab->mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
}
DBUG_RETURN(result);
}
@@ -1205,23 +588,26 @@ err_with_reopen:
bool close_cached_connection_tables(THD *thd, LEX_STRING *connection)
{
- uint idx;
TABLE_LIST tmp, *tables= NULL;
bool result= FALSE;
+ TABLE_SHARE *share;
+ TDC_iterator tdc_it;
DBUG_ENTER("close_cached_connections");
DBUG_ASSERT(thd);
bzero(&tmp, sizeof(TABLE_LIST));
- mysql_mutex_lock(&LOCK_open);
-
- for (idx= 0; idx < table_def_cache.records; idx++)
+ tdc_it.init();
+ while ((share= tdc_it.next()))
{
- TABLE_SHARE *share= (TABLE_SHARE *) my_hash_element(&table_def_cache, idx);
-
+ mysql_mutex_lock(&share->tdc.LOCK_table_share);
/* Ignore if table is not open or does not have a connect_string */
- if (!share->connect_string.length || !share->ref_count)
+ if (!share->connect_string.length || !share->tdc.ref_count)
+ {
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
continue;
+ }
+ mysql_mutex_unlock(&share->tdc.LOCK_table_share);
/* Compare the connection string */
if (connection &&
@@ -1241,7 +627,7 @@ bool close_cached_connection_tables(THD *thd, LEX_STRING *connection)
tables= (TABLE_LIST *) memdup_root(thd->mem_root, (char*)&tmp,
sizeof(TABLE_LIST));
}
- mysql_mutex_unlock(&LOCK_open);
+ tdc_it.deinit();
if (tables)
result= close_cached_tables(thd, tables, FALSE, LONG_TIMEOUT);
@@ -1262,11 +648,25 @@ bool close_cached_connection_tables(THD *thd, LEX_STRING *connection)
static void mark_temp_tables_as_free_for_reuse(THD *thd)
{
- for (TABLE *table= thd->temporary_tables ; table ; table= table->next)
+ DBUG_ENTER("mark_temp_tables_as_free_for_reuse");
+
+ if (thd->query_id == 0)
{
- if ((table->query_id == thd->query_id) && ! table->open_by_handler)
- mark_tmp_table_for_reuse(table);
+ /* Thread has not executed any statement and has not used any tmp tables */
+ DBUG_VOID_RETURN;
}
+
+ if (thd->have_temporary_tables())
+ {
+ thd->lock_temporary_tables();
+ for (TABLE *table= thd->temporary_tables ; table ; table= table->next)
+ {
+ if ((table->query_id == thd->query_id) && ! table->open_by_handler)
+ mark_tmp_table_for_reuse(table);
+ }
+ thd->unlock_temporary_tables(1);
+ }
+ DBUG_VOID_RETURN;
}
@@ -1280,6 +680,7 @@ static void mark_temp_tables_as_free_for_reuse(THD *thd)
void mark_tmp_table_for_reuse(TABLE *table)
{
+ DBUG_ENTER("mark_tmp_table_for_reuse");
DBUG_ASSERT(table->s->tmp_table);
table->query_id= 0;
@@ -1310,6 +711,7 @@ void mark_tmp_table_for_reuse(TABLE *table)
LOCK TABLES is allowed (but ignored) for a temporary table.
*/
table->reginfo.lock_type= TL_WRITE;
+ DBUG_VOID_RETURN;
}
@@ -1361,8 +763,6 @@ static void mark_used_tables_as_free_for_reuse(THD *thd, TABLE *table)
static void close_open_tables(THD *thd)
{
- mysql_mutex_assert_not_owner(&LOCK_open);
-
DBUG_PRINT("info", ("thd->open_tables: 0x%lx", (long) thd->open_tables));
while (thd->open_tables)
@@ -1381,38 +781,46 @@ static void close_open_tables(THD *thd)
access the table cache key
@param[in] extra
- HA_EXTRA_PREPRE_FOR_DROP if the table is being dropped
- HA_EXTRA_PREPARE_FOR_REANME if the table is being renamed
- HA_EXTRA_NOT_USED no drop/rename
- In case of drop/reanme the documented behaviour is to
+ HA_EXTRA_PREPARE_FOR_DROP
+ - The table is dropped
+ HA_EXTRA_PREPARE_FOR_RENAME
+ - The table is renamed
+ HA_EXTRA_NOT_USED
+ - The table is marked as closed in the
+ locked_table_list but kept there so one can call
+ locked_table_list->reopen_tables() to put it back.
+
+ In case of drop/rename the documented behavior is to
implicitly remove the table from LOCK TABLES
- list.
+ list.
@pre Must be called with an X MDL lock on the table.
*/
void
close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
- ha_extra_function extra)
+ ha_extra_function extra,
+ TABLE *skip_table)
{
char key[MAX_DBKEY_LENGTH];
uint key_length= share->table_cache_key.length;
const char *db= key;
const char *table_name= db + share->db.length + 1;
+ bool remove_from_locked_tables= extra != HA_EXTRA_NOT_USED;
memcpy(key, share->table_cache_key.str, key_length);
- mysql_mutex_assert_not_owner(&LOCK_open);
for (TABLE **prev= &thd->open_tables; *prev; )
{
TABLE *table= *prev;
if (table->s->table_cache_key.length == key_length &&
- !memcmp(table->s->table_cache_key.str, key, key_length))
+ !memcmp(table->s->table_cache_key.str, key, key_length) &&
+ table != skip_table)
{
thd->locked_tables_list.unlink_from_list(thd,
table->pos_in_locked_tables,
- extra != HA_EXTRA_NOT_USED);
+ remove_from_locked_tables);
/* Inform handler that there is a drop table or a rename going on */
if (extra != HA_EXTRA_NOT_USED && table->db_stat)
{
@@ -1434,9 +842,12 @@ close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
prev= &table->next;
}
}
- /* Remove the table share from the cache. */
- tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name,
- FALSE);
+ if (skip_table == NULL)
+ {
+ /* Remove the table share from the cache. */
+ tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db, table_name,
+ FALSE);
+ }
}
@@ -1463,7 +874,7 @@ void close_thread_tables(THD *thd)
TABLE *table;
DBUG_ENTER("close_thread_tables");
- thd_proc_info(thd, "closing tables");
+ THD_STAGE_INFO(thd, stage_closing_tables);
#ifdef EXTRA_DEBUG
DBUG_PRINT("tcache", ("open tables:"));
@@ -1588,16 +999,14 @@ void close_thread_tables(THD *thd)
/* move one table to free list */
-bool close_thread_table(THD *thd, TABLE **table_ptr)
+void close_thread_table(THD *thd, TABLE **table_ptr)
{
- bool found_old_table= 0;
TABLE *table= *table_ptr;
DBUG_ENTER("close_thread_table");
DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
table->s->table_name.str, (long) table));
DBUG_ASSERT(table->key_read == 0);
DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
- mysql_mutex_assert_not_owner(&LOCK_open);
/*
The metadata lock must be released after giving back
@@ -1621,34 +1030,22 @@ bool close_thread_table(THD *thd, TABLE **table_ptr)
if (! table->needs_reopen())
{
- /* Avoid having MERGE tables with attached children in unused_tables. */
+ /* Avoid having MERGE tables with attached children in table cache. */
table->file->extra(HA_EXTRA_DETACH_CHILDREN);
/* Free memory and reset for next loop. */
free_field_buffers_larger_than(table, MAX_TDC_BLOB_SIZE);
table->file->ha_reset();
}
- mysql_mutex_lock(&LOCK_open);
+ /*
+ Do this *before* entering the TABLE_SHARE::tdc.LOCK_table_share
+ critical section.
+ */
+ if (table->file != NULL)
+ table->file->unbind_psi();
- if (table->s->has_old_version() || table->needs_reopen() ||
- table_def_shutdown_in_progress)
- {
- free_cache_entry(table);
- found_old_table= 1;
- }
- else
- {
- DBUG_ASSERT(table->file);
- table_def_unuse_table(table);
- /*
- We free the least used table, not the subject table,
- to keep the LRU order.
- */
- if (table_cache_count > table_cache_size)
- free_cache_entry(unused_tables);
- }
- mysql_mutex_unlock(&LOCK_open);
- DBUG_RETURN(found_old_table);
+ tc_release_table(table);
+ DBUG_VOID_RETURN;
}
@@ -1662,6 +1059,10 @@ static inline uint tmpkeyval(THD *thd, TABLE *table)
/*
Close all temporary tables created by 'CREATE TEMPORARY TABLE' for thread
creates one DROP TEMPORARY TABLE binlog event for each pseudo-thread
+
+ Temporary tables created in a sql slave is closed by
+ Relay_log_info::close_temporary_tables()
+
*/
bool close_temporary_tables(THD *thd)
@@ -1676,6 +1077,7 @@ bool close_temporary_tables(THD *thd)
if (!thd->temporary_tables)
DBUG_RETURN(FALSE);
+ DBUG_ASSERT(!thd->rgi_slave);
/*
Ensure we don't have open HANDLERs for tables we are about to close.
@@ -1744,7 +1146,8 @@ bool close_temporary_tables(THD *thd)
/* We always quote db,table names though it is slight overkill */
if (found_user_tables &&
- !(was_quote_show= test(thd->variables.option_bits & OPTION_QUOTE_SHOW_CREATE)))
+ !(was_quote_show= MY_TEST(thd->variables.option_bits &
+ OPTION_QUOTE_SHOW_CREATE)))
{
thd->variables.option_bits |= OPTION_QUOTE_SHOW_CREATE;
}
@@ -1799,7 +1202,7 @@ bool close_temporary_tables(THD *thd)
qinfo.db_len= db.length();
thd->variables.character_set_client= cs_save;
- thd->stmt_da->can_overwrite_status= TRUE;
+ thd->get_stmt_da()->set_overwrite_status(true);
if ((error= (mysql_bin_log.write(&qinfo) || error)))
{
/*
@@ -1817,7 +1220,7 @@ bool close_temporary_tables(THD *thd)
sql_print_error("Failed to write the DROP statement for "
"temporary tables to binary log");
}
- thd->stmt_da->can_overwrite_status= FALSE;
+ thd->get_stmt_da()->set_overwrite_status(false);
thd->variables.pseudo_thread_id= save_pseudo_thread_id;
thd->thread_specific_used= save_thread_specific_used;
@@ -1876,7 +1279,8 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table,
@param thd thread handle
@param table table which should be checked
@param table_list list of tables
- @param check_alias whether to check tables' aliases
+ @param check_flag whether to check tables' aliases
+ Currently this is only used by INSERT
NOTE: to exclude derived tables from check we use following mechanism:
a) during derived table processing set THD::derived_tables_processing
@@ -1905,9 +1309,9 @@ TABLE_LIST *find_table_in_list(TABLE_LIST *table,
static
TABLE_LIST* find_dup_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list,
- bool check_alias)
+ uint check_flag)
{
- TABLE_LIST *res;
+ TABLE_LIST *res= 0;
const char *d_name, *t_name, *t_alias;
DBUG_ENTER("find_dup_table");
DBUG_PRINT("enter", ("table alias: %s", table->alias));
@@ -1943,17 +1347,15 @@ TABLE_LIST* find_dup_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list,
retry:
DBUG_PRINT("info", ("real table: %s.%s", d_name, t_name));
- for (TABLE_LIST *tl= table_list;;)
+ for (TABLE_LIST *tl= table_list; tl ; tl= tl->next_global, res= 0)
{
- if (tl &&
- tl->select_lex && tl->select_lex->master_unit() &&
+ if (tl->select_lex && tl->select_lex->master_unit() &&
tl->select_lex->master_unit()->executed)
{
/*
There is no sense to check tables of already executed parts
of the query
*/
- tl= tl->next_global;
continue;
}
/*
@@ -1962,23 +1364,29 @@ retry:
*/
if (! (res= find_table_in_global_list(tl, d_name, t_name)))
break;
+ tl= res; // We can continue search after this table
/* Skip if same underlying table. */
if (res->table && (res->table == table->table))
- goto next;
+ continue;
+
+ if (check_flag & CHECK_DUP_FOR_CREATE)
+ DBUG_RETURN(res);
/* Skip if table alias does not match. */
- if (check_alias)
+ if (check_flag & CHECK_DUP_ALLOW_DIFFERENT_ALIAS)
{
- if (lower_case_table_names ?
- my_strcasecmp(files_charset_info, t_alias, res->alias) :
- strcmp(t_alias, res->alias))
- goto next;
+ if (my_strcasecmp(table_alias_charset, t_alias, res->alias))
+ continue;
}
/*
- Skip if marked to be excluded (could be a derived table) or if
- entry is a prelocking placeholder.
+ If table is not excluded (could be a derived table) and table is not
+ a prelocking placeholder then we found either a duplicate entry
+ or a table that is part of a derived table (handled below).
+ Examples are:
+ INSERT INTO t1 SELECT * FROM t1;
+ INSERT INTO t1 SELECT * FROM view_containing_t1;
*/
if (res->select_lex &&
!res->select_lex->exclude_from_table_unique_test &&
@@ -1990,14 +1398,17 @@ retry:
processed in derived table or top select of multi-update/multi-delete
(exclude_from_table_unique_test) or prelocking placeholder.
*/
-next:
- tl= res->next_global;
DBUG_PRINT("info",
("found same copy of table or table which we should skip"));
}
if (res && res->belong_to_derived)
{
- /* Try to fix */
+ /*
+ We come here for queries of type:
+ INSERT INTO t1 (SELECT tmp.a FROM (select * FROM t1) as tmp);
+
+ Try to fix by materializing the derived table
+ */
TABLE_LIST *derived= res->belong_to_derived;
if (derived->is_merged_derived())
{
@@ -2029,7 +1440,7 @@ next:
TABLE_LIST*
unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list,
- bool check_alias)
+ uint check_flag)
{
TABLE_LIST *dup;
@@ -2043,12 +1454,12 @@ unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list,
for (child= table->next_global; child && child->parent_l == table;
child= child->next_global)
{
- if ((dup= find_dup_table(thd, child, child->next_global, check_alias)))
+ if ((dup= find_dup_table(thd, child, child->next_global, check_flag)))
break;
}
}
else
- dup= find_dup_table(thd, table, table_list, check_alias);
+ dup= find_dup_table(thd, table, table_list, check_flag);
return dup;
}
/*
@@ -2104,7 +1515,7 @@ void update_non_unique_table_error(TABLE_LIST *update,
return;
}
}
- my_error(ER_UPDATE_TABLE_USED, MYF(0), update->alias);
+ my_error(ER_UPDATE_TABLE_USED, MYF(0), update->alias, operation);
}
@@ -2117,12 +1528,9 @@ void update_non_unique_table_error(TABLE_LIST *update,
TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name)
{
- TABLE_LIST tl;
-
- tl.db= (char*) db;
- tl.table_name= (char*) table_name;
-
- return find_temporary_table(thd, &tl);
+ char key[MAX_DBKEY_LENGTH];
+ uint key_length= create_tmp_table_def_key(thd, key, db, table_name);
+ return find_temporary_table(thd, key, key_length);
}
@@ -2135,10 +1543,79 @@ TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name)
TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl)
{
+ const char *tmp_key;
char key[MAX_DBKEY_LENGTH];
- uint key_length= create_table_def_key(thd, key, tl, 1);
+ uint key_length;
- return find_temporary_table(thd, key, key_length);
+ key_length= get_table_def_key(tl, &tmp_key);
+ memcpy(key, tmp_key, key_length);
+ int4store(key + key_length, thd->variables.server_id);
+ int4store(key + key_length + 4, thd->variables.pseudo_thread_id);
+
+ return find_temporary_table(thd, key, key_length + TMP_TABLE_KEY_EXTRA);
+}
+
+
+static bool
+use_temporary_table(THD *thd, TABLE *table, TABLE **out_table)
+{
+ *out_table= table;
+ if (!table)
+ return false;
+ /*
+ Temporary tables are not safe for parallel replication. They were
+ designed to be visible to one thread only, so have no table locking.
+ Thus there is no protection against two conflicting transactions
+ committing in parallel and things like that.
+
+ So for now, anything that uses temporary tables will be serialised
+ with anything before it, when using parallel replication.
+
+ ToDo: We might be able to introduce a reference count or something
+ on temp tables, and have slave worker threads wait for it to reach
+ zero before being allowed to use the temp table. Might not be worth
+ it though, as statement-based replication using temporary tables is
+ in any case rather fragile.
+ */
+ if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec &&
+ thd->wait_for_prior_commit())
+ return true;
+ /*
+ We need to set the THD as it may be different in case of
+ parallel replication
+ */
+ if (table->in_use != thd)
+ {
+ table->in_use= thd;
+#ifdef REMOVE_AFTER_MERGE_WITH_10
+ if (thd->rgi_slave)
+ {
+ /*
+ We may be stealing an opened temporary tables from one slave
+ thread to another, we need to let the performance schema know that,
+ for aggregates per thread to work properly.
+ */
+ table->file->unbind_psi();
+ table->file->rebind_psi();
+ }
+#endif
+ }
+ return false;
+}
+
+bool
+find_and_use_temporary_table(THD *thd, const char *db, const char *table_name,
+ TABLE **out_table)
+{
+ return use_temporary_table(thd, find_temporary_table(thd, db, table_name),
+ out_table);
+}
+
+
+bool
+find_and_use_temporary_table(THD *thd, const TABLE_LIST *tl, TABLE **out_table)
+{
+ return use_temporary_table(thd, find_temporary_table(thd, tl), out_table);
}
@@ -2152,16 +1629,22 @@ TABLE *find_temporary_table(THD *thd,
const char *table_key,
uint table_key_length)
{
+ TABLE *result= 0;
+ if (!thd->have_temporary_tables())
+ return NULL;
+
+ thd->lock_temporary_tables();
for (TABLE *table= thd->temporary_tables; table; table= table->next)
{
if (table->s->table_cache_key.length == table_key_length &&
!memcmp(table->s->table_cache_key.str, table_key, table_key_length))
{
- return table;
+ result= table;
+ break;
}
}
-
- return NULL;
+ thd->unlock_temporary_tables(0);
+ return result;
}
@@ -2186,35 +1669,33 @@ TABLE *find_temporary_table(THD *thd,
thd->temporary_tables list, it's impossible to tell here whether
we're dealing with an internal or a user temporary table.
- If is_trans is not null, we return the type of the table:
- either transactional (e.g. innodb) as TRUE or non-transactional
- (e.g. myisam) as FALSE.
+ @param thd Thread handler
+ @param table Temporary table to be deleted
+ @param is_trans Is set to the type of the table:
+ transactional (e.g. innodb) as TRUE or non-transactional
+ (e.g. myisam) as FALSE.
@retval 0 the table was found and dropped successfully.
- @retval 1 the table was not found in the list of temporary tables
- of this thread
@retval -1 the table is in use by a outer query
*/
-int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans)
+int drop_temporary_table(THD *thd, TABLE *table, bool *is_trans)
{
- TABLE *table;
DBUG_ENTER("drop_temporary_table");
DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
- table_list->db, table_list->table_name));
-
- if (!(table= find_temporary_table(thd, table_list)))
- DBUG_RETURN(1);
+ table->s->db.str, table->s->table_name.str));
/* Table might be in use by some outer statement. */
if (table->query_id && table->query_id != thd->query_id)
{
+ DBUG_PRINT("info", ("table->query_id: %lu thd->query_id: %lu",
+ (ulong) table->query_id, (ulong) thd->query_id));
+
my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr());
DBUG_RETURN(-1);
}
- if (is_trans != NULL)
- *is_trans= table->file->has_transactions();
+ *is_trans= table->file->has_transactions();
/*
If LOCK TABLES list is not empty and contains this table,
@@ -2225,6 +1706,7 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans)
DBUG_RETURN(0);
}
+
/*
unlink from thd->temporary tables and close temporary table
*/
@@ -2237,6 +1719,7 @@ void close_temporary_table(THD *thd, TABLE *table,
table->s->db.str, table->s->table_name.str,
(long) table, table->alias.c_ptr()));
+ thd->lock_temporary_tables();
if (table->prev)
{
table->prev->next= table->next;
@@ -2256,12 +1739,14 @@ void close_temporary_table(THD *thd, TABLE *table,
if (thd->temporary_tables)
table->next->prev= 0;
}
- if (thd->slave_thread)
+ if (thd->rgi_slave)
{
/* natural invariant of temporary_tables */
DBUG_ASSERT(slave_open_temp_tables || !thd->temporary_tables);
- slave_open_temp_tables--;
+ thread_safe_decrement32(&slave_open_temp_tables, &thread_running_lock);
+ table->in_use= 0; // No statistics
}
+ thd->unlock_temporary_tables(0);
close_temporary(table, free_share, delete_table);
DBUG_VOID_RETURN;
}
@@ -2282,13 +1767,6 @@ void close_temporary(TABLE *table, bool free_share, bool delete_table)
DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
table->s->db.str, table->s->table_name.str));
- /* in_use is not set for replication temporary tables during shutdown */
- if (table->in_use)
- {
- table->file->update_global_table_stats();
- table->file->update_global_index_stats();
- }
-
free_io_cache(table);
closefrm(table, 0);
if (delete_table)
@@ -2316,15 +1794,12 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db,
char *key;
uint key_length;
TABLE_SHARE *share= table->s;
- TABLE_LIST table_list;
DBUG_ENTER("rename_temporary_table");
if (!(key=(char*) alloc_root(&share->mem_root, MAX_DBKEY_LENGTH)))
DBUG_RETURN(1); /* purecov: inspected */
- table_list.db= (char*) db;
- table_list.table_name= (char*) table_name;
- key_length= create_table_def_key(thd, key, &table_list, 1);
+ key_length= create_tmp_table_def_key(thd, key, db, table_name);
share->set_table_cache_key(key, key_length);
DBUG_RETURN(0);
}
@@ -2349,19 +1824,19 @@ bool rename_temporary_table(THD* thd, TABLE *table, const char *db,
*/
bool wait_while_table_is_used(THD *thd, TABLE *table,
- enum ha_extra_function function,
- enum_tdc_remove_table_type remove_type)
+ enum ha_extra_function function)
{
DBUG_ENTER("wait_while_table_is_used");
DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu",
table->s->table_name.str, (ulong) table->s,
- table->db_stat, table->s->version));
+ table->db_stat, table->s->tdc.version));
- if (thd->mdl_context.upgrade_shared_lock_to_exclusive(
- table->mdl_ticket, thd->variables.lock_wait_timeout))
+ if (thd->mdl_context.upgrade_shared_lock(
+ table->mdl_ticket, MDL_EXCLUSIVE,
+ thd->variables.lock_wait_timeout))
DBUG_RETURN(TRUE);
- tdc_remove_table(thd, remove_type,
+ tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN,
table->s->db.str, table->s->table_name.str,
FALSE);
/* extra() call must come only after all instances above are closed */
@@ -2407,78 +1882,13 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name,
tdc_remove_table(thd, TDC_RT_REMOVE_ALL, db_name, table_name,
FALSE);
/* Remove the table from the storage engine and rm the .frm. */
- quick_rm_table(table_type, db_name, table_name, 0);
- }
+ quick_rm_table(thd, table_type, db_name, table_name, 0);
+ }
DBUG_VOID_RETURN;
}
/**
- Check that table exists in table definition cache, on disk
- or in some storage engine.
-
- @param thd Thread context
- @param table Table list element
- @param fast_check Check only if share or .frm file exists
- @param[out] exists Out parameter which is set to TRUE if table
- exists and to FALSE otherwise.
-
- @note This function acquires LOCK_open internally.
-
- @note If there is no .FRM file for the table but it exists in one
- of engines (e.g. it was created on another node of NDB cluster)
- this function will fetch and create proper .FRM file for it.
-
- @retval TRUE Some error occurred
- @retval FALSE No error. 'exists' out parameter set accordingly.
-*/
-
-bool check_if_table_exists(THD *thd, TABLE_LIST *table, bool fast_check,
- bool *exists)
-{
- char path[FN_REFLEN + 1];
- TABLE_SHARE *share;
- DBUG_ENTER("check_if_table_exists");
-
- *exists= TRUE;
-
- DBUG_ASSERT(fast_check ||
- thd->mdl_context.
- is_lock_owner(MDL_key::TABLE, table->db,
- table->table_name, MDL_SHARED));
-
- mysql_mutex_lock(&LOCK_open);
- share= get_cached_table_share(table->db, table->table_name);
- mysql_mutex_unlock(&LOCK_open);
-
- if (share)
- goto end;
-
- build_table_filename(path, sizeof(path) - 1, table->db, table->table_name,
- reg_ext, 0);
-
- if (!access(path, F_OK))
- goto end;
-
- if (fast_check)
- {
- *exists= FALSE;
- goto end;
- }
-
- /* .FRM file doesn't exist. Check if some engine can provide it. */
- if (ha_check_if_table_exists(thd, table->db, table->table_name, exists))
- {
- my_printf_error(ER_OUT_OF_RESOURCES, "Failed to open '%-.64s', error while "
- "unpacking from engine", MYF(0), table->table_name);
- DBUG_RETURN(TRUE);
- }
-end:
- DBUG_RETURN(FALSE);
-}
-
-
-/**
An error handler which converts, if possible, ER_LOCK_DEADLOCK error
that can occur when we are trying to acquire a metadata lock to
a request for back-off and re-start of open_tables() process.
@@ -2496,9 +1906,9 @@ public:
virtual bool handle_condition(THD *thd,
uint sql_errno,
const char* sqlstate,
- MYSQL_ERROR::enum_warning_level level,
+ Sql_condition::enum_warning_level level,
const char* msg,
- MYSQL_ERROR ** cond_hdl);
+ Sql_condition ** cond_hdl);
private:
/** Open table context to be used for back-off request. */
@@ -2515,9 +1925,9 @@ private:
bool MDL_deadlock_handler::handle_condition(THD *,
uint sql_errno,
const char*,
- MYSQL_ERROR::enum_warning_level,
+ Sql_condition::enum_warning_level,
const char*,
- MYSQL_ERROR ** cond_hdl)
+ Sql_condition ** cond_hdl)
{
*cond_hdl= NULL;
if (! m_is_active && sql_errno == ER_LOCK_DEADLOCK)
@@ -2669,160 +2079,92 @@ open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx,
/**
- Check if table's share is being removed from the table definition
- cache and, if yes, wait until the flush is complete.
-
- @param thd Thread context.
- @param table_list Table which share should be checked.
- @param timeout Timeout for waiting.
- @param deadlock_weight Weight of this wait for deadlock detector.
-
- @retval FALSE Success. Share is up to date or has been flushed.
- @retval TRUE Error (OOM, our was killed, the wait resulted
- in a deadlock or timeout). Reported.
+ Open a base table.
+
+ @param thd Thread context.
+ @param table_list Open first table in list.
+ @param mem_root Temporary MEM_ROOT to be used for
+ parsing .FRMs for views.
+ @param ot_ctx Context with flags which modify how open works
+ and which is used to recover from a failed
+ open_table() attempt.
+ Some examples of flags:
+ MYSQL_OPEN_IGNORE_FLUSH - Open table even if
+ someone has done a flush. No version number
+ checking is done.
+ MYSQL_OPEN_HAS_MDL_LOCK - instead of acquiring
+ metadata locks rely on that caller already has
+ appropriate ones.
+
+ Uses a cache of open tables to find a TABLE instance not in use.
+
+ If TABLE_LIST::open_strategy is set to OPEN_IF_EXISTS, the table is
+ opened only if it exists. If the open strategy is OPEN_STUB, the
+ underlying table is never opened. In both cases, metadata locks are
+ always taken according to the lock strategy.
+
+ The function used to open temporary tables, but now it opens base tables
+ only.
+
+ @retval TRUE Open failed. "action" parameter may contain type of action
+ needed to remedy problem before retrying again.
+ @retval FALSE Success. Members of TABLE_LIST structure are filled properly
+ (e.g. TABLE_LIST::table is set for real tables and
+ TABLE_LIST::view is set for views).
*/
-static bool
-tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name,
- ulong wait_timeout, uint deadlock_weight)
-{
- TABLE_SHARE *share;
- bool res= FALSE;
-
- mysql_mutex_lock(&LOCK_open);
- if ((share= get_cached_table_share(db, table_name)) &&
- share->has_old_version())
- {
- struct timespec abstime;
- set_timespec(abstime, wait_timeout);
- res= share->wait_for_old_version(thd, &abstime, deadlock_weight);
- }
- mysql_mutex_unlock(&LOCK_open);
- return res;
-}
-
-
-/*
- Open a table.
-
- SYNOPSIS
- open_table()
- thd Thread context.
- table_list Open first table in list.
- action INOUT Pointer to variable of enum_open_table_action type
- which will be set according to action which is
- required to remedy problem appeared during attempt
- to open table.
- flags Bitmap of flags to modify how open works:
- MYSQL_OPEN_IGNORE_FLUSH - Open table even if
- someone has done a flush or there is a pending
- exclusive metadata lock requests against it
- (i.e. request high priority metadata lock).
- No version number checking is done.
- MYSQL_OPEN_TEMPORARY_ONLY - Open only temporary
- table not the base table or view.
- MYSQL_OPEN_TAKE_UPGRADABLE_MDL - Obtain upgradable
- metadata lock for tables on which we are going to
- take some kind of write table-level lock.
-
- IMPLEMENTATION
- Uses a cache of open tables to find a table not in use.
-
- If TABLE_LIST::open_strategy is set to OPEN_IF_EXISTS, the table is opened
- only if it exists. If the open strategy is OPEN_STUB, the underlying table
- is never opened. In both cases, metadata locks are always taken according
- to the lock strategy.
-
- RETURN
- TRUE Open failed. "action" parameter may contain type of action
- needed to remedy problem before retrying again.
- FALSE Success. Members of TABLE_LIST structure are filled properly (e.g.
- TABLE_LIST::table is set for real tables and TABLE_LIST::view is
- set for views).
-*/
-
-
bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
Open_table_context *ot_ctx)
{
- reg1 TABLE *table;
- char key[MAX_DBKEY_LENGTH];
+ TABLE *table;
+ const char *key;
uint key_length;
char *alias= table_list->alias;
uint flags= ot_ctx->get_flags();
MDL_ticket *mdl_ticket;
- int error;
TABLE_SHARE *share;
- my_hash_value_type hash_value;
+ uint gts_flags;
DBUG_ENTER("open_table");
+ /*
+ The table must not be opened already. The table can be pre-opened for
+ some statements if it is a temporary table.
+
+ open_temporary_table() must be used to open temporary tables.
+ */
+ DBUG_ASSERT(!table_list->table);
+
/* an open table operation needs a lot of the stack space */
if (check_stack_overrun(thd, STACK_MIN_SIZE_FOR_OPEN, (uchar *)&alias))
DBUG_RETURN(TRUE);
- if (thd->killed)
+ if (!(flags & MYSQL_OPEN_IGNORE_KILLED) && thd->killed)
+ {
+ thd->send_kill_message();
DBUG_RETURN(TRUE);
-
- key_length= (create_table_def_key(thd, key, table_list, 1) -
- TMP_TABLE_KEY_EXTRA);
+ }
/*
- Unless requested otherwise, try to resolve this table in the list
- of temporary tables of this thread. In MySQL temporary tables
- are always thread-local and "shadow" possible base tables with the
- same name. This block implements the behaviour.
- TODO: move this block into a separate function.
+ Check if we're trying to take a write lock in a read only transaction.
+
+ Note that we allow write locks on log tables as otherwise logging
+ to general/slow log would be disabled in read only transactions.
*/
- if (table_list->open_type != OT_BASE_ONLY &&
- ! (flags & MYSQL_OPEN_SKIP_TEMPORARY))
+ if (table_list->mdl_request.type >= MDL_SHARED_WRITE &&
+ thd->tx_read_only &&
+ !(flags & (MYSQL_LOCK_LOG_TABLE | MYSQL_OPEN_HAS_MDL_LOCK)))
{
- for (table= thd->temporary_tables; table ; table=table->next)
- {
- if (table->s->table_cache_key.length == key_length +
- TMP_TABLE_KEY_EXTRA &&
- !memcmp(table->s->table_cache_key.str, key,
- key_length + TMP_TABLE_KEY_EXTRA))
- {
- /*
- We're trying to use the same temporary table twice in a query.
- Right now we don't support this because a temporary table
- is always represented by only one TABLE object in THD, and
- it can not be cloned. Emit an error for an unsupported behaviour.
- */
- if (table->query_id)
- {
- DBUG_PRINT("error",
- ("query_id: %lu server_id: %u pseudo_thread_id: %lu",
- (ulong) table->query_id, (uint) thd->server_id,
- (ulong) thd->variables.pseudo_thread_id));
- my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr());
- DBUG_RETURN(TRUE);
- }
- table->query_id= thd->query_id;
- thd->thread_specific_used= TRUE;
- DBUG_PRINT("info",("Using temporary table"));
- goto reset;
- }
- }
+ my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0));
+ DBUG_RETURN(true);
}
- if (table_list->open_type == OT_TEMPORARY_ONLY ||
- (flags & MYSQL_OPEN_TEMPORARY_ONLY))
- {
- if (table_list->open_strategy == TABLE_LIST::OPEN_NORMAL)
- {
- my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->table_name);
- DBUG_RETURN(TRUE);
- }
- else
- DBUG_RETURN(FALSE);
- }
+ key_length= get_table_def_key(table_list, &key);
/*
- The table is not temporary - if we're in pre-locked or LOCK TABLES
- mode, let's try to find the requested table in the list of pre-opened
- and locked tables. If the table is not there, return an error - we can't
- open not pre-opened tables in pre-locked/LOCK TABLES mode.
+ If we're in pre-locked or LOCK TABLES mode, let's try to find the
+ requested table in the list of pre-opened and locked tables. If the
+ table is not there, return an error - we can't open not pre-opened
+ tables in pre-locked/LOCK TABLES mode.
TODO: move this block into a separate function.
*/
if (thd->locked_tables_mode &&
@@ -2896,7 +2238,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
MDL_SHARED))
{
char path[FN_REFLEN + 1];
- enum legacy_db_type not_used;
build_table_filename(path, sizeof(path) - 1,
table_list->db, table_list->table_name, reg_ext, 0);
/*
@@ -2906,7 +2247,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
during prelocking process (in this case in theory we still
should hold shared metadata lock on it).
*/
- if (dd_frm_type(thd, path, &not_used) == FRMTYPE_VIEW)
+ if (dd_frm_is_view(thd, path))
{
/*
If parent_l of the table_list is non null then a merge table
@@ -2919,7 +2260,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
}
if (!tdc_open_view(thd, table_list, alias, key, key_length,
- mem_root, 0))
+ mem_root, CHECK_METADATA_VERSION))
{
DBUG_ASSERT(table_list->view != 0);
DBUG_RETURN(FALSE); // VIEW
@@ -2928,7 +2269,7 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
}
/*
No table in the locked tables list. In case of explicit LOCK TABLES
- this can happen if a user did not include the able into the list.
+ this can happen if a user did not include the table into the list.
In case of pre-locked mode locked tables list is generated automatically,
so we may only end up here if the table did not exist when
locked tables list was created.
@@ -2953,12 +2294,12 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
global read lock until end of this statement in order to have
this statement blocked by active FLUSH TABLES WITH READ LOCK.
- We don't block acquire this protection under LOCK TABLES as
+ We don't need to acquire this protection under LOCK TABLES as
such protection already acquired at LOCK TABLES time and
not released until UNLOCK TABLES.
We don't block statements which modify only temporary tables
- as these tables are not preserved by backup by any form of
+ as these tables are not preserved by any form of
backup which uses FLUSH TABLES WITH READ LOCK.
TODO: The fact that we sometimes acquire protection against
@@ -3018,52 +2359,61 @@ bool open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root,
mdl_ticket= table_list->mdl_request.ticket;
}
- hash_value= my_calc_hash(&table_def_cache, (uchar*) key, key_length);
-
-
if (table_list->open_strategy == TABLE_LIST::OPEN_IF_EXISTS)
{
- bool exists;
-
- if (check_if_table_exists(thd, table_list, 0, &exists))
- DBUG_RETURN(TRUE);
-
- if (!exists)
+ if (!ha_table_exists(thd, table_list->db, table_list->table_name))
DBUG_RETURN(FALSE);
-
- /* Table exists. Let us try to open it. */
}
else if (table_list->open_strategy == TABLE_LIST::OPEN_STUB)
DBUG_RETURN(FALSE);
+ /* Table exists. Let us try to open it. */
+
+ if (table_list->i_s_requested_object & OPEN_TABLE_ONLY)
+ gts_flags= GTS_TABLE;
+ else if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
+ gts_flags= GTS_VIEW;
+ else
+ gts_flags= GTS_TABLE | GTS_VIEW;
+
retry_share:
- mysql_mutex_lock(&LOCK_open);
+ share= tdc_acquire_share(thd, table_list->db, table_list->table_name,
+ key, key_length,
+ table_list->mdl_request.key.tc_hash_value(),
+ gts_flags, &table);
- if (!(share= get_table_share_with_discover(thd, table_list, key,
- key_length,
- (OPEN_VIEW |
- ((table_list->required_type ==
- FRMTYPE_VIEW) ?
- OPEN_VIEW_ONLY : 0)),
- &error,
- hash_value)))
+ if (!share)
{
- mysql_mutex_unlock(&LOCK_open);
/*
- If thd->is_error() is not set, we either need discover
- (error == 7), or the error was silenced by the prelocking
- handler (error == 0), in which case we should skip this
- table.
+ Hide "Table doesn't exist" errors if the table belongs to a view.
+ The check for thd->is_error() is necessary to not push an
+ unwanted error in case the error was already silenced.
+ @todo Rework the alternative ways to deal with ER_NO_SUCH TABLE.
*/
- if (error == 7 && !thd->is_error())
+ if (thd->is_error())
{
- (void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER,
- table_list);
+ if (table_list->parent_l)
+ {
+ thd->clear_error();
+ my_error(ER_WRONG_MRG_TABLE, MYF(0));
+ }
+ else if (table_list->belong_to_view)
+ {
+ TABLE_LIST *view= table_list->belong_to_view;
+ thd->clear_error();
+ my_error(ER_VIEW_INVALID, MYF(0),
+ view->view_db.str, view->view_name.str);
+ }
}
DBUG_RETURN(TRUE);
}
+ /*
+ Check if this TABLE_SHARE-object corresponds to a view. Note, that there is
+ no need to check TABLE_SHARE::tdc.flushed as we do for regular tables,
+ because view shares are always up to date.
+ */
if (share->is_view)
{
/*
@@ -3073,7 +2423,7 @@ retry_share:
if (table_list->parent_l)
{
my_error(ER_WRONG_MRG_TABLE, MYF(0));
- goto err_unlock;
+ goto err_lock;
}
/*
@@ -3081,13 +2431,7 @@ retry_share:
that it was a view when the statement was prepared.
*/
if (check_and_update_table_version(thd, table_list, share))
- goto err_unlock;
- if (table_list->i_s_requested_object & OPEN_TABLE_ONLY)
- {
- my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db,
- table_list->table_name);
- goto err_unlock;
- }
+ goto err_lock;
/* Open view */
if (open_new_frm(thd, share, alias,
@@ -3096,37 +2440,22 @@ retry_share:
READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
thd->open_options,
0, table_list, mem_root))
- goto err_unlock;
+ goto err_lock;
/* TODO: Don't free this */
- release_table_share(share);
+ tdc_release_share(share);
DBUG_ASSERT(table_list->view);
- mysql_mutex_unlock(&LOCK_open);
DBUG_RETURN(FALSE);
}
- /*
- Note that situation when we are trying to open a table for what
- was a view during previous execution of PS will be handled in by
- the caller. Here we should simply open our table even if
- TABLE_LIST::view is true.
- */
-
- if (table_list->i_s_requested_object & OPEN_VIEW_ONLY)
- {
- my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db,
- table_list->table_name);
- goto err_unlock;
- }
-
- if (!(flags & MYSQL_OPEN_IGNORE_FLUSH) ||
- (share->protected_against_usage() &&
- !(flags & MYSQL_OPEN_FOR_REPAIR)))
+ if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
{
- if (share->has_old_version())
+ if (share->tdc.flushed)
{
+ DBUG_PRINT("info", ("Found old share version: %lu current: %lu",
+ share->tdc.version, tdc_refresh_version()));
/*
We already have an MDL lock. But we have encountered an old
version of table in the table definition cache which is possible
@@ -3136,13 +2465,14 @@ retry_share:
Release our reference to share, wait until old version of
share goes away and then try to get new version of table share.
*/
+ if (table)
+ tc_release_table(table);
+ else
+ tdc_release_share(share);
+
MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
bool wait_result;
- DBUG_PRINT("info", ("old version of table share found"));
- release_table_share(share);
- mysql_mutex_unlock(&LOCK_open);
-
thd->push_internal_handler(&mdl_deadlock_handler);
wait_result= tdc_wait_for_old_version(thd, table_list->db,
table_list->table_name,
@@ -3156,7 +2486,7 @@ retry_share:
goto retry_share;
}
- if (thd->open_tables && thd->open_tables->s->version != share->version)
+ if (thd->open_tables && thd->open_tables->s->tdc.flushed)
{
/*
If the version changes while we're opening the tables,
@@ -3164,34 +2494,24 @@ retry_share:
and try to reopen them. Note: refresh_version is currently
changed only during FLUSH TABLES.
*/
- DBUG_PRINT("info", ("share version differs between tables"));
- release_table_share(share);
- mysql_mutex_unlock(&LOCK_open);
+ if (table)
+ tc_release_table(table);
+ else
+ tdc_release_share(share);
(void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES,
NULL);
DBUG_RETURN(TRUE);
}
}
- if (!share->free_tables.is_empty())
+ if (table)
{
- table= share->free_tables.front();
- table_def_use_table(thd, table);
- /*
- We need to release share as we have EXTRA reference to it in our hands.
- */
- DBUG_PRINT("info", ("release temporarily acquired table share"));
- release_table_share(share);
+ DBUG_ASSERT(table->file != NULL);
+ table->file->rebind_psi();
}
else
{
- /*
- We have too many TABLE instances around let us try to get rid of them.
- */
- while (table_cache_count > table_cache_size && unused_tables)
- free_cache_entry(unused_tables);
-
- mysql_mutex_unlock(&LOCK_open);
+ enum open_frm_error error;
/* make a new table */
if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
@@ -3210,16 +2530,14 @@ retry_share:
{
my_free(table);
- if (error == 7)
+ if (error == OPEN_FRM_DISCOVER)
(void) ot_ctx->request_backoff_action(Open_table_context::OT_DISCOVER,
table_list);
else if (share->crashed)
(void) ot_ctx->request_backoff_action(Open_table_context::OT_REPAIR,
table_list);
-
goto err_lock;
}
-
if (open_table_entry_fini(thd, share, table))
{
closefrm(table, 0);
@@ -3227,13 +2545,10 @@ retry_share:
goto err_lock;
}
- mysql_mutex_lock(&LOCK_open);
/* Add table to the share's used tables list. */
- table_def_add_used_table(thd, table);
+ tc_add_table(thd, table);
}
- mysql_mutex_unlock(&LOCK_open);
-
table->mdl_ticket= mdl_ticket;
table->next= thd->open_tables; /* Link into simple list */
@@ -3250,15 +2565,27 @@ retry_share:
table_list->updatable= 1; // It is not derived table nor non-updatable VIEW
table_list->table= table;
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (table->part_info)
+ {
+ /* Set all [named] partitions as used. */
+ if (table->part_info->set_partition_bitmaps(table_list))
+ DBUG_RETURN(true);
+ }
+ else if (table_list->partition_names)
+ {
+ /* Don't allow PARTITION () clause on a nonpartitioned table */
+ my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(true);
+ }
+#endif
+
table->init(thd, table_list);
DBUG_RETURN(FALSE);
err_lock:
- mysql_mutex_lock(&LOCK_open);
-err_unlock:
- release_table_share(share);
- mysql_mutex_unlock(&LOCK_open);
+ tdc_release_share(share);
DBUG_PRINT("exit", ("failed"));
DBUG_RETURN(TRUE);
@@ -3278,7 +2605,7 @@ err_unlock:
TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name)
{
char key[MAX_DBKEY_LENGTH];
- uint key_length= create_table_def_key(key, db, table_name);
+ uint key_length= tdc_create_key(key, db, table_name);
for (TABLE *table= list; table ; table=table->next)
{
@@ -3306,9 +2633,9 @@ TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name)
upgrade the lock and ER_TABLE_NOT_LOCKED_FOR_WRITE will be
reported.
- @return Pointer to TABLE instance with MDL_SHARED_NO_WRITE,
- MDL_SHARED_NO_READ_WRITE, or MDL_EXCLUSIVE metadata
- lock, NULL otherwise.
+ @return Pointer to TABLE instance with MDL_SHARED_UPGRADABLE
+ MDL_SHARED_NO_WRITE, MDL_SHARED_NO_READ_WRITE, or
+ MDL_EXCLUSIVE metadata lock, NULL otherwise.
*/
TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db,
@@ -3378,9 +2705,9 @@ Locked_tables_list::init_locked_tables(THD *thd)
{
TABLE_LIST *src_table_list= table->pos_in_table_list;
char *db, *table_name, *alias;
- size_t db_len= src_table_list->db_length;
- size_t table_name_len= src_table_list->table_name_length;
- size_t alias_len= strlen(src_table_list->alias);
+ size_t db_len= table->s->db.length;
+ size_t table_name_len= table->s->table_name.length;
+ size_t alias_len= table->alias.length();
TABLE_LIST *dst_table_list;
if (! multi_alloc_root(&m_locked_tables_root,
@@ -3390,23 +2717,15 @@ Locked_tables_list::init_locked_tables(THD *thd)
&alias, alias_len + 1,
NullS))
{
- unlock_locked_tables(0);
+ reset();
return TRUE;
}
- memcpy(db, src_table_list->db, db_len + 1);
- memcpy(table_name, src_table_list->table_name, table_name_len + 1);
- memcpy(alias, src_table_list->alias, alias_len + 1);
- /**
- Sic: remember the *actual* table level lock type taken, to
- acquire the exact same type in reopen_tables().
- E.g. if the table was locked for write, src_table_list->lock_type is
- TL_WRITE_DEFAULT, whereas reginfo.lock_type has been updated from
- thd->update_lock_default.
- */
+ memcpy(db, table->s->db.str, db_len + 1);
+ memcpy(table_name, table->s->table_name.str, table_name_len + 1);
+ strmake(alias, table->alias.ptr(), alias_len);
dst_table_list->init_one_table(db, db_len, table_name, table_name_len,
- alias,
- src_table_list->table->reginfo.lock_type);
+ alias, table->reginfo.lock_type);
dst_table_list->table= table;
dst_table_list->mdl_request.ticket= src_table_list->mdl_request.ticket;
@@ -3427,7 +2746,7 @@ Locked_tables_list::init_locked_tables(THD *thd)
(m_locked_tables_count+1));
if (m_reopen_array == NULL)
{
- unlock_locked_tables(0);
+ reset();
return TRUE;
}
}
@@ -3448,42 +2767,82 @@ Locked_tables_list::init_locked_tables(THD *thd)
void
Locked_tables_list::unlock_locked_tables(THD *thd)
{
- if (thd)
+ DBUG_ASSERT(!thd->in_sub_stmt &&
+ !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+ /*
+ Sic: we must be careful to not close open tables if
+ we're not in LOCK TABLES mode: unlock_locked_tables() is
+ sometimes called implicitly, expecting no effect on
+ open tables, e.g. from begin_trans().
+ */
+ if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+ return;
+
+ for (TABLE_LIST *table_list= m_locked_tables;
+ table_list; table_list= table_list->next_global)
{
- DBUG_ASSERT(!thd->in_sub_stmt &&
- !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
/*
- Sic: we must be careful to not close open tables if
- we're not in LOCK TABLES mode: unlock_locked_tables() is
- sometimes called implicitly, expecting no effect on
- open tables, e.g. from begin_trans().
+ Clear the position in the list, the TABLE object will be
+ returned to the table cache.
*/
- if (thd->locked_tables_mode != LTM_LOCK_TABLES)
- return;
+ if (table_list->table) // If not closed
+ table_list->table->pos_in_locked_tables= NULL;
+ }
+ thd->leave_locked_tables_mode();
- for (TABLE_LIST *table_list= m_locked_tables;
- table_list; table_list= table_list->next_global)
- {
- /*
- Clear the position in the list, the TABLE object will be
- returned to the table cache.
- */
- if (table_list->table) // If not closed
- table_list->table->pos_in_locked_tables= NULL;
- }
- thd->leave_locked_tables_mode();
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+ close_thread_tables(thd);
+
+ /*
+ We rely on the caller to implicitly commit the
+ transaction and release transactional locks.
+ */
- DBUG_ASSERT(thd->transaction.stmt.is_empty());
- close_thread_tables(thd);
- /*
- We rely on the caller to implicitly commit the
- transaction and release transactional locks.
- */
- }
/*
After closing tables we can free memory used for storing lock
request for metadata locks and TABLE_LIST elements.
*/
+ reset();
+}
+
+
+/**
+ Remove all meta data locks associated with table and release locked
+ table mode if there is no locked tables anymore
+*/
+
+void
+Locked_tables_list::unlock_locked_table(THD *thd, MDL_ticket *mdl_ticket)
+{
+ /*
+ Ensure we are in locked table mode.
+ As this function is only called on error condition it's better
+ to check this condition here than in the caller.
+ */
+ if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+ return;
+
+ if (mdl_ticket)
+ {
+ /*
+ Under LOCK TABLES we may have several instances of table open
+ and locked and therefore have to remove several metadata lock
+ requests associated with them.
+ */
+ thd->mdl_context.release_all_locks_for_name(mdl_ticket);
+ }
+
+ if (thd->lock->table_count == 0)
+ unlock_locked_tables(thd);
+}
+
+
+/*
+ Free memory allocated for storing locks
+*/
+
+void Locked_tables_list::reset()
+{
free_root(&m_locked_tables_root, MYF(0));
m_locked_tables= NULL;
m_locked_tables_last= &m_locked_tables;
@@ -3521,7 +2880,8 @@ void Locked_tables_list::unlink_from_list(THD *thd,
If mode is not LTM_LOCK_TABLES, we needn't do anything. Moreover,
outside this mode pos_in_locked_tables value is not trustworthy.
*/
- if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+ if (thd->locked_tables_mode != LTM_LOCK_TABLES &&
+ thd->locked_tables_mode != LTM_PRELOCKED_UNDER_LOCK_TABLES)
return;
/*
@@ -3548,6 +2908,7 @@ void Locked_tables_list::unlink_from_list(THD *thd,
m_locked_tables_last= table_list->prev_global;
else
table_list->next_global->prev_global= table_list->prev_global;
+ m_locked_tables_count--;
}
}
@@ -3601,8 +2962,13 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count)
m_locked_tables_last= table_list->prev_global;
else
table_list->next_global->prev_global= table_list->prev_global;
+ m_locked_tables_count--;
}
}
+
+ /* If no tables left, do an automatic UNLOCK TABLES */
+ if (thd->lock && thd->lock->table_count == 0)
+ unlock_locked_tables(thd);
}
@@ -3619,24 +2985,37 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count)
*/
bool
-Locked_tables_list::reopen_tables(THD *thd)
+Locked_tables_list::reopen_tables(THD *thd, bool need_reopen)
{
Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN);
size_t reopen_count= 0;
MYSQL_LOCK *lock;
MYSQL_LOCK *merged_lock;
+ DBUG_ENTER("Locked_tables_list::reopen_tables");
for (TABLE_LIST *table_list= m_locked_tables;
table_list; table_list= table_list->next_global)
{
- if (table_list->table) /* The table was not closed */
- continue;
+ if (need_reopen)
+ {
+ if (!table_list->table || !table_list->table->needs_reopen())
+ continue;
+ /* no need to remove the table from the TDC here, thus (TABLE*)1 */
+ close_all_tables_for_name(thd, table_list->table->s,
+ HA_EXTRA_NOT_USED, (TABLE*)1);
+ DBUG_ASSERT(table_list->table == NULL);
+ }
+ else
+ {
+ if (table_list->table) /* The table was not closed */
+ continue;
+ }
/* Links into thd->open_tables upon success */
if (open_table(thd, table_list, thd->mem_root, &ot_ctx))
{
unlink_all_closed_tables(thd, 0, reopen_count);
- return TRUE;
+ DBUG_RETURN(TRUE);
}
table_list->table->pos_in_locked_tables= table_list;
/* See also the comment on lock type in init_locked_tables(). */
@@ -3668,70 +3047,70 @@ Locked_tables_list::reopen_tables(THD *thd)
unlink_all_closed_tables(thd, lock, reopen_count);
if (! thd->killed)
my_error(ER_LOCK_DEADLOCK, MYF(0));
- return TRUE;
+ DBUG_RETURN(TRUE);
}
thd->lock= merged_lock;
}
- return FALSE;
+ DBUG_RETURN(FALSE);
}
+/**
+ Add back a locked table to the locked list that we just removed from it.
+ This is needed in CREATE OR REPLACE TABLE where we are dropping, creating
+ and re-opening a locked table.
-/*
- Function to assign a new table map id to a table share.
-
- PARAMETERS
-
- share - Pointer to table share structure
+ @return 0 0k
+ @return 1 error
+*/
- DESCRIPTION
+bool Locked_tables_list::restore_lock(THD *thd, TABLE_LIST *dst_table_list,
+ TABLE *table, MYSQL_LOCK *lock)
+{
+ MYSQL_LOCK *merged_lock;
+ DBUG_ENTER("restore_lock");
+ DBUG_ASSERT(!strcmp(dst_table_list->table_name, table->s->table_name.str));
- We are intentionally not checking that share->mutex is locked
- since this function should only be called when opening a table
- share and before it is entered into the table_def_cache (meaning
- that it cannot be fetched by another thread, even accidentally).
+ /* Ensure we have the memory to add the table back */
+ if (!(merged_lock= mysql_lock_merge(thd->lock, lock)))
+ DBUG_RETURN(1);
+ thd->lock= merged_lock;
- PRE-CONDITION(S)
+ /* Link to the new table */
+ dst_table_list->table= table;
+ /*
+ The lock type may have changed (normally it should not as create
+ table will lock the table in write mode
+ */
+ dst_table_list->lock_type= table->reginfo.lock_type;
+ table->pos_in_locked_tables= dst_table_list;
- share is non-NULL
- The LOCK_open mutex is locked.
+ add_back_last_deleted_lock(dst_table_list);
- POST-CONDITION(S)
+ table->mdl_ticket->downgrade_lock(table->reginfo.lock_type >=
+ TL_WRITE_ALLOW_WRITE ?
+ MDL_SHARED_NO_READ_WRITE :
+ MDL_SHARED_READ);
- share->table_map_id is given a value that with a high certainty is
- not used by any other table (the only case where a table id can be
- reused is on wrap-around, which means more than 4 billion table
- share opens have been executed while one table was open all the
- time).
+ DBUG_RETURN(0);
+}
- share->table_map_id is not ~0UL.
- */
-static ulong last_table_id= ~0UL;
+/*
+ Add back the last deleted lock structure.
+ This should be followed by a call to reopen_tables() to
+ open the table.
+*/
-void assign_new_table_id(TABLE_SHARE *share)
+void Locked_tables_list::add_back_last_deleted_lock(TABLE_LIST *dst_table_list)
{
-
- DBUG_ENTER("assign_new_table_id");
-
- /* Preconditions */
- DBUG_ASSERT(share != NULL);
- mysql_mutex_assert_owner(&LOCK_open);
-
- ulong tid= ++last_table_id; /* get next id */
- /*
- There is one reserved number that cannot be used. Remember to
- change this when 6-byte global table id's are introduced.
- */
- if (unlikely(tid == ~0UL))
- tid= ++last_table_id;
- share->table_map_id= tid;
- DBUG_PRINT("info", ("table_id=%lu", tid));
-
- /* Post conditions */
- DBUG_ASSERT(share->table_map_id != ~0UL);
-
- DBUG_VOID_RETURN;
+ /* Link the lock back in the locked tables list */
+ dst_table_list->prev_global= m_locked_tables_last;
+ *m_locked_tables_last= dst_table_list;
+ m_locked_tables_last= &dst_table_list->next_global;
+ dst_table_list->next_global= 0;
+ m_locked_tables_count++;
}
+
#ifndef DBUG_OFF
/* Cause a spurious statement reprepare for debug purposes. */
static bool inject_reprepare(THD *thd)
@@ -3882,42 +3261,44 @@ check_and_update_routine_version(THD *thd, Sroutine_hash_entry *rt,
*/
bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
- char *cache_key, uint cache_key_length,
+ const char *cache_key, uint cache_key_length,
MEM_ROOT *mem_root, uint flags)
{
TABLE not_used;
- int error;
- my_hash_value_type hash_value;
TABLE_SHARE *share;
+ bool err= TRUE;
- hash_value= my_calc_hash(&table_def_cache, (uchar*) cache_key,
- cache_key_length);
- mysql_mutex_lock(&LOCK_open);
+ if (!(share= tdc_acquire_share(thd, table_list->db, table_list->table_name,
+ cache_key, cache_key_length, GTS_VIEW)))
+ return TRUE;
- if (!(share= get_table_share(thd, table_list, cache_key,
- cache_key_length,
- OPEN_VIEW, &error,
- hash_value)))
- goto err;
+ DBUG_ASSERT(share->is_view);
- if (share->is_view &&
- !open_new_frm(thd, share, alias,
- (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
- HA_GET_INDEX | HA_TRY_READ_ONLY),
- READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD |
- flags, thd->open_options, &not_used, table_list,
- mem_root))
+ if (flags & CHECK_METADATA_VERSION)
{
- release_table_share(share);
- mysql_mutex_unlock(&LOCK_open);
- return FALSE;
+ /*
+ Check TABLE_SHARE-version of view only if we have been instructed to do
+ so. We do not need to check the version if we're executing CREATE VIEW or
+ ALTER VIEW statements.
+
+ In the future, this functionality should be moved out from
+ tdc_open_view(), and tdc_open_view() should became a part of a clean
+ table-definition-cache interface.
+ */
+ if (check_and_update_table_version(thd, table_list, share))
+ goto ret;
}
- my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str, "VIEW");
- release_table_share(share);
-err:
- mysql_mutex_unlock(&LOCK_open);
- return TRUE;
+ err= open_new_frm(thd, share, alias,
+ (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
+ HA_GET_INDEX | HA_TRY_READ_ONLY),
+ READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD | flags,
+ thd->open_options, &not_used, table_list, mem_root);
+
+ret:
+ tdc_release_share(share);
+
+ return err;
}
@@ -3971,40 +3352,19 @@ static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry)
static bool auto_repair_table(THD *thd, TABLE_LIST *table_list)
{
- char cache_key[MAX_DBKEY_LENGTH];
- uint cache_key_length;
TABLE_SHARE *share;
TABLE *entry;
- int not_used;
bool result= TRUE;
- my_hash_value_type hash_value;
-
- cache_key_length= create_table_def_key(thd, cache_key, table_list, 0);
thd->clear_error();
- hash_value= my_calc_hash(&table_def_cache, (uchar*) cache_key,
- cache_key_length);
- mysql_mutex_lock(&LOCK_open);
-
- if (!(share= get_table_share(thd, table_list, cache_key,
- cache_key_length,
- OPEN_VIEW, &not_used,
- hash_value)))
- goto end_unlock;
+ if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME))))
+ return result;
- if (share->is_view)
- {
- release_table_share(share);
- goto end_unlock;
- }
+ if (!(share= tdc_acquire_share_shortlived(thd, table_list, GTS_TABLE)))
+ goto end_free;
- if (!(entry= (TABLE*)my_malloc(sizeof(TABLE), MYF(MY_WME))))
- {
- release_table_share(share);
- goto end_unlock;
- }
- mysql_mutex_unlock(&LOCK_open);
+ DBUG_ASSERT(! share->is_view);
if (open_table_from_share(thd, share, table_list->alias,
(uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
@@ -4029,16 +3389,14 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list)
closefrm(entry, 0);
result= FALSE;
}
- my_free(entry);
- mysql_mutex_lock(&LOCK_open);
- release_table_share(share);
+ tdc_release_share(share);
/* Remove the repaired share from the table cache. */
tdc_remove_table(thd, TDC_RT_REMOVE_ALL,
table_list->db, table_list->table_name,
- TRUE);
-end_unlock:
- mysql_mutex_unlock(&LOCK_open);
+ FALSE);
+end_free:
+ my_free(entry);
return result;
}
@@ -4142,6 +3500,7 @@ request_backoff_action(enum_open_table_action action_arg,
table->table_name,
table->table_name_length,
table->alias, TL_WRITE);
+ m_failed_table->open_strategy= table->open_strategy;
m_failed_table->mdl_request.set_type(MDL_EXCLUSIVE);
}
m_action= action_arg;
@@ -4159,9 +3518,9 @@ public:
virtual bool handle_condition(THD *thd,
uint sql_errno,
const char* sqlstate,
- MYSQL_ERROR::enum_warning_level level,
+ Sql_condition::enum_warning_level level,
const char* msg,
- MYSQL_ERROR ** cond_hdl)
+ Sql_condition ** cond_hdl)
{
if (sql_errno == ER_LOCK_DEADLOCK)
{
@@ -4186,8 +3545,7 @@ public:
*/
bool
-Open_table_context::
-recover_from_failed_open()
+Open_table_context::recover_from_failed_open()
{
bool result= FALSE;
MDL_deadlock_discovery_repair_handler handler;
@@ -4206,17 +3564,31 @@ recover_from_failed_open()
case OT_DISCOVER:
{
if ((result= lock_table_names(m_thd, m_failed_table, NULL,
- get_timeout(),
- MYSQL_OPEN_SKIP_TEMPORARY)))
+ get_timeout(), 0)))
break;
tdc_remove_table(m_thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
m_failed_table->table_name, FALSE);
- ha_create_table_from_engine(m_thd, m_failed_table->db,
- m_failed_table->table_name);
- m_thd->warning_info->clear_warning_info(m_thd->query_id);
+ m_thd->get_stmt_da()->clear_warning_info(m_thd->query_id);
m_thd->clear_error(); // Clear error message
+
+ No_such_table_error_handler no_such_table_handler;
+ bool open_if_exists= m_failed_table->open_strategy == TABLE_LIST::OPEN_IF_EXISTS;
+
+ if (open_if_exists)
+ m_thd->push_internal_handler(&no_such_table_handler);
+
+ result= !tdc_acquire_share(m_thd, m_failed_table->db,
+ m_failed_table->table_name,
+ GTS_TABLE | GTS_FORCE_DISCOVERY | GTS_NOLOCK);
+ if (open_if_exists)
+ {
+ m_thd->pop_internal_handler();
+ if (result && no_such_table_handler.safely_trapped_errors())
+ result= FALSE;
+ }
+
/*
Rollback to start of the current statement to release exclusive lock
on table which was discovered but preserve locks from previous statements
@@ -4228,8 +3600,7 @@ recover_from_failed_open()
case OT_REPAIR:
{
if ((result= lock_table_names(m_thd, m_failed_table, NULL,
- get_timeout(),
- MYSQL_OPEN_SKIP_TEMPORARY)))
+ get_timeout(), 0)))
break;
tdc_remove_table(m_thd, TDC_RT_REMOVE_ALL, m_failed_table->db,
@@ -4269,9 +3640,12 @@ recover_from_failed_open()
/*
Return a appropriate read lock type given a table object.
- @param thd Thread context
- @param prelocking_ctx Prelocking context.
- @param table_list Table list element for table to be locked.
+ @param thd Thread context
+ @param prelocking_ctx Prelocking context.
+ @param table_list Table list element for table to be locked.
+ @param routine_modifies_data
+ Some routine that is invoked by statement
+ modifies data.
@remark Due to a statement-based replication limitation, statements such as
INSERT INTO .. SELECT FROM .. and CREATE TABLE .. SELECT FROM need
@@ -4284,9 +3658,13 @@ recover_from_failed_open()
This also applies to SELECT/SET/DO statements which use stored
functions. Calls to such functions are going to be logged as a
whole and thus should be serialized against concurrent changes
- to tables used by those functions. This can be avoided if functions
- only read data but doing so requires more complex analysis than it
- is done now.
+ to tables used by those functions. This is avoided when functions
+ do not modify data but only read it, since in this case nothing is
+ written to the binary log. Argument routine_modifies_data
+ denotes the same. So effectively, if the statement is not a
+ update query and routine_modifies_data is false, then
+ prelocking_placeholder does not take importance.
+
Furthermore, this does not apply to I_S and log tables as it's
always unsafe to replicate such tables under statement-based
replication as the table on the slave might contain other data
@@ -4301,7 +3679,8 @@ recover_from_failed_open()
thr_lock_type read_lock_type_for_table(THD *thd,
Query_tables_list *prelocking_ctx,
- TABLE_LIST *table_list)
+ TABLE_LIST *table_list,
+ bool routine_modifies_data)
{
/*
In cases when this function is called for a sub-statement executed in
@@ -4316,7 +3695,7 @@ thr_lock_type read_lock_type_for_table(THD *thd,
(table_list->table->s->table_category == TABLE_CATEGORY_LOG) ||
(table_list->table->s->table_category == TABLE_CATEGORY_PERFORMANCE) ||
!(is_update_query(prelocking_ctx->sql_command) ||
- table_list->prelocking_placeholder ||
+ (routine_modifies_data && table_list->prelocking_placeholder) ||
(thd->locked_tables_mode > LTM_LOCK_TABLES)))
return TL_READ;
else
@@ -4329,19 +3708,21 @@ thr_lock_type read_lock_type_for_table(THD *thd,
and, if prelocking strategy prescribes so, extend the prelocking set
with tables and routines used by it.
- @param[in] thd Thread context.
- @param[in] prelocking_ctx Prelocking context.
- @param[in] rt Element of 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[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.
+ @param[in] thd Thread context.
+ @param[in] prelocking_ctx Prelocking context.
+ @param[in] rt Element of 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[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.
+ @param[out] routine_modifies_data Set to TRUE if it was detected that this
+ routine does modify table data.
@retval FALSE Success.
@retval TRUE Failure (Conflicting metadata lock, OOM, other errors).
@@ -4353,11 +3734,13 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
Prelocking_strategy *prelocking_strategy,
bool has_prelocking_list,
Open_table_context *ot_ctx,
- bool *need_prelocking)
+ bool *need_prelocking, bool *routine_modifies_data)
{
MDL_key::enum_mdl_namespace mdl_type= rt->mdl_request.key.mdl_namespace();
DBUG_ENTER("open_and_process_routine");
+ *routine_modifies_data= false;
+
switch (mdl_type)
{
case MDL_key::FUNCTION:
@@ -4410,10 +3793,13 @@ open_and_process_routine(THD *thd, Query_tables_list *prelocking_ctx,
DBUG_RETURN(TRUE);
/* 'sp' is NULL when there is no such routine. */
- if (sp && !has_prelocking_list)
+ if (sp)
{
- prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp,
- need_prelocking);
+ *routine_modifies_data= sp->modifies_data();
+
+ if (!has_prelocking_list)
+ prelocking_strategy->handle_routine(thd, prelocking_ctx, rt, sp,
+ need_prelocking);
}
}
else
@@ -4605,9 +3991,35 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
tables->db, tables->table_name, tables)); //psergey: invalid read of size 1 here
(*counter)++;
- /* Not a placeholder: must be a base table or a view. Let us open it. */
- DBUG_ASSERT(!tables->table);
+ /*
+ Not a placeholder: must be a base/temporary table or a view. Let us open it.
+ */
+ if (tables->table)
+ {
+ /*
+ If this TABLE_LIST object has an associated open TABLE object
+ (TABLE_LIST::table is not NULL), that TABLE object must be a pre-opened
+ temporary table.
+ */
+ DBUG_ASSERT(is_temporary_table(tables));
+ }
+ else if (tables->open_type == OT_TEMPORARY_ONLY)
+ {
+ /*
+ OT_TEMPORARY_ONLY means that we are in CREATE TEMPORARY TABLE statement.
+ Also such table list element can't correspond to prelocking placeholder
+ or to underlying table of merge table.
+ So existing temporary table should have been preopened by this moment
+ and we can simply continue without trying to open temporary or base
+ table.
+ */
+ DBUG_ASSERT(tables->open_strategy);
+ DBUG_ASSERT(!tables->prelocking_placeholder);
+ DBUG_ASSERT(!tables->parent_l);
+ DBUG_RETURN(0);
+ }
+ /* Not a placeholder: must be a base table or a view. Let us open it. */
if (tables->prelocking_placeholder)
{
/*
@@ -4618,7 +4030,35 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
*/
No_such_table_error_handler no_such_table_handler;
thd->push_internal_handler(&no_such_table_handler);
- error= open_table(thd, tables, new_frm_mem, ot_ctx);
+
+ /*
+ We're opening a table from the prelocking list.
+
+ Since this table list element might have been added after pre-opening
+ of temporary tables we have to try to open temporary table for it.
+
+ We can't simply skip this table list element and postpone opening of
+ temporary tabletill the execution of substatement for several reasons:
+ - Temporary table can be a MERGE table with base underlying tables,
+ so its underlying tables has to be properly open and locked at
+ prelocking stage.
+ - Temporary table can be a MERGE table and we might be in PREPARE
+ phase for a prepared statement. In this case it is important to call
+ HA_ATTACH_CHILDREN for all merge children.
+ This is necessary because merge children remember "TABLE_SHARE ref type"
+ and "TABLE_SHARE def version" in the HA_ATTACH_CHILDREN operation.
+ If HA_ATTACH_CHILDREN is not called, these attributes are not set.
+ Then, during the first EXECUTE, those attributes need to be updated.
+ That would cause statement re-preparing (because changing those
+ attributes during EXECUTE is caught by THD::m_reprepare_observers).
+ The problem is that since those attributes are not set in merge
+ children, another round of PREPARE will not help.
+ */
+ error= open_temporary_table(thd, tables);
+
+ if (!error && !tables->table)
+ error= open_table(thd, tables, new_frm_mem, ot_ctx);
+
thd->pop_internal_handler();
safe_to_ignore_table= no_such_table_handler.safely_trapped_errors();
}
@@ -4632,12 +4072,29 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
*/
Repair_mrg_table_error_handler repair_mrg_table_handler;
thd->push_internal_handler(&repair_mrg_table_handler);
- error= open_table(thd, tables, new_frm_mem, ot_ctx);
+
+ error= open_temporary_table(thd, tables);
+ if (!error && !tables->table)
+ error= open_table(thd, tables, new_frm_mem, ot_ctx);
+
thd->pop_internal_handler();
safe_to_ignore_table= repair_mrg_table_handler.safely_trapped_errors();
}
else
- error= open_table(thd, tables, new_frm_mem, ot_ctx);
+ {
+ if (tables->parent_l)
+ {
+ /*
+ Even if we are opening table not from the prelocking list we
+ still might need to look for a temporary table if this table
+ list element corresponds to underlying table of a merge table.
+ */
+ error= open_temporary_table(thd, tables);
+ }
+
+ if (!error && !tables->table)
+ error= open_table(thd, tables, new_frm_mem, ot_ctx);
+ }
free_root(new_frm_mem, MYF(MY_KEEP_PREALLOC));
@@ -4696,16 +4153,7 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
if (error)
goto end;
- if (tables->lock_type != TL_UNLOCK && ! thd->locked_tables_mode)
- {
- if (tables->lock_type == TL_WRITE_DEFAULT)
- tables->table->reginfo.lock_type= thd->update_lock_default;
- else if (tables->lock_type == TL_READ_DEFAULT)
- tables->table->reginfo.lock_type=
- read_lock_type_for_table(thd, lex, tables);
- else
- tables->table->reginfo.lock_type= tables->lock_type;
- }
+ /* Copy grant information from TABLE_LIST instance to TABLE one. */
tables->table->grant= tables->grant;
/* Check and update metadata version of a base table. */
@@ -4727,6 +4175,32 @@ open_and_process_table(THD *thd, TABLE_LIST *tables, uint *counter, uint flags,
goto end;
}
+ if (get_use_stat_tables_mode(thd) > NEVER && tables->table)
+ {
+ TABLE_SHARE *table_share= tables->table->s;
+ if (table_share && table_share->table_category == TABLE_CATEGORY_USER &&
+ table_share->tmp_table == NO_TMP_TABLE)
+ {
+ if (table_share->stats_cb.stats_can_be_read ||
+ !alloc_statistics_for_table_share(thd, table_share, FALSE))
+ {
+ if (table_share->stats_cb.stats_can_be_read)
+ {
+ KEY *key_info= table_share->key_info;
+ KEY *key_info_end= key_info + table_share->keys;
+ KEY *table_key_info= tables->table->key_info;
+ for ( ; key_info < key_info_end; key_info++, table_key_info++)
+ table_key_info->read_stats= key_info->read_stats;
+ Field **field_ptr= table_share->field;
+ Field **table_field_ptr= tables->table->field;
+ for ( ; *field_ptr; field_ptr++, table_field_ptr++)
+ (*table_field_ptr)->read_stats= (*field_ptr)->read_stats;
+ tables->table->stats_is_read= table_share->stats_cb.stats_is_read;
+ }
+ }
+ }
+ }
+
process_view_routines:
/*
Again we may need cache all routines used by this view and add
@@ -4753,10 +4227,9 @@ end:
DBUG_RETURN(error);
}
-extern "C" uchar *schema_set_get_key(const uchar *record, size_t *length,
+extern "C" uchar *schema_set_get_key(const TABLE_LIST *table, size_t *length,
my_bool not_used __attribute__((unused)))
{
- TABLE_LIST *table=(TABLE_LIST*) record;
*length= table->db_length;
return (uchar*) table->db;
}
@@ -4797,7 +4270,7 @@ lock_table_names(THD *thd,
MDL_request_list mdl_requests;
TABLE_LIST *table;
MDL_request global_request;
- Hash_set<TABLE_LIST, schema_set_get_key> schema_set;
+ Hash_set<TABLE_LIST> schema_set(schema_set_get_key);
ulong org_lock_wait_timeout= lock_wait_timeout;
/* Check if we are using CREATE TABLE ... IF NOT EXISTS */
bool create_table;
@@ -4809,26 +4282,33 @@ lock_table_names(THD *thd,
for (table= tables_start; table && table != tables_end;
table= table->next_global)
{
- if (table->mdl_request.type >= MDL_SHARED_NO_WRITE &&
- !(table->open_type == OT_TEMPORARY_ONLY ||
- (flags & MYSQL_OPEN_TEMPORARY_ONLY) ||
- (table->open_type != OT_BASE_ONLY &&
- ! (flags & MYSQL_OPEN_SKIP_TEMPORARY) &&
- find_temporary_table(thd, table))))
+ if (table->mdl_request.type < MDL_SHARED_UPGRADABLE ||
+ table->open_type == OT_TEMPORARY_ONLY ||
+ (table->open_type == OT_TEMPORARY_OR_BASE && is_temporary_table(table)))
{
- if (! (flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK) &&
- schema_set.insert(table))
- DBUG_RETURN(TRUE);
- mdl_requests.push_front(&table->mdl_request);
+ continue;
}
+
+ /* Write lock on normal tables is not allowed in a read only transaction. */
+ if (thd->tx_read_only)
+ {
+ my_error(ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION, MYF(0));
+ DBUG_RETURN(true);
+ }
+
+ if (! (flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK) &&
+ schema_set.insert(table))
+ DBUG_RETURN(TRUE);
+
+ mdl_requests.push_front(&table->mdl_request);
}
if (mdl_requests.is_empty())
DBUG_RETURN(FALSE);
- /* Check if CREATE TABLE IF NOT EXISTS was used */
- create_table= (tables_start && tables_start->open_strategy ==
- TABLE_LIST::OPEN_IF_EXISTS);
+ /* Check if CREATE TABLE without REPLACE was used */
+ create_table= (thd->lex->sql_command == SQLCOM_CREATE_TABLE &&
+ !(thd->lex->create_info.options & HA_LEX_CREATE_REPLACE));
if (!(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK))
{
@@ -4836,7 +4316,7 @@ lock_table_names(THD *thd,
Scoped locks: Take intention exclusive locks on all involved
schemas.
*/
- Hash_set<TABLE_LIST, schema_set_get_key>::Iterator it(schema_set);
+ Hash_set<TABLE_LIST>::Iterator it(schema_set);
while ((table= it++))
{
MDL_request *schema_request= new (thd->mem_root) MDL_request;
@@ -4865,12 +4345,9 @@ lock_table_names(THD *thd,
for (;;)
{
- bool exists= TRUE;
- bool res;
-
if (create_table)
thd->push_internal_handler(&error_handler); // Avoid warnings & errors
- res= thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout);
+ bool res= thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout);
if (create_table)
thd->pop_internal_handler();
if (!res)
@@ -4880,17 +4357,14 @@ lock_table_names(THD *thd,
DBUG_RETURN(TRUE); // Return original error
/*
- We come here in the case of lock timeout when executing
- CREATE TABLE IF NOT EXISTS.
- Verify that table really exists (it should as we got a lock conflict)
+ We come here in the case of lock timeout when executing CREATE TABLE.
+ Verify that table does exist (it usually does, as we got a lock conflict)
*/
- if (check_if_table_exists(thd, tables_start, 1, &exists))
- DBUG_RETURN(TRUE); // Should never happen
- if (exists)
+ if (ha_table_exists(thd, tables_start->db, tables_start->table_name))
{
if (thd->lex->create_info.options & HA_LEX_CREATE_IF_NOT_EXISTS)
{
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_TABLE_EXISTS_ERROR, ER(ER_TABLE_EXISTS_ERROR),
tables_start->table_name);
}
@@ -4898,17 +4372,16 @@ lock_table_names(THD *thd,
my_error(ER_TABLE_EXISTS_ERROR, MYF(0), tables_start->table_name);
DBUG_RETURN(TRUE);
}
- /* purecov: begin inspected */
/*
- We got error from acquire_locks but table didn't exists.
- In theory this should never happen, except maybe in
- CREATE or DROP DATABASE scenario.
+ We got error from acquire_locks, but the table didn't exists.
+ This could happen if another connection runs a statement
+ involving this non-existent table, and this statement took the mdl,
+ but didn't error out with ER_NO_SUCH_TABLE yet (yes, a race condition).
We play safe and restart the original acquire_locks with the
- original timeout
+ original timeout.
*/
create_table= 0;
lock_wait_timeout= org_lock_wait_timeout;
- /* purecov: end */
}
}
@@ -4940,34 +4413,33 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start,
for (table= tables_start; table && table != tables_end;
table= table->next_global)
{
- if (table->mdl_request.type >= MDL_SHARED_NO_WRITE &&
- !(table->open_type == OT_TEMPORARY_ONLY ||
- (flags & MYSQL_OPEN_TEMPORARY_ONLY) ||
- (table->open_type != OT_BASE_ONLY &&
- ! (flags & MYSQL_OPEN_SKIP_TEMPORARY) &&
- find_temporary_table(thd, table))))
+ if (table->mdl_request.type < MDL_SHARED_UPGRADABLE ||
+ table->open_type == OT_TEMPORARY_ONLY ||
+ (table->open_type == OT_TEMPORARY_OR_BASE && is_temporary_table(table)))
{
- /*
- We don't need to do anything about the found TABLE instance as it
- will be handled later in open_tables(), we only need to check that
- an upgradable lock is already acquired. When we enter LOCK TABLES
- mode, SNRW locks are acquired before all other locks. So if under
- LOCK TABLES we find that there is TABLE instance with upgradeable
- lock, all other instances of TABLE for the same table will have the
- same ticket.
-
- Note that this works OK even for CREATE TABLE statements which
- request X type of metadata lock. This is because under LOCK TABLES
- such statements don't create the table but only check if it exists
- or, in most complex case, only insert into it.
- Thus SNRW lock should be enough.
-
- Note that find_table_for_mdl_upgrade() will report an error if
- no suitable ticket is found.
- */
- if (!find_table_for_mdl_upgrade(thd, table->db, table->table_name, false))
- return TRUE;
+ continue;
}
+
+ /*
+ We don't need to do anything about the found TABLE instance as it
+ will be handled later in open_tables(), we only need to check that
+ an upgradable lock is already acquired. When we enter LOCK TABLES
+ mode, SNRW locks are acquired before all other locks. So if under
+ LOCK TABLES we find that there is TABLE instance with upgradeable
+ lock, all other instances of TABLE for the same table will have the
+ same ticket.
+
+ Note that this works OK even for CREATE TABLE statements which
+ request X type of metadata lock. This is because under LOCK TABLES
+ such statements don't create the table but only check if it exists
+ or, in most complex case, only insert into it.
+ Thus SNRW lock should be enough.
+
+ Note that find_table_for_mdl_upgrade() will report an error if
+ no suitable ticket is found.
+ */
+ if (!find_table_for_mdl_upgrade(thd, table->db, table->table_name, false))
+ return TRUE;
}
return FALSE;
@@ -5007,11 +4479,12 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
Prelocking_strategy *prelocking_strategy)
{
/*
- We use pointers to "next_global" member in the last processed TABLE_LIST
- element and to the "next" member in the last processed Sroutine_hash_entry
- element as iterators over, correspondingly, the table list and stored routines
- list which stay valid and allow to continue iteration when new elements are
- added to the tail of the lists.
+ We use pointers to "next_global" member in the last processed
+ TABLE_LIST element and to the "next" member in the last processed
+ Sroutine_hash_entry element as iterators over, correspondingly,
+ the table list and stored routines list which stay valid and allow
+ to continue iteration when new elements are added to the tail of
+ the lists.
*/
TABLE_LIST **table_to_open;
Sroutine_hash_entry **sroutine_to_open;
@@ -5019,6 +4492,7 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
Open_table_context ot_ctx(thd, flags);
bool error= FALSE;
MEM_ROOT new_frm_mem;
+ bool some_routine_modifies_data= FALSE;
bool has_prelocking_list;
DBUG_ENTER("open_tables");
@@ -5031,11 +4505,11 @@ bool open_tables(THD *thd, TABLE_LIST **start, uint *counter, uint flags,
}
/*
- Initialize temporary MEM_ROOT for new .FRM parsing. Do not allocate
+ Initialize temporary MEM_ROOT for new .FRM parsing. Do not alloctaate
anything yet, to avoid penalty for statements which don't use views
and thus new .FRM format.
*/
- init_sql_alloc(&new_frm_mem, 8024, 0);
+ init_sql_alloc(&new_frm_mem, 8024, 0, MYF(0));
thd->current_tablenr= 0;
restart:
@@ -5054,7 +4528,7 @@ restart:
table_to_open= start;
sroutine_to_open= &thd->lex->sroutines_list.first;
*counter= 0;
- thd_proc_info(thd, "Opening tables");
+ THD_STAGE_INFO(thd, stage_opening_tables);
prelocking_strategy->reset(thd);
/*
@@ -5101,7 +4575,7 @@ restart:
for (table= *start; table && table != thd->lex->first_not_own_table();
table= table->next_global)
{
- if (table->mdl_request.type >= MDL_SHARED_NO_WRITE)
+ if (table->mdl_request.type >= MDL_SHARED_UPGRADABLE)
table->mdl_request.ticket= NULL;
}
}
@@ -5154,6 +4628,10 @@ restart:
if (ot_ctx.recover_from_failed_open())
goto err;
+ /* Re-open temporary tables after close_tables_for_reopen(). */
+ if (open_temporary_tables(thd, *start))
+ goto err;
+
error= FALSE;
goto restart;
}
@@ -5186,11 +4664,16 @@ restart:
sroutine_to_open= &rt->next, rt= rt->next)
{
bool need_prelocking= false;
+ bool routine_modifies_data;
TABLE_LIST **save_query_tables_last= thd->lex->query_tables_last;
error= open_and_process_routine(thd, thd->lex, rt, prelocking_strategy,
has_prelocking_list, &ot_ctx,
- &need_prelocking);
+ &need_prelocking,
+ &routine_modifies_data);
+
+ // Remember if any of SF modifies data.
+ some_routine_modifies_data|= routine_modifies_data;
if (need_prelocking && ! thd->lex->requires_prelocking())
thd->lex->mark_as_requiring_prelocking(save_query_tables_last);
@@ -5207,6 +4690,10 @@ restart:
if (ot_ctx.recover_from_failed_open())
goto err;
+ /* Re-open temporary tables after close_tables_for_reopen(). */
+ if (open_temporary_tables(thd, *start))
+ goto err;
+
error= FALSE;
goto restart;
}
@@ -5228,6 +4715,10 @@ restart:
children, attach the children to their parents. At end of statement,
the children are detached. Attaching and detaching are always done,
even under LOCK TABLES.
+
+ We also convert all TL_WRITE_DEFAULT and TL_READ_DEFAULT locks to
+ appropriate "real" lock types to be used for locking and to be passed
+ to storage engine.
*/
for (tables= *start; tables; tables= tables->next_global)
{
@@ -5244,10 +4735,23 @@ restart:
goto err;
}
}
+
+ /* Set appropriate TABLE::lock_type. */
+ if (tbl && tables->lock_type != TL_UNLOCK && !thd->locked_tables_mode)
+ {
+ if (tables->lock_type == TL_WRITE_DEFAULT)
+ tbl->reginfo.lock_type= thd->update_lock_default;
+ else if (tables->lock_type == TL_READ_DEFAULT)
+ tbl->reginfo.lock_type=
+ read_lock_type_for_table(thd, thd->lex, tables,
+ some_routine_modifies_data);
+ else
+ tbl->reginfo.lock_type= tables->lock_type;
+ }
}
err:
- thd_proc_info(thd, "After opening tables");
+ THD_STAGE_INFO(thd, stage_after_opening_tables);
free_root(&new_frm_mem, MYF(0)); // Free pre-alloced block
if (error && *table_to_open)
@@ -5309,6 +4813,25 @@ handle_routine(THD *thd, Query_tables_list *prelocking_ctx,
}
+/*
+ @note this can be changed to use a hash, instead of scanning the linked
+ list, if the performance of this function will ever become an issue
+*/
+bool table_already_fk_prelocked(TABLE_LIST *tl, LEX_STRING *db,
+ LEX_STRING *table, thr_lock_type lock_type)
+{
+ for (; tl; tl= tl->next_global )
+ {
+ if (tl->lock_type >= lock_type &&
+ tl->prelocking_placeholder == TABLE_LIST::FK &&
+ strcmp(tl->db, db->str) == 0 &&
+ strcmp(tl->table_name, table->str) == 0)
+ return true;
+ }
+ return false;
+}
+
+
/**
Defines how prelocking algorithm for DML statements should handle table list
elements:
@@ -5348,6 +4871,52 @@ handle_table(THD *thd, Query_tables_list *prelocking_ctx,
add_tables_and_routines_for_triggers(thd, prelocking_ctx, table_list))
return TRUE;
}
+
+ if (table_list->table->file->referenced_by_foreign_key())
+ {
+ List <FOREIGN_KEY_INFO> fk_list;
+ List_iterator<FOREIGN_KEY_INFO> fk_list_it(fk_list);
+ FOREIGN_KEY_INFO *fk;
+ Query_arena *arena, backup;
+
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+
+ table_list->table->file->get_parent_foreign_key_list(thd, &fk_list);
+ if (thd->is_error())
+ {
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ return TRUE;
+ }
+
+ *need_prelocking= TRUE;
+
+ while ((fk= fk_list_it++))
+ {
+ // FK_OPTION_RESTRICT and FK_OPTION_NO_ACTION only need read access
+ uint8 op= table_list->trg_event_map;
+ thr_lock_type lock_type;
+
+ if ((op & (1 << TRG_EVENT_DELETE) && fk_modifies_child(fk->delete_method))
+ || (op & (1 << TRG_EVENT_UPDATE) && fk_modifies_child(fk->update_method)))
+ lock_type= TL_WRITE_ALLOW_WRITE;
+ else
+ lock_type= TL_READ;
+
+ if (table_already_fk_prelocked(prelocking_ctx->query_tables,
+ fk->foreign_db, fk->foreign_table,
+ lock_type))
+ continue;
+
+ TABLE_LIST *tl= (TABLE_LIST *) thd->alloc(sizeof(TABLE_LIST));
+ tl->init_one_table_for_prelocking(fk->foreign_db->str, fk->foreign_db->length,
+ fk->foreign_table->str, fk->foreign_table->length,
+ NULL, lock_type, false, table_list->belong_to_view,
+ op, &prelocking_ctx->query_tables_last);
+ }
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ }
}
return FALSE;
@@ -5514,11 +5083,15 @@ static bool check_lock_and_start_stmt(THD *thd,
engine is important as, for example, InnoDB uses it to determine
what kind of row locks should be acquired when executing statement
in prelocked mode or under LOCK TABLES with @@innodb_table_locks = 0.
+
+ Last argument routine_modifies_data for read_lock_type_for_table()
+ is ignored, as prelocking placeholder will never be set here.
*/
+ DBUG_ASSERT(table_list->prelocking_placeholder == false);
if (table_list->lock_type == TL_WRITE_DEFAULT)
lock_type= thd->update_lock_default;
else if (table_list->lock_type == TL_READ_DEFAULT)
- lock_type= read_lock_type_for_table(thd, prelocking_ctx, table_list);
+ lock_type= read_lock_type_for_table(thd, prelocking_ctx, table_list, true);
else
lock_type= table_list->lock_type;
@@ -5633,16 +5206,20 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type,
bool error;
DBUG_ENTER("open_ltable");
+ /* Ignore temporary tables as they have already ben opened*/
+ if (table_list->table)
+ DBUG_RETURN(table_list->table);
+
/* should not be used in a prelocked_mode context, see NOTE above */
DBUG_ASSERT(thd->locked_tables_mode < LTM_PRELOCKED);
- thd_proc_info(thd, "Opening table");
+ THD_STAGE_INFO(thd, stage_opening_tables);
thd->current_tablenr= 0;
/* open_ltable can be used only for BASIC TABLEs */
table_list->required_type= FRMTYPE_TABLE;
/* This function can't properly handle requests for such metadata locks. */
- DBUG_ASSERT(table_list->mdl_request.type < MDL_SHARED_NO_WRITE);
+ DBUG_ASSERT(table_list->mdl_request.type < MDL_SHARED_UPGRADABLE);
while ((error= open_table(thd, table_list, thd->mem_root, &ot_ctx)) &&
ot_ctx.can_recover_from_failed_open())
@@ -5705,7 +5282,7 @@ end:
trans_rollback_stmt(thd);
close_thread_tables(thd);
}
- thd_proc_info(thd, "After opening table");
+ THD_STAGE_INFO(thd, stage_after_opening_tables);
DBUG_RETURN(table);
}
@@ -5751,6 +5328,8 @@ bool open_and_lock_tables(THD *thd, TABLE_LIST *tables,
if (lock_tables(thd, tables, counter, flags))
goto err;
+ (void) read_statistics_for_tables_if_needed(thd, tables);
+
if (derived)
{
if (mysql_handle_derived(thd->lex, DT_INIT))
@@ -5881,7 +5460,6 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count,
uint flags)
{
TABLE_LIST *table;
-
DBUG_ENTER("lock_tables");
/*
We can't meet statement requiring prelocking if we already
@@ -6023,6 +5601,36 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count,
}
+/*
+ Restart transaction for tables
+
+ This is used when we had to do an implicit commit after tables are opened
+ and want to restart transactions on tables.
+
+ This is used in case of:
+ LOCK TABLES xx
+ CREATE OR REPLACE TABLE xx;
+*/
+
+bool restart_trans_for_tables(THD *thd, TABLE_LIST *table)
+{
+ DBUG_ENTER("restart_trans_for_tables");
+
+ for (; table; table= table->next_global)
+ {
+ if (table->placeholder())
+ continue;
+
+ if (check_lock_and_start_stmt(thd, thd->lex, table))
+ {
+ DBUG_ASSERT(0); // Should never happen
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
/**
Prepare statement for reopening of tables and recalculation of set of
prelocked tables.
@@ -6084,12 +5692,17 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables,
the opened TABLE instance will be addded to THD::temporary_tables list.
@param thd Thread context.
+ @param hton Storage engine of the table, if known,
+ or NULL otherwise.
@param path Path (without .frm)
@param db Database name.
@param table_name Table name.
@param add_to_temporary_tables_list Specifies if the opened TABLE
instance should be linked into
THD::temporary_tables list.
+ @param open_in_engine Indicates that we need to open table
+ in storage engine in addition to
+ constructing TABLE object for it.
@note This function is used:
- by alter_table() to open a temporary table;
@@ -6099,26 +5712,34 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables,
@retval NULL on error.
*/
-TABLE *open_table_uncached(THD *thd, const char *path, const char *db,
+TABLE *open_table_uncached(THD *thd, handlerton *hton,
+ const char *path, const char *db,
const char *table_name,
- bool add_to_temporary_tables_list)
+ bool add_to_temporary_tables_list,
+ bool open_in_engine)
{
TABLE *tmp_table;
TABLE_SHARE *share;
char cache_key[MAX_DBKEY_LENGTH], *saved_cache_key, *tmp_path;
uint key_length;
- TABLE_LIST table_list;
DBUG_ENTER("open_table_uncached");
DBUG_PRINT("enter",
("table: '%s'.'%s' path: '%s' server_id: %u "
"pseudo_thread_id: %lu",
db, table_name, path,
- (uint) thd->server_id, (ulong) thd->variables.pseudo_thread_id));
+ (uint) thd->variables.server_id,
+ (ulong) thd->variables.pseudo_thread_id));
+
+ if (add_to_temporary_tables_list)
+ {
+ /* Temporary tables are not safe for parallel replication. */
+ if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec &&
+ thd->wait_for_prior_commit())
+ DBUG_RETURN(NULL);
+ }
- table_list.db= (char*) db;
- table_list.table_name= (char*) table_name;
/* Create the cache_key for temporary tables */
- key_length= create_table_def_key(thd, cache_key, &table_list, 1);
+ key_length= create_tmp_table_def_key(thd, cache_key, db, table_name);
if (!(tmp_table= (TABLE*) my_malloc(sizeof(*tmp_table) + sizeof(*share) +
strlen(path)+1 + key_length,
@@ -6132,14 +5753,30 @@ TABLE *open_table_uncached(THD *thd, const char *path, const char *db,
init_tmp_table_share(thd, share, saved_cache_key, key_length,
strend(saved_cache_key)+1, tmp_path);
+ share->db_plugin= ha_lock_engine(thd, hton);
+
+ if (open_table_def(thd, share, GTS_TABLE | GTS_USE_DISCOVERY))
+ {
+ /* No need to lock share->mutex as this is not needed for tmp tables */
+ free_table_share(share);
+ my_free(tmp_table);
+ DBUG_RETURN(0);
+ }
- if (open_table_def(thd, share, 0) ||
- open_table_from_share(thd, share, table_name,
+ share->m_psi= PSI_CALL_get_table_share(true, share);
+
+ if (open_table_from_share(thd, share, table_name,
+ open_in_engine ?
(uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE |
- HA_GET_INDEX),
+ HA_GET_INDEX) : 0,
READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
ha_open_options,
- tmp_table, FALSE))
+ tmp_table,
+ /*
+ Set "is_create_table" if the table does not
+ exist in SE
+ */
+ open_in_engine ? false : true))
{
/* No need to lock share->mutex as this is not needed for tmp tables */
free_table_share(share);
@@ -6148,19 +5785,24 @@ TABLE *open_table_uncached(THD *thd, const char *path, const char *db,
}
tmp_table->reginfo.lock_type= TL_WRITE; // Simulate locked
+ tmp_table->grant.privilege= TMP_TABLE_ACLS;
share->tmp_table= (tmp_table->file->has_transactions() ?
TRANSACTIONAL_TMP_TABLE : NON_TRANSACTIONAL_TMP_TABLE);
if (add_to_temporary_tables_list)
{
+ thd->lock_temporary_tables();
/* growing temp list at the head */
tmp_table->next= thd->temporary_tables;
if (tmp_table->next)
tmp_table->next->prev= tmp_table;
thd->temporary_tables= tmp_table;
thd->temporary_tables->prev= 0;
- if (thd->slave_thread)
- slave_open_temp_tables++;
+ if (thd->rgi_slave)
+ {
+ thread_safe_increment32(&slave_open_temp_tables, &thread_running_lock);
+ }
+ thd->unlock_temporary_tables(0);
}
tmp_table->pos_in_table_list= 0;
DBUG_PRINT("tmptable", ("opened table: '%s'.'%s' 0x%lx", tmp_table->s->db.str,
@@ -6270,6 +5912,164 @@ static void update_field_dependencies(THD *thd, Field *field, TABLE *table)
}
+/**
+ Find a temporary table specified by TABLE_LIST instance in the cache and
+ prepare its TABLE instance for use.
+
+ This function tries to resolve this table in the list of temporary tables
+ of this thread. Temporary tables are thread-local and "shadow" base
+ tables with the same name.
+
+ @note In most cases one should use open_temporary_tables() instead
+ of this call.
+
+ @note One should finalize process of opening temporary table for table
+ list element by calling open_and_process_table(). This function
+ is responsible for table version checking and handling of merge
+ tables.
+
+ @note We used to check global_read_lock before opening temporary tables.
+ However, that limitation was artificial and is removed now.
+
+ @return Error status.
+ @retval FALSE On success. If a temporary table exists for the given
+ key, tl->table is set.
+ @retval TRUE On error. my_error() has been called.
+*/
+
+bool open_temporary_table(THD *thd, TABLE_LIST *tl)
+{
+ TABLE *table;
+ DBUG_ENTER("open_temporary_table");
+ DBUG_PRINT("enter", ("table: '%s'.'%s'", tl->db, tl->table_name));
+
+ /*
+ Code in open_table() assumes that TABLE_LIST::table can
+ be non-zero only for pre-opened temporary tables.
+ */
+ DBUG_ASSERT(tl->table == NULL);
+
+ /*
+ This function should not be called for cases when derived or I_S
+ tables can be met since table list elements for such tables can
+ have invalid db or table name.
+ Instead open_temporary_tables() should be used.
+ */
+ DBUG_ASSERT(!tl->derived && !tl->schema_table);
+
+ if (tl->open_type == OT_BASE_ONLY || !thd->have_temporary_tables())
+ {
+ DBUG_PRINT("info", ("skip_temporary is set or no temporary tables"));
+ DBUG_RETURN(FALSE);
+ }
+
+ if (find_and_use_temporary_table(thd, tl, &table))
+ DBUG_RETURN(TRUE);
+ if (!table)
+ {
+ if (tl->open_type == OT_TEMPORARY_ONLY &&
+ tl->open_strategy == TABLE_LIST::OPEN_NORMAL)
+ {
+ my_error(ER_NO_SUCH_TABLE, MYF(0), tl->db, tl->table_name);
+ DBUG_RETURN(TRUE);
+ }
+ DBUG_RETURN(FALSE);
+ }
+
+ /*
+ Temporary tables are not safe for parallel replication. They were
+ designed to be visible to one thread only, so have no table locking.
+ Thus there is no protection against two conflicting transactions
+ committing in parallel and things like that.
+
+ So for now, anything that uses temporary tables will be serialised
+ with anything before it, when using parallel replication.
+
+ ToDo: We might be able to introduce a reference count or something
+ on temp tables, and have slave worker threads wait for it to reach
+ zero before being allowed to use the temp table. Might not be worth
+ it though, as statement-based replication using temporary tables is
+ in any case rather fragile.
+ */
+ if (thd->rgi_slave && thd->rgi_slave->is_parallel_exec &&
+ thd->wait_for_prior_commit())
+ DBUG_RETURN(true);
+
+#ifdef WITH_PARTITION_STORAGE_ENGINE
+ if (tl->partition_names)
+ {
+ /* Partitioned temporary tables is not supported. */
+ DBUG_ASSERT(!table->part_info);
+ my_error(ER_PARTITION_CLAUSE_ON_NONPARTITIONED, MYF(0));
+ DBUG_RETURN(true);
+ }
+#endif
+
+ if (table->query_id)
+ {
+ /*
+ We're trying to use the same temporary table twice in a query.
+ Right now we don't support this because a temporary table is always
+ represented by only one TABLE object in THD, and it can not be
+ cloned. Emit an error for an unsupported behaviour.
+ */
+
+ DBUG_PRINT("error",
+ ("query_id: %lu server_id: %u pseudo_thread_id: %lu",
+ (ulong) table->query_id, (uint) thd->variables.server_id,
+ (ulong) thd->variables.pseudo_thread_id));
+ my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr());
+ DBUG_RETURN(TRUE);
+ }
+
+ table->query_id= thd->query_id;
+ thd->thread_specific_used= TRUE;
+
+ tl->updatable= 1; // It is not derived table nor non-updatable VIEW.
+ tl->table= table;
+
+ table->init(thd, tl);
+
+ DBUG_PRINT("info", ("Using temporary table"));
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Pre-open temporary tables corresponding to table list elements.
+
+ @note One should finalize process of opening temporary tables
+ by calling open_tables(). This function is responsible
+ for table version checking and handling of merge tables.
+
+ @return Error status.
+ @retval FALSE On success. If a temporary tables exists for the
+ given element, tl->table is set.
+ @retval TRUE On error. my_error() has been called.
+*/
+
+bool open_temporary_tables(THD *thd, TABLE_LIST *tl_list)
+{
+ TABLE_LIST *first_not_own= thd->lex->first_not_own_table();
+ DBUG_ENTER("open_temporary_tables");
+
+ for (TABLE_LIST *tl= tl_list; tl && tl != first_not_own; tl= tl->next_global)
+ {
+ if (tl->derived || tl->schema_table)
+ {
+ /*
+ Derived and I_S tables will be handled by a later call to open_tables().
+ */
+ continue;
+ }
+
+ if (open_temporary_table(thd, tl))
+ DBUG_RETURN(TRUE);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
/*
Find a field by name in a view that uses merge algorithm.
@@ -6669,6 +6469,7 @@ find_field_in_table_ref(THD *thd, TABLE_LIST *table_list,
*/
table_name && table_name[0] &&
(my_strcasecmp(table_alias_charset, table_list->alias, table_name) ||
+ (db_name && db_name[0] && (!table_list->db || !table_list->db[0])) ||
(db_name && db_name[0] && table_list->db && table_list->db[0] &&
(table_list->schema_table ?
my_strcasecmp(system_charset_info, db_name, table_list->db) :
@@ -6929,7 +6730,7 @@ find_field_in_tables(THD *thd, Item_ident *item,
if (!table_ref->belong_to_view &&
!table_ref->belong_to_derived)
{
- SELECT_LEX *current_sel= thd->lex->current_select;
+ SELECT_LEX *current_sel= item->context->select_lex;
SELECT_LEX *last_select= table_ref->select_lex;
bool all_merged= TRUE;
for (SELECT_LEX *sl= current_sel; sl && sl!=last_select;
@@ -7142,7 +6943,10 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter,
for (uint i= 0; (item=li++); i++)
{
- if (field_name && item->real_item()->type() == Item::FIELD_ITEM)
+ if (field_name &&
+ (item->real_item()->type() == Item::FIELD_ITEM ||
+ ((item->type() == Item::REF_ITEM) &&
+ (((Item_ref *)item)->ref_type() == Item_ref::VIEW_REF))))
{
Item_ident *item_field= (Item_ident*) item;
@@ -7268,35 +7072,6 @@ find_item_in_list(Item *find, List<Item> &items, uint *counter,
break;
}
}
- else if (table_name && item->type() == Item::REF_ITEM &&
- ((Item_ref *)item)->ref_type() == Item_ref::VIEW_REF)
- {
- /*
- TODO:Here we process prefixed view references only. What we should
- really do is process all types of Item_refs. But this will currently
- lead to a clash with the way references to outer SELECTs (from the
- HAVING clause) are handled in e.g. :
- SELECT 1 FROM t1 AS t1_o GROUP BY a
- HAVING (SELECT t1_o.a FROM t1 AS t1_i GROUP BY t1_i.a LIMIT 1).
- Processing all Item_refs here will cause t1_o.a to resolve to itself.
- We still need to process the special case of Item_direct_view_ref
- because in the context of views they have the same meaning as
- Item_field for tables.
- */
- Item_ident *item_ref= (Item_ident *) item;
- if (field_name && item_ref->name && item_ref->table_name &&
- !my_strcasecmp(system_charset_info, item_ref->name, field_name) &&
- !my_strcasecmp(table_alias_charset, item_ref->table_name,
- table_name) &&
- (!db_name || (item_ref->db_name &&
- !strcmp (item_ref->db_name, db_name))))
- {
- found= li.ref();
- *counter= i;
- *resolution= RESOLVED_IGNORING_ALIAS;
- break;
- }
- }
}
if (!found)
{
@@ -7634,14 +7409,6 @@ mark_common_columns(THD *thd, TABLE_LIST *table_ref_1, TABLE_LIST *table_ref_2,
*/
result= FALSE;
- /*
- Save the lists made during natural join matching (because
- the matching done only once but we need the list in case
- of prepared statements).
- */
- table_ref_1->persistent_used_items= table_ref_1->used_items;
- table_ref_2->persistent_used_items= table_ref_2->used_items;
-
err:
if (arena)
thd->restore_active_arena(arena, &backup);
@@ -8236,7 +8003,7 @@ bool setup_fields(THD *thd, Item **ref_pointer_array,
thd->lex->allow_sum_func= save_allow_sum_func;
thd->mark_used_columns= save_mark_used_columns;
DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
- DBUG_RETURN(test(thd->is_error()));
+ DBUG_RETURN(MY_TEST(thd->is_error()));
}
@@ -8707,11 +8474,6 @@ insert_fields(THD *thd, Name_resolution_context *context, const char *db_name,
}
}
#endif
- /*
- field_iterator.create_item() builds used_items which we
- have to save because changes made once and they are persistent
- */
- tables->persistent_used_items= tables->used_items;
if ((field= field_iterator.field()))
{
@@ -8771,9 +8533,16 @@ insert_fields(THD *thd, Name_resolution_context *context, const char *db_name,
meaningful message than ER_BAD_TABLE_ERROR.
*/
if (!table_name)
- my_message(ER_NO_TABLES_USED, ER(ER_NO_TABLES_USED), MYF(0));
+ my_error(ER_NO_TABLES_USED, MYF(0));
+ else if (!db_name && !thd->db)
+ my_error(ER_NO_DB_ERROR, MYF(0));
else
- my_error(ER_BAD_TABLE_ERROR, MYF(0), table_name);
+ {
+ char name[FN_REFLEN];
+ my_snprintf(name, sizeof(name), "%s.%s",
+ db_name ? db_name : thd->db, table_name);
+ my_error(ER_BAD_TABLE_ERROR, MYF(0), name);
+ }
DBUG_RETURN(TRUE);
}
@@ -8881,7 +8650,7 @@ bool setup_on_expr(THD *thd, TABLE_LIST *table, bool is_update)
TODO
RETURN
- TRUE if some error occured (e.g. out of memory)
+ TRUE if some error occurred (e.g. out of memory)
FALSE if all is OK
*/
@@ -8962,7 +8731,7 @@ int setup_conds(THD *thd, TABLE_LIST *tables, List<TABLE_LIST> &leaves,
select_lex->where= *conds;
}
thd->lex->current_select->is_item_list_lookup= save_is_item_list_lookup;
- DBUG_RETURN(test(thd->is_error()));
+ DBUG_RETURN(MY_TEST(thd->is_error()));
err_no_arena:
select_lex->is_item_list_lookup= save_is_item_list_lookup;
@@ -8976,34 +8745,33 @@ err_no_arena:
******************************************************************************/
-/*
- Fill fields with given items.
+/**
+ Fill the fields of a table with the values of an Item list
- SYNOPSIS
- fill_record()
- thd thread handler
- fields Item_fields list to be filled
- values values to fill with
- ignore_errors TRUE if we should ignore errors
+ @param thd thread handler
+ @param table_arg the table that is being modified
+ @param fields Item_fields list to be filled
+ @param values values to fill with
+ @param ignore_errors TRUE if we should ignore errors
- NOTE
+ @details
fill_record() may set table->auto_increment_field_not_null and a
caller should make sure that it is reset after their last call to this
function.
- RETURN
- FALSE OK
- TRUE error occured
+ @return Status
+ @retval true An error occurred.
+ @retval false OK.
*/
-static bool
-fill_record(THD * thd, List<Item> &fields, List<Item> &values,
+bool
+fill_record(THD * thd, TABLE *table_arg, List<Item> &fields, List<Item> &values,
bool ignore_errors)
{
List_iterator_fast<Item> f(fields),v(values);
Item *value, *fld;
Item_field *field;
- TABLE *table= 0, *vcol_table= 0;
+ TABLE *vcol_table= 0;
bool save_abort_on_warning= thd->abort_on_warning;
bool save_no_errors= thd->no_errors;
DBUG_ENTER("fill_record");
@@ -9020,27 +8788,28 @@ fill_record(THD * thd, List<Item> &fields, List<Item> &values,
thus we safely can take table from the first field.
*/
fld= (Item_field*)f++;
- if (!(field= fld->filed_for_view_update()))
+ if (!(field= fld->field_for_view_update()))
{
my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name);
goto err;
}
- table= field->field->table;
- table->auto_increment_field_not_null= FALSE;
+ DBUG_ASSERT(field->field->table == table_arg);
+ table_arg->auto_increment_field_not_null= FALSE;
f.rewind();
}
else if (thd->lex->unit.insert_table_with_stored_vcol)
vcol_table= thd->lex->unit.insert_table_with_stored_vcol;
+
while ((fld= f++))
{
- if (!(field= fld->filed_for_view_update()))
+ if (!(field= fld->field_for_view_update()))
{
my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name);
goto err;
}
value=v++;
Field *rfield= field->field;
- table= rfield->table;
+ TABLE* table= rfield->table;
if (rfield == table->next_number_field)
table->auto_increment_field_not_null= TRUE;
if (rfield->vcol_info &&
@@ -9048,7 +8817,7 @@ fill_record(THD * thd, List<Item> &fields, List<Item> &values,
value->type() != Item::NULL_ITEM &&
table->s->table_category != TABLE_CATEGORY_TEMPORARY)
{
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN,
ER(ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN),
rfield->field_name, table->s->table_name.str);
@@ -9059,6 +8828,7 @@ fill_record(THD * thd, List<Item> &fields, List<Item> &values,
my_message(ER_UNKNOWN_ERROR, ER(ER_UNKNOWN_ERROR), MYF(0));
goto err;
}
+ rfield->set_explicit_default(value);
DBUG_ASSERT(vcol_table == 0 || vcol_table == table);
vcol_table= table;
}
@@ -9073,8 +8843,8 @@ fill_record(THD * thd, List<Item> &fields, List<Item> &values,
err:
thd->abort_on_warning= save_abort_on_warning;
thd->no_errors= save_no_errors;
- if (table)
- table->auto_increment_field_not_null= FALSE;
+ if (fields.elements)
+ table_arg->auto_increment_field_not_null= FALSE;
DBUG_RETURN(TRUE);
}
@@ -9083,88 +8853,84 @@ err:
Fill fields in list with values from the list of items and invoke
before triggers.
- SYNOPSIS
- fill_record_n_invoke_before_triggers()
- thd thread context
- fields Item_fields list to be filled
- values values to fill with
- ignore_errors TRUE if we should ignore errors
- triggers object holding list of triggers to be invoked
- event event type for triggers to be invoked
+ @param thd thread context
+ @param table the table that is being modified
+ @param fields Item_fields list to be filled
+ @param values values to fill with
+ @param ignore_errors TRUE if we should ignore errors
+ @param event event type for triggers to be invoked
- NOTE
+ @detail
This function assumes that fields which values will be set and triggers
to be invoked belong to the same table, and that TABLE::record[0] and
record[1] buffers correspond to new and old versions of row respectively.
- RETURN
- FALSE OK
- TRUE error occured
+ @return Status
+ @retval true An error occurred.
+ @retval false OK.
*/
bool
-fill_record_n_invoke_before_triggers(THD *thd, List<Item> &fields,
+fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, List<Item> &fields,
List<Item> &values, bool ignore_errors,
- Table_triggers_list *triggers,
enum trg_event_type event)
{
bool result;
- result= (fill_record(thd, fields, values, ignore_errors) ||
+ Table_triggers_list *triggers= table->triggers;
+ result= (fill_record(thd, table, fields, values, ignore_errors) ||
(triggers && triggers->process_triggers(thd, event,
TRG_ACTION_BEFORE, TRUE)));
/*
Re-calculate virtual fields to cater for cases when base columns are
updated by the triggers.
*/
- if (!result && triggers)
+ if (!result && triggers && table)
{
- TABLE *table= 0;
List_iterator_fast<Item> f(fields);
Item *fld;
Item_field *item_field;
if (fields.elements)
{
fld= (Item_field*)f++;
- item_field= fld->filed_for_view_update();
- if (item_field && item_field->field &&
- (table= item_field->field->table) &&
- table->vfield)
+ item_field= fld->field_for_view_update();
+ if (item_field && item_field->field && table && table->vfield)
+ {
+ DBUG_ASSERT(table == item_field->field->table);
result= update_virtual_fields(thd, table, VCOL_UPDATE_FOR_WRITE);
+ }
}
}
return result;
}
-/*
- Fill field buffer with values from Field list
+/**
+ Fill the field buffer of a table with the values of an Item list
- SYNOPSIS
- fill_record()
- thd thread handler
- ptr pointer on pointer to record
- values list of fields
- ignore_errors TRUE if we should ignore errors
- use_value forces usage of value of the items instead of result
+ @param thd thread handler
+ @param table_arg the table that is being modified
+ @param ptr pointer on pointer to record of fields
+ @param values values to fill with
+ @param ignore_errors TRUE if we should ignore errors
+ @param use_value forces usage of value of the items instead of result
- NOTE
+ @details
fill_record() may set table->auto_increment_field_not_null and a
caller should make sure that it is reset after their last call to this
function.
- RETURN
- FALSE OK
- TRUE error occured
+ @return Status
+ @retval true An error occurred.
+ @retval false OK.
*/
bool
-fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors,
- bool use_value)
+fill_record(THD *thd, TABLE *table, Field **ptr, List<Item> &values,
+ bool ignore_errors, bool use_value)
{
List_iterator_fast<Item> v(values);
List<TABLE> tbl_list;
Item *value;
- TABLE *table= 0;
Field *field;
bool abort_on_warning_saved= thd->abort_on_warning;
DBUG_ENTER("fill_record");
@@ -9179,7 +8945,7 @@ fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors,
On INSERT or UPDATE fields are checked to be from the same table,
thus we safely can take table from the first field.
*/
- table= (*ptr)->table;
+ DBUG_ASSERT((*ptr)->table == table);
/*
Reset the table->auto_increment_field_not_null as it is valid for
@@ -9199,7 +8965,7 @@ fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors,
value->type() != Item::NULL_ITEM &&
table->s->table_category != TABLE_CATEGORY_TEMPORARY)
{
- push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN,
ER(ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN),
field->field_name, table->s->table_name.str);
@@ -9210,6 +8976,7 @@ fill_record(THD *thd, Field **ptr, List<Item> &values, bool ignore_errors,
else
if (value->save_in_field(field, 0) < 0)
goto err;
+ field->set_explicit_default(value);
}
/* Update virtual fields*/
thd->abort_on_warning= FALSE;
@@ -9227,36 +8994,34 @@ err:
/*
- Fill fields in array with values from the list of items and invoke
+ Fill fields in an array with values from the list of items and invoke
before triggers.
- SYNOPSIS
- fill_record_n_invoke_before_triggers()
- thd thread context
- ptr NULL-ended array of fields to be filled
- values values to fill with
- ignore_errors TRUE if we should ignore errors
- triggers object holding list of triggers to be invoked
- event event type for triggers to be invoked
+ @param thd thread context
+ @param table the table that is being modified
+ @param ptr the fields to be filled
+ @param values values to fill with
+ @param ignore_errors TRUE if we should ignore errors
+ @param event event type for triggers to be invoked
- NOTE
+ @detail
This function assumes that fields which values will be set and triggers
to be invoked belong to the same table, and that TABLE::record[0] and
record[1] buffers correspond to new and old versions of row respectively.
- RETURN
- FALSE OK
- TRUE error occured
+ @return Status
+ @retval true An error occurred.
+ @retval false OK.
*/
bool
-fill_record_n_invoke_before_triggers(THD *thd, Field **ptr,
+fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Field **ptr,
List<Item> &values, bool ignore_errors,
- Table_triggers_list *triggers,
enum trg_event_type event)
{
bool result;
- result= (fill_record(thd, ptr, values, ignore_errors, FALSE) ||
+ Table_triggers_list *triggers= table->triggers;
+ result= (fill_record(thd, table, ptr, values, ignore_errors, FALSE) ||
(triggers && triggers->process_triggers(thd, event,
TRG_ACTION_BEFORE, TRUE)));
/*
@@ -9265,7 +9030,7 @@ fill_record_n_invoke_before_triggers(THD *thd, Field **ptr,
*/
if (!result && triggers && *ptr)
{
- TABLE *table= (*ptr)->table;
+ DBUG_ASSERT(table == (*ptr)->table);
if (table->vfield)
result= update_virtual_fields(thd, table, VCOL_UPDATE_FOR_WRITE);
}
@@ -9298,7 +9063,7 @@ my_bool mysql_rm_tmp_tables(void)
/* Remove all SQLxxx tables from directory */
- for (idx=0 ; idx < (uint) dirp->number_off_files ; idx++)
+ for (idx=0 ; idx < (uint) dirp->number_of_files ; idx++)
{
file=dirp->dir_entry+idx;
@@ -9316,7 +9081,7 @@ my_bool mysql_rm_tmp_tables(void)
memcpy(filePathCopy, filePath, filePath_len - ext_len);
filePathCopy[filePath_len - ext_len]= 0;
init_tmp_table_share(thd, &share, "", 0, "", filePathCopy);
- if (!open_table_def(thd, &share, 0) &&
+ if (!open_table_def(thd, &share) &&
((handler_file= get_new_handler(&share, thd->mem_root,
share.db_type()))))
{
@@ -9336,7 +9101,6 @@ my_bool mysql_rm_tmp_tables(void)
my_dirend(dirp);
}
delete thd;
- my_pthread_setspecific_ptr(THR_THD, 0);
DBUG_RETURN(0);
}
@@ -9345,201 +9109,6 @@ my_bool mysql_rm_tmp_tables(void)
unireg support functions
*****************************************************************************/
-/*
- free all unused tables
-
- NOTE
- This is called by 'handle_manager' when one wants to periodicly flush
- all not used tables.
-*/
-
-void tdc_flush_unused_tables()
-{
- mysql_mutex_lock(&LOCK_open);
- while (unused_tables)
- free_cache_entry(unused_tables);
- mysql_mutex_unlock(&LOCK_open);
-}
-
-
-/**
- A callback to the server internals that is used to address
- special cases of the locking protocol.
- Invoked when acquiring an exclusive lock, for each thread that
- has a conflicting shared metadata lock.
-
- This function:
- - aborts waiting of the thread on a data lock, to make it notice
- the pending exclusive lock and back off.
- - if the thread is an INSERT DELAYED thread, sends it a KILL
- signal to terminate it.
-
- @note This function does not wait for the thread to give away its
- locks. Waiting is done outside for all threads at once.
-
- @param thd Current thread context
- @param in_use The thread to wake up
- @param needs_thr_lock_abort Indicates that to wake up thread
- this call needs to abort its waiting
- on table-level lock.
-
- @retval TRUE if the thread was woken up
- @retval FALSE otherwise.
-
- @note It is one of two places where border between MDL and the
- rest of the server is broken.
-*/
-
-bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
- bool needs_thr_lock_abort)
-{
- bool signalled= FALSE;
- if ((in_use->system_thread & SYSTEM_THREAD_DELAYED_INSERT) &&
- !in_use->killed)
- {
- in_use->killed= KILL_SYSTEM_THREAD;
- mysql_mutex_lock(&in_use->mysys_var->mutex);
- if (in_use->mysys_var->current_cond)
- {
- mysql_mutex_lock(in_use->mysys_var->current_mutex);
- mysql_cond_broadcast(in_use->mysys_var->current_cond);
- mysql_mutex_unlock(in_use->mysys_var->current_mutex);
- }
- mysql_mutex_unlock(&in_use->mysys_var->mutex);
- signalled= TRUE;
- }
-
- if (needs_thr_lock_abort)
- {
- mysql_mutex_lock(&in_use->LOCK_thd_data);
- for (TABLE *thd_table= in_use->open_tables;
- thd_table ;
- thd_table= thd_table->next)
- {
- /*
- Check for TABLE::needs_reopen() is needed since in some places we call
- handler::close() for table instance (and set TABLE::db_stat to 0)
- and do not remove such instances from the THD::open_tables
- for some time, during which other thread can see those instances
- (e.g. see partitioning code).
- */
- if (!thd_table->needs_reopen())
- signalled|= mysql_lock_abort_for_thread(thd, thd_table);
- }
- mysql_mutex_unlock(&in_use->LOCK_thd_data);
- }
- return signalled;
-}
-
-
-/**
- Remove all or some (depending on parameter) instances of TABLE and
- TABLE_SHARE from the table definition cache.
-
- @param thd Thread context
- @param remove_type Type of removal:
- TDC_RT_REMOVE_ALL - remove all TABLE instances and
- TABLE_SHARE instance. There
- should be no used TABLE objects
- and caller should have exclusive
- metadata lock on the table.
- TDC_RT_REMOVE_NOT_OWN - remove all TABLE instances
- except those that belong to
- this thread. There should be
- no TABLE objects used by other
- threads and caller should have
- exclusive metadata lock on the
- table.
- TDC_RT_REMOVE_UNUSED - remove all unused TABLE
- instances (if there are no
- used instances will also
- remove TABLE_SHARE).
- @param db Name of database
- @param table_name Name of table
- @param has_lock If TRUE, LOCK_open is already acquired
-
- @note It assumes that table instances are already not used by any
- (other) thread (this should be achieved by using meta-data locks).
-*/
-
-void tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type,
- const char *db, const char *table_name,
- bool has_lock)
-{
- char key[MAX_DBKEY_LENGTH];
- uint key_length;
- TABLE *table;
- TABLE_SHARE *share;
- DBUG_ENTER("tdc_remove_table");
- DBUG_PRINT("enter",("name: %s remove_type: %d", table_name, remove_type));
-
- if (! has_lock)
- mysql_mutex_lock(&LOCK_open);
- else
- {
- mysql_mutex_assert_owner(&LOCK_open);
- }
-
- DBUG_ASSERT(remove_type == TDC_RT_REMOVE_UNUSED ||
- thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, table_name,
- MDL_EXCLUSIVE));
-
- key_length= create_table_def_key(key, db, table_name);
-
- if ((share= (TABLE_SHARE*) my_hash_search(&table_def_cache,(uchar*) key,
- key_length)))
- {
- if (share->ref_count)
- {
- I_P_List_iterator<TABLE, TABLE_share> it(share->free_tables);
-#ifndef DBUG_OFF
- if (remove_type == TDC_RT_REMOVE_ALL)
- {
- DBUG_ASSERT(share->used_tables.is_empty());
- }
- else if (remove_type == TDC_RT_REMOVE_NOT_OWN ||
- remove_type == TDC_RT_REMOVE_NOT_OWN_AND_MARK_NOT_USABLE)
- {
- I_P_List_iterator<TABLE, TABLE_share> it2(share->used_tables);
- while ((table= it2++))
- if (table->in_use != thd)
- {
- DBUG_ASSERT(0);
- }
- }
-#endif
- /*
- Mark share to ensure that it gets automatically deleted once
- it is no longer referenced.
-
- Note that code in TABLE_SHARE::wait_for_old_version() assumes
- that marking share as old and removal of its unused tables
- and of the share itself from TDC happens atomically under
- protection of LOCK_open, or, putting it another way, that
- TDC does not contain old shares which don't have any tables
- used.
- */
- if (remove_type == TDC_RT_REMOVE_NOT_OWN)
- share->remove_from_cache_at_close();
- else
- {
- /* Ensure that no can open the table while it's used */
- share->protect_against_usage();
- }
-
- while ((table= it++))
- free_cache_entry(table);
- }
- else
- (void) my_hash_delete(&table_def_cache, (uchar*) share);
- }
-
- if (! has_lock)
- mysql_mutex_unlock(&LOCK_open);
- DBUG_VOID_RETURN;
-}
-
-
int setup_ftfuncs(SELECT_LEX *select_lex)
{
List_iterator<Item_func_match> li(*(select_lex->ftfunc_list)),
@@ -9677,6 +9246,12 @@ bool is_equal(const LEX_STRING *a, const LEX_STRING *b)
must call close_system_tables() to close systems tables opened
with this call.
+ NOTES
+ In some situations we use this function to open system tables for
+ writing. It happens, for examples, with statistical tables when
+ they are updated by an ANALYZE command. In these cases we should
+ guarantee that system tables will not be deadlocked.
+
RETURN
FALSE Success
TRUE Error
@@ -9700,6 +9275,7 @@ open_system_tables_for_read(THD *thd, TABLE_LIST *table_list,
*/
lex->reset_n_backup_query_tables_list(&query_tables_list_backup);
thd->reset_n_backup_open_tables_state(backup);
+ thd->lex->sql_command= SQLCOM_SELECT;
if (open_and_lock_tables(thd, table_list, FALSE,
MYSQL_OPEN_IGNORE_FLUSH |
@@ -9829,12 +9405,7 @@ open_log_table(THD *thd, TABLE_LIST *one_table, Open_tables_backup *backup)
DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_LOG);
/* Make sure all columns get assigned to a default value */
table->use_all_columns();
- table->no_replicate= 1;
- /*
- Don't set automatic timestamps as we may want to use time of logging,
- not from query start
- */
- table->timestamp_field_type= TIMESTAMP_NO_AUTO_SET;
+ DBUG_ASSERT(table->no_replicate);
}
else
thd->restore_backup_open_tables_state(backup);
@@ -9890,6 +9461,7 @@ int dynamic_column_error_message(enum_dyncol_func_result rc)
switch (rc) {
case ER_DYNCOL_YES:
case ER_DYNCOL_OK:
+ case ER_DYNCOL_TRUNCATED:
break; // it is not an error
case ER_DYNCOL_FORMAT:
my_error(ER_DYN_COL_WRONG_FORMAT, MYF(0));