diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
commit | a175314c3e5827eb193872241446f2f8f5c9d33c (patch) | |
tree | cd3d60ca99ae00829c52a6ca79150a5b6e62528b /sql/temporary_tables.cc | |
parent | Initial commit. (diff) | |
download | mariadb-10.5-upstream.tar.xz mariadb-10.5-upstream.zip |
Adding upstream version 1:10.5.12.upstream/1%10.5.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | sql/temporary_tables.cc | 1581 |
1 files changed, 1581 insertions, 0 deletions
diff --git a/sql/temporary_tables.cc b/sql/temporary_tables.cc new file mode 100644 index 00000000..981958ab --- /dev/null +++ b/sql/temporary_tables.cc @@ -0,0 +1,1581 @@ +/* + Copyright (c) 2016, 2021, 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 "mariadb.h" +#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 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 object + Failure NULL +*/ +TABLE *THD::create_and_open_tmp_table(LEX_CUSTRING *frm, + const char *path, + const char *db, + const char *table_name, + bool open_internal_tables) +{ + DBUG_ENTER("THD::create_and_open_tmp_table"); + + TMP_TABLE_SHARE *share; + TABLE *table= NULL; + + if ((share= create_temporary_table(frm, path, db, table_name))) + { + open_options|= HA_OPEN_FOR_CREATE; + table= open_temporary_table(share, table_name); + open_options&= ~HA_OPEN_FOR_CREATE; + + /* + 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(0); + } + + /* Open any related tables */ + if (open_internal_tables && table->internal_tables && + open_and_lock_internal_tables(table, true)) + { + drop_temporary_table(table, NULL, false); + DBUG_RETURN(0); + } + } + + 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 + @param state [IN] State of temp table to open + + @return Success Pointer to first used table instance. + Failure NULL +*/ +TABLE *THD::find_temporary_table(const char *db, + const char *table_name, + Temporary_table_state state) +{ + 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, state); + 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, + Temporary_table_state state) +{ + DBUG_ENTER("THD::find_temporary_table"); + TABLE *table= find_temporary_table(tl->get_db_name(), tl->get_table_name(), + state); + 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, size_t 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.str, tl->table_name.str)); + + TMP_TABLE_SHARE *share; + TABLE *table= NULL; + + /* + 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); + DBUG_ASSERT(!tl->schema_table); + DBUG_ASSERT(has_temporary_tables()); + + if (tl->open_type == OT_BASE_ONLY) + { + DBUG_PRINT("info", ("skip_temporary is set or no temporary tables")); + DBUG_RETURN(false); + } + + if (!tl->db.str) + { + DBUG_PRINT("info", + ("Table reference to a temporary table must have database set")); + 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. + */ + + if (rgi_slave && + rgi_slave->is_parallel_exec && + find_temporary_table(tl) && + wait_for_prior_commit()) + DBUG_RETURN(true); + + /* + 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()); + /* + 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. + */ + if (table && rgi_slave && + rgi_slave->is_parallel_exec && + wait_for_prior_commit()) + DBUG_RETURN(true); + + if (!table && is_error()) + DBUG_RETURN(true); // Error when opening table + } + + 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.str, tl->table_name.str); + DBUG_RETURN(true); + } + DBUG_RETURN(false); + } + +#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) +{ + TABLE_LIST *first_not_own; + DBUG_ENTER("THD::open_temporary_tables"); + + if (!has_temporary_tables()) + DBUG_RETURN(0); + + 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())) + { + table->file->extra(HA_EXTRA_PREPARE_FOR_DROP); + 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 LEX_CSTRING *db, + const LEX_CSTRING *table_name) +{ + char *key; + uint key_length; + TABLE_SHARE *share= table->s; + DBUG_ENTER("THD::rename_temporary_table"); + + 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->str, table_name->str); + 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())) + { + /* + We need to set the THD as it may be different in case of + parallel replication + */ + tab->in_use= this; + if (delete_table) + tab->file->extra(HA_EXTRA_PREPARE_FOR_DROP); + 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; + 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(MY_WME | MY_IGNORE_ENOENT))) + error= true; + if (base->drop_table(base, path) > 0) + { + error= true; + sql_print_warning("Could not remove temporary table: '%s', error: %d", + path, my_errno); + } + + 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; +} + + +/** + Remove and return the specified table's TABLE_SHARE from the temporary + tables list. + + @param table [IN] Table + + @return TMP_TABLE_SHARE of the specified table. +*/ +TMP_TABLE_SHARE *THD::save_tmp_table_share(TABLE *table) +{ + DBUG_ENTER("THD::save_tmp_table_share"); + + TMP_TABLE_SHARE *share; + + lock_temporary_tables(); + DBUG_ASSERT(temporary_tables); + share= tmp_table_share(table); + temporary_tables->remove(share); + unlock_temporary_tables(); + + DBUG_RETURN(share); +} + + +/** + Add the specified TMP_TABLE_SHARE to the temporary tables list. + + @param share [IN] Table share + + @return void +*/ +void THD::restore_tmp_table_share(TMP_TABLE_SHARE *share) +{ + DBUG_ENTER("THD::restore_tmp_table_share"); + + lock_temporary_tables(); + DBUG_ASSERT(temporary_tables); + temporary_tables->push_front(share); + unlock_temporary_tables(); + + 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 +*/ +bool THD::has_temporary_tables() +{ + DBUG_ENTER("THD::has_temporary_tables"); + bool result= +#ifdef HAVE_REPLICATION + rgi_slave ? (rgi_slave->rli->save_temporary_tables && + !rgi_slave->rli->save_temporary_tables->is_empty()) : +#endif + 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 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(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(key_memory_table_share, + 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); + + /* + 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(key_memory_table_share, + 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); + + bool found= false; + while (!found && (table= tables_it++)) + { + switch (state) + { + case TMP_TABLE_IN_USE: found= table->query_id > 0; break; + case TMP_TABLE_NOT_IN_USE: found= table->query_id == 0; break; + case TMP_TABLE_ANY: found= true; break; + } + } + if (table && unlikely(table->needs_reopen())) + { + share->all_tmp_tables.remove(table); + free_temporary_table(table); + it.rewind(); + continue; + } + result= table; + break; + } + } + + 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 + + @return Success A pointer to table object + Failure NULL +*/ +TABLE *THD::open_temporary_table(TMP_TABLE_SHARE *share, + const char *alias_arg) +{ + TABLE *table; + LEX_CSTRING alias= {alias_arg, strlen(alias_arg) }; + DBUG_ENTER("THD::open_temporary_table"); + + + if (!(table= (TABLE *) my_malloc(key_memory_TABLE, sizeof(TABLE), + MYF(MY_WME)))) + { + DBUG_RETURN(NULL); /* Out of memory */ + } + + if (open_table_from_share(this, share, &alias, + (uint) HA_OPEN_KEYFILE, + EXTRA_RECORD, + (ha_open_options | + (open_options & HA_OPEN_FOR_CREATE)), + table, false)) + { + my_free(table); + DBUG_RETURN(NULL); + } + + table->reginfo.lock_type= TL_WRITE; /* Simulate locked */ + table->grant.privilege= TMP_TABLE_ACLS; + table->query_id= query_id; + share->tmp_table= (table->file->has_transaction_manager() ? + TRANSACTIONAL_TMP_TABLE : NON_TRANSACTIONAL_TMP_TABLE); + share->not_usable_by_query_cache= 1; + + /* 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) + slave_open_temp_tables++; + + DBUG_PRINT("tmptable", ("Opened table: '%s'.'%s table: %p", + table->s->db.str, + table->s->table_name.str, 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; + + /* The following can happen if find_temporary_table() returns NULL */ + 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 + */ + 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'%p alias: '%s'", + table->s->db.str, table->s->table_name.str, + 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. */ + 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); + bool at_least_one_create_logged; + + /* + 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 (at_least_one_create_logged= false; + 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()) + { + if (share->table_creation_was_logged) + { + at_least_one_create_logged= true; + /* + We are going to add ` around the table names and possible more + due to special characters. + */ + append_identifier(this, &s_query, &share->table_name); + s_query.append(','); + } + rm_temporary_table(share->db_type(), share->path.str); + free_table_share(share); + my_free(share); + } + + if (at_least_one_create_logged) + { + 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); + transaction->stmt.mark_dropped_temp_table(); + bool error2= mysql_bin_log.write(&qinfo); + if (unlikely(error|= error2)) + { + /* + 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); + } + +#ifdef HAVE_REPLICATION + if (rgi_slave) + { + mysql_mutex_lock(&rgi_slave->rli->data_lock); + temporary_tables= rgi_slave->rli->save_temporary_tables; + m_tmp_tables_locked= true; + } +#endif + + 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; + } + +#ifdef HAVE_REPLICATION + 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; + } +#endif + + DBUG_VOID_RETURN; +} + + +/** + Close unused TABLE instances for given temporary table. + + @param tl [IN] TABLE_LIST + + Initial use case was TRUNCATE, which expects only one instance (which is used + by TRUNCATE itself) to be open. Most probably some ALTER TABLE variants and + REPAIR may have similar expectations. +*/ + +void THD::close_unused_temporary_table_instances(const TABLE_LIST *tl) +{ + TMP_TABLE_SHARE *share= find_tmp_table_share(tl); + + if (share) + { + All_share_tables_list::Iterator tables_it(share->all_tmp_tables); + + while (TABLE *table= tables_it++) + { + if (table->query_id == 0) + { + /* Note: removing current list element doesn't invalidate iterator. */ + share->all_tmp_tables.remove(table); + free_temporary_table(table); + } + } + } +} |