diff options
author | Nirbhay Choubey <nirbhay@mariadb.com> | 2016-06-10 16:19:59 -0400 |
---|---|---|
committer | Nirbhay Choubey <nirbhay@mariadb.com> | 2016-06-10 18:39:43 -0400 |
commit | 7305be2f7e724e5e62961606794beab199d79045 (patch) | |
tree | 403bd132ee82a16946e3208f5d535de6e5945b80 /sql | |
parent | 547511153fb1f59688752aa5524ae411b5960c92 (diff) | |
download | mariadb-git-7305be2f7e724e5e62961606794beab199d79045.tar.gz |
MDEV-5535: Cannot reopen temporary table
mysqld maintains a list of TABLE objects for all temporary
tables created within a session in THD. Here each table is
represented by a TABLE object.
A query referencing a particular temporary table for more
than once, however, failed with ER_CANT_REOPEN_TABLE error
because a TABLE_SHARE was allocate together with the TABLE,
so temporary tables always had only one TABLE per TABLE_SHARE.
This patch lift this restriction by separating TABLE and
TABLE_SHARE objects and storing TABLE_SHAREs for temporary
tables in a list in THD, and TABLEs in a list within their
respective TABLE_SHAREs.
Diffstat (limited to 'sql')
-rw-r--r-- | sql/CMakeLists.txt | 4 | ||||
-rw-r--r-- | sql/log_event.cc | 2 | ||||
-rw-r--r-- | sql/rpl_rli.cc | 51 | ||||
-rw-r--r-- | sql/rpl_rli.h | 2 | ||||
-rw-r--r-- | sql/slave.cc | 7 | ||||
-rw-r--r-- | sql/sp_head.cc | 2 | ||||
-rw-r--r-- | sql/sql_admin.cc | 8 | ||||
-rw-r--r-- | sql/sql_alter.cc | 6 | ||||
-rw-r--r-- | sql/sql_base.cc | 955 | ||||
-rw-r--r-- | sql/sql_base.h | 26 | ||||
-rw-r--r-- | sql/sql_cache.cc | 44 | ||||
-rw-r--r-- | sql/sql_class.cc | 28 | ||||
-rw-r--r-- | sql/sql_class.h | 174 | ||||
-rw-r--r-- | sql/sql_db.cc | 5 | ||||
-rw-r--r-- | sql/sql_handler.cc | 14 | ||||
-rw-r--r-- | sql/sql_insert.cc | 9 | ||||
-rw-r--r-- | sql/sql_parse.cc | 24 | ||||
-rw-r--r-- | sql/sql_partition_admin.cc | 14 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 8 | ||||
-rw-r--r-- | sql/sql_show.cc | 3 | ||||
-rw-r--r-- | sql/sql_table.cc | 128 | ||||
-rw-r--r-- | sql/sql_test.cc | 2 | ||||
-rw-r--r-- | sql/sql_trigger.cc | 4 | ||||
-rw-r--r-- | sql/sql_view.cc | 4 | ||||
-rw-r--r-- | sql/sys_vars.cc | 5 | ||||
-rw-r--r-- | sql/table.cc | 2 | ||||
-rw-r--r-- | sql/table.h | 25 | ||||
-rw-r--r-- | sql/table_cache.cc | 4 | ||||
-rw-r--r-- | sql/table_cache.h | 3 | ||||
-rw-r--r-- | sql/temporary_tables.cc | 1499 | ||||
-rw-r--r-- | sql/wsrep_applier.cc | 12 | ||||
-rw-r--r-- | sql/wsrep_mysqld.cc | 23 | ||||
-rw-r--r-- | sql/wsrep_thd.cc | 9 |
33 files changed, 1890 insertions, 1216 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 062e59bee5b..089d793b2b0 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -1,5 +1,5 @@ # Copyright (c) 2006, 2014, Oracle and/or its affiliates. -# Copyright (c) 2010, 2015, MariaDB +# Copyright (c) 2010, 2016, MariaDB Corporation # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -141,7 +141,7 @@ SET (SQL_SOURCE item_windowfunc.cc sql_window.cc sql_cte.cc sql_cte.h ${WSREP_SOURCES} - table_cache.cc encryption.cc + table_cache.cc encryption.cc temporary_tables.cc ${CMAKE_CURRENT_BINARY_DIR}/sql_builtin.cc ${GEN_SOURCES} ${GEN_DIGEST_SOURCES} diff --git a/sql/log_event.cc b/sql/log_event.cc index c966d36b732..f7e718fccd0 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -6159,7 +6159,7 @@ int Load_log_event::do_apply_event(NET* net, rpl_group_info *rgi, update it inside mysql_load(). */ List<Item> tmp_list; - if (open_temporary_tables(thd, &tables) || + if (thd->open_temporary_tables(&tables) || mysql_load(thd, &ex, &tables, field_list, tmp_list, tmp_list, handle_dup, ignore, net != 0)) thd->is_slave_error= 1; diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index f9d0910a2a7..f8f06d613f4 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -1,5 +1,6 @@ /* Copyright (c) 2006, 2013, Oracle and/or its affiliates. Copyright (c) 2010, 2013, Monty Program Ab + Copyright (c) 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,8 +53,8 @@ Relay_log_info::Relay_log_info(bool is_slave_recovery) no_storage(FALSE), replicate_same_server_id(::replicate_same_server_id), info_fd(-1), cur_log_fd(-1), relay_log(&sync_relaylog_period), sync_counter(0), is_relay_log_recovery(is_slave_recovery), - save_temporary_tables(0), mi(0), - inuse_relaylog_list(0), last_inuse_relaylog(0), + save_temporary_tables(0), + mi(0), inuse_relaylog_list(0), last_inuse_relaylog(0), cur_log_old_open_count(0), group_relay_log_pos(0), event_relay_log_pos(0), #if HAVE_valgrind @@ -1062,24 +1063,53 @@ void Relay_log_info::inc_group_relay_log_pos(ulonglong log_pos, void Relay_log_info::close_temporary_tables() { - TABLE *table,*next; DBUG_ENTER("Relay_log_info::close_temporary_tables"); - for (table=save_temporary_tables ; table ; table=next) + TMP_TABLE_SHARE *share; + TABLE *table; + + if (!save_temporary_tables) { - next=table->next; + /* There are no temporary tables. */ + DBUG_VOID_RETURN; + } + + while ((share= save_temporary_tables->pop_front())) + { + /* + Iterate over the list of tables for this TABLE_SHARE and close them. + */ + while ((table= share->all_tmp_tables.pop_front())) + { + DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'", + table->s->db.str, table->s->table_name.str)); + + /* Reset in_use as the table may have been created by another thd */ + table->in_use= 0; + /* + Lets not free TABLE_SHARE here as there could be multiple TABLEs opened + for the same table (TABLE_SHARE). + */ + closefrm(table); + my_free(table); + } - /* Reset in_use as the table may have been created by another thd */ - table->in_use=0; /* Don't ask for disk deletion. For now, anyway they will be deleted when slave restarts, but it is a better intention to not delete them. */ - DBUG_PRINT("info", ("table: 0x%lx", (long) table)); - close_temporary(table, 1, 0); + + free_table_share(share); + my_free(share); } - save_temporary_tables= 0; + + /* By now, there mustn't be any elements left in the list. */ + DBUG_ASSERT(save_temporary_tables->is_empty()); + + my_free(save_temporary_tables); + save_temporary_tables= NULL; slave_open_temp_tables= 0; + DBUG_VOID_RETURN; } @@ -1756,6 +1786,7 @@ void rpl_group_info::cleanup_context(THD *thd, bool error) } m_table_map.clear_tables(); slave_close_thread_tables(thd); + if (error) { thd->mdl_context.release_transactional_locks(); diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index 3eaee90d0f6..c5b495cbc30 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -148,7 +148,7 @@ public: Protected by data_lock. */ - TABLE *save_temporary_tables; + All_tmp_tables_list *save_temporary_tables; /* standard lock acquisition order to avoid deadlocks: diff --git a/sql/slave.cc b/sql/slave.cc index 3fd4b2a19c3..f02ef5e036b 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2015, MariaDB + Copyright (c) 2008, 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -4895,8 +4895,11 @@ err_during_init: /* TODO: see if we can do this conditionally in next_event() instead to avoid unneeded position re-init + + We only reset THD::temporary_tables to 0 here and not free it, as this + could be used by slave through Relay_log_info::save_temporary_tables. */ - thd->temporary_tables = 0; // remove tempation from destructor to close them + thd->temporary_tables= 0; rli->sql_driver_thd= 0; thd->rgi_fake= thd->rgi_slave= NULL; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index d58b51afc5e..06a2022d914 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3077,7 +3077,7 @@ int sp_instr::exec_open_and_lock_tables(THD *thd, TABLE_LIST *tables) Check whenever we have access to tables for this statement and open and lock them before executing instructions core function. */ - if (open_temporary_tables(thd, tables) || + if (thd->open_temporary_tables(tables) || check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE) || open_and_lock_tables(thd, tables, TRUE, 0)) result= -1; diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index 5c400e44bc7..4414f11ecd4 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -54,7 +54,7 @@ static bool admin_recreate_table(THD *thd, TABLE_LIST *table_list) DEBUG_SYNC(thd, "ha_admin_try_alter"); tmp_disable_binlog(thd); // binlogging is done by caller if wanted - result_code= (open_temporary_tables(thd, table_list) || + result_code= (thd->open_temporary_tables(table_list) || mysql_recreate_table(thd, table_list, false)); reenable_binlog(thd); /* @@ -445,7 +445,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, da->push_warning_info(&tmp_wi); - open_error= (open_temporary_tables(thd, table) || + open_error= (thd->open_temporary_tables(table) || open_and_lock_tables(thd, table, TRUE, 0)); da->pop_warning_info(); @@ -460,7 +460,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, mode. It does make sense for the user to see such errors. */ - open_error= (open_temporary_tables(thd, table) || + open_error= (thd->open_temporary_tables(table) || open_and_lock_tables(thd, table, TRUE, 0)); } thd->prepare_derived_at_open= FALSE; @@ -952,7 +952,7 @@ send_result_message: table->mdl_request.ticket= NULL; DEBUG_SYNC(thd, "ha_admin_open_ltable"); table->mdl_request.set_type(MDL_SHARED_WRITE); - if (!open_temporary_tables(thd, table) && + if (!thd->open_temporary_tables(table) && (table->table= open_ltable(thd, table, lock_type, 0))) { uint save_flags; diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc index bff45e089a4..737d3cca729 100644 --- a/sql/sql_alter.cc +++ b/sql/sql_alter.cc @@ -1,4 +1,5 @@ /* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,7 +17,6 @@ #include "sql_parse.h" // check_access #include "sql_table.h" // mysql_alter_table, // mysql_exchange_partition -#include "sql_base.h" // open_temporary_tables #include "sql_alter.h" #include "wsrep_mysqld.h" @@ -305,10 +305,8 @@ bool Sql_cmd_alter_table::execute(THD *thd) thd->enable_slow_log= opt_log_slow_admin_statements; #ifdef WITH_WSREP - TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl); - if ((!thd->is_current_stmt_binlog_format_row() || - !find_temporary_table(thd, first_table))) + !thd->find_temporary_table(first_table))) { WSREP_TO_ISOLATION_BEGIN(((lex->name.str) ? select_lex->db : NULL), ((lex->name.str) ? lex->name.str : NULL), diff --git a/sql/sql_base.cc b/sql/sql_base.cc index e6f8b291778..e14fb1b4e5f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -172,47 +172,13 @@ static bool auto_repair_table(THD *thd, TABLE_LIST *table_list); /** - Create a table cache/table definition cache key - - @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. - - @note - The table cache_key is created from: - db_name + \0 - table_name + \0 - - 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. -*/ - -uint create_tmp_table_def_key(THD *thd, char *key, - const char *db, const char *table_name) -{ - 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; -} - - -/** 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 + key in a buffer provided 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 @@ -304,7 +270,7 @@ static my_bool list_open_tables_callback(TDC_element *element, (*arg->start_list)->in_use= 0; mysql_mutex_lock(&element->LOCK_table_share); - TDC_element::All_share_tables_list::Iterator it(element->all_tables); + All_share_tables_list::Iterator it(element->all_tables); TABLE *table; while ((table= it++)) if (table->in_use) @@ -602,96 +568,6 @@ bool close_cached_connection_tables(THD *thd, LEX_STRING *connection) } -/** - Mark all temporary tables which were used by the current statement or - substatement as free for reuse, but only if the query_id can be cleared. - - @param thd thread context - - @remark For temp tables associated with a open SQL HANDLER the query_id - is not reset until the HANDLER is closed. -*/ - -static void mark_temp_tables_as_free_for_reuse(THD *thd) -{ - rpl_group_info *rgi_slave; - DBUG_ENTER("mark_temp_tables_as_free_for_reuse"); - - if (thd->query_id == 0) - { - /* Thread has not executed any statement and has not used any tmp tables */ - DBUG_VOID_RETURN; - } - - rgi_slave=thd->rgi_slave; - if ((!rgi_slave && thd->temporary_tables) || - (rgi_slave && unlikely(rgi_slave->rli->save_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(); - if (rgi_slave) - { - /* - Temporary tables are shared with other by sql execution threads. - As a safety messure, clear the pointer to the common area. - */ - thd->temporary_tables= 0; - } - } - DBUG_VOID_RETURN; -} - - -/** - Reset a single temporary table. - Effectively this "closes" one temporary table, - in a session. - - @param table Temporary table. -*/ - -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; - table->file->ha_reset(); - - /* Detach temporary MERGE children from temporary parent. */ - DBUG_ASSERT(table->file); - table->file->extra(HA_EXTRA_DETACH_CHILDREN); - - /* - Reset temporary table lock type to it's default value (TL_WRITE). - - Statements such as INSERT INTO .. SELECT FROM tmp, CREATE TABLE - .. SELECT FROM tmp and UPDATE may under some circumstances modify - the lock type of the tables participating in the statement. This - isn't a problem for non-temporary tables since their lock type is - reset at every open, but the same does not occur for temporary - tables for historical reasons. - - Furthermore, the lock type of temporary tables is not really that - important because they can only be used by one query at a time and - not even twice in a query -- a temporary table is represented by - only one TABLE object. Nonetheless, it's safer from a maintenance - point of view to reset the lock type of this singleton TABLE object - as to not cause problems when the table is reused. - - Even under LOCK TABLES mode its okay to reset the lock type as - LOCK TABLES is allowed (but ignored) for a temporary table. - */ - table->reginfo.lock_type= TL_WRITE; - DBUG_VOID_RETURN; -} - - /* Mark all tables in the list which were used by current substatement as free for reuse. @@ -894,7 +770,7 @@ void close_thread_tables(THD *thd) /* Mark all temporary tables used by this statement as free for reuse. */ - mark_temp_tables_as_free_for_reuse(thd); + thd->mark_tmp_tables_as_free_for_reuse(); if (thd->locked_tables_mode) { @@ -1008,196 +884,6 @@ void close_thread_table(THD *thd, TABLE **table_ptr) } -/* close_temporary_tables' internal, 4 is due to uint4korr definition */ -static inline uint tmpkeyval(THD *thd, TABLE *table) -{ - return uint4korr(table->s->table_cache_key.str + table->s->table_cache_key.length - 4); -} - - -/* - 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) -{ - DBUG_ENTER("close_temporary_tables"); - TABLE *table; - TABLE *next= NULL; - TABLE *prev_table; - /* Assume thd->variables.option_bits has OPTION_QUOTE_SHOW_CREATE */ - bool was_quote_show= TRUE; - bool error= 0; - - 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. - This is necessary when close_temporary_tables() is called as part - of execution of BINLOG statement (e.g. for format description event). - */ - mysql_ha_rm_temporary_tables(thd); - if (!mysql_bin_log.is_open()) - { - TABLE *tmp_next; - for (TABLE *t= thd->temporary_tables; t; t= tmp_next) - { - tmp_next= t->next; - mysql_lock_remove(thd, thd->lock, t); - close_temporary(t, 1, 1); - } - thd->temporary_tables= 0; - DBUG_RETURN(FALSE); - } - - /* Better add "if exists", in case a RESET MASTER has been done */ - const char stub[]= "DROP /*!40005 TEMPORARY */ TABLE IF EXISTS "; - char buf[FN_REFLEN]; - String s_query(buf, sizeof(buf), system_charset_info); - bool found_user_tables= FALSE; - - s_query.copy(stub, sizeof(stub)-1, system_charset_info); - - /* - Insertion sort of temp tables by pseudo_thread_id to build ordered list - of sublists of equal pseudo_thread_id - */ - - for (prev_table= thd->temporary_tables, table= prev_table->next; - table; - prev_table= table, table= table->next) - { - TABLE *prev_sorted /* same as for prev_table */, *sorted; - if (is_user_table(table)) - { - if (!found_user_tables) - found_user_tables= true; - for (prev_sorted= NULL, sorted= thd->temporary_tables; sorted != table; - prev_sorted= sorted, sorted= sorted->next) - { - if (!is_user_table(sorted) || - tmpkeyval(thd, sorted) > tmpkeyval(thd, table)) - { - /* move into the sorted part of the list from the unsorted */ - prev_table->next= table->next; - table->next= sorted; - if (prev_sorted) - { - prev_sorted->next= table; - } - else - { - thd->temporary_tables= table; - } - table= prev_table; - break; - } - } - } - } - - /* We always quote db,table names though it is slight overkill */ - if (found_user_tables && - !(was_quote_show= MY_TEST(thd->variables.option_bits & - OPTION_QUOTE_SHOW_CREATE))) - { - thd->variables.option_bits |= OPTION_QUOTE_SHOW_CREATE; - } - - /* scan sorted tmps to generate sequence of DROP */ - for (table= thd->temporary_tables; table; table= next) - { - if (is_user_table(table)) - { - bool save_thread_specific_used= thd->thread_specific_used; - my_thread_id save_pseudo_thread_id= thd->variables.pseudo_thread_id; - char db_buf[FN_REFLEN]; - String db(db_buf, sizeof(db_buf), system_charset_info); - - /* Set pseudo_thread_id to be that of the processed table */ - thd->variables.pseudo_thread_id= tmpkeyval(thd, table); - - db.copy(table->s->db.str, table->s->db.length, system_charset_info); - /* Reset s_query() if changed by previous loop */ - s_query.length(sizeof(stub)-1); - - /* Loop forward through all tables that belong to a common database - within the sublist of common pseudo_thread_id to create single - DROP query - */ - for (; - table && is_user_table(table) && - (ulong) tmpkeyval(thd, table) == - (ulong) thd->variables.pseudo_thread_id && - table->s->db.length == db.length() && - memcmp(table->s->db.str, db.ptr(), db.length()) == 0; - table= next) - { - /* - We are going to add ` around the table names and possible more - due to special characters - */ - append_identifier(thd, &s_query, table->s->table_name.str, - strlen(table->s->table_name.str)); - s_query.append(','); - next= table->next; - mysql_lock_remove(thd, thd->lock, table); - close_temporary(table, 1, 1); - } - thd->clear_error(); - CHARSET_INFO *cs_save= thd->variables.character_set_client; - thd->variables.character_set_client= system_charset_info; - thd->thread_specific_used= TRUE; - Query_log_event qinfo(thd, s_query.ptr(), - s_query.length() - 1 /* to remove trailing ',' */, - FALSE, TRUE, FALSE, 0); - qinfo.db= db.ptr(); - qinfo.db_len= db.length(); - thd->variables.character_set_client= cs_save; - - thd->get_stmt_da()->set_overwrite_status(true); - if ((error= (mysql_bin_log.write(&qinfo) || error))) - { - /* - If we're here following THD::cleanup, thence the connection - has been closed already. So lets print a message to the - error log instead of pushing yet another error into the - stmt_da. - - Also, we keep the error flag so that we propagate the error - up in the stack. This way, if we're the SQL thread we notice - that close_temporary_tables failed. (Actually, the SQL - thread only calls close_temporary_tables while applying old - Start_log_event_v3 events.) - */ - sql_print_error("Failed to write the DROP statement for " - "temporary tables to binary log"); - } - 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; - } - else - { - next= table->next; - close_temporary(table, 1, 1); - } - } - if (!was_quote_show) - thd->variables.option_bits&= ~OPTION_QUOTE_SHOW_CREATE; /* restore option */ - thd->temporary_tables=0; - - DBUG_RETURN(error); -} - /* Find table in list. @@ -1470,291 +1156,6 @@ void update_non_unique_table_error(TABLE_LIST *update, /** - Find temporary table specified by database and table names in the - THD::temporary_tables list. - - @return TABLE instance if a temporary table has been found; NULL otherwise. -*/ - -TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name) -{ - 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); -} - - -/** - Find a temporary table specified by TABLE_LIST instance in the - THD::temporary_tables list. - - @return TABLE instance if a temporary table has been found; NULL otherwise. -*/ - -TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl) -{ - const char *tmp_key; - char key[MAX_DBKEY_LENGTH]; - uint 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. - */ - MYSQL_UNBIND_TABLE(table->file); - MYSQL_REBIND_TABLE(table->file); - } -#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); -} - - -/** - Find a temporary table specified by a key in the THD::temporary_tables list. - - @return TABLE instance if a temporary table has been found; NULL otherwise. -*/ - -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)) - { - result= table; - break; - } - } - thd->unlock_temporary_tables(); - return result; -} - - -/** - Drop a temporary table. - - Try to locate the table in the list of thd->temporary_tables. - If the table is found: - - if the table is being used by some outer statement, fail. - - if the table is locked with LOCK TABLES or by prelocking, - unlock it and remove it from the list of locked tables - (THD::lock). Currently only transactional temporary tables - are locked. - - Close the temporary table, remove its .FRM - - remove the table from the list of temporary tables - - This function is used to drop user temporary tables, as well as - internal tables created in CREATE TEMPORARY TABLE ... SELECT - or ALTER TABLE. Even though part of the work done by this function - is redundant when the table is internal, as long as we - link both internal and user temporary tables into the same - thd->temporary_tables list, it's impossible to tell here whether - we're dealing with an internal or a user temporary table. - - @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 is in use by a outer query -*/ - -int drop_temporary_table(THD *thd, TABLE *table, bool *is_trans) -{ - DBUG_ENTER("drop_temporary_table"); - DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'", - 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); - } - - *is_trans= table->file->has_transactions(); - - /* - If LOCK TABLES list is not empty and contains this table, - unlock the table and remove the table from this list. - */ - mysql_lock_remove(thd, thd->lock, table); - close_temporary_table(thd, table, 1, 1); - DBUG_RETURN(0); -} - - -/* - unlink from thd->temporary tables and close temporary table -*/ - -void close_temporary_table(THD *thd, TABLE *table, - bool free_share, bool delete_table) -{ - DBUG_ENTER("close_temporary_table"); - DBUG_PRINT("tmptable", ("closing table: '%s'.'%s' 0x%lx alias: '%s'", - 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; - if (table->prev->next) - table->next->prev= table->prev; - } - else - { - /* removing the item from the list */ - DBUG_ASSERT(table == thd->temporary_tables); - /* - slave must reset its temporary list pointer to zero to exclude - passing non-zero value to end_slave via rli->save_temporary_tables - when no temp tables opened, see an invariant below. - */ - thd->temporary_tables= table->next; - if (thd->temporary_tables) - table->next->prev= 0; - } - if (thd->rgi_slave) - { - /* natural invariant of temporary_tables */ - DBUG_ASSERT(slave_open_temp_tables || !thd->temporary_tables); - thread_safe_decrement32(&slave_open_temp_tables); - table->in_use= 0; // No statistics - } - thd->unlock_temporary_tables(); - close_temporary(table, free_share, delete_table); - DBUG_VOID_RETURN; -} - - -/* - Close and delete a temporary table - - NOTE - This dosn't unlink table from thd->temporary - If this is needed, use close_temporary_table() -*/ - -void close_temporary(TABLE *table, bool free_share, bool delete_table) -{ - handlerton *table_type= table->s->db_type(); - DBUG_ENTER("close_temporary"); - DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'", - table->s->db.str, table->s->table_name.str)); - - closefrm(table); - if (delete_table) - rm_temporary_table(table_type, table->s->path.str); - if (free_share) - { - free_table_share(table->s); - my_free(table); - } - DBUG_VOID_RETURN; -} - - -/* - Used by ALTER TABLE when the table is a temporary one. It changes something - only if the ALTER contained a RENAME clause (otherwise, table_name is the old - name). - Prepares a table cache key, which is the concatenation of db, table_name and - thd->slave_proxy_id, separated by '\0'. -*/ - -bool rename_temporary_table(THD* thd, TABLE *table, const char *db, - const char *table_name) -{ - char *key; - uint key_length; - TABLE_SHARE *share= table->s; - DBUG_ENTER("rename_temporary_table"); - - if (!(key=(char*) alloc_root(&share->mem_root, MAX_DBKEY_LENGTH))) - DBUG_RETURN(1); /* purecov: inspected */ - - key_length= create_tmp_table_def_key(thd, key, db, table_name); - share->set_table_cache_key(key, key_length); - DBUG_RETURN(0); -} - - -/** Force all other threads to stop using the table by upgrading metadata lock on it and remove unused TABLE instances from cache. @@ -1819,7 +1220,7 @@ void drop_open_table(THD *thd, TABLE *table, const char *db_name, { DBUG_ENTER("drop_open_table"); if (table->s->tmp_table) - close_temporary_table(thd, table, 1, 1); + thd->drop_temporary_table(table, NULL, true); else { DBUG_ASSERT(table == thd->open_tables); @@ -3935,7 +3336,7 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, 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 till 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. @@ -3951,7 +3352,7 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, 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); + error= thd->open_temporary_table(tables); if (!error && !tables->table) error= open_table(thd, tables, ot_ctx); @@ -3970,7 +3371,8 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, Repair_mrg_table_error_handler repair_mrg_table_handler; thd->push_internal_handler(&repair_mrg_table_handler); - error= open_temporary_table(thd, tables); + error= thd->open_temporary_table(tables); + if (!error && !tables->table) error= open_table(thd, tables, ot_ctx); @@ -3986,7 +3388,7 @@ open_and_process_table(THD *thd, LEX *lex, TABLE_LIST *tables, 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); + error= thd->open_temporary_table(tables); } if (!error && !tables->table) @@ -4158,6 +3560,7 @@ end: new locks, so use open_tables_check_upgradable_mdl() instead. @param thd Thread context. + @param options DDL options. @param tables_start Start of list of tables on which upgradable locks should be acquired. @param tables_end End of list of tables. @@ -4360,6 +3763,7 @@ open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start, Open all tables in list @param[in] thd Thread context. + @param[in] options DDL options. @param[in,out] start List of tables to be open (it can be adjusted for statement that uses tables only implicitly, e.g. for "SELECT f1()"). @@ -4533,7 +3937,7 @@ restart: goto error; /* Re-open temporary tables after close_tables_for_reopen(). */ - if (open_temporary_tables(thd, *start)) + if (thd->open_temporary_tables(*start)) goto error; error= FALSE; @@ -4595,7 +3999,7 @@ restart: goto error; /* Re-open temporary tables after close_tables_for_reopen(). */ - if (open_temporary_tables(thd, *start)) + if (thd->open_temporary_tables(*start)) goto error; error= FALSE; @@ -5050,7 +4454,7 @@ 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*/ + /* Ignore temporary tables as they have already been opened. */ if (table_list->table) DBUG_RETURN(table_list->table); @@ -5137,8 +4541,9 @@ end: Open all tables in list, locks them and optionally process derived tables. @param thd Thread context. + @param options DDL options. @param tables List of tables for open and locking. - @param derived If to handle derived tables. + @param derived Whether to handle derived tables. @param flags Bitmap of options to be used to open and lock tables (see open_tables() and mysql_lock_tables() for details). @@ -5533,176 +4938,6 @@ void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, } -/** - Open a single table without table caching and don't add it to - THD::open_tables. Depending on the 'add_to_temporary_tables_list' value, - 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 frm frm image - @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; - - when creating a temporary table with CREATE TEMPORARY TABLE. - - @return TABLE instance for opened table. - @retval NULL on error. -*/ - -TABLE *open_table_uncached(THD *thd, handlerton *hton, - LEX_CUSTRING *frm, - const char *path, const char *db, - const char *table_name, - 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; - 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->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); - } - - /* Create the cache_key for temporary tables */ - 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, - MYF(MY_WME)))) - DBUG_RETURN(0); /* purecov: inspected */ - - share= (TABLE_SHARE*) (tmp_table+1); - tmp_path= (char*) (share+1); - saved_cache_key= strmov(tmp_path, path)+1; - memcpy(saved_cache_key, cache_key, key_length); - - 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); - - /* - Use the frm image, if possible, open the file otherwise. - - The image might be unavailable in ALTER TABLE, when the discovering - engine took over the ownership (see TABLE::read_frm_image). - */ - int res= frm->str - ? share->init_from_binary_frm_image(thd, false, frm->str, frm->length) - : open_table_def(thd, share, GTS_TABLE | GTS_USE_DISCOVERY); - - if (res) - { - /* 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); - } - - 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) : 0, - READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD, - ha_open_options, - 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); - my_free(tmp_table); - DBUG_RETURN(0); - } - - 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->rgi_slave) - { - thread_safe_increment32(&slave_open_temp_tables); - } - thd->unlock_temporary_tables(); - } - tmp_table->pos_in_table_list= 0; - DBUG_PRINT("tmptable", ("opened table: '%s'.'%s' 0x%lx", tmp_table->s->db.str, - tmp_table->s->table_name.str, (long) tmp_table)); - DBUG_RETURN(tmp_table); -} - - -/** - Delete a temporary table. - - @param base Handlerton for table to be deleted. - @param path Path to the table to be deleted (i.e. path - to its .frm without an extension). - - @retval false - success. - @retval true - failure. -*/ - -bool rm_temporary_table(handlerton *base, const char *path) -{ - bool error=0; - handler *file; - char frm_path[FN_REFLEN + 1]; - DBUG_ENTER("rm_temporary_table"); - - strxnmov(frm_path, sizeof(frm_path) - 1, path, reg_ext, NullS); - if (mysql_file_delete(key_file_frm, frm_path, MYF(0))) - error=1; /* purecov: inspected */ - file= get_new_handler((TABLE_SHARE*) 0, current_thd->mem_root, base); - if (file && file->ha_delete_table(path)) - { - error=1; - sql_print_warning("Could not remove temporary table: '%s', error: %d", - path, my_errno); - } - delete file; - DBUG_RETURN(error); -} - - /***************************************************************************** * The following find_field_in_XXX procedures implement the core of the * name resolution functionality. The entry point to resolve a column name in a @@ -5771,164 +5006,6 @@ 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. diff --git a/sql/sql_base.h b/sql/sql_base.h index 040e2897582..8750e6989e1 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -59,8 +59,6 @@ enum find_item_error_report_type {REPORT_ALL_ERRORS, REPORT_EXCEPT_NOT_FOUND, IGNORE_ERRORS, REPORT_EXCEPT_NON_UNIQUE, IGNORE_EXCEPT_NON_UNIQUE}; -uint create_tmp_table_def_key(THD *thd, char *key, const char *db, - const char *table_name); uint get_table_def_key(const TABLE_LIST *table_list, const char **key); TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update, uint lock_flags); @@ -124,11 +122,6 @@ bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx); bool get_key_map_from_key_list(key_map *map, TABLE *table, List<String> *index_list); -TABLE *open_table_uncached(THD *thd, handlerton *hton, - LEX_CUSTRING *frm, const char *path, - const char *db, const char *table_name, - bool add_to_temporary_tables_list, - bool open_in_engine); TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name); TABLE *find_write_locked_table(TABLE *list, const char *db, const char *table_name); @@ -138,21 +131,12 @@ thr_lock_type read_lock_type_for_table(THD *thd, bool routine_modifies_data); my_bool mysql_rm_tmp_tables(void); -bool rm_temporary_table(handlerton *base, const char *path); void close_tables_for_reopen(THD *thd, TABLE_LIST **tables, const MDL_savepoint &start_of_statement_svp); TABLE_LIST *find_table_in_list(TABLE_LIST *table, TABLE_LIST *TABLE_LIST::*link, const char *db_name, const char *table_name); -TABLE *find_temporary_table(THD *thd, const char *db, const char *table_name); -bool find_and_use_temporary_table(THD *thd, const char *db, - const char *table_name, TABLE **out_table); -TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl); -bool find_and_use_temporary_table(THD *thd, const TABLE_LIST *tl, - TABLE **out_table); -TABLE *find_temporary_table(THD *thd, const char *table_key, - uint table_key_length); void close_thread_tables(THD *thd); void switch_to_nullable_trigger_fields(List<Item> &items, TABLE *); bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, @@ -272,17 +256,8 @@ bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags, bool lock_tables(THD *thd, TABLE_LIST *tables, uint counter, uint flags); int decide_logging_format(THD *thd, TABLE_LIST *tables); void close_thread_table(THD *thd, TABLE **table_ptr); -bool close_temporary_tables(THD *thd); TABLE_LIST *unique_table(THD *thd, TABLE_LIST *table, TABLE_LIST *table_list, bool check_alias); -int drop_temporary_table(THD *thd, TABLE *table, bool *is_trans); -void close_temporary_table(THD *thd, TABLE *table, bool free_share, - bool delete_table); -void close_temporary(TABLE *table, bool free_share, bool delete_table); -bool rename_temporary_table(THD* thd, TABLE *table, const char *new_db, - const char *table_name); -bool open_temporary_tables(THD *thd, TABLE_LIST *tl_list); -bool open_temporary_table(THD *thd, TABLE_LIST *tl); bool is_equal(const LEX_STRING *a, const LEX_STRING *b); class Open_tables_backup; @@ -311,7 +286,6 @@ bool tdc_open_view(THD *thd, TABLE_LIST *table_list, uint flags); TABLE *find_table_for_mdl_upgrade(THD *thd, const char *db, const char *table_name, bool no_error); -void mark_tmp_table_for_reuse(TABLE *table); int update_virtual_fields(THD *thd, TABLE *table, enum enum_vcol_update_mode vcol_update_mode= VCOL_UPDATE_FOR_READ); diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index 42b80d9b143..6d1a8efaf22 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1999,36 +1999,32 @@ lookup: for (; block_table != block_table_end; block_table++) { TABLE_LIST table_list; - TABLE *tmptable; + TMP_TABLE_SHARE *tmptable; Query_cache_table *table = block_table->parent; /* - Check that we have not temporary tables with same names of tables - of this query. If we have such tables, we will not send data from - query cache, because temporary tables hide real tables by which + Check that we do not have temporary tables with same names as that of + base tables from this query. If we have such tables, we will not send + data from query cache, because temporary tables hide real tables by which query in query cache was made. */ - for (tmptable= thd->temporary_tables; tmptable ; tmptable= tmptable->next) + if ((tmptable= + thd->find_tmp_table_share_w_base_key((char *) table->data(), + table->key_length()))) { - if (tmptable->s->table_cache_key.length - TMP_TABLE_KEY_EXTRA == - table->key_length() && - !memcmp(tmptable->s->table_cache_key.str, table->data(), - table->key_length())) - { - DBUG_PRINT("qcache", - ("Temporary table detected: '%s.%s'", - tmptable->s->db.str, tmptable->alias.c_ptr())); - unlock(); - /* - We should not store result of this query because it contain - temporary tables => assign following variable to make check - faster. - */ - thd->query_cache_is_applicable= 0; // Query can't be cached - thd->lex->safe_to_cache_query= 0; // For prepared statements - BLOCK_UNLOCK_RD(query_block); - DBUG_RETURN(-1); - } + DBUG_PRINT("qcache", + ("Temporary table detected: '%s.%s'", + tmptable->db.str, tmptable->table_name.str)); + unlock(); + /* + We should not store result of this query because it contain + temporary tables => assign following variable to make check + faster. + */ + thd->query_cache_is_applicable= 0; // Query can't be cached + thd->lex->safe_to_cache_query= 0; // For prepared statements + BLOCK_UNLOCK_RD(query_block); + DBUG_RETURN(-1); } bzero((char*) &table_list,sizeof(table_list)); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 4b6e4bce3c0..e7324de4ed2 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -37,7 +37,7 @@ #include "tztime.h" // MYSQL_TIME <-> my_time_t #include "sql_acl.h" // NO_ACCESS, // acl_getroot_no_password -#include "sql_base.h" // close_temporary_tables +#include "sql_base.h" #include "sql_handler.h" // mysql_ha_cleanup #include "rpl_rli.h" #include "rpl_filter.h" @@ -883,7 +883,8 @@ THD::THD(my_thread_id id, bool is_wsrep_applier) main_da(0, false, false), m_stmt_da(&main_da), tdc_hash_pins(0), - xid_hash_pins(0) + xid_hash_pins(0), + m_tmp_tables_locked(false) #ifdef WITH_WSREP , wsrep_applier(is_wsrep_applier), @@ -1586,7 +1587,7 @@ void THD::cleanup(void) locked_tables_list.unlock_locked_tables(this); delete_dynamic(&user_var_events); - close_temporary_tables(this); + close_temporary_tables(); transaction.xid_state.xa_state= XA_NOTR; trans_rollback(this); @@ -4319,7 +4320,8 @@ void THD::restore_backup_open_tables_state(Open_tables_backup *backup) Before we will throw away current open tables state we want to be sure that it was properly cleaned up. */ - DBUG_ASSERT(open_tables == 0 && temporary_tables == 0 && + DBUG_ASSERT(open_tables == 0 && + temporary_tables == 0 && derived_tables == 0 && lock == 0 && locked_tables_mode == LTM_NONE && @@ -6982,24 +6984,6 @@ THD::signal_wakeup_ready() } -void THD::rgi_lock_temporary_tables() -{ - mysql_mutex_lock(&rgi_slave->rli->data_lock); - temporary_tables= rgi_slave->rli->save_temporary_tables; -} - -void THD::rgi_unlock_temporary_tables() -{ - rgi_slave->rli->save_temporary_tables= temporary_tables; - mysql_mutex_unlock(&rgi_slave->rli->data_lock); -} - -bool THD::rgi_have_temporary_tables() -{ - return rgi_slave->rli->save_temporary_tables != 0; -} - - void wait_for_commit::reinit() { diff --git a/sql/sql_class.h b/sql/sql_class.h index 36ee298caed..783f8b9df11 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1264,6 +1264,61 @@ enum enum_locked_tables_mode LTM_PRELOCKED_UNDER_LOCK_TABLES }; +/** + The following structure is an extension to TABLE_SHARE and is + exclusively for temporary tables. + + @note: + Although, TDC_element has data members (like next, prev & + all_tables) to store the list of TABLE_SHARE & TABLE objects + related to a particular TABLE_SHARE, they cannot be moved to + TABLE_SHARE in order to be reused for temporary tables. This + is because, as concurrent threads iterating through hash of + TDC_element's may need access to all_tables, but if all_tables + is made part of TABLE_SHARE, then TDC_element->share->all_tables + is not always guaranteed to be valid, as TDC_element can live + longer than TABLE_SHARE. +*/ +struct TMP_TABLE_SHARE : public TABLE_SHARE +{ +private: + /* + Link to all temporary table shares. Declared as private to + avoid direct manipulation with those objects. One should + use methods of I_P_List template instead. + */ + TMP_TABLE_SHARE *tmp_next; + TMP_TABLE_SHARE **tmp_prev; + + friend struct All_tmp_table_shares; + +public: + /* + Doubly-linked (back-linked) lists of used and unused TABLE objects + for this share. + */ + All_share_tables_list all_tmp_tables; +}; + +/** + Helper class which specifies which members of TMP_TABLE_SHARE are + used for participation in the list of temporary tables. +*/ + +struct All_tmp_table_shares +{ + static inline TMP_TABLE_SHARE **next_ptr(TMP_TABLE_SHARE *l) + { + return &l->tmp_next; + } + static inline TMP_TABLE_SHARE ***prev_ptr(TMP_TABLE_SHARE *l) + { + return &l->tmp_prev; + } +}; + +/* Also used in rpl_rli.h. */ +typedef I_P_List <TMP_TABLE_SHARE, All_tmp_table_shares> All_tmp_tables_list; /** Class that holds information about tables which were opened and locked @@ -1293,15 +1348,20 @@ public: base tables that were opened with @see open_tables(). */ TABLE *open_tables; + /** - List of temporary tables used by this thread. Contains user-level - temporary tables, created with CREATE TEMPORARY TABLE, and - internal temporary tables, created, e.g., to resolve a SELECT, + A list of temporary tables used by this thread. This includes + user-level temporary tables, created with CREATE TEMPORARY TABLE, + and internal temporary tables, created, e.g., to resolve a SELECT, or for an intermediate table used in ALTER. - XXX Why are internal temporary tables added to this list? */ - TABLE *temporary_tables; + All_tmp_tables_list *temporary_tables; + + /* + Derived tables. + */ TABLE *derived_tables; + /* During a MySQL session, one can lock tables in two modes: automatic or manual. In automatic mode all necessary tables are locked just before @@ -1379,8 +1439,11 @@ public: void reset_open_tables_state(THD *thd) { - open_tables= temporary_tables= derived_tables= 0; - extra_lock= lock= 0; + open_tables= 0; + temporary_tables= 0; + derived_tables= 0; + extra_lock= 0; + lock= 0; locked_tables_mode= LTM_NONE; state_flags= 0U; m_reprepare_observer= NULL; @@ -3544,13 +3607,13 @@ public: */ DBUG_PRINT("debug", ("temporary_tables: %s, in_sub_stmt: %s, system_thread: %s", - YESNO(temporary_tables), YESNO(in_sub_stmt), + YESNO(has_thd_temporary_tables()), YESNO(in_sub_stmt), show_system_thread(system_thread))); if (in_sub_stmt == 0) { if (wsrep_binlog_format() == BINLOG_FORMAT_ROW) set_current_stmt_binlog_format_row(); - else if (temporary_tables == NULL) + else if (!has_thd_temporary_tables()) set_current_stmt_binlog_format_stmt(); } DBUG_VOID_RETURN; @@ -3950,10 +4013,6 @@ private: LEX_STRING invoker_user; LEX_STRING invoker_host; - /* Protect against add/delete of temporary tables in parallel replication */ - void rgi_lock_temporary_tables(); - void rgi_unlock_temporary_tables(); - bool rgi_have_temporary_tables(); public: /* Flag, mutex and condition for a thread to wait for a signal from another @@ -3971,26 +4030,85 @@ public: */ rpl_gtid last_commit_gtid; - inline void lock_temporary_tables() + LF_PINS *tdc_hash_pins; + LF_PINS *xid_hash_pins; + bool fix_xid_hash_pins(); + +/* Members related to temporary tables. */ +public: + bool has_thd_temporary_tables(); + + TABLE *create_and_open_tmp_table(handlerton *hton, + LEX_CUSTRING *frm, + const char *path, + const char *db, + const char *table_name, + bool open_in_engine); + + TABLE *find_temporary_table(const char *db, const char *table_name); + TABLE *find_temporary_table(const TABLE_LIST *tl); + + TMP_TABLE_SHARE *find_tmp_table_share_w_base_key(const char *key, + uint key_length); + TMP_TABLE_SHARE *find_tmp_table_share(const char *db, + const char *table_name); + TMP_TABLE_SHARE *find_tmp_table_share(const TABLE_LIST *tl); + TMP_TABLE_SHARE *find_tmp_table_share(const char *key, uint key_length); + + bool open_temporary_table(TABLE_LIST *tl); + bool open_temporary_tables(TABLE_LIST *tl); + + bool close_temporary_tables(); + bool rename_temporary_table(TABLE *table, const char *db, + const char *table_name); + bool drop_temporary_table(TABLE *table, bool *is_trans, bool delete_table); + bool rm_temporary_table(handlerton *hton, const char *path); + void mark_tmp_tables_as_free_for_reuse(); + void mark_tmp_table_as_free_for_reuse(TABLE *table); + +private: + /* Whether a lock has been acquired? */ + bool m_tmp_tables_locked; + + /* Opened table states. */ + enum Temporary_table_state { + TMP_TABLE_IN_USE, + TMP_TABLE_NOT_IN_USE, + TMP_TABLE_ANY + }; + + bool has_temporary_tables(); + uint create_tmp_table_def_key(char *key, const char *db, + const char *table_name); + TMP_TABLE_SHARE *create_temporary_table(handlerton *hton, LEX_CUSTRING *frm, + const char *path, const char *db, + const char *table_name); + TABLE *find_temporary_table(const char *key, uint key_length, + Temporary_table_state state); + TABLE *open_temporary_table(TMP_TABLE_SHARE *share, const char *alias, + bool open_in_engine); + bool find_and_use_tmp_table(const TABLE_LIST *tl, TABLE **out_table); + bool use_temporary_table(TABLE *table, TABLE **out_table); + void close_temporary_table(TABLE *table); + bool log_events_and_free_tmp_shares(); + void free_tmp_table_share(TMP_TABLE_SHARE *share, bool delete_table); + void free_temporary_table(TABLE *table); + bool lock_temporary_tables(); + void unlock_temporary_tables(); + + inline uint tmpkeyval(TMP_TABLE_SHARE *share) { - if (rgi_slave) - rgi_lock_temporary_tables(); + return uint4korr(share->table_cache_key.str + + share->table_cache_key.length - 4); } - inline void unlock_temporary_tables() - { - if (rgi_slave) - rgi_unlock_temporary_tables(); - } - inline bool have_temporary_tables() + + inline TMP_TABLE_SHARE *tmp_table_share(TABLE *table) { - return (temporary_tables || - (rgi_slave && rgi_have_temporary_tables())); + DBUG_ASSERT(table->s->tmp_table); + return static_cast<TMP_TABLE_SHARE *>(table->s); } - LF_PINS *tdc_hash_pins; - LF_PINS *xid_hash_pins; - bool fix_xid_hash_pins(); - +public: inline ulong wsrep_binlog_format() const { return WSREP_FORMAT(variables.binlog_format); diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 2ba67cb1b00..be835e45ec7 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -1,6 +1,6 @@ /* Copyright (c) 2000, 2014, Oracle and/or its affiliates. - Copyright (c) 2009, 2015, MariaDB + Copyright (c) 2009, 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -886,7 +886,8 @@ mysql_rm_db_internal(THD *thd,char *db, bool if_exists, bool silent) { LEX_STRING db_name= { table->db, table->db_length }; LEX_STRING table_name= { table->table_name, table->table_name_length }; - if (table->open_type == OT_BASE_ONLY || !find_temporary_table(thd, table)) + if (table->open_type == OT_BASE_ONLY || + !thd->find_temporary_table(table)) (void) delete_statistics_for_table(thd, &db_name, &table_name); } } diff --git a/sql/sql_handler.cc b/sql/sql_handler.cc index e8ade81062b..95417c73c74 100644 --- a/sql/sql_handler.cc +++ b/sql/sql_handler.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2001, 2015, Oracle and/or its affiliates. - Copyright (c) 2011, 2015, MariaDB + Copyright (c) 2011, 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -178,9 +178,8 @@ static void mysql_ha_close_table(SQL_HANDLER *handler) { /* Must be a temporary table */ table->file->ha_index_or_rnd_end(); - table->query_id= thd->query_id; table->open_by_handler= 0; - mark_tmp_table_for_reuse(table); + thd->mark_tmp_table_as_free_for_reuse(table); } my_free(handler->lock); handler->init(); @@ -294,7 +293,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen) open_ltable() or open_table() because we would like to be able to open a temporary table. */ - error= (open_temporary_tables(thd, tables) || + error= (thd->open_temporary_tables(tables) || open_tables(thd, &tables, &counter, 0)); if (error) @@ -389,8 +388,7 @@ bool mysql_ha_open(THD *thd, TABLE_LIST *tables, SQL_HANDLER *reopen) /* Assert that the above check prevents opening of views and merge tables. For temporary tables, TABLE::next can be set even if only one table - was opened for HANDLER as it is used to link them together - (see thd->temporary_tables). + was opened for HANDLER as it is used to link them together. */ DBUG_ASSERT(sql_handler->table->next == NULL || sql_handler->table->s->tmp_table); @@ -1195,10 +1193,10 @@ void mysql_ha_set_explicit_lock_duration(THD *thd) Remove temporary tables from the HANDLER's hash table. The reason for having a separate function, rather than calling mysql_ha_rm_tables() is that it is not always feasible (e.g. in - close_temporary_tables) to obtain a TABLE_LIST containing the + THD::close_temporary_tables) to obtain a TABLE_LIST containing the temporary tables. - @See close_temporary_tables + @See THD::close_temporary_tables() @param thd Thread identifier. */ void mysql_ha_rm_temporary_tables(THD *thd) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 9f63216af88..13ff5a74b0b 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -4078,7 +4078,12 @@ static TABLE *create_table_from_items(THD *thd, } else { - if (open_temporary_table(thd, create_table)) + /* + The pointer to the newly created temporary table has been stored in + table->create_info. + */ + create_table->table= create_info->table; + if (!create_table->table) { /* This shouldn't happen as creation of temporary table should make @@ -4087,7 +4092,6 @@ static TABLE *create_table_from_items(THD *thd, */ DBUG_ASSERT(0); } - DBUG_ASSERT(create_table->table == create_info->table); } } else @@ -4461,6 +4465,7 @@ void select_create::abort_result_set() if (table) { bool tmp_table= table->s->tmp_table; + table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE); table->auto_increment_field_not_null= FALSE; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index a1883d2fc21..8b3d50e40e4 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2000, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2015, MariaDB + Copyright (c) 2008, 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,10 @@ // set_handler_table_locks, // lock_global_read_lock, // make_global_read_lock_block_commit -#include "sql_base.h" // find_temporary_table +#include "sql_base.h" // open_tables, open_and_lock_tables, + // lock_tables, unique_table, + // close_thread_tables, is_temporary_table + // table_cache.h #include "sql_cache.h" // QUERY_CACHE_FLAGS_SIZE, query_cache_* #include "sql_show.h" // mysqld_list_*, mysqld_show_*, // calc_sum_of_all_status @@ -418,7 +421,7 @@ static bool some_non_temp_table_to_be_updated(THD *thd, TABLE_LIST *tables) for (TABLE_LIST *table= tables; table; table= table->next_global) { DBUG_ASSERT(table->db && table->table_name); - if (table->updating && !find_temporary_table(thd, table)) + if (table->updating && !thd->find_tmp_table_share(table)) return 1; } return 0; @@ -1967,7 +1970,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->set_query(fields, query_length); general_log_print(thd, command, "%s %s", table_list.table_name, fields); - if (open_temporary_tables(thd, &table_list)) + if (thd->open_temporary_tables(&table_list)) break; if (check_table_access(thd, SELECT_ACL, &table_list, @@ -3264,7 +3267,7 @@ mysql_execute_command(THD *thd) */ if (sql_command_flags[lex->sql_command] & CF_PREOPEN_TMP_TABLES) { - if (open_temporary_tables(thd, all_tables)) + if (thd->open_temporary_tables(all_tables)) goto error; } @@ -4066,7 +4069,7 @@ end_with_restore_list: Temporary tables should be opened for SHOW CREATE TABLE, but not for SHOW CREATE VIEW. */ - if (open_temporary_tables(thd, all_tables)) + if (thd->open_temporary_tables(all_tables)) goto error; /* @@ -4274,7 +4277,8 @@ end_with_restore_list: */ if (first_table->lock_type != TL_WRITE_DELAYED) { - if ((res= open_temporary_tables(thd, all_tables))) + res= (thd->open_temporary_tables(all_tables)) ? TRUE : FALSE; + if (res) break; } @@ -4586,7 +4590,7 @@ end_with_restore_list: { if (!lex->tmp_table() && (!thd->is_current_stmt_binlog_format_row() || - !find_temporary_table(thd, table))) + !thd->find_temporary_table(table))) { WSREP_TO_ISOLATION_BEGIN(NULL, NULL, all_tables); break; @@ -4739,7 +4743,7 @@ end_with_restore_list: CF_PREOPEN_TMP_TABLES was set and the tables would be pre-opened in a usual way, they would have been closed. */ - if (open_temporary_tables(thd, all_tables)) + if (thd->open_temporary_tables(all_tables)) goto error; if (lock_tables_precheck(thd, all_tables)) @@ -6780,7 +6784,7 @@ static bool check_show_access(THD *thd, TABLE_LIST *table) /* Open temporary tables to be able to detect them during privilege check. */ - if (open_temporary_tables(thd, dst_table)) + if (thd->open_temporary_tables(dst_table)) return TRUE; if (check_access(thd, SELECT_ACL, dst_table->db, diff --git a/sql/sql_partition_admin.cc b/sql/sql_partition_admin.cc index 9e67a21e8f4..18f25ab7cf7 100644 --- a/sql/sql_partition_admin.cc +++ b/sql/sql_partition_admin.cc @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. Copyright (c) 2014, SkySQL Ab. + Copyright (c) 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -534,12 +535,9 @@ bool Sql_cmd_alter_table_exchange_partition:: #ifdef WITH_WSREP if (WSREP_ON) { - /* Forward declaration */ - TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl); - if ((!thd->is_current_stmt_binlog_format_row() || /* TODO: Do we really need to check for temp tables in this case? */ - !find_temporary_table(thd, table_list)) && + !thd->find_temporary_table(table_list)) && wsrep_to_isolation_begin(thd, table_list->db, table_list->table_name, NULL)) { @@ -783,11 +781,9 @@ bool Sql_cmd_alter_table_truncate_partition::execute(THD *thd) DBUG_RETURN(TRUE); #ifdef WITH_WSREP - /* Forward declaration */ - TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl); - - if (WSREP(thd) && (!thd->is_current_stmt_binlog_format_row() || - !find_temporary_table(thd, first_table)) && + if (WSREP(thd) && + (!thd->is_current_stmt_binlog_format_row() || + !thd->find_temporary_table(first_table)) && wsrep_to_isolation_begin( thd, first_table->db, first_table->table_name, NULL) ) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 0c7e26c7b04..e8a7dce5771 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2002, 2015, Oracle and/or its affiliates. - Copyright (c) 2008, 2015, MariaDB + Copyright (c) 2008, 2016, MariaDB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1259,7 +1259,7 @@ static bool mysql_test_insert(Prepared_statement *stmt, */ if (table_list->lock_type != TL_WRITE_DELAYED) { - if (open_temporary_tables(thd, table_list)) + if (thd->open_temporary_tables(table_list)) goto error; } @@ -2028,7 +2028,7 @@ static bool mysql_test_create_view(Prepared_statement *stmt) Since we can't pre-open temporary tables for SQLCOM_CREATE_VIEW, (see mysql_create_view) we have to do it here instead. */ - if (open_temporary_tables(thd, tables)) + if (thd->open_temporary_tables(tables)) goto err; if (open_normal_and_derived_tables(thd, tables, MYSQL_OPEN_FORCE_SHARED_MDL, @@ -2280,7 +2280,7 @@ static bool check_prepared_statement(Prepared_statement *stmt) */ if (sql_command_flags[sql_command] & CF_PREOPEN_TMP_TABLES) { - if (open_temporary_tables(thd, tables)) + if (thd->open_temporary_tables(tables)) goto error; } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index ee968f173e1..923114ae0d4 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -4155,7 +4155,7 @@ fill_schema_table_by_open(THD *thd, bool is_show_fields_or_keys, 'only_view_structure()'. */ lex->sql_command= SQLCOM_SHOW_FIELDS; - result= (open_temporary_tables(thd, table_list) || + result= (thd->open_temporary_tables(table_list) || open_normal_and_derived_tables(thd, table_list, (MYSQL_OPEN_IGNORE_FLUSH | MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL | @@ -4218,6 +4218,7 @@ end: all tables open within this Open_tables_state. */ thd->temporary_tables= NULL; + close_thread_tables(thd); /* Release metadata lock we might have acquired. diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 296953b3375..0be329e76c2 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -25,7 +25,7 @@ #include "sql_table.h" #include "sql_parse.h" // test_if_data_home_dir #include "sql_cache.h" // query_cache_* -#include "sql_base.h" // open_table_uncached, lock_table_names +#include "sql_base.h" // lock_table_names #include "lock.h" // mysql_unlock_tables #include "strfunc.h" // find_type2, find_set #include "sql_truncate.h" // regenerate_locked_table @@ -2030,7 +2030,7 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, LEX_STRING db_name= { table->db, table->db_length }; LEX_STRING table_name= { table->table_name, table->table_name_length }; if (table->open_type == OT_BASE_ONLY || - !find_temporary_table(thd, table)) + !thd->find_temporary_table(table)) (void) delete_statistics_for_table(thd, &db_name, &table_name); } } @@ -2283,23 +2283,17 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, */ DBUG_ASSERT(!(thd->locked_tables_mode && table->open_type != OT_BASE_ONLY && - find_temporary_table(thd, table) && + thd->find_temporary_table(table) && table->mdl_request.ticket != NULL)); - /* - drop_temporary_table may return one of the following error codes: - . 0 - a temporary table was successfully dropped. - . 1 - a temporary table was not found. - . -1 - a temporary table is used by an outer statement. - */ if (table->open_type == OT_BASE_ONLY || !is_temporary_table(table)) error= 1; else { table_creation_was_logged= table->table->s->table_creation_was_logged; - if ((error= drop_temporary_table(thd, table->table, &is_trans)) == -1) + if (thd->drop_temporary_table(table->table, &is_trans, true)) { - DBUG_ASSERT(thd->in_sub_stmt); + error= 1; goto err; } table->table= 0; @@ -4635,7 +4629,8 @@ err: which was created. @param[out] key_count Number of keys in table which was created. - If one creates a temporary table, this is automatically opened + If one creates a temporary table, its is automatically opened and its + TABLE_SHARE is added to THD::all_temp_tables list. Note that this function assumes that caller already have taken exclusive metadata lock on table being created or used some other @@ -4695,20 +4690,22 @@ int create_table_impl(THD *thd, /* Check if table exists */ if (create_info->tmp_table()) { - TABLE *tmp_table; - if (find_and_use_temporary_table(thd, db, table_name, &tmp_table)) - goto err; + /* + If a table exists, it must have been pre-opened. Try looking for one + in-use in THD::all_temp_tables list of TABLE_SHAREs. + */ + TABLE *tmp_table= thd->find_temporary_table(db, table_name); + if (tmp_table) { bool table_creation_was_logged= tmp_table->s->table_creation_was_logged; if (options.or_replace()) { - bool tmp; /* We are using CREATE OR REPLACE on an existing temporary table Remove the old table so that we can re-create it. */ - if (drop_temporary_table(thd, tmp_table, &tmp)) + if (thd->drop_temporary_table(tmp_table, NULL, true)) goto err; } else if (options.if_not_exists()) @@ -4847,17 +4844,12 @@ int create_table_impl(THD *thd, create_info->table= 0; if (!frm_only && create_info->tmp_table()) { - /* - Open a table (skipping table cache) and add it into - THD::temporary_tables list. - */ - - TABLE *table= open_table_uncached(thd, create_info->db_type, frm, path, - db, table_name, true, true); + TABLE *table= thd->create_and_open_tmp_table(create_info->db_type, frm, + path, db, table_name, true); if (!table) { - (void) rm_temporary_table(create_info->db_type, path); + (void) thd->rm_temporary_table(create_info->db_type, path); goto err; } @@ -7143,7 +7135,8 @@ static bool mysql_inplace_alter_table(THD *thd, HA_EXTRA_NOT_USED, NULL); table_list->table= table= NULL; - close_temporary_table(thd, altered_table, true, false); + + thd->drop_temporary_table(altered_table, NULL, false); /* Replace the old .FRM with the new .FRM, but keep the old name for now. @@ -7233,7 +7226,7 @@ static bool mysql_inplace_alter_table(THD *thd, thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); /* QQ; do something about metadata locks ? */ } - close_temporary_table(thd, altered_table, true, false); + thd->drop_temporary_table(altered_table, NULL, false); // Delete temporary .frm/.par (void) quick_rm_table(thd, create_info->db_type, alter_ctx->new_db, alter_ctx->tmp_name, FN_IS_TMP | NO_HA_TABLE); @@ -8385,7 +8378,12 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, { if (table->s->tmp_table != NO_TMP_TABLE) { - if (find_temporary_table(thd, alter_ctx.new_db, alter_ctx.new_name)) + /* + Check whether a temporary table exists with same requested new name. + If such table exists, there must be a corresponding TABLE_SHARE in + THD::all_temp_tables list. + */ + if (thd->find_tmp_table_share(alter_ctx.new_db, alter_ctx.new_name)) { my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias); DBUG_RETURN(true); @@ -8821,11 +8819,11 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, // We assume that the table is non-temporary. DBUG_ASSERT(!table->s->tmp_table); - if (!(altered_table= open_table_uncached(thd, new_db_type, &frm, - alter_ctx.get_tmp_path(), - alter_ctx.new_db, - alter_ctx.tmp_name, - true, false))) + if (!(altered_table= + thd->create_and_open_tmp_table(new_db_type, &frm, + alter_ctx.get_tmp_path(), + alter_ctx.new_db, alter_ctx.tmp_name, + false))) goto err_new_table_cleanup; /* Set markers for fields in TABLE object for altered table. */ @@ -8865,7 +8863,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, { ha_alter_info.report_unsupported_error("LOCK=NONE/SHARED", "LOCK=EXCLUSIVE"); - close_temporary_table(thd, altered_table, true, false); + thd->drop_temporary_table(altered_table, NULL, false); goto err_new_table_cleanup; } break; @@ -8876,7 +8874,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, Alter_info::ALTER_TABLE_LOCK_NONE) { ha_alter_info.report_unsupported_error("LOCK=NONE", "LOCK=SHARED"); - close_temporary_table(thd, altered_table, true, false); + thd->drop_temporary_table(altered_table, NULL, false); goto err_new_table_cleanup; } break; @@ -8890,7 +8888,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, { ha_alter_info.report_unsupported_error("ALGORITHM=INPLACE", "ALGORITHM=COPY"); - close_temporary_table(thd, altered_table, true, false); + thd->drop_temporary_table(altered_table, NULL, false); goto err_new_table_cleanup; } // COPY with LOCK=NONE is not supported, no point in trying. @@ -8898,7 +8896,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, Alter_info::ALTER_TABLE_LOCK_NONE) { ha_alter_info.report_unsupported_error("LOCK=NONE", "LOCK=SHARED"); - close_temporary_table(thd, altered_table, true, false); + thd->drop_temporary_table(altered_table, NULL, false); goto err_new_table_cleanup; } // Otherwise use COPY @@ -8906,7 +8904,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, break; case HA_ALTER_ERROR: default: - close_temporary_table(thd, altered_table, true, false); + thd->drop_temporary_table(altered_table, NULL, false); goto err_new_table_cleanup; } @@ -8925,7 +8923,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, } else { - close_temporary_table(thd, altered_table, true, false); + thd->drop_temporary_table(altered_table, NULL, false); } } @@ -8978,13 +8976,18 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if (create_info->tmp_table()) { - if (!open_table_uncached(thd, new_db_type, &frm, - alter_ctx.get_tmp_path(), - alter_ctx.new_db, alter_ctx.tmp_name, - true, true)) + TABLE *tmp_table= + thd->create_and_open_tmp_table(new_db_type, &frm, + alter_ctx.get_tmp_path(), + alter_ctx.new_db, alter_ctx.tmp_name, + true); + if (!tmp_table) + { goto err_new_table_cleanup; + } } + /* Open the table since we need to copy the data. */ if (table->s->tmp_table != NO_TMP_TABLE) { @@ -8992,18 +8995,24 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, tbl.init_one_table(alter_ctx.new_db, strlen(alter_ctx.new_db), alter_ctx.tmp_name, strlen(alter_ctx.tmp_name), alter_ctx.tmp_name, TL_READ_NO_INSERT); - /* Table is in thd->temporary_tables */ - (void) open_temporary_table(thd, &tbl); + /* + Table can be found in the list of open tables in THD::all_temp_tables + list. + */ + tbl.table= thd->find_temporary_table(&tbl); new_table= tbl.table; } else { - /* table is a normal table: Create temporary table in same directory */ - /* Open our intermediate table. */ - new_table= open_table_uncached(thd, new_db_type, &frm, - alter_ctx.get_tmp_path(), - alter_ctx.new_db, alter_ctx.tmp_name, - true, true); + /* + table is a normal table: Create temporary table in same directory. + Open our intermediate table. + */ + new_table= + thd->create_and_open_tmp_table(new_db_type, &frm, + alter_ctx.get_tmp_path(), + alter_ctx.new_db, alter_ctx.tmp_name, + true); } if (!new_table) goto err_new_table_cleanup; @@ -9071,10 +9080,10 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, new_table->s->table_creation_was_logged= table->s->table_creation_was_logged; /* Remove link to old table and rename the new one */ - close_temporary_table(thd, table, true, true); + thd->drop_temporary_table(table, NULL, true); /* Should pass the 'new_name' as we store table name in the cache */ - if (rename_temporary_table(thd, new_table, - alter_ctx.new_db, alter_ctx.new_name)) + if (thd->rename_temporary_table(new_table, alter_ctx.new_db, + alter_ctx.new_name)) goto err_new_table_cleanup; /* We don't replicate alter table statement on temporary tables */ if (!thd->is_current_stmt_binlog_format_row() && @@ -9086,10 +9095,10 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, /* Close the intermediate table that will be the new table, but do - not delete it! Even altough MERGE tables do not have their children - attached here it is safe to call close_temporary_table(). + not delete it! Even though MERGE tables do not have their children + attached here it is safe to call THD::drop_temporary_table(). */ - close_temporary_table(thd, new_table, true, false); + thd->drop_temporary_table(new_table, NULL, false); new_table= NULL; DEBUG_SYNC(thd, "alter_table_before_rename_result_table"); @@ -9231,8 +9240,7 @@ err_new_table_cleanup: my_free(const_cast<uchar*>(frm.str)); if (new_table) { - /* close_temporary_table() frees the new_table pointer. */ - close_temporary_table(thd, new_table, true, true); + thd->drop_temporary_table(new_table, NULL, true); } else (void) quick_rm_table(thd, new_db_type, @@ -9732,7 +9740,7 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, /* Allow to open real tables only. */ table->required_type= FRMTYPE_TABLE; - if (open_temporary_tables(thd, table) || + if (thd->open_temporary_tables(table) || open_and_lock_tables(thd, table, FALSE, 0)) { t= NULL; diff --git a/sql/sql_test.cc b/sql/sql_test.cc index 50d51dcc8cc..76586a9fbba 100644 --- a/sql/sql_test.cc +++ b/sql/sql_test.cc @@ -82,7 +82,7 @@ static my_bool print_cached_tables_callback(TDC_element *element, TABLE *entry; mysql_mutex_lock(&element->LOCK_table_share); - TDC_element::All_share_tables_list::Iterator it(element->all_tables); + All_share_tables_list::Iterator it(element->all_tables); while ((entry= it++)) { THD *in_use= entry->in_use; diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 63093620805..5213c21e8a2 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -24,7 +24,7 @@ #include "sql_parse.h" // parse_sql #include "parse_file.h" #include "sp.h" -#include "sql_base.h" // find_temporary_table +#include "sql_base.h" #include "sql_show.h" // append_definer, append_identifier #include "sql_table.h" // build_table_filename, // check_n_cut_mysql50_prefix @@ -507,7 +507,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) DBUG_ASSERT(tables->next_global == 0); /* We do not allow creation of triggers on temporary tables. */ - if (create && find_temporary_table(thd, tables)) + if (create && thd->find_tmp_table_share(tables)) { my_error(ER_TRG_ON_VIEW_OR_TEMP_TABLE, MYF(0), tables->alias); goto end; diff --git a/sql/sql_view.cc b/sql/sql_view.cc index b66f678adfc..b33590637b9 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2004, 2013, Oracle and/or its affiliates. - Copyright (c) 2011, 2015, MariaDB + Copyright (c) 2011, 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -429,7 +429,7 @@ bool mysql_create_view(THD *thd, TABLE_LIST *views, lex->link_first_table_back(view, link_to_local); view->open_type= OT_BASE_ONLY; - if (open_temporary_tables(thd, lex->query_tables) || + if (thd->open_temporary_tables(lex->query_tables) || open_and_lock_tables(thd, lex->query_tables, TRUE, 0)) { view= lex->unlink_first_table(&link_to_local); diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 4bf202813f3..a722a1cd1c7 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2002, 2015, Oracle and/or its affiliates. - Copyright (c) 2012, 2015, MariaDB + Copyright (c) 2012, 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -477,7 +477,8 @@ static bool binlog_format_check(sys_var *self, THD *thd, set_var *var) switching @@SESSION.binlog_format from MIXED to STATEMENT when there are open temp tables and we are logging in row format. */ - if (thd->temporary_tables && var->type == OPT_SESSION && + if (thd->has_thd_temporary_tables() && + var->type == OPT_SESSION && var->save_result.ulonglong_value == BINLOG_FORMAT_STMT && ((thd->variables.binlog_format == BINLOG_FORMAT_MIXED && thd->is_current_stmt_binlog_format_row()) || diff --git a/sql/table.cc b/sql/table.cc index 6cbf2a4a917..13b6fbf9cce 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -3922,7 +3922,7 @@ bool TABLE_SHARE::visit_subgraph(Wait_for_flush *wait_for_flush, tdc->all_tables_refs++; mysql_mutex_unlock(&tdc->LOCK_table_share); - TDC_element::All_share_tables_list::Iterator tables_it(tdc->all_tables); + All_share_tables_list::Iterator tables_it(tdc->all_tables); /* In case of multiple searches running in parallel, avoid going diff --git a/sql/table.h b/sql/table.h index 81869f6754d..c207588a049 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2,6 +2,7 @@ #define TABLE_INCLUDED /* Copyright (c) 2000, 2013, Oracle and/or its affiliates. Copyright (c) 2009, 2014, SkySQL Ab. + Copyright (c) 2016, MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -1012,7 +1013,7 @@ private: One should use methods of I_P_List template instead. */ TABLE *share_all_next, **share_all_prev; - friend struct TDC_element; + friend struct All_share_tables; public: @@ -1435,6 +1436,19 @@ struct TABLE_share } }; +struct All_share_tables +{ + static inline TABLE **next_ptr(TABLE *l) + { + return &l->share_all_next; + } + static inline TABLE ***prev_ptr(TABLE *l) + { + return &l->share_all_prev; + } +}; + +typedef I_P_List <TABLE, All_share_tables> All_share_tables_list; enum enum_schema_table_state { @@ -2662,15 +2676,6 @@ inline bool is_infoschema_db(const char *name) TYPELIB *typelib(MEM_ROOT *mem_root, List<String> &strings); -/** - return true if the table was created explicitly. -*/ -inline bool is_user_table(TABLE * table) -{ - const char *name= table->s->table_name.str; - return strncmp(name, tmp_file_prefix, tmp_file_prefix_length); -} - inline void mark_as_null_row(TABLE *table) { table->null_row=1; diff --git a/sql/table_cache.cc b/sql/table_cache.cc index c7e95b4df14..bfe51df5de1 100644 --- a/sql/table_cache.cc +++ b/sql/table_cache.cc @@ -965,7 +965,7 @@ void tdc_release_share(TABLE_SHARE *share) static void kill_delayed_threads_for_table(TDC_element *element) { - TDC_element::All_share_tables_list::Iterator it(element->all_tables); + All_share_tables_list::Iterator it(element->all_tables); TABLE *tab; mysql_mutex_assert_owner(&element->LOCK_table_share); @@ -1086,7 +1086,7 @@ bool tdc_remove_table(THD *thd, enum_tdc_remove_table_type remove_type, if (remove_type == TDC_RT_REMOVE_NOT_OWN || remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) { - TDC_element::All_share_tables_list::Iterator it(element->all_tables); + All_share_tables_list::Iterator it(element->all_tables); while ((table= it++)) { my_refs++; diff --git a/sql/table_cache.h b/sql/table_cache.h index ceb7299b08a..c05baae1f15 100644 --- a/sql/table_cache.h +++ b/sql/table_cache.h @@ -27,9 +27,6 @@ struct TDC_element TABLE_SHARE *share; typedef I_P_List <TABLE, TABLE_share> TABLE_list; - typedef I_P_List <TABLE, I_P_List_adapter<TABLE, &TABLE::share_all_next, - &TABLE::share_all_prev> > - All_share_tables_list; /** Protects ref_count, m_flush_tickets, all_tables, free_tables, flushed, all_tables_refs. diff --git a/sql/temporary_tables.cc b/sql/temporary_tables.cc new file mode 100644 index 00000000000..c23bbd4ff19 --- /dev/null +++ b/sql/temporary_tables.cc @@ -0,0 +1,1499 @@ +/* + Copyright (c) 2016 MariaDB Corporation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/** + All methods pertaining to temporary tables. +*/ + +#include "sql_acl.h" /* TMP_TABLE_ACLS */ +#include "sql_base.h" /* tdc_create_key */ +#include "lock.h" /* mysql_lock_remove */ +#include "log_event.h" /* Query_log_event */ +#include "sql_show.h" /* append_identifier */ +#include "sql_handler.h" /* mysql_ha_rm_temporary_tables */ +#include "rpl_rli.h" /* rpl_group_info */ + +#define IS_USER_TABLE(A) ((A->tmp_table == TRANSACTIONAL_TMP_TABLE) || \ + (A->tmp_table == NON_TRANSACTIONAL_TMP_TABLE)) + +/** + Check whether temporary tables exist. The decision is made based on the + existence of TMP_TABLE_SHAREs in Open_tables_state::temporary_tables list. + + @return false Temporary tables exist + true No temporary table exist +*/ +bool THD::has_thd_temporary_tables() +{ + DBUG_ENTER("THD::has_thd_temporary_tables"); + bool result= (temporary_tables && !temporary_tables->is_empty()); + DBUG_RETURN(result); +} + + +/** + Create a temporary table, open it and return the TABLE handle. + + @param hton [IN] Handlerton + @param frm [IN] Binary frm image + @param path [IN] File path (without extension) + @param db [IN] Schema name + @param table_name [IN] Table name + @param open_in_engine [IN] Whether open table in SE + + + @return Success A pointer to table object + Failure NULL +*/ +TABLE *THD::create_and_open_tmp_table(handlerton *hton, + LEX_CUSTRING *frm, + const char *path, + const char *db, + const char *table_name, + bool open_in_engine) +{ + DBUG_ENTER("THD::create_and_open_tmp_table"); + + TMP_TABLE_SHARE *share; + TABLE *table= NULL; + + if ((share= create_temporary_table(hton, frm, path, db, table_name))) + { + table= open_temporary_table(share, table_name, open_in_engine); + + /* + Failed to open a temporary table instance. As we are not passing + the created TMP_TABLE_SHARE to the caller, we must remove it from + the list and free it here. + */ + if (!table) + { + /* Remove the TABLE_SHARE from the list of temporary tables. */ + temporary_tables->remove(share); + + /* Free the TMP_TABLE_SHARE. */ + free_tmp_table_share(share, false); + } + } + + DBUG_RETURN(table); +} + + +/** + Check whether an open table with db/table name is in use. + + @param db [IN] Database name + @param table_name [IN] Table name + + @return Success Pointer to first used table instance. + Failure NULL +*/ +TABLE *THD::find_temporary_table(const char *db, + const char *table_name) +{ + DBUG_ENTER("THD::find_temporary_table"); + + TABLE *table; + char key[MAX_DBKEY_LENGTH]; + uint key_length; + bool locked; + + if (!has_temporary_tables()) + { + DBUG_RETURN(NULL); + } + + key_length= create_tmp_table_def_key(key, db, table_name); + + locked= lock_temporary_tables(); + table = find_temporary_table(key, key_length, TMP_TABLE_IN_USE); + if (locked) + { + DBUG_ASSERT(m_tmp_tables_locked); + unlock_temporary_tables(); + } + + DBUG_RETURN(table); +} + + +/** + Check whether an open table specified in TABLE_LIST is in use. + + @return tl [IN] TABLE_LIST + + @return Success Pointer to first used table instance. + Failure NULL +*/ +TABLE *THD::find_temporary_table(const TABLE_LIST *tl) +{ + DBUG_ENTER("THD::find_temporary_table"); + + if (!has_temporary_tables()) + { + DBUG_RETURN(NULL); + } + + TABLE *table= find_temporary_table(tl->get_db_name(), tl->get_table_name()); + DBUG_RETURN(table); +} + + +/** + Check whether a temporary table exists with the specified key. + The key, in this case, is not the usual key used for temporary tables. + It does not contain server_id & pseudo_thread_id. This function is + essentially used use to check whether there is any temporary table + which _shadows_ a base table. + (see: Query_cache::send_result_to_client()) + + @return Success A pointer to table share object + Failure NULL +*/ +TMP_TABLE_SHARE *THD::find_tmp_table_share_w_base_key(const char *key, + uint key_length) +{ + DBUG_ENTER("THD::find_tmp_table_share_w_base_key"); + + TMP_TABLE_SHARE *share; + TMP_TABLE_SHARE *result= NULL; + bool locked; + + if (!has_temporary_tables()) + { + DBUG_RETURN(NULL); + } + + locked= lock_temporary_tables(); + + All_tmp_tables_list::Iterator it(*temporary_tables); + while ((share= it++)) + { + if ((share->table_cache_key.length - TMP_TABLE_KEY_EXTRA) == key_length + && !memcmp(share->table_cache_key.str, key, key_length)) + { + result= share; + } + } + + if (locked) + { + DBUG_ASSERT(m_tmp_tables_locked); + unlock_temporary_tables(); + } + + DBUG_RETURN(result); +} + + +/** + Lookup the TMP_TABLE_SHARE using the given db/table_name.The server_id and + pseudo_thread_id used to generate table definition key is taken from THD + (see create_tmp_table_def_key()). Return NULL is none found. + + @return Success A pointer to table share object + Failure NULL +*/ +TMP_TABLE_SHARE *THD::find_tmp_table_share(const char *db, + const char *table_name) +{ + DBUG_ENTER("THD::find_tmp_table_share"); + + TMP_TABLE_SHARE *share; + char key[MAX_DBKEY_LENGTH]; + uint key_length; + + key_length= create_tmp_table_def_key(key, db, table_name); + share= find_tmp_table_share(key, key_length); + + DBUG_RETURN(share); +} + + +/** + Lookup TMP_TABLE_SHARE using the specified TABLE_LIST element. + Return NULL is none found. + + @param tl [IN] Table + + @return Success A pointer to table share object + Failure NULL +*/ +TMP_TABLE_SHARE *THD::find_tmp_table_share(const TABLE_LIST *tl) +{ + DBUG_ENTER("THD::find_tmp_table_share"); + TMP_TABLE_SHARE *share= find_tmp_table_share(tl->get_db_name(), + tl->get_table_name()); + DBUG_RETURN(share); +} + + +/** + Lookup TMP_TABLE_SHARE using the specified table definition key. + Return NULL is none found. + + @return Success A pointer to table share object + Failure NULL +*/ +TMP_TABLE_SHARE *THD::find_tmp_table_share(const char *key, uint key_length) +{ + DBUG_ENTER("THD::find_tmp_table_share"); + + TMP_TABLE_SHARE *share; + TMP_TABLE_SHARE *result= NULL; + bool locked; + + if (!has_temporary_tables()) + { + DBUG_RETURN(NULL); + } + + locked= lock_temporary_tables(); + + All_tmp_tables_list::Iterator it(*temporary_tables); + while ((share= it++)) + { + if (share->table_cache_key.length == key_length && + !(memcmp(share->table_cache_key.str, key, key_length))) + { + result= share; + break; + } + } + + if (locked) + { + DBUG_ASSERT(m_tmp_tables_locked); + unlock_temporary_tables(); + } + + DBUG_RETURN(result); +} + + +/** + Find a temporary table specified by TABLE_LIST instance in the open table + list and prepare its TABLE instance for use. If + + 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 THD::open_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. + + @param tl [IN] TABLE_LIST + + @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 THD::open_temporary_table(TABLE_LIST *tl) +{ + DBUG_ENTER("THD::open_temporary_table"); + DBUG_PRINT("enter", ("table: '%s'.'%s'", tl->db, tl->table_name)); + + TMP_TABLE_SHARE *share; + TABLE *table= NULL; + + /* + Since temporary tables are not safe for parallel replication, lets + wait for the prior commits in case the table is found to be in use. + */ + if (rgi_slave && + rgi_slave->is_parallel_exec && + find_temporary_table(tl) && + wait_for_prior_commit()) + DBUG_RETURN(true); + + /* + 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 THD::open_tables() should be used. + */ + DBUG_ASSERT(!tl->derived && !tl->schema_table); + + if (tl->open_type == OT_BASE_ONLY || !has_temporary_tables()) + { + DBUG_PRINT("info", ("skip_temporary is set or no temporary tables")); + DBUG_RETURN(false); + } + + /* + First check if there is a reusable open table available in the + open table list. + */ + if (find_and_use_tmp_table(tl, &table)) + { + DBUG_RETURN(true); /* Error */ + } + + /* + No reusable table was found. We will have to open a new instance. + */ + if (!table && (share= find_tmp_table_share(tl))) + { + table= open_temporary_table(share, tl->get_table_name(), 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 (rgi_slave && + rgi_slave->is_parallel_exec && + 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 + + table->query_id= query_id; + thread_specific_used= true; + + /* It is neither a derived table nor non-updatable view. */ + tl->updatable= true; + tl->table= table; + + table->init(this, 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. + + @param tl [IN] TABLE_LIST + + @return false On success. If a temporary table exists + for the given element, tl->table is set. + true On error. my_error() has been called. +*/ +bool THD::open_temporary_tables(TABLE_LIST *tl) +{ + DBUG_ENTER("THD::open_temporary_tables"); + + TABLE_LIST *first_not_own= lex->first_not_own_table(); + + for (TABLE_LIST *table= tl; table && table != first_not_own; + table= table->next_global) + { + if (table->derived || table->schema_table) + { + /* + Derived and I_S tables will be handled by a later call to open_tables(). + */ + continue; + } + + if (open_temporary_table(table)) + { + DBUG_RETURN(true); + } + } + + DBUG_RETURN(false); +} + + +/** + 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(). + + @return false Success + true Failure +*/ +bool THD::close_temporary_tables() +{ + DBUG_ENTER("THD::close_temporary_tables"); + + TMP_TABLE_SHARE *share; + TABLE *table; + + bool error= false; + + if (!has_thd_temporary_tables()) + { + if (temporary_tables) + { + my_free(temporary_tables); + temporary_tables= NULL; + } + DBUG_RETURN(false); + } + + DBUG_ASSERT(!rgi_slave); + + /* + Ensure we don't have open HANDLERs for tables we are about to close. + This is necessary when THD::close_temporary_tables() is called as + part of execution of BINLOG statement (e.g. for format description event). + */ + mysql_ha_rm_temporary_tables(this); + + /* Close all open temporary tables. */ + All_tmp_tables_list::Iterator it(*temporary_tables); + while ((share= it++)) + { + /* Traverse the table list. */ + while ((table= share->all_tmp_tables.pop_front())) + { + free_temporary_table(table); + } + } + + // Write DROP TEMPORARY TABLE query log events to binary log. + if (mysql_bin_log.is_open()) + { + error= log_events_and_free_tmp_shares(); + } + else + { + while ((share= temporary_tables->pop_front())) + { + free_tmp_table_share(share, true); + } + } + + /* By now, there mustn't be any elements left in the list. */ + DBUG_ASSERT(temporary_tables->is_empty()); + + my_free(temporary_tables); + temporary_tables= NULL; + + DBUG_RETURN(error); +} + + +/** + Rename a temporary table. + + @param table [IN] Table handle + @param db [IN] New schema name + @param table_name [IN] New table name + + @return false Success + true Error +*/ +bool THD::rename_temporary_table(TABLE *table, + const char *db, + const char *table_name) +{ + DBUG_ENTER("THD::rename_temporary_table"); + + char *key; + uint key_length; + + TABLE_SHARE *share= table->s; + + if (!(key= (char *) alloc_root(&share->mem_root, MAX_DBKEY_LENGTH))) + { + DBUG_RETURN(true); + } + + /* + Temporary tables are renamed by simply changing their table definition key. + */ + key_length= create_tmp_table_def_key(key, db, table_name); + share->set_table_cache_key(key, key_length); + + DBUG_RETURN(false); +} + + +/** + Drop a temporary table. + + Try to locate the table in the list of open temporary tables. + If the table is found: + - If the table is locked with LOCK TABLES or by prelocking, + unlock it and remove it from the list of locked tables + (THD::lock). Currently only transactional temporary tables + are locked. + - Close the temporary table, remove its .FRM. + - Remove the table share from the list of temporary table shares. + + This function is used to drop user temporary tables, as well as + internal tables created in CREATE TEMPORARY TABLE ... SELECT + or ALTER TABLE. + + @param table [IN] Temporary table to be deleted + @param is_trans [OUT] Is set to the type of the table: + transactional (e.g. innodb) as true or + non-transactional (e.g. myisam) as false. + @paral delete_table [IN] Whether to delete the table files? + + @return false Table was dropped + true Error +*/ +bool THD::drop_temporary_table(TABLE *table, + bool *is_trans, + bool delete_table) +{ + DBUG_ENTER("THD::drop_temporary_table"); + + TMP_TABLE_SHARE *share; + TABLE *tab; + bool result= false; + bool locked; + + DBUG_ASSERT(table); + DBUG_PRINT("tmptable", ("Dropping table: '%s'.'%s'", + table->s->db.str, table->s->table_name.str)); + + locked= lock_temporary_tables(); + + share= tmp_table_share(table); + + /* Table might be in use by some outer statement. */ + All_share_tables_list::Iterator it(share->all_tmp_tables); + while ((tab= it++)) + { + if (tab != table && tab->query_id != 0) + { + /* Found a table instance in use. This table cannot be be dropped. */ + my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr()); + result= true; + goto end; + } + } + + if (is_trans) + *is_trans= table->file->has_transactions(); + + /* + Iterate over the list of open tables and close them. + */ + while ((tab= share->all_tmp_tables.pop_front())) + { + free_temporary_table(tab); + } + + DBUG_ASSERT(temporary_tables); + + /* Remove the TABLE_SHARE from the list of temporary tables. */ + temporary_tables->remove(share); + + /* Free the TABLE_SHARE and/or delete the files. */ + free_tmp_table_share(share, delete_table); + +end: + if (locked) + { + DBUG_ASSERT(m_tmp_tables_locked); + unlock_temporary_tables(); + } + + DBUG_RETURN(result); +} + + +/** + Delete the temporary table files. + + @param base [IN] Handlerton for table to be deleted. + @param path [IN] Path to the table to be deleted (i.e. path + to its .frm without an extension). + + @return false Success + true Error +*/ +bool THD::rm_temporary_table(handlerton *base, const char *path) +{ + DBUG_ENTER("THD::rm_temporary_table"); + + bool error= false; + handler *file; + char frm_path[FN_REFLEN + 1]; + + strxnmov(frm_path, sizeof(frm_path) - 1, path, reg_ext, NullS); + if (mysql_file_delete(key_file_frm, frm_path, MYF(0))) + { + error= true; + } + file= get_new_handler((TABLE_SHARE*) 0, current_thd->mem_root, base); + if (file && file->ha_delete_table(path)) + { + error= true; + sql_print_warning("Could not remove temporary table: '%s', error: %d", + path, my_errno); + } + + delete file; + DBUG_RETURN(error); +} + + +/** + Mark all temporary tables which were used by the current statement or + sub-statement as free for reuse, but only if the query_id can be cleared. + + @remark For temp tables associated with a open SQL HANDLER the query_id + is not reset until the HANDLER is closed. +*/ +void THD::mark_tmp_tables_as_free_for_reuse() +{ + DBUG_ENTER("THD::mark_tmp_tables_as_free_for_reuse"); + + TMP_TABLE_SHARE *share; + TABLE *table; + bool locked; + + if (query_id == 0) + { + /* + Thread has not executed any statement and has not used any + temporary tables. + */ + DBUG_VOID_RETURN; + } + + if (!has_temporary_tables()) + { + DBUG_VOID_RETURN; + } + + locked= lock_temporary_tables(); + + All_tmp_tables_list::Iterator it(*temporary_tables); + while ((share= it++)) + { + All_share_tables_list::Iterator tables_it(share->all_tmp_tables); + while ((table= tables_it++)) + { + if ((table->query_id == query_id) && !table->open_by_handler) + { + mark_tmp_table_as_free_for_reuse(table); + } + } + } + + if (locked) + { + DBUG_ASSERT(m_tmp_tables_locked); + unlock_temporary_tables(); + } + + if (rgi_slave) + { + /* + Temporary tables are shared with other by sql execution threads. + As a safety measure, clear the pointer to the common area. + */ + temporary_tables= NULL; + } + + DBUG_VOID_RETURN; +} + + +/** + Reset a single temporary table. Effectively this "closes" one temporary + table in a session. + + @param table Temporary table + + @return void +*/ +void THD::mark_tmp_table_as_free_for_reuse(TABLE *table) +{ + DBUG_ENTER("THD::mark_tmp_table_as_free_for_reuse"); + + DBUG_ASSERT(table->s->tmp_table); + + table->query_id= 0; + table->file->ha_reset(); + + /* Detach temporary MERGE children from temporary parent. */ + DBUG_ASSERT(table->file); + table->file->extra(HA_EXTRA_DETACH_CHILDREN); + + /* + Reset temporary table lock type to it's default value (TL_WRITE). + + Statements such as INSERT INTO .. SELECT FROM tmp, CREATE TABLE + .. SELECT FROM tmp and UPDATE may under some circumstances modify + the lock type of the tables participating in the statement. This + isn't a problem for non-temporary tables since their lock type is + reset at every open, but the same does not occur for temporary + tables for historical reasons. + + Furthermore, the lock type of temporary tables is not really that + important because they can only be used by one query at a time. + Nonetheless, it's safer from a maintenance point of view to reset + the lock type of this singleton TABLE object as to not cause problems + when the table is reused. + + Even under LOCK TABLES mode its okay to reset the lock type as + LOCK TABLES is allowed (but ignored) for a temporary table. + */ + table->reginfo.lock_type= TL_WRITE; + DBUG_VOID_RETURN; +} + + +/** + If its a replication slave, report whether slave temporary tables + exist (Relay_log_info::save_temporary_tables) or report about THD + temporary table (Open_tables_state::temporary_tables) otherwise. + + @return false Temporary tables exist + true No temporary table exist +*/ +inline bool THD::has_temporary_tables() +{ + DBUG_ENTER("THD::has_temporary_tables"); + bool result= (rgi_slave + ? (rgi_slave->rli->save_temporary_tables && + !rgi_slave->rli->save_temporary_tables->is_empty()) + : (has_thd_temporary_tables())); + DBUG_RETURN(result); +} + + +/** + Create a table definition key. + + @param key [OUT] Buffer for the key to be created (must + be of size MAX_DBKRY_LENGTH) + @param db [IN] Database name + @param table_name [IN] Table name + + @return Key length. + + @note + The table key is create from: + db + \0 + table_name + \0 + + Additionally, we add the following to make each temporary table unique on + the slave. + + 4 bytes of master thread id + 4 bytes of pseudo thread id +*/ +uint THD::create_tmp_table_def_key(char *key, const char *db, + const char *table_name) +{ + DBUG_ENTER("THD::create_tmp_table_def_key"); + + uint key_length; + + key_length= tdc_create_key(key, db, table_name); + int4store(key + key_length, variables.server_id); + int4store(key + key_length + 4, variables.pseudo_thread_id); + key_length += TMP_TABLE_KEY_EXTRA; + + DBUG_RETURN(key_length); +} + + +/** + Create a temporary table. + + @param hton [IN] Handlerton + @param frm [IN] Binary frm image + @param path [IN] File path (without extension) + @param db [IN] Schema name + @param table_name [IN] Table name + + @return Success A pointer to table share object + Failure NULL +*/ +TMP_TABLE_SHARE *THD::create_temporary_table(handlerton *hton, + LEX_CUSTRING *frm, + const char *path, + const char *db, + const char *table_name) +{ + DBUG_ENTER("THD::create_temporary_table"); + + TMP_TABLE_SHARE *share; + char key_cache[MAX_DBKEY_LENGTH]; + char *saved_key_cache; + char *tmp_path; + uint key_length; + bool locked; + int res; + + /* Temporary tables are not safe for parallel replication. */ + if (rgi_slave && + rgi_slave->is_parallel_exec && + wait_for_prior_commit()) + DBUG_RETURN(NULL); + + /* Create the table definition key for the temporary table. */ + key_length= create_tmp_table_def_key(key_cache, db, table_name); + + if (!(share= (TMP_TABLE_SHARE *) my_malloc(sizeof(TMP_TABLE_SHARE) + + strlen(path) + 1 + key_length, + MYF(MY_WME)))) + { + DBUG_RETURN(NULL); /* Out of memory */ + } + + tmp_path= (char *)(share + 1); + saved_key_cache= strmov(tmp_path, path) + 1; + memcpy(saved_key_cache, key_cache, key_length); + + init_tmp_table_share(this, share, saved_key_cache, key_length, + strend(saved_key_cache) + 1, tmp_path); + + share->db_plugin= ha_lock_engine(this, hton); + + /* + Prefer using frm image over file. The image might not be available in + ALTER TABLE, when the discovering engine took over the ownership (see + TABLE::read_frm_image). + */ + res= (frm->str) + ? share->init_from_binary_frm_image(this, false, frm->str, frm->length) + : open_table_def(this, share, GTS_TABLE | GTS_USE_DISCOVERY); + + if (res) + { + /* + No need to lock share->mutex as this is not needed for temporary tables. + */ + free_table_share(share); + my_free(share); + DBUG_RETURN(NULL); + } + + share->m_psi= PSI_CALL_get_table_share(true, share); + + locked= lock_temporary_tables(); + + /* Initialize the all_tmp_tables list. */ + share->all_tmp_tables.empty(); + + /* + We need to alloc & initialize temporary_tables if this happens + to be the very first temporary table. + */ + if (!temporary_tables) + { + if ((temporary_tables= + (All_tmp_tables_list *) my_malloc(sizeof(All_tmp_tables_list), + MYF(MY_WME)))) + { + temporary_tables->empty(); + } + else + { + DBUG_RETURN(NULL); /* Out of memory */ + } + } + + /* Add share to the head of the temporary table share list. */ + temporary_tables->push_front(share); + + if (locked) + { + DBUG_ASSERT(m_tmp_tables_locked); + unlock_temporary_tables(); + } + + DBUG_RETURN(share); +} + + +/** + Find a table with the specified key. + + @param key [IN] Key + @param key_length [IN] Key length + @param state [IN] Open table state to look for + + @return Success Pointer to the table instance. + Failure NULL +*/ +TABLE *THD::find_temporary_table(const char *key, uint key_length, + Temporary_table_state state) +{ + DBUG_ENTER("THD::find_temporary_table"); + + TMP_TABLE_SHARE *share; + TABLE *table; + TABLE *result= NULL; + bool locked; + + locked= lock_temporary_tables(); + + All_tmp_tables_list::Iterator it(*temporary_tables); + while ((share= it++)) + { + if (share->table_cache_key.length == key_length && + !(memcmp(share->table_cache_key.str, key, key_length))) + { + /* A matching TMP_TABLE_SHARE is found. */ + All_share_tables_list::Iterator tables_it(share->all_tmp_tables); + + while ((table= tables_it++)) + { + switch (state) + { + case TMP_TABLE_IN_USE: + if (table->query_id > 0) + { + result= table; + goto done; + } + break; + case TMP_TABLE_NOT_IN_USE: + if (table->query_id == 0) + { + result= table; + goto done; + } + break; + case TMP_TABLE_ANY: + { + result= table; + goto done; + } + break; + default: /* Invalid */ + DBUG_ASSERT(0); + goto done; + } + } + } + } + +done: + if (locked) + { + DBUG_ASSERT(m_tmp_tables_locked); + unlock_temporary_tables(); + } + + DBUG_RETURN(result); +} + + + +/** + Open a table from the specified TABLE_SHARE with the given alias. + + @param share [IN] Table share + @param alias [IN] Table alias + @param open_in_engine [IN] Whether open table in SE + + @return Success A pointer to table object + Failure NULL +*/ +TABLE *THD::open_temporary_table(TMP_TABLE_SHARE *share, + const char *alias, + bool open_in_engine) +{ + DBUG_ENTER("THD::open_temporary_table"); + + TABLE *table; + + if (!(table= (TABLE *) my_malloc(sizeof(TABLE), MYF(MY_WME)))) + { + DBUG_RETURN(NULL); /* Out of memory */ + } + + if (open_table_from_share(this, share, alias, + (open_in_engine) ? + (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | + HA_GET_INDEX) : 0, + (uint) (READ_KEYINFO | COMPUTE_TYPES | + EXTRA_RECORD), + ha_open_options, + table, + open_in_engine ? false : true)) + { + my_free(table); + DBUG_RETURN(NULL); + } + + table->reginfo.lock_type= TL_WRITE; /* Simulate locked */ + table->grant.privilege= TMP_TABLE_ACLS; + share->tmp_table= (table->file->has_transactions() ? + TRANSACTIONAL_TMP_TABLE : NON_TRANSACTIONAL_TMP_TABLE); + + table->pos_in_table_list= 0; + table->query_id= query_id; + + /* Add table to the head of table list. */ + share->all_tmp_tables.push_front(table); + + /* Increment Slave_open_temp_table_definitions status variable count. */ + if (rgi_slave) + { + thread_safe_increment32(&slave_open_temp_tables); + } + + DBUG_PRINT("tmptable", ("Opened table: '%s'.'%s' 0x%lx", table->s->db.str, + table->s->table_name.str, (long) table)); + DBUG_RETURN(table); +} + + +/** + Find a reusable table in the open table list using the specified TABLE_LIST. + + @param tl [IN] Table list + @param out_table [OUT] Pointer to the requested TABLE object + + @return Success false + Failure true +*/ +bool THD::find_and_use_tmp_table(const TABLE_LIST *tl, + TABLE **out_table) +{ + DBUG_ENTER("THD::find_and_use_tmp_table"); + + char key[MAX_DBKEY_LENGTH]; + uint key_length; + bool result; + + key_length= create_tmp_table_def_key(key, tl->get_db_name(), + tl->get_table_name()); + result= + use_temporary_table(find_temporary_table(key, key_length, + TMP_TABLE_NOT_IN_USE), + out_table); + + DBUG_RETURN(result); +} + +/** + Mark table as in-use. + + @param table [IN] Table to be marked in-use + @param out_table [OUT] Pointer to the specified table + + @return false Success + true Error +*/ +bool THD::use_temporary_table(TABLE *table, TABLE **out_table) +{ + DBUG_ENTER("THD::use_temporary_table"); + + *out_table= table; + if (!table) + 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 (rgi_slave && + rgi_slave->is_parallel_exec && + wait_for_prior_commit()) + DBUG_RETURN(true); + + /* + We need to set the THD as it may be different in case of + parallel replication + */ + if (table->in_use != this) + { + table->in_use= this; + } + + DBUG_RETURN(false); +} + + +/** + Close a temporary table. + + @param table [IN] Table handle + + @return void +*/ +void THD::close_temporary_table(TABLE *table) +{ + DBUG_ENTER("THD::close_temporary_table"); + + DBUG_PRINT("tmptable", ("closing table: '%s'.'%s' 0x%lx alias: '%s'", + table->s->db.str, table->s->table_name.str, + (long) table, table->alias.c_ptr())); + + closefrm(table); + my_free(table); + + if (rgi_slave) + { + /* Natural invariant of temporary_tables */ + DBUG_ASSERT(slave_open_temp_tables || !temporary_tables); + /* Decrement Slave_open_temp_table_definitions status variable count. */ + thread_safe_decrement32(&slave_open_temp_tables); + } + + DBUG_VOID_RETURN; +} + + +/** + Write query log events with "DROP TEMPORARY TABLES .." for each pseudo + thread to the binary log. + + @return false Success + true Error +*/ +bool THD::log_events_and_free_tmp_shares() +{ + DBUG_ENTER("THD::log_events_and_free_tmp_shares"); + + DBUG_ASSERT(!rgi_slave); + + TMP_TABLE_SHARE *share; + TMP_TABLE_SHARE *sorted; + TMP_TABLE_SHARE *prev_sorted; + // Assume thd->variables.option_bits has OPTION_QUOTE_SHOW_CREATE. + bool was_quote_show= true; + bool error= false; + bool found_user_tables= false; + // Better add "IF EXISTS" in case a RESET MASTER has been done. + const char stub[]= "DROP /*!40005 TEMPORARY */ TABLE IF EXISTS "; + char buf[FN_REFLEN]; + + String s_query(buf, sizeof(buf), system_charset_info); + s_query.copy(stub, sizeof(stub) - 1, system_charset_info); + + /* + Insertion sort of temporary tables by pseudo_thread_id to build ordered + list of sublists of equal pseudo_thread_id. + */ + All_tmp_tables_list::Iterator it_sorted(*temporary_tables); + All_tmp_tables_list::Iterator it_unsorted(*temporary_tables); + uint sorted_count= 0; + while((share= it_unsorted++)) + { + if (IS_USER_TABLE(share)) + { + prev_sorted= NULL; + + if (!found_user_tables) found_user_tables= true; + + for (uint i= 0; i < sorted_count; i ++) + { + sorted= it_sorted ++; + + if (!IS_USER_TABLE(sorted) || + (tmpkeyval(sorted) > tmpkeyval(share))) + { + /* + Insert this share before the current element in + the sorted part of the list. + */ + temporary_tables->remove(share); + + if (prev_sorted) + { + temporary_tables->insert_after(prev_sorted, share); + } + else + { + temporary_tables->push_front(share); + } + break; + } + prev_sorted= sorted; + } + it_sorted.rewind(); + } + sorted_count ++; + } + + /* + We always quote db & table names. + */ + if (found_user_tables && + !(was_quote_show= MY_TEST(variables.option_bits & + OPTION_QUOTE_SHOW_CREATE))) + { + variables.option_bits |= OPTION_QUOTE_SHOW_CREATE; + } + + /* + Scan sorted temporary tables to generate sequence of DROP. + */ + share= temporary_tables->pop_front(); + while (share) + { + if (IS_USER_TABLE(share)) + { + bool save_thread_specific_used= thread_specific_used; + my_thread_id save_pseudo_thread_id= variables.pseudo_thread_id; + char db_buf[FN_REFLEN]; + String db(db_buf, sizeof(db_buf), system_charset_info); + + /* + Set pseudo_thread_id to be that of the processed table. + */ + variables.pseudo_thread_id= tmpkeyval(share); + + db.copy(share->db.str, share->db.length, system_charset_info); + /* + Reset s_query() if changed by previous loop. + */ + s_query.length(sizeof(stub) - 1); + + /* + Loop forward through all tables that belong to a common database + within the sublist of common pseudo_thread_id to create single + DROP query. + */ + for (; + share && IS_USER_TABLE(share) && + tmpkeyval(share) == variables.pseudo_thread_id && + share->db.length == db.length() && + memcmp(share->db.str, db.ptr(), db.length()) == 0; + /* Get the next TABLE_SHARE in the list. */ + share= temporary_tables->pop_front()) + { + /* + We are going to add ` around the table names and possible more + due to special characters. + */ + append_identifier(this, &s_query, share->table_name.str, + share->table_name.length); + s_query.append(','); + rm_temporary_table(share->db_type(), share->path.str); + free_table_share(share); + my_free(share); + } + + clear_error(); + CHARSET_INFO *cs_save= variables.character_set_client; + variables.character_set_client= system_charset_info; + thread_specific_used= true; + + Query_log_event qinfo(this, s_query.ptr(), + s_query.length() - 1 /* to remove trailing ',' */, + false, true, false, 0); + qinfo.db= db.ptr(); + qinfo.db_len= db.length(); + variables.character_set_client= cs_save; + + get_stmt_da()->set_overwrite_status(true); + if ((error= (mysql_bin_log.write(&qinfo) || error))) + { + /* + If we're here following THD::cleanup, thence the connection + has been closed already. So lets print a message to the + error log instead of pushing yet another error into the + stmt_da. + + Also, we keep the error flag so that we propagate the error + up in the stack. This way, if we're the SQL thread we notice + that THD::close_tables failed. (Actually, the SQL + thread only calls THD::close_tables while applying + old Start_log_event_v3 events.) + */ + sql_print_error("Failed to write the DROP statement for " + "temporary tables to binary log"); + } + + get_stmt_da()->set_overwrite_status(false); + variables.pseudo_thread_id= save_pseudo_thread_id; + thread_specific_used= save_thread_specific_used; + } + else + { + free_tmp_table_share(share, true); + /* Get the next TABLE_SHARE in the list. */ + share= temporary_tables->pop_front(); + } + } + + if (!was_quote_show) + { + /* + Restore option. + */ + variables.option_bits&= ~OPTION_QUOTE_SHOW_CREATE; + } + + DBUG_RETURN(error); +} + + +/** + Delete the files and free the specified table share. + + @param share [IN] TABLE_SHARE to free + @param delete_table [IN] Whether to delete the table files? + + @return void +*/ +void THD::free_tmp_table_share(TMP_TABLE_SHARE *share, + bool delete_table) +{ + DBUG_ENTER("THD::free_tmp_table_share"); + + if (delete_table) + { + rm_temporary_table(share->db_type(), share->path.str); + } + free_table_share(share); + my_free(share); + + DBUG_VOID_RETURN; +} + + +/** + Free the specified table object. + + @param table [IN] Table object to free. + + @return void +*/ +void THD::free_temporary_table(TABLE *table) +{ + DBUG_ENTER("THD::free_temporary_table"); + + /* + If LOCK TABLES list is not empty and contains this table, unlock the table + and remove the table from this list. + */ + mysql_lock_remove(this, lock, table); + + close_temporary_table(table); + + DBUG_VOID_RETURN; +} + + +/** + On replication slave, acquire the Relay_log_info's data_lock and use slave + temporary tables. + + @return true Lock acquired + false Lock wasn't acquired +*/ +bool THD::lock_temporary_tables() +{ + DBUG_ENTER("THD::lock_temporary_tables"); + + /* Do not proceed if a lock has already been taken. */ + if (m_tmp_tables_locked) + { + DBUG_RETURN(false); + } + + if (rgi_slave) + { + mysql_mutex_lock(&rgi_slave->rli->data_lock); + temporary_tables= rgi_slave->rli->save_temporary_tables; + m_tmp_tables_locked= true; + } + + DBUG_RETURN(m_tmp_tables_locked); +} + + +/** + On replication slave, release the Relay_log_info::data_lock previously + acquired to use slave temporary tables. + + @return void +*/ +void THD::unlock_temporary_tables() +{ + DBUG_ENTER("THD::unlock_temporary_tables"); + + if (!m_tmp_tables_locked) + { + DBUG_VOID_RETURN; + } + + if (rgi_slave) + { + rgi_slave->rli->save_temporary_tables= temporary_tables; + temporary_tables= NULL; /* Safety */ + mysql_mutex_unlock(&rgi_slave->rli->data_lock); + m_tmp_tables_locked= false; + } + + DBUG_VOID_RETURN; +} + diff --git a/sql/wsrep_applier.cc b/sql/wsrep_applier.cc index cf1feb49f41..6086748b6d0 100644 --- a/sql/wsrep_applier.cc +++ b/sql/wsrep_applier.cc @@ -62,7 +62,6 @@ err: #include "transaction.h" // trans_commit(), trans_rollback() #include "rpl_rli.h" // class Relay_log_info; -#include "sql_base.h" // close_temporary_table() void wsrep_set_apply_format(THD* thd, Format_description_log_event* ev) { @@ -277,14 +276,11 @@ wsrep_cb_status_t wsrep_apply_cb(void* const ctx, wsrep_dump_rbr_buf_with_header(thd, buf, buf_len); } - TABLE *tmp; - while ((tmp = thd->temporary_tables)) + if (thd->has_thd_temporary_tables()) { - WSREP_DEBUG("Applier %lld, has temporary tables: %s.%s", - (longlong) thd->thread_id, - (tmp->s) ? tmp->s->db.str : "void", - (tmp->s) ? tmp->s->table_name.str : "void"); - close_temporary_table(thd, tmp, 1, 1); + WSREP_DEBUG("Applier %lld has temporary tables. Closing them now..", + thd->thread_id); + thd->close_temporary_tables(); } return rcode; diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index 54fb484bad0..9f7c3c9baa7 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -1085,8 +1085,6 @@ static bool wsrep_prepare_keys_for_isolation(THD* thd, ka->keys= 0; ka->keys_len= 0; - extern TABLE* find_temporary_table(THD*, const TABLE_LIST*); - if (db || table) { TABLE_LIST tmp_table; @@ -1098,7 +1096,7 @@ static bool wsrep_prepare_keys_for_isolation(THD* thd, (table) ? table : "", MDL_INTENTION_EXCLUSIVE, MDL_STATEMENT); - if (!table || !find_temporary_table(thd, &tmp_table)) + if (!table || !thd->find_temporary_table(&tmp_table)) { if (!(ka->keys= (wsrep_key_t*)my_malloc(sizeof(wsrep_key_t), MYF(0)))) { @@ -1126,7 +1124,7 @@ static bool wsrep_prepare_keys_for_isolation(THD* thd, for (const TABLE_LIST* table= table_list; table; table= table->next_global) { - if (!find_temporary_table(thd, table)) + if (!thd->find_temporary_table(table)) { wsrep_key_t* tmp; tmp= (wsrep_key_t*)my_realloc( @@ -2443,26 +2441,13 @@ bool wsrep_create_like_table(THD* thd, TABLE_LIST* table, TABLE_LIST* src_table, HA_CREATE_INFO *create_info) { - TABLE *tmp_table; - bool is_tmp_table= FALSE; - - for (tmp_table= thd->temporary_tables; tmp_table; tmp_table=tmp_table->next) - { - if (!strcmp(src_table->db, tmp_table->s->db.str) && - !strcmp(src_table->table_name, tmp_table->s->table_name.str)) - { - is_tmp_table= TRUE; - break; - } - } if (create_info->tmp_table()) { - /* CREATE TEMPORARY TABLE LIKE must be skipped from replication */ WSREP_DEBUG("CREATE TEMPORARY TABLE LIKE... skipped replication\n %s", thd->query()); } - else if (!is_tmp_table) + else if (!(thd->find_temporary_table(src_table))) { /* this is straight CREATE TABLE LIKE... eith no tmp tables */ WSREP_TO_ISOLATION_BEGIN(table->db, table->table_name, NULL); @@ -2477,7 +2462,7 @@ bool wsrep_create_like_table(THD* thd, TABLE_LIST* table, bzero((void*) &tbl, sizeof(tbl)); tbl.db= src_table->db; tbl.table_name= tbl.alias= src_table->table_name; - tbl.table= tmp_table; + tbl.table= src_table->table; char buf[2048]; String query(buf, sizeof(buf), system_charset_info); query.length(0); // Have to zero it since constructor doesn't diff --git a/sql/wsrep_thd.cc b/sql/wsrep_thd.cc index 3835c925745..36768a37973 100644 --- a/sql/wsrep_thd.cc +++ b/sql/wsrep_thd.cc @@ -360,13 +360,10 @@ static void wsrep_replication_process(THD *thd) mysql_cond_broadcast(&COND_thread_count); mysql_mutex_unlock(&LOCK_thread_count); - TABLE *tmp; - while ((tmp = thd->temporary_tables)) + if(thd->has_thd_temporary_tables()) { - WSREP_WARN("Applier %lld, has temporary tables at exit: %s.%s", - (longlong) thd->thread_id, - (tmp->s) ? tmp->s->db.str : "void", - (tmp->s) ? tmp->s->table_name.str : "void"); + WSREP_WARN("Applier %lld has temporary tables at exit.", + thd->thread_id); } wsrep_return_from_bf_mode(thd, &shadow); DBUG_VOID_RETURN; |