summaryrefslogtreecommitdiff
path: root/sql/temporary_tables.cc
diff options
context:
space:
mode:
authorNirbhay Choubey <nirbhay@mariadb.com>2016-06-10 16:19:59 -0400
committerNirbhay Choubey <nirbhay@mariadb.com>2016-06-10 18:39:43 -0400
commit7305be2f7e724e5e62961606794beab199d79045 (patch)
tree403bd132ee82a16946e3208f5d535de6e5945b80 /sql/temporary_tables.cc
parent547511153fb1f59688752aa5524ae411b5960c92 (diff)
downloadmariadb-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/temporary_tables.cc')
-rw-r--r--sql/temporary_tables.cc1499
1 files changed, 1499 insertions, 0 deletions
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;
+}
+