← Back to team overview

maria-developers team mailing list archive

Re: [Commits] c285dbe: MDEV-5535: Cannot reopen temporary table

 

Hi, Nirbhay!

I like the approach, there were only few comments, but still there were
some :)

See below.

On May 16, Nirbhay Choubey wrote:
> revision-id: c285dbece2881e1d864bb758d77edc899d568c04 (mariadb-10.1.8-82-gc285dbe)
> parent(s): 222ca736f737e888115c732c8ecad84faf0a2529
> author: Nirbhay Choubey
> committer: Nirbhay Choubey
> timestamp: 2016-05-16 23:59:10 -0400
> message:
> 
> MDEV-5535: Cannot reopen temporary table

Would be nice to have a somewhat more verbose commit comment here :)

> diff --git a/mysql-test/t/reopen_temp_table-master.test b/mysql-test/t/reopen_temp_table-master.test
> new file mode 100644
> index 0000000..5ac2ca8
> --- /dev/null
> +++ b/mysql-test/t/reopen_temp_table-master.test
> @@ -0,0 +1 @@
> +--tmpdir=$MYSQLTEST_VARDIR//tmp

Eh... I suppose you mean reopen_temp_table-master.opt
And, in fact, you can simply use reopen_temp_table.opt
Btw, why two slashes? And why do you need to set tmpdir at all?
I suspect you don't - because your tests apparently succeed without it :)

> diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
> index aa2cae5..f729733 100644
> --- a/sql/sql_insert.cc
> +++ b/sql/sql_insert.cc
> @@ -4051,7 +4051,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;

Where was this happening before you've added an explicit assignment?
(note the assert below - it worked, so somewhere this must've been assigned)

> +      if (!create_table->table)
>        {
>          /*
>            This shouldn't happen as creation of temporary table should make
> @@ -4060,7 +4065,6 @@ static TABLE *create_table_from_items(THD *thd,
>          */
>          DBUG_ASSERT(0);
>        }
> -      DBUG_ASSERT(create_table->table == create_info->table);

why?

>      }
>    }
>    else
> @@ -4308,6 +4326,27 @@ bool select_create::send_eof()
>      DBUG_RETURN(true);
>    }
>  
> +  if (table->s->tmp_table)
> +  {
> +    /*
> +      Now is good time to add the new table to THD temporary tables list.
> +      But, before that we need to check if same table got created by the sub-
> +      statement.
> +    */
> +    if (thd->temporary_tables.find_table_share(table->s->table_cache_key.str,
> +                                               table->s->table_cache_key.length))
> +    {
> +      my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table->alias.c_ptr());

ER_TABLE_EXISTS_ERROR, eh?
I'm not sure it's the best way to solve this. It's an error
that neither CREATE ... IF NOT EXISTS or CREATE OR REPLACE can fix.

But I don't have a good solution for this. If a concurrent thread
would've tried to create a table meanwhile (assuming, non-temporary),
it would wait on the metadata lock, that protects table creation.

so, logically, trying to create a table from inside create table
or trying to drop a used table from stored function (your ER_CANT_REOPEN_TABLE)
should fail with ER_LOCK_DEADLOCK or ER_LOCK_ABORTED - because it's, basically,
trying to get a conflicting metadata lock within the same thread, clearly a
case of the deadlock.

but somehow I believe that returning ER_LOCK_DEADLOCK on a purely myisam
test case with temporary tables - that will be confusing as hell :(

so, ok, let's keep your ER_TABLE_EXISTS_ERROR. But, if possible, please
commit this code (save_tmp_table_share and ER_CANT_REOPEN_TABLE) in a separate
commit, after the main MDEV-5535 commit. To have it clearly distinct
in the history, in case we'll want to change this behaviour later.

> +      abort_result_set();
> +      DBUG_RETURN(true);
> +    }
> +    else
> +    {
> +      DBUG_ASSERT(save_tmp_table_share);
> +      thd->temporary_tables.relink_table_share(save_tmp_table_share);
> +    }
> +  }
> +
>    /*
>      Do an implicit commit at end of statement for non-temporary
>      tables.  This can fail, but we should unlock the table
> diff --git a/sql/sql_table.cc b/sql/sql_table.cc
> index 40032c3..93fba14 100644
> --- a/sql/sql_table.cc
> +++ b/sql/sql_table.cc
> @@ -2281,23 +2281,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->temporary_tables.find_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->temporary_tables.drop_table(table->table, &is_trans, true))
>        {
> -        DBUG_ASSERT(thd->in_sub_stmt);
> +        error= 1;
>          goto err;

Why are changes in this hunk? (comment, assert, -1, etc)

>        }
>        table->table= 0;
> diff --git a/sql/table.h b/sql/table.h
> index ab39603..8b7c665 100644
> --- a/sql/table.h
> +++ b/sql/table.h
> @@ -600,6 +601,7 @@ struct TABLE_STATISTICS_CB
>  struct TABLE_SHARE
>  {
>    TABLE_SHARE() {}                    /* Remove gcc warning */
> +  TABLE_SHARE *next, *prev;

Eh? Did you forget to remove this?

>  
>    /** Category of this table. */
>    TABLE_CATEGORY table_category;
> diff --git a/sql/temporary_tables.cc b/sql/temporary_tables.cc
> new file mode 100644
> index 0000000..ed5b6b8
> --- /dev/null
> +++ b/sql/temporary_tables.cc
> @@ -0,0 +1,1457 @@
> +/*
> +  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
> +*/
> +
> +#include "sql_acl.h"                            /* TMP_TABLE_ACLS */
> +#include "sql_base.h"                           /* free_io_cache,
> +                                                   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 "temporary_tables.h"                   /* 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))
> +
> +
> +/*
> +  Initialize the Temporary_tables object. Currently it always returns
> +  false (success).
> +
> +  @param thd [IN]                     Thread handle
> +
> +  @return false                       Success
> +          true                        Error
> +*/
> +bool Temporary_tables::init(THD *thd)
> +{
> +  DBUG_ENTER("Temporary_tables::init");
> +  this->m_thd= thd;
> +  DBUG_RETURN(false);
> +}
> +
> +
> +/*
> +  Check whether temporary tables exist. The decision is made based on the
> +  existence of TMP_TABLE_SHAREs.
> +
> +  @param check_slave [IN]             Also check the slave temporary tables.
> +
> +  @return false                       Temporary tables exist
> +          true                        No temporary table exist
> +*/
> +bool Temporary_tables::is_empty(bool check_slave)

not a very good idea. You always use it as is_empty(true) or is_empty(false).
that is, the caller always knows whether to check slave or not.
but then you use a common function and it needs to do a completely
unnecessary  if() inside. Better to have two functions, like
is_empty() and is_empty_slave(). Or at least make this function inline, then
the compiler will have a chance to optimize it.

> +{
> +  DBUG_ENTER("Temporary_tables::is_empty");
> +
> +  bool result;
> +
> +  if (!m_thd)
> +  {
> +    DBUG_RETURN(true);
> +  }
> +
> +  rpl_group_info *rgi_slave= m_thd->rgi_slave;
> +
> +  if (check_slave && rgi_slave)
> +  {
> +    result= (rgi_slave->rli->save_temp_table_shares == NULL) ? true : false;
> +  }
> +  else
> +  {
> +    result= (m_table_shares == NULL) ? true : false;
> +  }
> +
> +  DBUG_RETURN(result);
> +}
> +
> +
> + /*
> +  Reset the Temporary_tables object. Currently, it always returns
> +  false (success).
> +
> +  @return false                       Success
> +          true                        Error
> +*/
> +bool Temporary_tables::reset()
> +{
> +  DBUG_ENTER("Temporary_tables::reset");
> +  m_table_shares= 0;
> +  DBUG_RETURN(false);
> +}
> +
> +
> +/*
> +  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
> +  @param created [OUT]                Whether table was created?

can it ever happen for *create==true but a return value is NULL?
or the other way around? how?

> +
> +
> +  @return Success                     A pointer to table object
> +          Failure                     NULL
> +*/
> +TABLE *Temporary_tables::create_and_open_table(handlerton *hton,
> +                                               LEX_CUSTRING *frm,
> +                                               const char *path,
> +                                               const char *db,
> +                                               const char *table_name,
> +                                               bool open_in_engine,
> +                                               bool *created)
> +{
> +  DBUG_ENTER("Temporary_tables::create_and_open_table");
> +
> +  TMP_TABLE_SHARE *share;
> +  TABLE *table= NULL;
> +  bool locked;
> +
> +  *created= false;
> +
> +  if (wait_for_prior_commit())
> +  {
> +    DBUG_RETURN(NULL);
> +  }
> +
> +  locked= lock_tables();

old code didn't seem to have it here. how comes?

> +
> +  if ((share= create_table(hton, frm, path, db, table_name)))
> +  {
> +    *created= true;
> +    table= open_table(share, table_name, open_in_engine);
> +  }
> +
> +  if (locked)
> +  {
> +    DBUG_ASSERT(m_locked);
> +    unlock_tables();
> +  }
> +
> +  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 *Temporary_tables::find_table(const char *db,
> +                                    const char *table_name)
> +{
> +  DBUG_ENTER("Temporary_tables::find_table");
> +
> +  TABLE *table;
> +  char key[MAX_DBKEY_LENGTH];
> +  uint key_length;
> +  bool locked;
> +
> +  if (is_empty(true))
> +  {
> +    DBUG_RETURN(NULL);
> +  }
> +
> +  key_length= create_table_def_key(key, db, table_name);
> +
> +  if (wait_for_prior_commit())
> +  {
> +    DBUG_RETURN(NULL);
> +  }
> +
> +  locked= lock_tables();
> +  table = find_table(key, key_length, TABLE_IN_USE);
> +  if (locked)
> +  {
> +    DBUG_ASSERT(m_locked);
> +    unlock_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 *Temporary_tables::find_table(const TABLE_LIST *tl)
> +{
> +  DBUG_ENTER("Temporary_tables::find_table");
> +  TABLE *table= find_table(tl->get_db_name(), tl->get_table_name());
> +  DBUG_RETURN(table);
> +}
> +
> +
> +/*
> +  Check whether an open table with the specified key is in use.
> +  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
> +*/
> +TABLE *Temporary_tables::find_table_reduced_key_length(const char *key,
> +                                                       uint key_length)
> +{
> +  DBUG_ENTER("Temporary_tables::find_table_reduced_key_length");
> +
> +  TABLE *result= NULL;
> +  bool locked;
> +
> +  locked= lock_tables();
> +
> +  for (TMP_TABLE_SHARE *share= m_table_shares; share; share= share->next)
> +  {
> +    if ((share->table_cache_key.length - TMP_TABLE_KEY_EXTRA) == key_length
> +        && !memcmp(share->table_cache_key.str, key, key_length))
> +    {
> +      /*
> +        A matching TMP_TABLE_SHARE is found. We now need to find a TABLE
> +        instance in use.
> +      */
> +      for (TABLE *table= share->table; table; table= table->next)
> +      {
> +        if (table->query_id != 0)
> +        {
> +          result= table;
> +          break;
> +        }
> +      }
> +    }
> +  }
> +
> +  if (locked)
> +  {
> +    DBUG_ASSERT(m_locked);
> +    unlock_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 m_thd
> +  (see create_table_def_key()). Return NULL is none found.
> +
> +  @return Success                     A pointer to table share object
> +          Failure                     NULL
> +*/
> +TMP_TABLE_SHARE *Temporary_tables::find_table_share(const char *db,
> +                                                    const char *table_name)
> +{
> +  DBUG_ENTER("Temporary_tables::find_table_share");
> +
> +  TMP_TABLE_SHARE *share;
> +  char key[MAX_DBKEY_LENGTH];
> +  uint key_length;
> +
> +  key_length= create_table_def_key(key, db, table_name);
> +  share= find_table_share(key, key_length);
> +
> +  DBUG_RETURN(share);
> +}
> +
> +
> +/*
> +  Lookup TMP_TABLE_SHARE using the specified TABLE_LIST element.
> +  Return NULL is none found.
> +
> +  @return Success                     A pointer to table share object
> +          Failure                     NULL
> +*/
> +TMP_TABLE_SHARE *Temporary_tables::find_table_share(const TABLE_LIST *tl)
> +{
> +  DBUG_ENTER("Temporary_tables::find_table_share");
> +  TMP_TABLE_SHARE *share= find_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 *Temporary_tables::find_table_share(const char *key,
> +                                                    uint key_length)
> +{
> +  DBUG_ENTER("Temporary_tables::find_table_share");
> +
> +  TMP_TABLE_SHARE *share;
> +  TMP_TABLE_SHARE *result= NULL;
> +  bool locked;
> +
> +  if (wait_for_prior_commit())
> +  {
> +    DBUG_RETURN(NULL);
> +  }
> +
> +  locked= lock_tables();
> +
> +  for (share= m_table_shares; share; share= share->next)
> +  {
> +    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_locked);
> +    unlock_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 Temporary_tables::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.

letter case is a bit weird, 'false', but 'TRUE' :)

> +*/
> +bool Temporary_tables::open_table(TABLE_LIST *tl)
> +{
> +  DBUG_ENTER("Temporary_tables::open_table");
> +
> +  TMP_TABLE_SHARE *share;
> +  TABLE *table= NULL;
> +  bool locked;
> +
> +  /*
> +    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 Temporary_tables::open_tables() should be used.
> +  */
> +  DBUG_ASSERT(!tl->derived && !tl->schema_table);
> +
> +  if (tl->open_type == OT_BASE_ONLY || is_empty(true))
> +  {
> +    DBUG_PRINT("info", ("skip_temporary is set or no temporary tables"));
> +    DBUG_RETURN(false);
> +  }
> +
> +  if (wait_for_prior_commit())

this wasn't in the original code

> +  {
> +    DBUG_RETURN(true);
> +  }
> +
> +  locked= lock_tables();

neither was this

> +
> +  /*
> +    First check if there is a reusable open table available in the
> +    open table list.
> +  */
> +  if (find_and_use_table(tl, &table))
> +  {
> +    if (locked)
> +    {
> +      DBUG_ASSERT(m_locked);
> +      unlock_tables();
> +    }
> +    DBUG_RETURN(true);                          /* Error */
> +  }
> +
> +  /*
> +    No reusable table was found. We will have to open a new instance.
> +  */
> +  if (!table && (share= find_table_share(tl)))
> +  {
> +    table= open_table(share, tl->get_table_name(), true);
> +  }
> +
> +  if (locked)
> +  {
> +    DBUG_ASSERT(m_locked);
> +    unlock_tables();
> +  }
> +
> +  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);
> +  }
> +
> +#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= m_thd->query_id;
> +  m_thd->thread_specific_used= true;
> +
> +  /* It is neither a derived table nor non-updatable view. */
> +  tl->updatable= true;
> +  tl->table= table;
> +
> +  table->init(m_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.

what kind of version checking can there be for temporary 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 Temporary_tables::open_tables(TABLE_LIST *tl)
> +{
> +  DBUG_ENTER("Temporary_tables::open_tables");
> +
> +  TABLE_LIST *first_not_own= m_thd->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 ((m_thd->temporary_tables.open_table(table)))

eh? couldn't you simply do

  if (open_table(table))

? why do you need this "m_thd->temporary_tables." ?

> +    {
> +      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 Temporary_tables::close_tables()
> +{
> +  DBUG_ENTER("Temporary_tables::close_tables");
> +
> +  TMP_TABLE_SHARE *share;
> +  TMP_TABLE_SHARE *share_next;
> +  TABLE *table, *table_next;
> +  bool error= false;
> +
> +  if (!m_table_shares)
> +  {
> +    DBUG_RETURN(false);
> +  }
> +  DBUG_ASSERT(!m_thd->rgi_slave);
> +
> +  /*
> +    Ensure we don't have open HANDLERs for tables we are about to close.
> +    This is necessary when Temporary_tables::close_tables() is called as
> +    part of execution of BINLOG statement (e.g. for format description event).
> +  */
> +  mysql_ha_rm_temporary_tables(m_thd);
> +
> +  /* Close all open temporary tables. */
> +  for (TMP_TABLE_SHARE *share= m_table_shares; share; share= share->next)
> +  {
> +    /* Traverse the table list. */
> +    table= share->table;
> +    while (table)
> +    {
> +      table_next= table->next;
> +      free_table(table);
> +      table= table_next;
> +    }
> +  }
> +
> +  // Write DROP TEMPORARY TABLE query log events to binary log.
> +  if (mysql_bin_log.is_open())
> +  {
> +    error= log_events_and_free_shares();
> +  }
> +  else
> +  {
> +    share= m_table_shares;
> +    while (share)
> +    {
> +      share_next= share->next;
> +      free_table_share(share, true);
> +      share= share_next;
> +    }
> +  }
> +  reset();
> +
> +  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 Temporary_tables::rename_table(TABLE *table,
> +                                    const char *db,
> +                                    const char *table_name)
> +{
> +  DBUG_ENTER("Temporary_tables::rename_table");
> +
> +  char *key;
> +  uint key_length;
> +
> +  TMP_TABLE_SHARE *share= static_cast<TMP_TABLE_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_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 Temporary_tables::drop_table(TABLE *table,
> +                                  bool *is_trans,
> +                                  bool delete_table)
> +{
> +  DBUG_ENTER("Temporary_tables::drop_table");
> +
> +  TMP_TABLE_SHARE *share;
> +  TABLE *tab;
> +  TABLE *tab_next;
> +  bool result, locked;
> +
> +  DBUG_ASSERT(table);
> +  DBUG_ASSERT(table->query_id);
> +  DBUG_PRINT("tmptable", ("Dropping table: '%s'.'%s'",
> +                          table->s->db.str, table->s->table_name.str));
> +
> +  if (wait_for_prior_commit())
> +  {
> +    DBUG_RETURN(true);
> +  }

old code didn't seem to have that. Why do you?

> +
> +  locked= lock_tables();
> +
> +  share= static_cast<TMP_TABLE_SHARE *>(table->s);

suggestion: have a function:

  static inline TMP_TABLE_SHARE *tmp_table_share(TABLE *t) {
     DBUG_ASSERT(t->s->tmp_table);
     return static_cast<TMP_TABLE_SHARE *>(table->s);
  }

> +
> +  /* Table might be in use by some outer statement. */
> +  for (tab= share->table; tab; tab= tab->next)
> +  {
> +    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 all tables referencing the
> +    same table share.

the comment sounds a bit odd now, when all open tables are in the
list inside the share.

> +  */
> +  tab= share->table;
> +  while (tab)
> +  {
> +    tab_next= tab->next;
> +    if ((result= free_table(tab))) goto end;
> +    tab= tab_next;
> +  }
> +
> +  result= free_table_share(share, delete_table);
> +
> +end:
> +  if (locked)
> +  {
> +    DBUG_ASSERT(m_locked);
> +    unlock_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 Temporary_tables::remove_table(handlerton *base, const char *path)
> +{
> +  DBUG_ENTER("Temporary_tables::remove_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 Temporary_tables::mark_tables_as_free_for_reuse()
> +{
> +  DBUG_ENTER("Temporary_tables::mark_tables_as_free_for_reuse");
> +
> +  bool locked;
> +
> +  if (m_thd->query_id == 0)
> +  {
> +    /*
> +      Thread has not executed any statement and has not used any
> +      temporary tables.
> +    */
> +    DBUG_VOID_RETURN;
> +  }
> +
> +  locked= lock_tables();
> +
> +  for (TMP_TABLE_SHARE *share= m_table_shares; share; share= share->next)
> +  {
> +    for (TABLE *table= share->table; table; table= table->next)
> +    {
> +      if ((table->query_id == m_thd->query_id) && !table->open_by_handler)
> +      {
> +        mark_table_as_free_for_reuse(table);
> +      }
> +    }
> +  }
> +
> +  if (locked)
> +  {
> +    DBUG_ASSERT(m_locked);
> +    unlock_tables();
> +  }
> +
> +  DBUG_VOID_RETURN;
> +}
> +
> +
> +/*
> +  Reset a single temporary table. Effectively this "closes" one temporary
> +  table in a session.
> +
> +  @param table              Temporary table
> +*/
> +void Temporary_tables::mark_table_as_free_for_reuse(TABLE *table)
> +{
> +  DBUG_ENTER("Temporary_tables::mark_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;
> +}
> +
> +
> +TMP_TABLE_SHARE *Temporary_tables::unlink_table_share(TABLE_SHARE *share)
> +{
> +  DBUG_ENTER("Temporary_tables::unlink_table");
> +  TMP_TABLE_SHARE *tmp_table_share;
> +
> +  lock_tables();
> +  tmp_table_share= static_cast<TMP_TABLE_SHARE *>(share);
> +  unlink<TMP_TABLE_SHARE>(&m_table_shares, tmp_table_share);
> +  unlock_tables();
> +
> +  DBUG_RETURN(tmp_table_share);
> +}
> +
> +
> +void Temporary_tables::relink_table_share(TMP_TABLE_SHARE *share)
> +{
> +  DBUG_ENTER("Temporary_tables::relink_table");
> +
> +  lock_tables();
> +  link<TMP_TABLE_SHARE>(&m_table_shares, share);
> +  unlock_tables();
> +
> +  DBUG_VOID_RETURN;
> +}
> +
> +
> +/*
> +  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 Temporary_tables::create_table_def_key(char *key, const char *db,
> +                                            const char *table_name)
> +{
> +  DBUG_ENTER("Temporary_tables::create_table_def_key");
> +
> +  uint key_length;
> +
> +  key_length= tdc_create_key(key, db, table_name);
> +  int4store(key + key_length, m_thd->variables.server_id);
> +  int4store(key + key_length + 4, m_thd->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 *Temporary_tables::create_table(handlerton *hton,
> +                                                LEX_CUSTRING *frm,
> +                                                const char *path,
> +                                                const char *db,
> +                                                const char *table_name)
> +{
> +  DBUG_ENTER("Temporary_tables::create_table");
> +
> +  TMP_TABLE_SHARE *share;
> +  char key_cache[MAX_DBKEY_LENGTH];
> +  char *saved_key_cache;
> +  char *tmp_path;
> +  uint key_length;
> +  int res;
> +
> +  if (wait_for_prior_commit())

you're doing it in almost every method. And when one method calls another,
you're doing it twice. Or thrice. Isn't is too much? (I don't know)

> +  {
> +    DBUG_RETURN(NULL);
> +  }
> +
> +  /* Create the table definition key for the temporary table. */
> +  key_length= create_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(m_thd, share, saved_key_cache, key_length,
> +                       strend(saved_key_cache) + 1, tmp_path);
> +
> +  share->table= 0;
> +  share->db_plugin= ha_lock_engine(m_thd, 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(m_thd, false, frm->str, frm->length)
> +    : open_table_def(m_thd, 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);
> +
> +  /* Add share to the head of the temporary table share list. */
> +  link<TMP_TABLE_SHARE>(&m_table_shares, share);
> +
> +  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 *Temporary_tables::find_table(const char *key, uint key_length,
> +                                    Table_state state)
> +{
> +  DBUG_ENTER("Temporary_tables::find_table");
> +
> +  for (TMP_TABLE_SHARE *share= m_table_shares; share; share= share->next)
> +  {
> +    if (share->table_cache_key.length == key_length &&
> +        !(memcmp(share->table_cache_key.str, key, key_length)))
> +    {
> +      /* A matching TMP_TABLE_SHARE is found. */
> +      for (TABLE *table= share->table; table; table= table->next)
> +      {
> +        switch (state)
> +        {
> +        case TABLE_IN_USE:
> +          if (table->query_id > 0) DBUG_RETURN(table);
> +          break;
> +        case TABLE_NOT_IN_USE:
> +          if (table->query_id == 0) DBUG_RETURN(table);
> +          break;
> +        case TABLE_ANY:
> +          DBUG_RETURN(table);
> +        default:                                /* Invalid */
> +          DBUG_ASSERT(0);
> +          DBUG_RETURN(NULL);
> +        }
> +      }
> +    }
> +  }
> +
> +  DBUG_RETURN(NULL);
> +}
> +
> +
> +/*
> +  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 Temporary_tables::find_and_use_table(const TABLE_LIST *tl,
> +                                          TABLE **out_table)
> +{
> +  DBUG_ENTER("Temporary_tables::find_and_use_table");
> +
> +  char key[MAX_DBKEY_LENGTH];
> +  uint key_length;
> +  bool result;
> +
> +  key_length= create_table_def_key(key, tl->get_db_name(),
> +                                   tl->get_table_name());
> +  result= use_table(find_table(key, key_length, TABLE_NOT_IN_USE), out_table);
> +
> +  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 *Temporary_tables::open_table(TMP_TABLE_SHARE *share,
> +                                    const char *alias,
> +                                    bool open_in_engine)
> +{
> +  DBUG_ENTER("Temporary_tables::open_table");
> +
> +  TABLE *table;
> +
> +  if (wait_for_prior_commit())
> +  {
> +    DBUG_RETURN(NULL);
> +  }
> +
> +  if (!(table= (TABLE *) my_malloc(sizeof(TABLE), MYF(MY_WME))))
> +  {
> +    DBUG_RETURN(NULL);                          /* Out of memory */
> +  }
> +
> +  if (open_table_from_share(m_thd, 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= m_thd->query_id;
> +
> +  /* Add table to the head of table list. */
> +  link<TABLE>(&share->table, table);

I don't know if that's correct. You use TABLE::next and TABLE::prev pointers
to link all tables of a given TMP_TABLE_SHARE into a list. But these
pointers are used to link all tables into a THD::open_tables list
(and may be for other purposes too?).
In fact, TABLE has dedicated pointers for this list that you want:

 /**
    Links for the list of all TABLE objects for this share.
    Declared as private to avoid direct manipulation with those objects.
    One should use methods of I_P_List template instead.
 */
 TABLE *share_all_next, **share_all_prev;

> +
> +  /* Increment Slave_open_temp_table_definitions status variable count. */
> +  if (m_thd->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);
> +}
> +
> +

function comment?

> +bool Temporary_tables::use_table(TABLE *table, TABLE **out_table)
> +{
> +  DBUG_ENTER("Temporary_tables::use_table");
> +
> +  *out_table= table;
> +  if (!table)
> +    DBUG_RETURN(false);
> +

old code had a block here about "Temporary tables are not safe for parallel
replication". it's in wait_for_prior_commit now, but you aren't calling it
here. why?

> +  /*
> +    We need to set the THD as it may be different in case of
> +    parallel replication
> +  */
> +  if (table->in_use != m_thd)
> +  {
> +    table->in_use= m_thd;
> +  }
> +
> +  DBUG_RETURN(false);
> +}
> +
> +
> +/*
> +  Close a temporary table.
> +
> +  @param table [IN]                   Table handle
> +
> +  @return false                       Success
> +          true                        Error
> +*/
> +bool Temporary_tables::close_table(TABLE *table)
> +{
> +  DBUG_ENTER("Temporary_tables::close_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()));
> +
> +  free_io_cache(table);

don't forget to remove free_io_cache() calls when rebasing your
work on top of the latest 10.2
(they were removed from close_temporary() in 260dd476b05 commit)

> +  closefrm(table, false);
> +  my_free(table);
> +
> +  /* Decrement Slave_open_temp_table_definitions status variable count. */
> +  if (m_thd->rgi_slave)
> +  {
> +    thread_safe_decrement32(&slave_open_temp_tables);
> +  }
> +
> +  DBUG_RETURN(false);
> +}
> +
> +
> +bool Temporary_tables::wait_for_prior_commit()
> +{
> +  DBUG_ENTER("Temporary_tables::wait_for_prior_commit");
> +
> +  /*
> +    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 (m_thd->rgi_slave &&
> +      m_thd->rgi_slave->rli->save_temp_table_shares &&
> +      m_thd->rgi_slave->is_parallel_exec &&
> +      m_thd->wait_for_prior_commit())
> +    DBUG_RETURN(true);
> +
> +  DBUG_RETURN(false);
> +}
> +
> +
> +/*
> +  Write query log events with "DROP TEMPORARY TABLES .." for each pseudo
> +  thread to the binary log.
> +
> +  @return false                       Success
> +          true                        Error
> +*/
> +bool Temporary_tables::log_events_and_free_shares()
> +{
> +  DBUG_ENTER("Temporary_tables::log_events_and_free_shares");
> +
> +  DBUG_ASSERT(!m_thd->rgi_slave);
> +
> +  TMP_TABLE_SHARE *share;
> +  TMP_TABLE_SHARE *next;
> +  TMP_TABLE_SHARE *prev_share;
> +  // 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.
> +  */
> +
> +  for (prev_share= m_table_shares, share= prev_share->next;
> +       share;
> +       prev_share= share, share= share->next)
> +  {
> +    TMP_TABLE_SHARE *prev_sorted;                   /* Same as for prev_share */
> +    TMP_TABLE_SHARE *sorted;
> +
> +    if (IS_USER_TABLE(share))
> +    {
> +      if (!found_user_tables)
> +        found_user_tables= true;
> +
> +      for (prev_sorted= NULL, sorted= m_table_shares;
> +           sorted != share;
> +           prev_sorted= sorted, sorted= sorted->next)
> +      {
> +        if (!IS_USER_TABLE(sorted) ||
> +            tmpkeyval(sorted) > tmpkeyval(share))
> +        {
> +          /*
> +            Move into the sorted part of the list from the unsorted.
> +          */
> +          prev_share->next= share->next;
> +          share->next= sorted;
> +          if (prev_sorted)
> +          {
> +            prev_sorted->next= share;
> +          }
> +          else
> +          {
> +            m_table_shares= share;
> +          }
> +          share= prev_share;
> +          break;
> +        }
> +      }
> +    }
> +  }
> +
> +  /*
> +    We always quote db & table names.
> +  */
> +  if (found_user_tables &&
> +      !(was_quote_show= MY_TEST(m_thd->variables.option_bits &
> +                                OPTION_QUOTE_SHOW_CREATE)))
> +  {
> +    m_thd->variables.option_bits |= OPTION_QUOTE_SHOW_CREATE;
> +  }
> +
> +  /*
> +    Scan sorted temporary tables to generate sequence of DROP.
> +  */
> +  for (share= m_table_shares; share; share= next)
> +  {
> +    if (IS_USER_TABLE(share))
> +    {
> +      bool save_thread_specific_used= m_thd->thread_specific_used;
> +      my_thread_id save_pseudo_thread_id= m_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.
> +      */
> +      m_thd->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) == m_thd->variables.pseudo_thread_id &&
> +           share->db.length == db.length() &&
> +           memcmp(share->db.str, db.ptr(), db.length()) == 0;
> +           share= next)
> +      {
> +        /*
> +          We are going to add ` around the table names and possible more
> +          due to special characters.
> +        */
> +        append_identifier(m_thd, &s_query, share->table_name.str,
> +                          share->table_name.length);
> +        s_query.append(',');
> +        next= share->next;
> +        remove_table(share->db_type(), share->path.str);
> +        ::free_table_share(share);
> +        my_free(share);
> +      }
> +
> +      m_thd->clear_error();
> +      CHARSET_INFO *cs_save= m_thd->variables.character_set_client;
> +      m_thd->variables.character_set_client= system_charset_info;
> +      m_thd->thread_specific_used= true;
> +
> +      Query_log_event qinfo(m_thd, s_query.ptr(),
> +                            s_query.length() - 1 /* to remove trailing ',' */,
> +                            false, true, false, 0);
> +      qinfo.db= db.ptr();
> +      qinfo.db_len= db.length();
> +      m_thd->variables.character_set_client= cs_save;
> +
> +      m_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 Temporary_tables::close_tables failed. (Actually, the SQL
> +          thread only calls Temporary_tables::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");
> +      }
> +
> +      m_thd->get_stmt_da()->set_overwrite_status(false);
> +      m_thd->variables.pseudo_thread_id= save_pseudo_thread_id;
> +      m_thd->thread_specific_used= save_thread_specific_used;
> +    }
> +    else
> +    {
> +      next= share->next;
> +      free_table_share(share, true);
> +    }
> +  }
> +
> +  if (!was_quote_show)
> +  {
> +    /*
> +      Restore option.
> +    */
> +    m_thd->variables.option_bits&= ~OPTION_QUOTE_SHOW_CREATE;
> +  }
> +
> +  DBUG_RETURN(error);
> +}
> +
> +/*
> +  Delete the files and free the specified table share.
> +*/
> +bool Temporary_tables::free_table_share(TMP_TABLE_SHARE *share,
> +                                        bool delete_table)
> +{
> +  DBUG_ENTER("Temporary_tables::free_table_share");
> +
> +  bool result= false;
> +
> +  /* Delete the share from table share list */
> +  unlink<TMP_TABLE_SHARE>(&m_table_shares, share);
> +
> +  if (delete_table)
> +  {
> +    result= remove_table(share->db_type(), share->path.str);
> +  }
> +
> +  ::free_table_share(share);
> +  my_free(share);
> +
> +  DBUG_RETURN(result);
> +}
> +
> +
> +/*
> +  Free the specified table object.
> +*/
> +bool Temporary_tables::free_table(TABLE *table)
> +{
> +  DBUG_ENTER("Temporary_tables::free_table");
> +
> +  bool result= false;
> +
> +  /* Delete the table from table list */
> +  unlink<TABLE>(&static_cast<TMP_TABLE_SHARE *>(table->s)->table, 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(m_thd, m_thd->lock, table);
> +
> +  result= close_table(table);
> +
> +  DBUG_RETURN(result);
> +}
> +
> +
> +bool Temporary_tables::lock_tables()
> +{
> +  /* Do not proceed if a lock has already been taken. */
> +  if (m_locked)
> +  {
> +    return false;
> +  }
> +
> +  rpl_group_info *rgi_slave= m_thd->rgi_slave;
> +  if (rgi_slave)
> +  {
> +    mysql_mutex_lock(&rgi_slave->rli->data_lock);
> +    m_table_shares= rgi_slave->rli->save_temp_table_shares;
> +    m_locked= true;
> +  }
> +  return m_locked;
> +}
> +
> +
> +void Temporary_tables::unlock_tables()
> +{
> +  if (!m_locked)
> +  {
> +    return;
> +  }
> +
> +  rpl_group_info *rgi_slave= m_thd->rgi_slave;
> +  if (rgi_slave)
> +  {
> +    rgi_slave->rli->save_temp_table_shares= m_table_shares;
> +    mysql_mutex_unlock(&rgi_slave->rli->data_lock);
> +    m_locked= false;
> +    /*
> +      Temporary tables are shared with other by sql execution threads.
> +      As a safety measure, clear the pointer to the common area.
> +    */
> +    reset();
> +  }
> +  return;
> +}
> +
> diff --git a/sql/temporary_tables.h b/sql/temporary_tables.h
> new file mode 100644
> index 0000000..5a5ed0d
> --- /dev/null
> +++ b/sql/temporary_tables.h
> @@ -0,0 +1,150 @@
> +#ifndef TEMPORARY_TABLES_H
> +#define TEMPORARY_TABLES_H
> +/*
> +  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
> +*/
> +
> +#define TMP_TABLE_KEY_EXTRA 8
> +
> +struct TMP_TABLE_SHARE: public TABLE_SHARE
> +{
> +  /* List of TABLE instances created out of this TABLE_SHARE. */
> +  TABLE *table;

Hmm, if you will use TABLE::share_all_prev and TABLE::share_all_next, as
I've written above, you should also use TDC_element that keeps the list
of TABLE's per TABLE_SHARE. And also it has next/prev pointers
to link all TABLE_SHARE's in a list... Perhaps you won't need
TMP_TABLE_SHARE after all? Could you talk with Svoj about it, please?
perhaps you can simply reuse existing TABLE_SHARE code
for linking TABLE_SHARE's into a list and linking TABLE's into
a per-TABLE_SHARE list? TDC_element is a bit of an overkill for
temporary tables, we can either live with that or extract those
bits that you need into a "mini-TDC_element" ? Dunno, best to discuss it
with Svoj.

> +
> +  /* Pointers to access TMP_TABLE_SHARE instances. */
> +  TMP_TABLE_SHARE *next;
> +  TMP_TABLE_SHARE *prev;
> +};
> +
> +
> +class Temporary_tables
> +{
> +public:
> +  Temporary_tables() : m_thd(0), m_table_shares(0), m_locked(false)
> +  {}
> +  bool init(THD *thd);
> +  bool is_empty(bool check_slave);
> +  bool reset();
> +
> +  TABLE *create_and_open_table(handlerton *hton, LEX_CUSTRING *frm,
> +                               const char *path, const char *db,
> +                               const char *table_name, bool open_in_engine,
> +                               bool *created);
> +
> +  TABLE *find_table(const char *db, const char *table_name);
> +  TABLE *find_table(const TABLE_LIST *tl);
> +  TABLE *find_table_reduced_key_length(const char *key, uint key_length);
> +
> +  TMP_TABLE_SHARE *find_table_share(const char *db, const char *table_name);
> +  TMP_TABLE_SHARE *find_table_share(const TABLE_LIST *tl);
> +  TMP_TABLE_SHARE *find_table_share(const char *key, uint key_length);
> +
> +  bool open_table(TABLE_LIST *tl);
> +  bool open_tables(TABLE_LIST *tl);
> +
> +  bool close_tables();
> +  bool rename_table(TABLE *table, const char *db, const char *table_name);
> +  bool drop_table(TABLE *table, bool *is_trans, bool delete_table);
> +  bool remove_table(handlerton *hton, const char *path);
> +  void mark_tables_as_free_for_reuse();
> +  void mark_table_as_free_for_reuse(TABLE *table);
> +
> +  TMP_TABLE_SHARE *unlink_table_share(TABLE_SHARE *share);
> +  void relink_table_share(TMP_TABLE_SHARE *share);
> +
> +private:
> +  /* THD handler */
> +  THD *m_thd;

already discussed

> +
> +  /* List of temporary table shares */
> +  TMP_TABLE_SHARE *m_table_shares;
> +
> +  /* Whether a lock has been acquired. */
> +  bool m_locked;
> +
> +  /* Opened table states. */
> +  enum Table_state {
> +    TABLE_IN_USE,
> +    TABLE_NOT_IN_USE,
> +    TABLE_ANY
> +  };
> +
> +  uint create_table_def_key(char *key,
> +                            const char *db,
> +                            const char *table_name);
> +
> +  TMP_TABLE_SHARE *create_table(handlerton *hton,
> +                                LEX_CUSTRING *frm,
> +                                const char *path,
> +                                const char *db,
> +                                const char *table_name);
> +
> +  TABLE *find_table(const char *key, uint key_length, Table_state state);
> +
> +  bool find_and_use_table(const TABLE_LIST *tl, TABLE **out_table);
> +
> +  TABLE *open_table(TMP_TABLE_SHARE *share, const char *alias,
> +                    bool open_in_engine);
> +
> +  bool use_table(TABLE *table, TABLE **out_table);
> +  bool close_table(TABLE *table);
> +  bool wait_for_prior_commit();
> +  bool log_events_and_free_shares();
> +
> +  bool free_table_share(TMP_TABLE_SHARE *share, bool delete_table);
> +  bool free_table(TABLE *table);
> +
> +  bool lock_tables();
> +  void unlock_tables();
> +
> +  /* List operations */
> +  template <class T>
> +  void link(T **list, T *element)
> +  {
> +    element->next= *list;
> +    if (element->next)
> +      element->next->prev= element;
> +    *list= element;
> +    (*list)->prev= 0;
> +  }
> +
> +  template <class T>
> +  void unlink(T **list, T *element)
> +  {
> +    if (element->prev)
> +    {
> +      element->prev->next= element->next;
> +      if (element->prev->next)
> +        element->next->prev= element->prev;
> +    }
> +    else
> +    {
> +      DBUG_ASSERT(element == *list);
> +
> +      *list= element->next;
> +      if (*list)
> +        element->next->prev= 0;
> +    }
> +  }

No need to reinvent the wheel, you can use I_P_List for that.

> +
> +  uint tmpkeyval(TMP_TABLE_SHARE *share)
> +  {
> +    return uint4korr(share->table_cache_key.str +
> +                     share->table_cache_key.length - 4);
> +  }
> +};
> +
> +#endif /* TEMPORARY_TABLES_H */

Regards,
Sergei
Chief Architect MariaDB
and security@xxxxxxxxxxx


Follow ups