List:Commits« Previous MessageNext Message »
From:Konstantin Osipov Date:June 9 2008 3:45pm
Subject:bzr commit into mysql-6.0 branch (konstantin:2665) WL#3726
View as plain text  
#At file:///opt/local/work/mysql-6.0-lock-tables-new/

 2665 Konstantin Osipov	2008-06-09
      WL#3726, review fixes.
      Prepare for removal of reopen_table(), reopen_tables(),
      reopen_table_entry(), close_data_file_and_leave_as_placeholer():
      implement class Locked_tables_list that stores TABLE_LIST
      elements for every table locked with LOCK TABLES.
      This allows one to use standard functions, such as close_open_table()
      and open_table() when closing and reopening locked tables
      in FLUSH TABLES, DROP TABLE or ALTER TABLE.
      Rewrite two places that previously used reopen_tables() to use
      Locked_tables_list instead.
modified:
  mysql-test/suite/rpl/r/rpl_trigger.result
  sql/mysql_priv.h
  sql/sp_head.cc
  sql/sql_acl.cc
  sql/sql_base.cc
  sql/sql_class.cc
  sql/sql_class.h
  sql/sql_insert.cc
  sql/sql_parse.cc
  sql/sql_partition.cc
  sql/sql_servers.cc
  sql/sql_table.cc
  sql/sql_trigger.cc
  sql/table.h

per-file comments:
  mysql-test/suite/rpl/r/rpl_trigger.result
    We take the table from the table cache now, thus no warning.
  sql/mysql_priv.h
    Add two new open_table() flags.
  sql/sp_head.cc
    Rename thd->mdl_el_root to thd->locked_tables_root.
  sql/sql_acl.cc
    Use the new implementation of unlock_locked_tables().
  sql/sql_base.cc
    Implement class Locked_tables_list.
    Implement close_all_tables_for_name().
    Rewrite close_cached_tables() to use the new reopen_tables().
  sql/sql_class.cc
    Deploy class Locked_tables_list.
  sql/sql_class.h
    Add declaration for class Locked_tables_list.
    Keep one instance of this class in class THD.
    Rename mdl_el_root to locked_tables_root.
  sql/sql_insert.cc
    Use the plain open_table() to open a just created table in 
    CREATE TABLE .. SELECT.
  sql/sql_parse.cc
    Use thd->locked_tables_list to enter and leave LTM_LOCK_TABLES mode.
  sql/sql_partition.cc
    Deploy the new method of working with partitioned table locks.
  sql/sql_servers.cc
    Update to the new signature of unlock_locked_tables().
  sql/sql_table.cc
    In mysql_rm_table_part2(), the branch that removes a table under
    LOCK TABLES, make sure that the table being dropped
    is also removed from THD::locked_tables_list.
  sql/sql_trigger.cc
    Use the new locking way.
  sql/table.h
    Add TABLE::pos_in_locked_tables, which is used only under
    LOCK TABLES.
=== modified file 'mysql-test/suite/rpl/r/rpl_trigger.result'
--- a/mysql-test/suite/rpl/r/rpl_trigger.result	2007-12-18 09:07:08 +0000
+++ b/mysql-test/suite/rpl/r/rpl_trigger.result	2008-06-09 13:45:11 +0000
@@ -890,8 +890,6 @@ s
 @
 root@localhost
 DROP TRIGGER trg1;
-Warnings:
-Warning	1454	No definer attribute for trigger 'test'.'trg1'. The trigger will be
activated under the authorization of the invoker, which may have insufficient privileges.
Please recreate the trigger.
 DROP TABLE t1;
 DROP TABLE t2;
 STOP SLAVE;

=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h	2008-06-04 12:27:06 +0000
+++ b/sql/mysql_priv.h	2008-06-09 13:45:11 +0000
@@ -1511,6 +1511,8 @@ bool wait_while_table_is_used(THD *thd, 
 void unlink_open_table(THD *thd, TABLE *find, bool unlock);
 void drop_open_table(THD *thd, TABLE *table, const char *db_name,
                      const char *table_name);
+void close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
+                               bool remove_from_locked_tables);
 void update_non_unique_table_error(TABLE_LIST *update,
                                    const char *operation,
                                    TABLE_LIST *duplicate);
@@ -1669,7 +1671,7 @@ void create_subpartition_name(char *out,
 
 typedef struct st_lock_param_type
 {
-  TABLE_LIST table_list;
+  TABLE_LIST *table_list;
   ulonglong copied;
   ulonglong deleted;
   THD *thd;
@@ -2137,6 +2139,23 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, 
 #define MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY      0x0008
 #define MYSQL_LOCK_PERF_SCHEMA                  0x0010
 #define MYSQL_OPEN_TAKE_UPGRADABLE_MDL          0x0020
+/**
+  Do not try to acquire a metadata lock on the table: we
+  already have one.
+*/
+#define MYSQL_OPEN_HAS_MDL_LOCK                 0x0040
+/**
+  If in locked tables mode, ignore the locked tables and get
+  a new instance of the table.
+*/
+#define MYSQL_OPEN_GET_NEW_TABLE                0x0080
+
+/** Please refer to the internals manual. */
+#define MYSQL_OPEN_REOPEN  (MYSQL_LOCK_IGNORE_FLUSH |\
+                            MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |\
+                            MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY |\
+                            MYSQL_OPEN_GET_NEW_TABLE |\
+                            MYSQL_OPEN_HAS_MDL_LOCK)
 
 void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock);
 void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock);

=== modified file 'sql/sp_head.cc'
--- a/sql/sp_head.cc	2008-06-06 19:19:04 +0000
+++ b/sql/sp_head.cc	2008-06-09 13:45:11 +0000
@@ -3934,8 +3934,8 @@ sp_head::add_used_tables_to_table_list(T
       table->belong_to_view= belong_to_view;
       table->trg_event_map= stab->trg_event_map;
       table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name,
-                                           thd->mdl_el_root ?
-                                           thd->mdl_el_root :
+                                           thd->locked_tables_root ?
+                                           thd->locked_tables_root :
                                            thd->mem_root);
 
       /* Everyting else should be zeroed */
@@ -3981,8 +3981,8 @@ sp_add_to_query_tables(THD *thd, LEX *le
   table->select_lex= lex->current_select;
   table->cacheable_table= 1;
   table->mdl_lock_data= mdl_alloc_lock(0, table->db, table->table_name,
-                                       thd->mdl_el_root ? thd->mdl_el_root :
-                                       thd->mem_root);
+                                       thd->locked_tables_root ?
+                                       thd->locked_tables_root : thd->mem_root);
 
   lex->add_to_query_tables(table);
   return table;

=== modified file 'sql/sql_acl.cc'
--- a/sql/sql_acl.cc	2008-05-23 13:54:03 +0000
+++ b/sql/sql_acl.cc	2008-06-09 13:45:11 +0000
@@ -676,7 +676,8 @@ my_bool acl_reload(THD *thd)
   my_bool return_val= 1;
   DBUG_ENTER("acl_reload");
 
-  unlock_locked_tables(thd);  // Can't have locked tables here
+  /* Can't have locked tables here. */
+  thd->locked_tables_list.unlock_locked_tables(thd);
 
   /*
     To avoid deadlocks we should obtain table locks before

=== modified file 'sql/sql_base.cc'
--- a/sql/sql_base.cc	2008-06-06 19:19:04 +0000
+++ b/sql/sql_base.cc	2008-06-09 13:45:11 +0000
@@ -1004,64 +1004,31 @@ bool close_cached_tables(THD *thd, TABLE
   if (thd->locked_tables_mode)
   {
     /*
-      If we are under LOCK TABLES we need to reopen tables without
+      If we are under LOCK TABLES, we need to reopen tables without
       opening a door for any concurrent threads to sneak in and get
       lock on our tables. To achieve this we use exclusive metadata
       locks.
     */
-    if (!tables)
-    {
-      for (TABLE *tab= thd->open_tables; tab; tab= tab->next)
-      {
-        /*
-          Checking TABLE::db_stat is essential in case when we have
-          several instances of the table open and locked.
-        */
-        if (tab->db_stat)
-        {
-          char dbname[NAME_LEN+1];
-          char tname[NAME_LEN+1];
-          /*
-            Since close_data_files_and_leave_as_placeholders() frees share's
-            memroot we need to make copies of database and table names.
-          */
-          strmov(dbname, tab->s->db.str);
-          strmov(tname, tab->s->table_name.str);
-          if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN))
-          {
-            result= TRUE;
-            goto err_with_reopen;
-          }
-          pthread_mutex_lock(&LOCK_open);
-          close_data_files_and_leave_as_placeholders(thd, dbname, tname);
-          pthread_mutex_unlock(&LOCK_open);
-        }
-      }
-    }
-    else
+    TABLE_LIST *tables_to_reopen= (tables ? tables :
+                                  thd->locked_tables_list.locked_tables());
+
+    for (TABLE_LIST *table_list= tables_to_reopen; table_list;
+         table_list= table_list->next_global)
     {
-      for (TABLE_LIST *table= tables; table; table= table->next_local)
+      /* A check that the table was locked for write is done by the caller. */
+      TABLE *table= find_locked_table(thd->open_tables, table_list->db,
+                                      table_list->table_name);
+
+      /* May return NULL if this table has already been closed via an alias. */
+      if (! table)
+        continue;
+
+      if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
       {
-        /* This should always succeed thanks to check in caller. */
-        TABLE *tab= find_write_locked_table(thd->open_tables, table->db,
-                                            table->table_name);
-        /*
-          Checking TABLE::db_stat is essential in case when we have
-          several instances of the table open and locked.
-        */
-        if (tab->db_stat)
-        {
-          if (wait_while_table_is_used(thd, tab, HA_EXTRA_FORCE_REOPEN))
-          {
-            result= TRUE;
-            goto err_with_reopen;
-          }
-          pthread_mutex_lock(&LOCK_open);
-          close_data_files_and_leave_as_placeholders(thd, table->db,
-                                                     table->table_name);
-          pthread_mutex_unlock(&LOCK_open);
-        }
+        result= TRUE;
+        goto err_with_reopen;
       }
+      close_all_tables_for_name(thd, table->s, FALSE);
     }
   }
 
@@ -1117,16 +1084,12 @@ bool close_cached_tables(THD *thd, TABLE
 err_with_reopen:
   if (thd->locked_tables_mode)
   {
-    pthread_mutex_lock(&LOCK_open);
     /*
       No other thread has the locked tables open; reopen them and get the
       old locks. This should always succeed (unless some external process
       has removed the tables)
     */
-    thd->in_lock_tables=1;
-    result|= reopen_tables(thd, 1);
-    thd->in_lock_tables=0;
-    pthread_mutex_unlock(&LOCK_open);
+    thd->locked_tables_list.reopen_tables(thd);
     /*
       Since mdl_downgrade_exclusive_lock() won't do anything with shared
       metadata lock it is much simplier to go through all open tables rather
@@ -1262,6 +1225,8 @@ static void mark_used_tables_as_free_for
 {
   for (; table ; table= table->next)
   {
+    DBUG_ASSERT(table->pos_in_locked_tables == NULL ||
+                table->pos_in_locked_tables->table == table);
     if (table->query_id == thd->query_id)
     {
       table->query_id= 0;
@@ -1306,6 +1271,70 @@ static void close_open_tables(THD *thd)
 }
 
 
+/**
+  Close all open instances of the table but keep the MDL lock,
+  if any.  Works both under LOCK TABLES and in normal mode.
+
+  @param     thd     thread handle
+  @param[in] share   table share, but is just a handy way to
+                     access the table cache key
+
+  @param[in] remove_from_locked_tables
+                     TRUE if the table is being dropped or renamed.
+                     In that case the documented behaviour is to
+                     implicitly remove the table from LOCK TABLES
+                     list.
+*/
+
+void
+close_all_tables_for_name(THD *thd, TABLE_SHARE *share,
+                          bool remove_from_locked_tables)
+{
+  char key[MAX_DBKEY_LENGTH];
+  uint key_length= share->table_cache_key.length;
+
+  memcpy(key, share->table_cache_key.str, key_length);
+
+  safe_mutex_assert_not_owner(&LOCK_open);
+  /*
+    We need to hold LOCK_open while changing the open_tables
+    list, since another thread may work on it.
+    @sa notify_thread_having_shared_lock()
+  */
+  pthread_mutex_lock(&LOCK_open);
+
+  for (TABLE **prev= &thd->open_tables; *prev; )
+  {
+    TABLE *table= *prev;
+
+    if (table->s->table_cache_key.length == key_length &&
+        !memcmp(table->s->table_cache_key.str, key, key_length))
+    {
+      /*
+        Does nothing if the table is not locked.
+        This allows one to use this function after a table
+        has been unlocked, e.g. in partition management.
+      */
+      mysql_lock_remove(thd, thd->lock, table, FALSE);
+
+      thd->locked_tables_list.unlink_from_list(thd,
+                                               table->pos_in_locked_tables,
+                                               remove_from_locked_tables);
+
+      table->s->version= 0;
+      close_thread_table(thd, prev);
+    }
+    else
+    {
+      /* Step to next entry in open_tables list. */
+      prev= &table->next;
+    }
+  }
+  broadcast_refresh();
+  pthread_mutex_unlock(&LOCK_open);
+}
+
+
 /*
   Close all tables used by the current substatement, or all tables
   used by this thread if we are on the upper level.
@@ -2276,7 +2305,8 @@ void unlink_open_table(THD *thd, TABLE *
 
 
 /**
-    Auxiliary routine which closes and drops open table.
+    Auxiliary routine which closes and drops a just created table
+    in CREATE TABLE ... SELECT.
 
     @param  thd         Thread handle
     @param  table       TABLE object for table to be dropped
@@ -2284,14 +2314,9 @@ void unlink_open_table(THD *thd, TABLE *
     @param  table_name  Name of this table
 
     @note This routine assumes that table to be closed is open only
-          by calling thread so we needn't wait until other threads
-          will close the table. Also unless called under implicit or
-          explicit LOCK TABLES mode it assumes that table to be
-          dropped is already unlocked. In the former case it will
-          also remove lock on the table. But one should not rely on
-          this behaviour as it may change in future.
-          Currently, however, this function is never called for a
-          table that was locked with LOCK TABLES.
+          by the calling thread so we needn't wait until other threads
+          will close the table.
+          It is not supposed to work under LOCK TABLES.
 */
 
 void drop_open_table(THD *thd, TABLE *table, const char *db_name,
@@ -2301,13 +2326,15 @@ void drop_open_table(THD *thd, TABLE *ta
     close_temporary_table(thd, table, 1, 1);
   else
   {
+    DBUG_ASSERT(! thd->locked_tables_mode);
+    DBUG_ASSERT(table == thd->open_tables);
+
     handlerton *table_type= table->s->db_type();
+    /* Ensure the table is removed from the cache. */
+    table->s->version= 0;
+
     pthread_mutex_lock(&LOCK_open);
-    /*
-      unlink_open_table() also tells threads waiting for refresh or close
-      that something has happened.
-    */
-    unlink_open_table(thd, table, FALSE);
+    close_thread_table(thd, &thd->open_tables);
     quick_rm_table(table_type, db_name, table_name, 0);
     pthread_mutex_unlock(&LOCK_open);
   }
@@ -2503,6 +2530,62 @@ void table_share_release_hook(void *shar
 
 
 /*
+  A helper function that acquires an MDL lock for a table
+  being opened.
+*/
+
+static bool
+open_table_get_mdl_lock(THD *thd, TABLE_LIST *table_list,
+                        MDL_LOCK_DATA *mdl_lock_data,
+                        uint flags,
+                        enum_open_table_action *action)
+{
+  mdl_add_lock(&thd->mdl_context, mdl_lock_data);
+
+  if (table_list->open_type)
+  {
+    /*
+      In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table
+      may not yet exist. Let's acquire an exclusive lock for that
+      case. If later it turns out the table existsed, we will
+      downgrade the lock to shared. Note that, according to the
+      locking protocol, all exclusive locks must be acquired before
+      shared locks. This invariant is preserved here and is also
+      enforced by asserts in metadata locking subsystem.
+    */
+    mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE);
+    if (mdl_acquire_exclusive_locks(&thd->mdl_context))
+      return 1;
+  }
+  else
+  {
+    bool retry;
+
+    /*
+      There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we
+      want to be sure that caller doesn't pass us both flags simultaneously.
+    */
+    DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) ||
+                !(flags & MYSQL_LOCK_IGNORE_FLUSH));
+
+    if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL &&
+        table_list->lock_type >= TL_WRITE_ALLOW_WRITE)
+      mdl_set_lock_type(mdl_lock_data, MDL_SHARED_UPGRADABLE);
+    if (flags & MYSQL_LOCK_IGNORE_FLUSH)
+      mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO);
+
+    if (mdl_acquire_shared_lock(mdl_lock_data, &retry))
+    {
+      if (retry)
+        *action= OT_BACK_OFF_AND_RETRY;
+      return 1;
+    }
+  }
+  return 0;
+}
+
+
+/*
   Open a table.
 
   SYNOPSIS
@@ -2621,7 +2704,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     open not pre-opened tables in pre-locked/LOCK TABLES mode.
     TODO: move this block into a separate function.
   */
-  if (thd->locked_tables_mode)
+  if (thd->locked_tables_mode &&
+      ! (flags & MYSQL_OPEN_GET_NEW_TABLE))
   {						// Using table locks
     TABLE *best_table= 0;
     int best_distance= INT_MIN;
@@ -2749,46 +2833,11 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   */
 
   mdl_lock_data= table_list->mdl_lock_data;
-  mdl_add_lock(&thd->mdl_context, mdl_lock_data);
-
-  if (table_list->open_type)
+  if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK))
   {
-    /*
-      In case of CREATE TABLE .. If NOT EXISTS .. SELECT, the table
-      may not yet exist. Let's acquire an exclusive lock for that
-      case. If later it turns out the table existsed, we will
-      downgrade the lock to shared. Note that, according to the
-      locking protocol, all exclusive locks must be acquired before
-      shared locks. This invariant is preserved here and is also
-      enforced by asserts in metadata locking subsystem.
-    */
-    mdl_set_lock_type(mdl_lock_data, MDL_EXCLUSIVE);
-    if (mdl_acquire_exclusive_locks(&thd->mdl_context))
-      DBUG_RETURN(0);
-  }
-  else
-  {
-    bool retry;
-
-    /*
-      There is no MDL_SHARED_UPGRADABLE_HIGH_PRIO type of metadata lock so we
-      want to be sure that caller doesn't pass us both flags simultaneously.
-    */
-    DBUG_ASSERT(!(flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL) ||
-                !(flags & MYSQL_LOCK_IGNORE_FLUSH));
-
-    if (flags & MYSQL_OPEN_TAKE_UPGRADABLE_MDL &&
-        table_list->lock_type >= TL_WRITE_ALLOW_WRITE)
-      mdl_set_lock_type(mdl_lock_data, MDL_SHARED_UPGRADABLE);
-    if (flags & MYSQL_LOCK_IGNORE_FLUSH)
-      mdl_set_lock_type(mdl_lock_data, MDL_SHARED_HIGH_PRIO);
-
-    if (mdl_acquire_shared_lock(mdl_lock_data, &retry))
-    {
-      if (retry)
-        *action= OT_BACK_OFF_AND_RETRY;
+    if (open_table_get_mdl_lock(thd, table_list, mdl_lock_data, flags,
+                                action))
       DBUG_RETURN(0);
-    }
   }
 
   pthread_mutex_lock(&LOCK_open);
@@ -3211,6 +3260,7 @@ bool reopen_table(TABLE *table)
   /* Avoid wiping out TABLE's position in new share's used tables list. */
   tmp.share_next= table->share_next;
   tmp.share_prev= table->share_prev;
+  tmp.pos_in_locked_tables= table->pos_in_locked_tables;
 
   delete table->triggers;
   if (table->file)
@@ -3367,6 +3417,260 @@ static bool reattach_merge(THD *thd, TAB
 }
 
 
+/***********************************************************************
+  class Locked_tables_list implementation. Declared in sql_class.h
+************************************************************************/
+
+/**
+  Enter LTM_LOCK_TABLES mode.
+
+  Enter the LOCK TABLES mode using all the tables that are
+  currently open and locked in this connection.
+  Initializes a TABLE_LIST instance for every locked table.
+
+  @param  thd  thread handle
+
+  @return TRUE if out of memory.
+*/
+
+bool
+Locked_tables_list::init_locked_tables(THD *thd)
+{
+  DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE);
+  DBUG_ASSERT(m_locked_tables == NULL);
+
+  for (TABLE *table= thd->open_tables; table; table= table->next)
+  {
+    TABLE_LIST *src_table_list= table->pos_in_table_list;
+    char *db, *table_name, *alias;
+    size_t db_len= strlen(src_table_list->db) + 1;
+    size_t table_name_len= strlen(src_table_list->table_name) + 1;
+    size_t alias_len= strlen(src_table_list->alias) + 1;
+    TABLE_LIST *dst_table_list;
+
+    if (! multi_alloc_root(&m_locked_tables_root,
+                           &dst_table_list, sizeof(*dst_table_list),
+                           &db, db_len,
+                           &table_name, table_name_len,
+                           &alias, alias_len,
+                           0))
+    {
+      unlock_locked_tables(0);
+      return TRUE;
+    }
+
+    /**
+      Sic: remember the *actual* table level lock type taken, to
+      acquire the exact same type in reopen_tables().
+      E.g. if the table was locked for write, src_table_list->lock_type is
+      TL_WRITE_DEFAULT, whereas reginfo.lock_type has been updated from
+      thd->update_lock_default.
+    */
+    dst_table_list->init_one_table(db, table_name, alias,
+                                   src_table_list->table->reginfo.lock_type);
+    dst_table_list->mdl_lock_data= src_table_list->mdl_lock_data;
+    dst_table_list->table= table;
+    memcpy(db, src_table_list->db, db_len);
+    memcpy(table_name, src_table_list->table_name, table_name_len);
+    memcpy(alias, src_table_list->alias, alias_len);
+    /* Link last into the list of tables */
+    *(dst_table_list->prev_global= m_locked_tables_last)= dst_table_list;
+    m_locked_tables_last= &dst_table_list->next_global;
+    table->pos_in_locked_tables= dst_table_list;
+  }
+  thd->locked_tables_mode= LTM_LOCK_TABLES;
+  return FALSE;
+}
+
+/**
+  Leave LTM_LOCK_TABLES mode if it's been entered.
+
+  If not in LOCK TABLES, do nothing.
+  Otherwise close all locked tables, free memory,
+  and leave the mode.
+*/
+
+void
+Locked_tables_list::unlock_locked_tables(THD *thd)
+
+{
+  if (thd)
+  {
+    DBUG_ASSERT(!thd->in_sub_stmt &&
+                !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+    /*
+      Sic: we must be careful to not close open tables if
+      we're not in LOCK TABLES mode: unlock_locked_tables() is
+      sometimes called implicitly, expecting no effect on
+      open tables, e.g. from begin_trans().
+    */
+    if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+      return;
+
+    for (TABLE_LIST *table_list= m_locked_tables;
+         table_list; table_list= table_list->next_global)
+    {
+      /*
+        Clear the position in the list, the TABLE object will be
+        returned to the table cache.
+      */
+      table_list->table->pos_in_locked_tables= NULL;
+    }
+    thd->locked_tables_mode= LTM_NONE;
+
+    close_thread_tables(thd);
+  }
+  /*
+    After closing tables we can free memory used for storing lock
+    request for metadata locks and TABLE_LIST elements.
+  */
+  free_root(&m_locked_tables_root, MYF(0));
+  m_locked_tables= NULL;
+  m_locked_tables_last= &m_locked_tables;
+}
+
+
+/**
+  Unlink a locked table from the locked tables list, either
+  temporarily or permanently. 
+
+  @param  thd        thread handle
+  @param  table_list the element of locked tables list.
+                     The implementation assumes that this argument
+                     points to a TABLE_LIST element linked into
+                     the locked tables list. Passing a TABLE_LIST
+                     instance that is not part of locked tables
+                     list will lead to a crash.
+  @parma  remove_from_locked_tables
+                      TRUE if the table is removed from the list
+                      permanently.
+
+  @sa Locked_tables_list::reopen_tables()
+*/
+
+
+void Locked_tables_list::unlink_from_list(THD *thd,
+                                          TABLE_LIST *table_list,
+                                          bool remove_from_locked_tables)
+{
+  /*
+    If mode is not LTM_LOCK_TABLES, we needn't do anything. Moreover,
+    outside this mode pos_in_locked_tables value is not trustworthy.
+  */
+  if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+    return;
+
+  /*
+    table_list must be set and point to pos_in_locked_tables of some
+    table.
+  */
+  DBUG_ASSERT(table_list->table->pos_in_locked_tables == table_list);
+
+  /* Clear the pointer, the table will be returned to the table cache. */
+  table_list->table->pos_in_locked_tables= NULL;
+
+  /* Mark the table as closed in the locked tables list. */
+  table_list->table= NULL;
+
+  /*
+    If the table is being dropped or renamed, remove it from
+    the locked tables list (implicitly drop the LOCK TABLES lock
+    on it).
+  */
+  if (remove_from_locked_tables)
+  {
+    *table_list->prev_global= table_list->next_global;
+    if (table_list->next_global == NULL)
+      m_locked_tables_last= table_list->prev_global;
+    else
+      table_list->next_global->prev_global= table_list->prev_global;
+  }
+}
+
+/**
+  This is an attempt to recover (somewhat) in case of an error.
+  If we failed to reopen a closed table, let's unlink it from the list
+  and forget about it.
+*/
+
+void Locked_tables_list::unlink_all_closed_tables()
+{
+  for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list=
+       table_list->next_global)
+  {
+    if (table_list->table == NULL)
+    {
+      /* Unlink from list. */
+      *table_list->prev_global= table_list->next_global;
+      if (table_list->next_global == NULL)
+        m_locked_tables_last= table_list->prev_global;
+      else
+        table_list->next_global->prev_global= table_list->prev_global;
+    }
+  }
+}
+
+
+/**
+  Reopen the tables locked with LOCK TABLES and temporarily closed
+  by a DDL statement or FLUSH TABLES.
+
+  @return TRUE if an error reopening the tables.
+  May happen in case of some fatal system error only, e.g. a disk
+  corruption, out of memory or a serious bug in the locking.
+*/
+
+bool
+Locked_tables_list::reopen_tables(THD *thd)
+{
+  enum enum_open_table_action ot_action_unused;
+  bool lt_refresh_unused;
+
+  for (TABLE_LIST *table_list= m_locked_tables;
+       table_list; table_list= table_list->next_global)
+  {
+    MYSQL_LOCK *lock;
+
+    if (table_list->table)                      /* The table was not closed */
+      continue;
+
+    /* Links into thd->open_tables upon success */
+    table_list->table= open_table(thd, table_list, thd->mem_root,
+                                  &ot_action_unused, MYSQL_OPEN_REOPEN);
+    if (! table_list->table)
+    {
+      unlink_all_closed_tables();
+      return TRUE;
+    }
+    table_list->table->pos_in_locked_tables= table_list;
+    /* See also the comment on lock type in init_locked_tables(). */
+    table_list->table->reginfo.lock_type= table_list->lock_type;
+    thd->in_lock_tables= 1;
+    lock= mysql_lock_tables(thd, &table_list->table, 1,
+                            MYSQL_OPEN_REOPEN, &lt_refresh_unused);
+    thd->in_lock_tables= 0;
+    if (lock)
+      lock= mysql_lock_merge(thd->lock, lock);
+    if (lock == NULL)
+    {
+      /*
+        No one's seen this branch work. Recover and report an
+        error just in case.
+      */
+      pthread_mutex_lock(&LOCK_open);
+      close_thread_table(thd, &thd->open_tables);
+      pthread_mutex_unlock(&LOCK_open);
+      table_list->table= 0;
+      unlink_all_closed_tables();
+      my_error(ER_LOCK_DEADLOCK, MYF(0));
+      return TRUE;
+    }
+    thd->lock= lock;
+  }
+  return FALSE;
+}
+
+
 /**
     Reopen all tables with closed data files.
 
@@ -3519,36 +3823,6 @@ bool reopen_tables(THD *thd, bool get_lo
 }
 
 
-/**
-   Unlock and close tables open and locked by LOCK TABLES statement.
-
-   @param thd Current thread's context.
-*/
-
-void unlock_locked_tables(THD *thd)
-{
-  DBUG_ASSERT(!thd->in_sub_stmt &&
-              !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
-
-  /*
-    Sic: we must be careful to not close open tables if
-    we're not in LOCK TABLES mode: unlock_locked_tables() is
-    sometimes called implicitly, expecting no effect on
-    open tables, e.g. from begin_trans().
-  */
-  if (thd->locked_tables_mode != LTM_LOCK_TABLES)
-    return;
-
-  thd->locked_tables_mode= LTM_NONE;
-  close_thread_tables(thd);
-  /*
-    After closing tables we can free memory used for storing lock
-    request objects for metadata locks
-  */
-  free_root(&thd->locked_tables_root, MYF(MY_MARK_BLOCKS_FREE));
-}
-
-
 /*
   Function to assign a new table map id to a table share.
 
@@ -8559,21 +8833,10 @@ int abort_and_upgrade_lock(ALTER_PARTITI
   DBUG_ENTER("abort_and_upgrade_locks");
 
   lpt->old_lock_type= lpt->table->reginfo.lock_type;
-  /* If MERGE child, forward lock handling to parent. */
-  mysql_lock_abort(lpt->thd, lpt->table->parent ? lpt->table->parent :
-                   lpt->table, TRUE);
-  if (mdl_upgrade_shared_lock_to_exclusive(&lpt->thd->mdl_context,
-                                           lpt->table->mdl_lock_data))
-  {
-    mysql_lock_downgrade_write(lpt->thd,
-                               lpt->table->parent ? lpt->table->parent :
-                                                    lpt->table,
-                               lpt->old_lock_type);
+
+  if (wait_while_table_is_used(lpt->thd, lpt->table, HA_EXTRA_FORCE_REOPEN))
     DBUG_RETURN(1);
-  }
-  pthread_mutex_lock(&LOCK_open);
-  expel_table_from_cache(lpt->thd, lpt->db, lpt->table_name);
-  pthread_mutex_unlock(&LOCK_open);
+
   DBUG_RETURN(0);
 }
 

=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc	2008-06-06 19:19:04 +0000
+++ b/sql/sql_class.cc	2008-06-09 13:45:11 +0000
@@ -540,7 +540,7 @@ THD::THD()
           when the DDL blocker is engaged.
   */
    DDL_exception(FALSE),
-   mdl_el_root(NULL)
+   locked_tables_root(NULL)
 {
   ulong tmp;
 
@@ -650,8 +650,6 @@ THD::THD()
   thr_lock_owner_init(&main_lock_id, &lock_info);
 
   m_internal_handler= NULL;
-
-  init_sql_alloc(&locked_tables_root, ALLOC_ROOT_MIN_BLOCK_SIZE, 0);
 }
 
 
@@ -846,7 +844,7 @@ void THD::cleanup(void)
     ha_rollback(this);
     xid_cache_delete(&transaction.xid_state);
   }
-  unlock_locked_tables(this);
+  locked_tables_list.unlock_locked_tables(this);
   mysql_ha_cleanup(this);
   delete_dynamic(&user_var_events);
   hash_free(&user_vars);
@@ -947,7 +945,6 @@ THD::~THD()
 #endif
 
   free_root(&main_mem_root, MYF(0));
-  free_root(&locked_tables_root, MYF(0));
   DBUG_VOID_RETURN;
 }
 

=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h	2008-06-06 19:19:04 +0000
+++ b/sql/sql_class.h	2008-06-09 13:45:11 +0000
@@ -1223,6 +1223,58 @@ private:
   */
 };
 
+/**
+  Tables that were locked with LOCK TABLES statement.
+
+  Encapsulates a list of TABLE_LIST instances for tables
+  locked by LOCK TABLES statement, memory root for metadata locks,
+  and, generally, the context of LOCK TABLES statement.
+
+  In LOCK TABLES mode, the locked tables are kept open between
+  statements.
+  Therefore, we can't allocate metadata locks on execution memory
+  root -- as well as tables, the locks need to stay around till
+  UNLOCK TABLES is called.
+  The locks are allocated in the memory root encapsulate in this
+  class.
+
+  Some SQL commands, like FLUSH TABLE or ALTER TABLE, demand that
+  the tables they operate on are closed, at least temporarily.
+  This class encapsulates a list of TABLE_LIST instances, one
+  for each element of LOCK TABLES list,
+  which helps conveniently close the TABLEs when it's necessary
+  and later reopen them.
+
+  Implemented in sql_base.cc
+*/
+
+class Locked_tables_list
+{
+private:
+  MEM_ROOT m_locked_tables_root;
+  TABLE_LIST *m_locked_tables;
+  TABLE_LIST **m_locked_tables_last;
+public:
+  Locked_tables_list()
+    :m_locked_tables(NULL),
+    m_locked_tables_last(&m_locked_tables)
+  {
+    init_sql_alloc(&m_locked_tables_root, MEM_ROOT_BLOCK_SIZE, 0);
+  }
+  void unlock_locked_tables(THD *thd);
+  ~Locked_tables_list()
+  {
+    unlock_locked_tables(0);
+  }
+  bool init_locked_tables(THD *thd);
+  TABLE_LIST *locked_tables() { return m_locked_tables; }
+  MEM_ROOT *locked_tables_root() { return &m_locked_tables_root; }
+  void unlink_from_list(THD *thd, TABLE_LIST *table_list,
+                        bool remove_from_locked_tables);
+  void unlink_all_closed_tables();
+  bool reopen_tables(THD *thd);
+};
+
 
 /**
   Storage engine specific thread local data.
@@ -1867,6 +1919,8 @@ public:
   */
   my_bool DDL_exception; // Allow some DDL if there is an exception
 
+  Locked_tables_list locked_tables_list;
+
 #ifdef WITH_PARTITION_STORAGE_ENGINE
   partition_info *work_part_info;
 #endif
@@ -1885,8 +1939,7 @@ public:
   unsigned long audit_class_mask[MYSQL_AUDIT_CLASS_MASK_SIZE];
 #endif
 
-  MEM_ROOT *mdl_el_root;
-  MEM_ROOT locked_tables_root;
+  MEM_ROOT *locked_tables_root;
 
   THD();
   ~THD();

=== modified file 'sql/sql_insert.cc'
--- a/sql/sql_insert.cc	2008-06-06 19:19:04 +0000
+++ b/sql/sql_insert.cc	2008-06-09 13:45:11 +0000
@@ -3456,16 +3456,26 @@ static TABLE *create_table_from_items(TH
 
       if (!(create_info->options & HA_LEX_CREATE_TMP_TABLE))
       {
-        pthread_mutex_lock(&LOCK_open);
-        if (reopen_name_locked_table(thd, create_table))
+        enum enum_open_table_action ot_action_unused;
+        /*
+          Here we open the destination table, on which we already have
+          exclusive metadata lock. This is needed for store_create_info()
+          to work. The table will be closed by close_thread_table() at
+          the end of this branch.
+        */
+        create_table->table= open_table(thd, create_table, thd->mem_root,
+                                        &ot_action_unused, MYSQL_OPEN_REOPEN);
+
+        if (! create_table->table)
         {
+          pthread_mutex_lock(&LOCK_open);
           quick_rm_table(create_info->db_type, create_table->db,
                          table_case_name(create_info, create_table->table_name),
                          0);
+          pthread_mutex_unlock(&LOCK_open);
         }
         else
           table= create_table->table;
-        pthread_mutex_unlock(&LOCK_open);
       }
       else
       {

=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc	2008-06-06 19:19:04 +0000
+++ b/sql/sql_parse.cc	2008-06-09 13:45:11 +0000
@@ -136,7 +136,7 @@ bool begin_trans(THD *thd)
     return 1;
   }
 
-  unlock_locked_tables(thd);
+  thd->locked_tables_list.unlock_locked_tables(thd);
 
   if (end_active_trans(thd))
     error= -1;
@@ -3273,7 +3273,7 @@ end_with_restore_list:
       done FLUSH TABLES WITH READ LOCK + BEGIN. If this assumption becomes
       false, mysqldump will not work.
     */
-    unlock_locked_tables(thd);
+    thd->locked_tables_list.unlock_locked_tables(thd);
     if (thd->options & OPTION_TABLE_LOCK)
     {
       end_active_trans(thd);
@@ -3326,29 +3326,27 @@ end_with_restore_list:
     */
     if (check_transactional_lock(thd, all_tables))
       goto error;
-    unlock_locked_tables(thd);
+    thd->locked_tables_list.unlock_locked_tables(thd);
     /* we must end the trasaction first, regardless of anything */
     if (end_active_trans(thd))
       goto error;
-    thd->in_lock_tables=1;
+
+    alloc_mdl_locks(all_tables, thd->locked_tables_list.locked_tables_root());
+
     thd->options|= OPTION_TABLE_LOCK;
-    alloc_mdl_locks(all_tables, &thd->locked_tables_root);
-    thd->mdl_el_root= &thd->locked_tables_root;
+    thd->in_lock_tables=1;
+    thd->locked_tables_root= thd->locked_tables_list.locked_tables_root();
 
-    if (!(res= open_and_lock_tables_derived(thd, all_tables, FALSE,
-                                            MYSQL_OPEN_TAKE_UPGRADABLE_MDL)))
-    {
-#ifdef HAVE_QUERY_CACHE
-      if (thd->variables.query_cache_wlock_invalidate)
-	query_cache.invalidate_locked_for_write(first_table);
-#endif /*HAVE_QUERY_CACHE*/
-      thd->locked_tables_mode= LTM_LOCK_TABLES;
-      (void) set_handler_table_locks(thd, all_tables, FALSE);
-      my_ok(thd);
-    }
-    else
+    res= (open_and_lock_tables_derived(thd, all_tables, FALSE,
+                                       MYSQL_OPEN_TAKE_UPGRADABLE_MDL) ||
+          thd->locked_tables_list.init_locked_tables(thd));
+
+    thd->in_lock_tables= 0;
+    thd->locked_tables_root= NULL;
+
+    if (res)
     {
-      /* 
+      /*
         Need to end the current transaction, so the storage engine (InnoDB)
         can free its locks if LOCK TABLES locked some tables before finding
         that it can't lock a table in its list
@@ -3357,8 +3355,15 @@ end_with_restore_list:
       end_active_trans(thd);
       thd->options&= ~(OPTION_TABLE_LOCK);
     }
-    thd->in_lock_tables=0;
-    thd->mdl_el_root= 0;
+    else
+    {
+#ifdef HAVE_QUERY_CACHE
+      if (thd->variables.query_cache_wlock_invalidate)
+        query_cache.invalidate_locked_for_write(first_table);
+#endif /*HAVE_QUERY_CACHE*/
+      (void) set_handler_table_locks(thd, all_tables, FALSE);
+      my_ok(thd);
+    }
     break;
   case SQLCOM_CREATE_DB:
   {
@@ -6138,8 +6143,8 @@ TABLE_LIST *st_select_lex::add_table_to_
   /* Link table in global list (all used tables) */
   lex->add_to_query_tables(ptr);
   ptr->mdl_lock_data= mdl_alloc_lock(0 , ptr->db, ptr->table_name,
-                                     thd->mdl_el_root ? thd->mdl_el_root :
-                                                        thd->mem_root);
+                                thd->locked_tables_root ?
+                                thd->locked_tables_root : thd->mem_root);
   DBUG_RETURN(ptr);
 }
 

=== modified file 'sql/sql_partition.cc'
--- a/sql/sql_partition.cc	2008-06-06 19:19:04 +0000
+++ b/sql/sql_partition.cc	2008-06-09 13:45:11 +0000
@@ -3953,7 +3953,6 @@ set_engine_all_partitions(partition_info
 
 static int fast_end_partition(THD *thd, ulonglong copied,
                               ulonglong deleted,
-                              TABLE *table,
                               TABLE_LIST *table_list, bool is_empty,
                               ALTER_PARTITION_PARAM_TYPE *lpt,
                               bool written_bin_log)
@@ -3972,11 +3971,7 @@ static int fast_end_partition(THD *thd, 
     error= 1;
 
   if (error)
-  {
-    /* If error during commit, no need to rollback, it's done. */
-    table->file->print_error(error, MYF(0));
     DBUG_RETURN(TRUE);
-  }
 
   if ((!is_empty) && (!written_bin_log) &&
       (!thd->lex->no_write_to_binlog))
@@ -4154,7 +4149,7 @@ uint prep_alter_part_table(THD *thd, TAB
           without any changes at all.
         */
         DBUG_RETURN(fast_end_partition(thd, ULL(0), ULL(0),
-                                       table, NULL,
+                                       NULL,
                                        TRUE, NULL, FALSE));
       }
       else if (new_part_no > curr_part_no)
@@ -5781,30 +5776,13 @@ static void release_log_entries(partitio
 */
 static void alter_partition_lock_handling(ALTER_PARTITION_PARAM_TYPE *lpt)
 {
-  int err;
-  if (lpt->thd->locked_tables_mode)
-  {
-    /*
-      When we have the table locked, it is necessary to reopen the table
-      since all table objects were closed and removed as part of the
-      ALTER TABLE of partitioning structure.
-    */
-    pthread_mutex_lock(&LOCK_open);
-    lpt->thd->in_lock_tables= 1;
-    err= reopen_tables(lpt->thd, 1);
-    lpt->thd->in_lock_tables= 0;
-    if (err)
-    {
-      /*
-       Issue a warning since we weren't able to regain the lock again.
-       We also need to unlink table from thread's open list and from
-       table_cache
-     */
-      unlink_open_table(lpt->thd, lpt->table, FALSE);
-      sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE");
-    }
-    pthread_mutex_unlock(&LOCK_open);
-  }
+  THD *thd= lpt->thd;
+
+  close_all_tables_for_name(thd, lpt->table->s, FALSE);
+  lpt->table= 0;
+  lpt->table_list->table= 0;
+  if (thd->locked_tables_list.reopen_tables(thd))
+    sql_print_warning("We failed to reacquire LOCKs in ALTER TABLE");
 }
 
 /*
@@ -5818,17 +5796,30 @@ static void alter_partition_lock_handlin
 
 static int alter_close_tables(ALTER_PARTITION_PARAM_TYPE *lpt)
 {
+  TABLE_SHARE *share= lpt->table->s;
   THD *thd= lpt->thd;
-  const char *db= lpt->db;
-  const char *table_name= lpt->table_name;
+  TABLE *table;
   DBUG_ENTER("alter_close_tables");
   /*
-    We need to also unlock tables and close all handlers.
-    We set lock to zero to ensure we don't do this twice
-    and we set db_stat to zero to ensure we don't close twice.
+    We must keep LOCK_open while manipulating with thd->open_tables.
+    Another thread may be working on it.
   */
   pthread_mutex_lock(&LOCK_open);
-  close_data_files_and_leave_as_placeholders(thd, db, table_name);
+  /*
+    We can safely remove locks for all tables with the same name:
+    later they will all be closed anyway in
+    alter_partition_lock_handling().
+  */
+  for (table= thd->open_tables; table ; table= table->next)
+  {
+    if (!strcmp(table->s->table_name.str, share->table_name.str) &&
+	!strcmp(table->s->db.str, share->db.str))
+    {
+      mysql_lock_remove(thd, thd->lock, table, TRUE);
+      table->file->close();
+      table->db_stat= 0;                        // Mark file closed
+    }
+  }
   pthread_mutex_unlock(&LOCK_open);
   DBUG_RETURN(0);
 }
@@ -5995,6 +5986,7 @@ uint fast_alter_partition_table(THD *thd
   DBUG_ENTER("fast_alter_partition_table");
 
   lpt->thd= thd;
+  lpt->table_list= table_list;
   lpt->part_info= part_info;
   lpt->alter_info= alter_info;
   lpt->create_info= create_info;
@@ -6349,7 +6341,7 @@ uint fast_alter_partition_table(THD *thd
     user
   */
   DBUG_RETURN(fast_end_partition(thd, lpt->copied, lpt->deleted,
-                                 table, table_list, FALSE, NULL,
+                                 table_list, FALSE, NULL,
                                  written_bin_log));
 err:
   close_thread_tables(thd);

=== modified file 'sql/sql_servers.cc'
--- a/sql/sql_servers.cc	2008-05-23 13:54:03 +0000
+++ b/sql/sql_servers.cc	2008-06-09 13:45:11 +0000
@@ -223,7 +223,8 @@ bool servers_reload(THD *thd)
   bool return_val= TRUE;
   DBUG_ENTER("servers_reload");
 
-  unlock_locked_tables(thd); // Can't have locked tables here
+  /* Can't have locked tables here */
+  thd->locked_tables_list.unlock_locked_tables(thd);
 
   DBUG_PRINT("info", ("locking servers_cache"));
   rw_wrlock(&THR_LOCK_servers);

=== modified file 'sql/sql_table.cc'
--- a/sql/sql_table.cc	2008-06-06 19:19:04 +0000
+++ b/sql/sql_table.cc	2008-06-09 13:45:11 +0000
@@ -1641,7 +1641,7 @@ int mysql_rm_table_part2(THD *thd, TABLE
     case -1:
       DBUG_ASSERT(thd->in_sub_stmt);
       error= 1;
-      goto err_with_placeholders;
+      goto err;
     default:
       // temporary table not found
       error= 0;
@@ -1678,18 +1678,19 @@ int mysql_rm_table_part2(THD *thd, TABLE
     {
       if (thd->locked_tables_mode)
       {
-        if (close_cached_table(thd, table->table))
+        if (wait_while_table_is_used(thd, table->table, HA_EXTRA_FORCE_REOPEN))
         {
           error= -1;
-          goto err_with_placeholders;
+          goto err;
         }
+        close_all_tables_for_name(thd, table->table->s, TRUE);
         table->table= 0;
       }
 
       if (thd->killed)
       {
         error= -1;
-        goto err_with_placeholders;
+        goto err;
       }
       alias= (lower_case_table_names == 2) ? table->alias : table->table_name;
       /* remove .frm file and engine files */
@@ -1823,7 +1824,7 @@ int mysql_rm_table_part2(THD *thd, TABLE
       */
     }
   }
-err_with_placeholders:
+err:
   if (!drop_temporary)
   {
     /*
@@ -1840,7 +1841,7 @@ err_with_placeholders:
     if (thd->locked_tables_mode &&
         thd->lock && thd->lock->table_count == 0 &&
non_temp_tables_count > 0)
     {
-      unlock_locked_tables(thd);
+      thd->locked_tables_list.unlock_locked_tables(thd);
       goto end;
     }
     for (table= tables; table; table= table->next_local)
@@ -4028,8 +4029,9 @@ static int prepare_for_repair(THD *thd, 
       Table was successfully open in mysql_admin_table(). Now we need
       to close it, but leave it protected by exclusive metadata lock.
     */
-    if (close_cached_table(thd, table))
+    if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
       goto end;
+    close_all_tables_for_name(thd, table_list->table->s, TRUE);
     table_list->table= 0;
   }
   /*
@@ -4767,6 +4769,7 @@ bool mysql_create_like_table(THD* thd, T
                                           db, table_name, reg_ext, 0);
     if (!access(dst_path, F_OK))
       goto table_exists;
+    table->mdl_lock_data= target_lock_data;
   }
 
   DBUG_EXECUTE_IF("sleep_create_like_before_copy", my_sleep(6000000););
@@ -4874,20 +4877,18 @@ bool mysql_create_like_table(THD* thd, T
         char buf[2048];
         String query(buf, sizeof(buf), system_charset_info);
         query.length(0);  // Have to zero it since constructor doesn't
-
+        enum enum_open_table_action ot_action_unused;
         /*
           Here we open the destination table, on which we already have
-          exclusive metada lock. This is needed for store_create_info()
-          to work. The table will be closed by unlink_open_table() at
-          the end of this function.
+          exclusive metadata lock. This is needed for store_create_info()
+          to work. The table will be closed by close_thread_table() at
+          the end of this branch.
         */
-        pthread_mutex_lock(&LOCK_open);
-        if (reopen_name_locked_table(thd, table))
-        {
-          pthread_mutex_unlock(&LOCK_open);
+        table->table= open_table(thd, table, thd->mem_root, &ot_action_unused,
+                                 MYSQL_OPEN_REOPEN);
+
+        if (! table->table)
           goto err;
-        }
-        pthread_mutex_unlock(&LOCK_open);
 
         IF_DBUG(int result=) store_create_info(thd, table, &query,
                                                create_info);
@@ -4895,8 +4896,14 @@ bool mysql_create_like_table(THD* thd, T
         DBUG_ASSERT(result == 0); // store_create_info() always return 0
         write_bin_log(thd, TRUE, query.ptr(), query.length());
 
+        DBUG_ASSERT(thd->open_tables == table->table);
         pthread_mutex_lock(&LOCK_open);
-        unlink_open_table(thd, table->table, FALSE);
+        /*
+          When opening the table, we ignored the locked tables
+          (MYSQL_OPEN_GET_NEW_TABLE). Now we can close the table without
+          risking to close some locked table.
+        */
+        close_thread_table(thd, &thd->open_tables);
         pthread_mutex_unlock(&LOCK_open);
       }
       else                                      // Case 1
@@ -5666,95 +5673,88 @@ TABLE *create_altered_table(THD *thd,
 /*
   Perform a fast or on-line alter table
 
-  SYNOPSIS
-    mysql_fast_or_online_alter_table()
-      thd              Thread handle
-      table            The original table
-      altered_table    A temporary table showing how we will change table
-      create_info      Information from the parsing phase about new
-                       table properties.
-      alter_info       Storage place for data used during different phases
-      ha_alter_flags   Bitmask that shows what will be changed
-      keys_onoff       Specifies if keys are to be enabled/disabled
-  RETURN
-     0  OK
-    >0  An error occured during the on-line alter table operation
-    -1  Error when re-opening table
-  NOTES
-    If mysql_alter_table does not need to copy the table, it is
-    either a fast alter table where the storage engine does not
-    need to know about the change, only the frm will change,
-    or the storage engine supports performing the alter table
-    operation directly, on-line without mysql having to copy
-    the table.
+  @param  thd            Thread handle
+  @param  table_list     The original table
+  @param  altered_table  A temporary table showing how we will change table
+  @param  create_info    Information from the parsing phase about new
+                         table properties.
+  @param  alter_info     Storage place for data used during different phases
+  @param  ha_alter_flags Bitmask that shows what will be changed
+  @param  keys_onoff     Specifies if keys are to be enabled/disabled
+
+  If mysql_alter_table() does not need to copy the table, it is
+  either a fast alter table, where the storage engine does not
+  need to know about the change, only the frm will change,
+  or the storage engine supports performing the alter table
+  operation directly, on-line, without mysql having to copy
+  the table.
+
+  @retval  0  success
+  @retval  1  error, that happened before we took an
+              exclusive metadata lock
+  @retval  2  error, that happened after we took an
+              exclusive metadata lock
 */
-int mysql_fast_or_online_alter_table(THD *thd,
-                                     TABLE *table,
-                                     TABLE *altered_table,
-                                     HA_CREATE_INFO *create_info,
-                                     HA_ALTER_INFO *alter_info,
-                                     HA_ALTER_FLAGS *ha_alter_flags,
-                                     enum enum_enable_or_disable keys_onoff)
+
+int
+mysql_fast_or_online_alter_table(THD *thd,
+                                 TABLE_LIST *table_list,
+                                 TABLE *altered_table,
+                                 HA_CREATE_INFO *create_info,
+                                 HA_ALTER_INFO *alter_info,
+                                 HA_ALTER_FLAGS *ha_alter_flags,
+                                 enum enum_enable_or_disable keys_onoff)
 {
-  int error= 0;
-  bool online= (table->file->ha_table_flags() & HA_ONLINE_ALTER)?true:false;
-  TABLE *t_table;
+  TABLE *table= table_list->table;
+  bool is_online_alter= table->file->ha_table_flags() & HA_ONLINE_ALTER;
+  int error;
+
+  DBUG_ENTER("mysql_fast_or_online_alter_table");
 
-  DBUG_ENTER(" mysql_fast_or_online_alter_table");
-  if (online)
+  if (is_online_alter)
   {
-   /*
-      Tell the handler to prepare for the online alter
-    */
-    if ((error= table->file->alter_table_phase1(thd,
-                                                altered_table,
-                                                create_info,
-                                                alter_info,
-                                                ha_alter_flags)))
-    {
-      goto err;
-    }
+    /* Tell the storage engine to prepare for the online ALTER. */
+    if (table->file->alter_table_phase1(thd, altered_table,
+                                        create_info, alter_info,
+                                        ha_alter_flags))
+      DBUG_RETURN(1);
 
     /*
-       Tell the storage engine to perform the online alter table
-       TODO: 
-       if check_if_supported_alter() returned HA_ALTER_SUPPORTED_WAIT_LOCK
-       we need to wrap the next call with a DDL lock.
-     */
-    if ((error= table->file->alter_table_phase2(thd,
-                                                altered_table,
-                                                create_info,
-                                                alter_info,
-                                                ha_alter_flags)))
-    {
-      goto err;
-    }
+      Tell the storage engine to perform the online ALTER.
+      @todo If check_if_supported_alter() returns
+      HA_ALTER_SUPPORTED_WAIT_LOCK we need to wrap the next call
+      with a DDL lock.
+    */
+    if (table->file->alter_table_phase2(thd, altered_table,
+                                        create_info, alter_info,
+                                        ha_alter_flags))
+      DBUG_RETURN(1);
   }
+
   /*
-    The final .frm file is already created as a temporary file
-    and will be renamed to the original table name.
+    Upgrade the shared metadata lock to exclusive and close all
+    instances of the table in the TDC except those used in this thread.
   */
   if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
-  {
-    error= 1;
-    goto err;
-  }
-  pthread_mutex_lock(&LOCK_open);
+    DBUG_RETURN(1);
+
   alter_table_manage_keys(table, table->file->indexes_are_disabled(),
                           keys_onoff);
-  close_data_files_and_leave_as_placeholders(thd, table->pos_in_table_list->db,
-                                          table->pos_in_table_list->table_name);
-  if (mysql_rename_table(NULL,
-			 altered_table->s->db.str,
-                         altered_table->s->table_name.str,
-			 table->s->db.str,
-                         table->s->table_name.str, FN_FROM_IS_TMP))
-  {
-    error= 1;
-    pthread_mutex_unlock(&LOCK_open);
-    goto err;
-  }
-  broadcast_refresh();
+
+  /* Now close all open instances of the table in this thread */
+  close_all_tables_for_name(thd, table->s, FALSE);
+  table_list->table= 0;                         /* The table was closed */
+
+  /*
+    The final .frm file is already created as a temporary file.
+    Let's rename it to the original table name.
+  */
+  pthread_mutex_lock(&LOCK_open);
+  error= mysql_rename_table(NULL,
+                            altered_table->s->db.str,
+                            altered_table->s->table_name.str,
+                            table_list->db, table_list->table_name,
+                            FN_FROM_IS_TMP);
   pthread_mutex_unlock(&LOCK_open);
 
   /*
@@ -5764,45 +5764,38 @@ int mysql_fast_or_online_alter_table(THD
     with LOCK_open.
   */
   error= ha_autocommit_or_rollback(thd, 0);
-
   if (ha_commit(thd))
-    error=1;
+    error= 1;
+
   if (error)
-    goto err;
-  if (online)
+    DBUG_RETURN(2);
+
+  if (is_online_alter)
   {
-    pthread_mutex_lock(&LOCK_open);
-    if (reopen_table(table))
-    {
-      error= -1;
-      goto err;
-    }
-    pthread_mutex_unlock(&LOCK_open);
-    t_table= table;
+    enum enum_open_table_action ot_action_unused;
+    /*
+      Here we open the destination table, on which we already have
+      an exclusive metadata lock.
+    */
+    table= open_table(thd, table_list, thd->mem_root,
+                      &ot_action_unused, MYSQL_OPEN_REOPEN);
 
-   /*
-      Tell the handler that the changed frm is on disk and table
-      has been re-opened
-   */
-    if ((error= t_table->file->alter_table_phase3(thd, t_table)))
-    {
-      goto err;
-    }
+    if (! table)
+      DBUG_RETURN(2);
 
     /*
-      We are going to reopen table down on the road, so we have to restore
-      state of the TABLE object which we used for obtaining of handler
-      object to make it suitable for reopening.
+      Tell the handler that the changed .frm is on disk and the
+      table has been re-opened successfully.
     */
-    DBUG_ASSERT(t_table == table);
+    if (table->file->alter_table_phase3(thd, table))
+      error= 2;
+
+    DBUG_ASSERT(thd->open_tables == table);
     pthread_mutex_lock(&LOCK_open);
-    close_handle_and_leave_table_as_placeholder(table);
+    close_thread_table(thd, &thd->open_tables);
     pthread_mutex_unlock(&LOCK_open);
   }
-
- err:
-  if (error)
-    DBUG_PRINT("info", ("Got error %u", error));
+  DBUG_ASSERT(table_list->table == 0);
   DBUG_RETURN(error);
 }
 
@@ -6575,13 +6568,14 @@ view_err:
       /*
         Then do a 'simple' rename of the table. First we need to close all
         instances of 'source' table.
-        Note that if close_cached_table() returns error here (i.e. if
+        Note that if wait_while_table_is_used() returns error here (i.e. if
         this thread was killed) then it must be that previous step of
-        simple rename did nothing and therefore we can safely reture
+        simple rename did nothing and therefore we can safely return
         without additional clean-up.
       */
-      if (close_cached_table(thd, table))
+      if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
         goto err;
+      close_all_tables_for_name(thd, table->s, TRUE);
       /*
         Then, we want check once again that target table does not exist.
         Actually the order of these two steps does not matter since
@@ -6810,32 +6804,24 @@ view_err:
     if (!need_copy_table)
     {
       error= mysql_fast_or_online_alter_table(thd,
-                                              table,
+                                              table_list,
                                               altered_table,
                                               create_info,
                                               &ha_alter_info,
                                               &ha_alter_flags,
                                               alter_info->keys_onoff);
-      if (thd->lock && !thd->locked_tables_mode)
-      {
-        mysql_unlock_tables(thd, thd->lock);
-        thd->lock=0;
-      }
       close_temporary_table(thd, altered_table, 1, 1);
 
-      if (error)
-      {
-        switch (error) {
-        case(-1):
-          goto err_with_placeholders;
-        default:
-          goto err;
-        }
-      }
-      else
-      {
-        pthread_mutex_lock(&LOCK_open);
+      switch (error) {
+      case 0:
         goto end_online;
+      case 1:
+        goto err;
+      case 2:
+        goto err_with_mdl;
+      default:
+        DBUG_ASSERT(0);
+        goto err;
       }
     }
 
@@ -6970,11 +6956,12 @@ view_err:
   if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
     goto err_new_table_cleanup;
 
-  pthread_mutex_lock(&LOCK_open);
 
-  close_data_files_and_leave_as_placeholders(thd, db, table_name);
+  close_all_tables_for_name(thd, table->s,
+                            new_name != table_name || new_db != db);
 
   error=0;
+  table_list->table= table= 0;                  /* Safety */
   save_old_db_type= old_db_type;
 
   /*
@@ -6990,6 +6977,7 @@ view_err:
     table is renamed and the SE is also changed, then an intermediate table
     is created and the additional call will not take place.
   */
+  pthread_mutex_lock(&LOCK_open);
   if (mysql_rename_table(old_db_type, db, table_name, db, old_name,
                          FN_TO_IS_TMP))
   {
@@ -7010,25 +6998,18 @@ view_err:
                             FN_FROM_IS_TMP);
   }
 
-  if (error)
-  {
-    /* This shouldn't happen. But let us play it safe. */
-    goto err_with_placeholders;
-  }
-
+  if (! error)
   (void) quick_rm_table(old_db_type, db, old_name, FN_IS_TMP);
 
-end_online:
-  if (thd->locked_tables_mode && new_name == table_name && new_db ==
db)
-  {
-    thd->in_lock_tables= 1;
-    error= reopen_tables(thd, 1);
-    thd->in_lock_tables= 0;
-    if (error)
-      goto err_with_placeholders;
-  }
   pthread_mutex_unlock(&LOCK_open);
 
+  if (error)
+    goto err_with_mdl;
+
+end_online:
+  if (thd->locked_tables_list.reopen_tables(thd))
+    goto err_with_mdl;
+
   thd_proc_info(thd, "end");
 
   DBUG_EXECUTE_IF("sleep_alter_before_main_binlog", my_sleep(6000000););
@@ -7070,9 +7051,6 @@ end_online:
   {
     if ((new_name != table_name || new_db != db))
     {
-      pthread_mutex_lock(&LOCK_open);
-      unlink_open_table(thd, table, FALSE);
-      pthread_mutex_unlock(&LOCK_open);
       mdl_release_lock(&thd->mdl_context, target_lock_data);
       mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data);
     }
@@ -7134,14 +7112,14 @@ err:
     mdl_release_lock(&thd->mdl_context, target_lock_data);
   DBUG_RETURN(TRUE);
 
-err_with_placeholders:
+err_with_mdl:
   /*
     An error happened while we were holding exclusive name metadata lock
-    on table being altered. To be safe under LOCK TABLES we should remove
-    placeholders from the list of open tables and relese metadata lock.
+    on table being altered. To be safe under LOCK TABLES we should
+    remove all references to the altered table from the list of locked
+    tables and release the exclusive metadata lock.
   */
-  unlink_open_table(thd, table, FALSE);
-  pthread_mutex_unlock(&LOCK_open);
+  thd->locked_tables_list.unlink_all_closed_tables();
   if (target_lock_data)
     mdl_release_lock(&thd->mdl_context, target_lock_data);
   mdl_release_all_locks_for_name(&thd->mdl_context, mdl_lock_data);

=== modified file 'sql/sql_trigger.cc'
--- a/sql/sql_trigger.cc	2008-06-06 19:19:04 +0000
+++ b/sql/sql_trigger.cc	2008-06-09 13:45:11 +0000
@@ -328,6 +328,7 @@ bool mysql_create_or_drop_trigger(THD *t
   bool result= TRUE;
   String stmt_query;
   bool need_start_waiting= FALSE;
+  bool lock_upgrade_done= FALSE;
 
   DBUG_ENTER("mysql_create_or_drop_trigger");
 
@@ -450,71 +451,53 @@ bool mysql_create_or_drop_trigger(THD *t
     if (!(tables->table= find_write_locked_table(thd->open_tables, tables->db,
                                                  tables->table_name)))
       goto end;
-    /*
-      Ensure that table is opened only by this thread and that no other
-      statement will open this table.
-    */
-    if (wait_while_table_is_used(thd, tables->table, HA_EXTRA_FORCE_REOPEN))
-      goto end;
-
-    pthread_mutex_lock(&LOCK_open);
+    /* Later on we will need it to downgrade the lock */
+    tables->mdl_lock_data= tables->table->mdl_lock_data;
   }
   else
   {
-    /*
-      Obtain exlusive meta-data lock on the table and remove TABLE
-      instances from cache.
-    */
-    if (lock_table_names(thd, tables))
+    tables->table= open_n_lock_single_table(thd, tables,
+                                            TL_WRITE_ALLOW_READ,
+                                            MYSQL_OPEN_TAKE_UPGRADABLE_MDL);
+    if (! tables->table)
       goto end;
-
-    pthread_mutex_lock(&LOCK_open);
-    expel_table_from_cache(0, tables->db, tables->table_name);
-
-    if (reopen_name_locked_table(thd, tables))
-      goto end_unlock;
+    tables->table->use_all_columns();
   }
   table= tables->table;
 
+  if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
+    goto end;
+
+  lock_upgrade_done= TRUE;
+
   if (!table->triggers)
   {
     if (!create)
     {
       my_error(ER_TRG_DOES_NOT_EXIST, MYF(0));
-      goto end_unlock;
+      goto end;
     }
 
     if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table)))
-      goto end_unlock;
+      goto end;
   }
 
+  pthread_mutex_lock(&LOCK_open);
   result= (create ?
            table->triggers->create_trigger(thd, tables, &stmt_query):
            table->triggers->drop_trigger(thd, tables, &stmt_query));
+  pthread_mutex_unlock(&LOCK_open);
 
-  /* Under LOCK TABLES we must reopen the table to activate the trigger. */
-  if (!result && thd->locked_tables_mode)
-  {
-    /* Make table suitable for reopening */
-    close_data_files_and_leave_as_placeholders(thd, tables->db,
-                                               tables->table_name);
-    thd->in_lock_tables= 1;
-    if (reopen_tables(thd, 1))
-    {
-      /* To be safe remove this table from the set of LOCKED TABLES */
-      unlink_open_table(thd, tables->table, FALSE);
-
-      /*
-        Ignore reopen_tables errors for now. It's better not leave master/slave
-        in a inconsistent state.
-      */
-      thd->clear_error();
-    }
-    thd->in_lock_tables= 0;
-  }
+  if (result)
+    goto end;
 
-end_unlock:
-  pthread_mutex_unlock(&LOCK_open);
+  close_all_tables_for_name(thd, table->s, FALSE);
+  /*
+    Reopen the table if we were under LOCK TABLES.
+    Ignore the return value for now. It's better to
+    keep master/slave in consistent state.
+  */
+  thd->locked_tables_list.reopen_tables(thd);
 
 end:
   if (!result)
@@ -525,11 +508,11 @@ end:
   /*
     If we are under LOCK TABLES we should restore original state of meta-data
     locks. Otherwise call to close_thread_tables() will take care about both
-    TABLE instance created by reopen_name_locked_table() and metadata lock.
+    TABLE instance created by open_n_lock_single_table() and metadata lock.
   */
-  if (thd->locked_tables_mode && tables && tables->table)
+  if (thd->locked_tables_mode && tables && lock_upgrade_done)
     mdl_downgrade_exclusive_lock(&thd->mdl_context,
-                                 tables->table->mdl_lock_data);
+                                 tables->mdl_lock_data);
 
   if (need_start_waiting)
     start_waiting_global_read_lock(thd);

=== modified file 'sql/table.h'
--- a/sql/table.h	2008-06-05 15:33:38 +0000
+++ b/sql/table.h	2008-06-09 13:45:11 +0000
@@ -626,6 +626,8 @@ public:
   /* Table's triggers, 0 if there are no of them */
   Table_triggers_list *triggers;
   TABLE_LIST *pos_in_table_list;/* Element referring to this table */
+  /* Position in thd->locked_table_list under LOCK TABLES */
+  TABLE_LIST *pos_in_locked_tables;
   ORDER		*group;
   const char	*alias;            	  /* alias or table name */
   uchar		*null_flags;

Thread
bzr commit into mysql-6.0 branch (konstantin:2665) WL#3726Konstantin Osipov9 Jun 2008