List:Commits« Previous MessageNext Message »
From:Konstantin Osipov Date:June 9 2008 8:49pm
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-tidyup/

 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 18:49:21 +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 18:49:21 +0000
@@ -1323,18 +1323,12 @@ TABLE *open_table(THD *thd, TABLE_LIST *
 bool tdc_open_view(THD *thd, TABLE_LIST *table_list, const char *alias,
                    char *cache_key, uint cache_key_length,
                    MEM_ROOT *mem_root, uint flags);
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list);
 TABLE *find_locked_table(TABLE *list, const char *db, const char *table_name);
 TABLE *find_write_locked_table(TABLE *list, const char *db,
                                const char *table_name);
 void detach_merge_children(TABLE *table, bool clear_refs);
 bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
                           TABLE_LIST *new_child_list, TABLE_LIST **new_last);
-bool reopen_table(TABLE *table);
-bool reopen_tables(THD *thd, bool get_locks);
-void close_data_files_and_leave_as_placeholders(THD *thd, const char *db,
-                                                const char *table_name);
-void close_handle_and_leave_table_as_placeholder(TABLE *table);
 bool open_new_frm(THD *thd, TABLE_SHARE *share, const char *alias,
                   uint db_stat, uint prgflag,
                   uint ha_open_flags, TABLE *outparam,
@@ -1505,12 +1499,12 @@ void add_join_on(TABLE_LIST *b,Item *exp
 void add_join_natural(TABLE_LIST *a,TABLE_LIST *b,List<String> *using_fields,
                       SELECT_LEX *lex);
 bool add_proc_to_list(THD *thd, Item *item);
-bool close_cached_table(THD *thd, TABLE *table);
 bool wait_while_table_is_used(THD *thd, TABLE *table,
                               enum ha_extra_function function);
-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 +1663,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 +2131,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 18:49:21 +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 18:49:21 +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 18:49:21 +0000
@@ -107,9 +107,6 @@ static bool table_def_inited= 0;
 
 static bool check_and_update_table_version(THD *thd, TABLE_LIST *tables,
                                            TABLE_SHARE *table_share);
-static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
-                               const char *alias, char *cache_key,
-                               uint cache_key_length);
 static bool open_table_entry_fini(THD *thd, TABLE_SHARE *share, TABLE *entry);
 static bool auto_repair_table(THD *thd, TABLE_LIST *table_list);
 static void free_cache_entry(TABLE *entry);
@@ -702,65 +699,6 @@ static void reference_table_share(TABLE_
 }
 
 
-/**
-   Close file handle, but leave the table in THD::open_tables list
-   to allow its future reopening.
-
-   @param table  Table handler
-
-   @note THD::killed will be set if we run out of memory
-
-   @note If closing a MERGE child, the calling function has to
-         take care for closing the parent too, if necessary.
-
-   @todo Get rid of this function once we refactor LOCK TABLES
-         to keep around TABLE_LIST elements used for opening
-         of tables until UNLOCK TABLES.
-*/
-
-void close_handle_and_leave_table_as_placeholder(TABLE *table)
-{
-  TABLE_SHARE *share, *old_share= table->s;
-  char *key_buff;
-  MEM_ROOT *mem_root= &table->mem_root;
-  DBUG_ENTER("close_handle_and_leave_table_as_lock");
-
-  DBUG_ASSERT(table->db_stat);
-
-  /*
-    Make a local copy of the table share and free the current one.
-    This has to be done to ensure that the table share is removed from
-    the table defintion cache as soon as the last instance is removed
-  */
-  if (multi_alloc_root(mem_root,
-                       &share, sizeof(*share),
-                       &key_buff, old_share->table_cache_key.length,
-                       NULL))
-  {
-    bzero((char*) share, sizeof(*share));
-    share->set_table_cache_key(key_buff, old_share->table_cache_key.str,
-                               old_share->table_cache_key.length);
-    share->tmp_table= INTERNAL_TMP_TABLE;       // for intern_close_table()
-  }
-
-  /*
-    When closing a MERGE parent or child table, detach the children first.
-    Do not clear child table references to allow for reopen.
-  */
-  if (table->child_l || table->parent)
-    detach_merge_children(table, FALSE);
-  table->file->close();
-  table->db_stat= 0;                            // Mark file closed
-  table_def_change_share(table, share);
-  release_table_share(table->s);
-  table->s= share;
-  table->file->change_table_ptr(table, table->s);
-
-  DBUG_VOID_RETURN;
-}
-
-
-
 /*
   Create a list for all open tables matching SQL expression
 
@@ -1004,64 +942,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 +1022,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 +1163,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 +1209,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.
@@ -2063,77 +2030,6 @@ static void relink_unused(TABLE *table)
 
 
 /**
-  Prepare an open merge table for close.
-
-  @param[in]     thd             thread context
-  @param[in]     table           table to prepare
-  @param[in,out] prev_pp         pointer to pointer of previous table
-
-  @detail
-    If the table is a MERGE parent, just detach the children.
-    If the table is a MERGE child, close the parent (incl. detach).
-*/
-
-static void unlink_open_merge(THD *thd, TABLE *table, TABLE ***prev_pp)
-{
-  DBUG_ENTER("unlink_open_merge");
-
-  if (table->parent)
-  {
-    /*
-      If MERGE child, close parent too. Closing includes detaching.
-
-      This is used for example in ALTER TABLE t1 RENAME TO t5 under
-      LOCK TABLES where t1 is a MERGE child:
-      CREATE TABLE t1 (c1 INT);
-      CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
-      LOCK TABLES t1 WRITE, t2 WRITE;
-      ALTER TABLE t1 RENAME TO t5;
-    */
-    TABLE *parent= table->parent;
-    TABLE **prv_p;
-
-    /* Find parent in open_tables list. */
-    for (prv_p= &thd->open_tables;
-         *prv_p && (*prv_p != parent);
-         prv_p= &(*prv_p)->next) {}
-    if (*prv_p)
-    {
-      /* Special treatment required if child follows parent in list. */
-      if (*prev_pp == &parent->next)
-        *prev_pp= prv_p;
-      /*
-        Remove parent from open_tables list and close it.
-        This includes detaching and hence clearing parent references.
-      */
-      close_thread_table(thd, prv_p);
-    }
-  }
-  else if (table->child_l)
-  {
-    /*
-      When closing a MERGE parent, detach the children first. It is
-      not necessary to clear the child or parent table reference of
-      this table because the TABLE is freed. But we need to clear
-      the child or parent references of the other belonging tables
-      so that they cannot be moved into the unused_tables chain with
-      these pointers set.
-
-      This is used for example in ALTER TABLE t2 RENAME TO t5 under
-      LOCK TABLES where t2 is a MERGE parent:
-      CREATE TABLE t1 (c1 INT);
-      CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1);
-      LOCK TABLES t1 WRITE, t2 WRITE;
-      ALTER TABLE t2 RENAME TO t5;
-    */
-    detach_merge_children(table, TRUE);
-  }
-
-  DBUG_VOID_RETURN;
-}
-
-
-/**
    Force all other threads to stop using the table by upgrading
    metadata lock on it and remove unused TABLE instances from cache.
 
@@ -2180,118 +2076,22 @@ bool wait_while_table_is_used(THD *thd, 
 
 
 /**
-   Upgrade metadata lock on the table and close all its instances.
-
-   @param thd   Thread handler
-   @param table Table to remove from cache
-
-   @retval FALSE Success.
-   @retval TRUE  Failure (e.g. because thread was killed).
-*/
-
-bool close_cached_table(THD *thd, TABLE *table)
-{
-  DBUG_ENTER("close_cached_table");
-
-  /* FIXME: check if we pass proper parameters everywhere. */
-  if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
-    DBUG_RETURN(TRUE);
-
-  /* Close lock if this is not got with LOCK TABLES */
-  if (! thd->locked_tables_mode)
-  {
-    mysql_unlock_tables(thd, thd->lock);
-    thd->lock=0;			// Start locked threads
-  }
-
-  pthread_mutex_lock(&LOCK_open);
-  /* Close all copies of 'table'.  This also frees all LOCK TABLES lock */
-  unlink_open_table(thd, table, TRUE);
-  pthread_mutex_unlock(&LOCK_open);
-  DBUG_RETURN(FALSE);
-}
-
-/**
-    Remove all instances of table from thread's open list and
-    table cache.
-
-    @param  thd     Thread context
-    @param  find    Table to remove
-    @param  unlock  TRUE  - free all locks on tables removed that are
-                            done with LOCK TABLES
-                    FALSE - otherwise
-
-    @note When unlock parameter is FALSE or current thread doesn't have
-          any tables locked with LOCK TABLES, tables are assumed to be
-          not locked (for example already unlocked).
-*/
-
-void unlink_open_table(THD *thd, TABLE *find, bool unlock)
-{
-  char key[MAX_DBKEY_LENGTH];
-  uint key_length= find->s->table_cache_key.length;
-  TABLE *list, **prev;
-  DBUG_ENTER("unlink_open_table");
-
-  safe_mutex_assert_owner(&LOCK_open);
-
-  memcpy(key, find->s->table_cache_key.str, key_length);
-  /*
-    Note that we need to hold LOCK_open while changing the
-    open_tables list. Another thread may work on it.
-    (See: notify_thread_having_shared_lock())
-    Closing a MERGE child before the parent would be fatal if the
-    other thread tries to abort the MERGE lock in between.
-  */
-  for (prev= &thd->open_tables; *prev; )
-  {
-    list= *prev;
-
-    if (list->s->table_cache_key.length == key_length &&
-	!memcmp(list->s->table_cache_key.str, key, key_length))
-    {
-      if (unlock && thd->locked_tables_mode)
-        mysql_lock_remove(thd, thd->lock,
-                          list->parent ? list->parent : list, TRUE);
-
-      /* Prepare MERGE table for close. Close parent if necessary. */
-      unlink_open_merge(thd, list, &prev);
-
-      /* Remove table from open_tables list. */
-      *prev= list->next;
-      /* Close table. */
-      free_cache_entry(list);
-    }
-    else
-    {
-      /* Step to next entry in open_tables list. */
-      prev= &list->next;
-    }
-  }
-
-  // Notify any 'refresh' threads
-  broadcast_refresh();
-  DBUG_VOID_RETURN;
-}
-
-
-/**
-    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
     @param  db_name     Name of database for this 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.
+    This routine assumes that table to be closed is open only
+    by the calling thread so we needn't wait until other threads
+    will close the table. It also assumes that the table is first
+    in thd->open_ables. To sum up, it's tuned to work with
+    CREATE TABLE ... SELECT and CREATE TABLE .. SELECT only.
+    Note, that currently CREATE TABLE ... SELECT is not supported
+    under LOCK TABLES. This function, still, can be called in
+    prelocked mode, e.g. if we do CREATE TABLE .. SELECT f1();
 */
 
 void drop_open_table(THD *thd, TABLE *table, const char *db_name,
@@ -2301,13 +2101,14 @@ void drop_open_table(THD *thd, TABLE *ta
     close_temporary_table(thd, table, 1, 1);
   else
   {
+    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);
   }
@@ -2358,72 +2159,6 @@ void wait_for_condition(THD *thd, pthrea
 }
 
 
-/*
-  Open table for which this thread has exclusive meta-data lock.
-
-  SYNOPSIS
-    reopen_name_locked_table()
-      thd         Thread handle
-      table_list  TABLE_LIST object for table to be open.
-
-  NOTE
-    This function assumes that its caller already acquired LOCK_open mutex.
-
-  RETURN VALUE
-    FALSE - Success
-    TRUE  - Error
-*/
-
-bool reopen_name_locked_table(THD* thd, TABLE_LIST* table_list)
-{
-  TABLE *table;
-  TABLE_SHARE *share;
-  char key[MAX_DBKEY_LENGTH];
-  uint key_length;
-  char *table_name= table_list->table_name;
-  DBUG_ENTER("reopen_name_locked_table");
-
-  if (thd->killed)
-    DBUG_RETURN(TRUE);
-
-  key_length= create_table_def_key(thd, key, table_list, 0);
-
-  if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
-    DBUG_RETURN(TRUE);
-
-  if (reopen_table_entry(thd, table, table_list, table_name, key, key_length))
-  {
-    my_free((uchar*)table, MYF(0));
-    DBUG_RETURN(TRUE);
-  }
-
-  share= table->s;
-  /*
-    We want to prevent other connections from opening this table until end
-    of statement as it is likely that modifications of table's metadata are
-    not yet finished (for example CREATE TRIGGER have to change .TRG file,
-    or we might want to drop table if CREATE TABLE ... SELECT fails).
-    This also allows us to assume that no other connection will sneak in
-    before we will get table-level lock on this table.
-  */
-  share->version=0;
-  table->in_use = thd;
-
-  table_def_add_used_table(thd, table);
-
-  table->next= thd->open_tables;
-  thd->open_tables= table;
-
-  table->tablenr=thd->current_tablenr++;
-  table->used_fields=0;
-  table->const_table=0;
-  table->null_row= table->maybe_null= table->force_index= 0;
-  table->status=STATUS_NO_RECORD;
-  table_list->table= table;
-  DBUG_RETURN(FALSE);
-}
-
-
 /**
     Check that table exists in table definition cache, on disk
     or in some storage engine.
@@ -2503,6 +2238,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 +2412,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;
@@ -2739,56 +2531,21 @@ TABLE *open_table(THD *thd, TABLE_LIST *
     if (thd->locked_tables_mode == LTM_PRELOCKED)
       my_error(ER_NO_SUCH_TABLE, MYF(0), table_list->db, table_list->alias);
     else
-      my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias);
-    DBUG_RETURN(0);
-  }
-
-  /*
-    Non pre-locked/LOCK TABLES mode, and the table is not temporary.
-    This is the normal use case.
-  */
-
-  mdl_lock_data= table_list->mdl_lock_data;
-  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))
-      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));
+      my_error(ER_TABLE_NOT_LOCKED, MYF(0), alias);
+    DBUG_RETURN(0);
+  }
 
-    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);
+  /*
+    Non pre-locked/LOCK TABLES mode, and the table is not temporary.
+    This is the normal use case.
+  */
 
-    if (mdl_acquire_shared_lock(mdl_lock_data, &retry))
-    {
-      if (retry)
-        *action= OT_BACK_OFF_AND_RETRY;
+  mdl_lock_data= table_list->mdl_lock_data;
+  if (! (flags & MYSQL_OPEN_HAS_MDL_LOCK))
+  {
+    if (open_table_get_mdl_lock(thd, table_list, mdl_lock_data, flags,
+                                action))
       DBUG_RETURN(0);
-    }
   }
 
   pthread_mutex_lock(&LOCK_open);
@@ -3128,424 +2885,260 @@ TABLE *find_write_locked_table(TABLE *li
 }
 
 
-/*
-  Reopen an table because the definition has changed.
+/***********************************************************************
+  class Locked_tables_list implementation. Declared in sql_class.h
+************************************************************************/
 
-  SYNOPSIS
-    reopen_table()
-    table	Table object
+/**
+  Enter LTM_LOCK_TABLES mode.
 
-  NOTES
-   The data file for the table is already closed and the share is released
-   The table has a 'dummy' share that mainly contains database and table name.
+  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.
 
- RETURN
-   0  ok
-   1  error. The old table object is not changed.
+  @param  thd  thread handle
+
+  @return TRUE if out of memory.
 */
 
-bool reopen_table(TABLE *table)
+bool
+Locked_tables_list::init_locked_tables(THD *thd)
 {
-  TABLE tmp;
-  bool error= 1;
-  Field **field;
-  uint key,part;
-  TABLE_LIST table_list;
-  THD *thd= table->in_use;
-  DBUG_ENTER("reopen_table");
-  DBUG_PRINT("tcache", ("table: '%s'.'%s' %p", table->s->db.str,
-                        table->s->table_name.str, table));
-
-  DBUG_ASSERT(table->s->ref_count == 0);
-  DBUG_ASSERT(!table->sort.io_cache);
-  DBUG_ASSERT(!table->children_attached);
+  DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE);
+  DBUG_ASSERT(m_locked_tables == NULL);
 
-#ifdef EXTRA_DEBUG
-  if (table->db_stat)
-    sql_print_error("Table %s had a open data handler in reopen_table",
-		    table->alias);
-#endif
-  bzero((char*) &table_list, sizeof(TABLE_LIST));
-  table_list.db=         table->s->db.str;
-  table_list.table_name= table->s->table_name.str;
-  table_list.table=      table;
-
-  if (reopen_table_entry(thd, &tmp, &table_list,
-                         table->alias,
-                         table->s->table_cache_key.str,
-                         table->s->table_cache_key.length))
-    goto end;
-
-  /* This list copies variables set by open_table */
-  tmp.tablenr=		table->tablenr;
-  tmp.used_fields=	table->used_fields;
-  tmp.const_table=	table->const_table;
-  tmp.null_row=		table->null_row;
-  tmp.maybe_null=	table->maybe_null;
-  tmp.status=		table->status;
-
-  tmp.s->table_map_id=  table->s->table_map_id;
-
-  /* Get state */
-  tmp.in_use=    	thd;
-  tmp.reginfo.lock_type=table->reginfo.lock_type;
-  tmp.grant=		table->grant;
-
-  /* Replace table in open list */
-  tmp.next=		table->next;
-  tmp.prev=		table->prev;
-
-  /* Preserve MERGE parent. */
-  tmp.parent=           table->parent;
-  /* Fix MERGE child list and check for unchanged union. */
-  if ((table->child_l || tmp.child_l) &&
-      fix_merge_after_open(table->child_l, table->child_last_l,
-                           tmp.child_l, tmp.child_last_l))
-  {
-    (void) closefrm(&tmp, 1); // close file, free everything
-    goto end;
-  }
-  tmp.mdl_lock_data=         table->mdl_lock_data;
-
-  table_def_change_share(table, tmp.s);
-  /* 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;
+  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;
+    }
 
-  delete table->triggers;
-  if (table->file)
-    (void) closefrm(table, 1);		// close file, free everything
+    /**
+      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;
+}
 
-  *table= tmp;
-  table->default_column_bitmaps();
-  table->file->change_table_ptr(table, table->s);
+/**
+  Leave LTM_LOCK_TABLES mode if it's been entered.
 
-  DBUG_ASSERT(table->alias != 0);
-  for (field=table->field ; *field ; field++)
-  {
-    (*field)->table= (*field)->orig_table= table;
-    (*field)->table_name= &table->alias;
-  }
-  for (key=0 ; key < table->s->keys ; key++)
+  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)
   {
-    for (part=0 ; part < table->key_info[key].usable_key_parts ; part++)
-      table->key_info[key].key_part[part].field->table= table;
+    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);
   }
-  if (table->triggers)
-    table->triggers->set_table(table);
   /*
-    Do not attach MERGE children here. The children might be reopened
-    after the parent. Attach children after reopening all tables that
-    require reopen. See for example reopen_tables().
+    After closing tables we can free memory used for storing lock
+    request for metadata locks and TABLE_LIST elements.
   */
-
-  broadcast_refresh();
-  error=0;
-
- end:
-  DBUG_RETURN(error);
+  free_root(&m_locked_tables_root, MYF(0));
+  m_locked_tables= NULL;
+  m_locked_tables_last= &m_locked_tables;
 }
 
 
 /**
-    Close all instances of a table open by this thread and replace
-    them with placeholder in THD::open_tables list for future reopening.
-
-    @param thd        Thread context
-    @param db         Database name for the table to be closed
-    @param table_name Name of the table to be closed
+  Unlink a locked table from the locked tables list, either
+  temporarily or permanently. 
 
-    @note This function assumes that if we are not under LOCK TABLES,
-          then there is only one table open and locked. This means that
-          the function probably has to be adjusted before it can be used
-          anywhere outside ALTER TABLE.
+  @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.
 
-    @note Must not use TABLE_SHARE::table_name/db of the table being closed,
-          the strings are used in a loop even after the share may be freed.
+  @sa Locked_tables_list::reopen_tables()
 */
 
-void close_data_files_and_leave_as_placeholders(THD *thd, const char *db,
-                                               const char *table_name)
+
+void Locked_tables_list::unlink_from_list(THD *thd,
+                                          TABLE_LIST *table_list,
+                                          bool remove_from_locked_tables)
 {
-  TABLE *table;
-  DBUG_ENTER("close_data_files_and_leave_as_placeholders");
+  /*
+    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;
 
-  safe_mutex_assert_owner(&LOCK_open);
+  /*
+    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);
 
-  if (! thd->locked_tables_mode)
-  {
-    /*
-      If we are not under LOCK TABLES we should have only one table
-      open and locked so it makes sense to remove the lock at once.
-    */
-    mysql_unlock_tables(thd, thd->lock);
-    thd->lock= 0;
-  }
+  /* 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;
 
-  for (table=thd->open_tables; table ; table=table->next)
+  /*
+    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)
   {
-    if (!strcmp(table->s->table_name.str, table_name) &&
-	!strcmp(table->s->db.str, db))
-    {
-      if (thd->locked_tables_mode)
-      {
-        if (table->parent)
-        {
-          /*
-            If MERGE child, need to reopen parent too. This means that
-            the first child to be closed will detach all children from
-            the parent and close it. OTOH in most cases a MERGE table
-            won't have multiple children with the same db.table_name.
-          */
-          mysql_lock_remove(thd, thd->lock, table->parent, TRUE);
-          close_handle_and_leave_table_as_placeholder(table->parent);
-        }
-        else
-          mysql_lock_remove(thd, thd->lock, table, TRUE);
-      }
-      table->s->version= 0;
-      close_handle_and_leave_table_as_placeholder(table);
-    }
+    *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;
   }
-  DBUG_VOID_RETURN;
 }
 
-
 /**
-  Reattach MERGE children after reopen.
-
-  @param[in]     thd            thread context
-  @param[in,out] err_tables_p   pointer to pointer of tables in error
-
-  @return       status
-    @retval     FALSE           OK, err_tables_p unchanged
-    @retval     TRUE            Error, err_tables_p contains table(s)
+  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.
 */
 
-static bool reattach_merge(THD *thd, TABLE **err_tables_p)
+void Locked_tables_list::unlink_all_closed_tables()
 {
-  TABLE *table;
-  TABLE *next;
-  TABLE **prv_p= &thd->open_tables;
-  bool error= FALSE;
-  DBUG_ENTER("reattach_merge");
-
-  for (table= thd->open_tables; table; table= next)
+  for (TABLE_LIST *table_list= m_locked_tables; table_list; table_list=
+       table_list->next_global)
   {
-    next= table->next;
-    DBUG_PRINT("tcache", ("check table: '%s'.'%s' %p  next: %p",
-                          table->s->db.str, table->s->table_name.str,
-                          table, next));
-    /* Reattach children for MERGE tables with "closed data files" only. */
-    if (table->child_l && !table->children_attached)
-    {
-      DBUG_PRINT("tcache", ("MERGE parent, attach children"));
-      if(table->file->extra(HA_EXTRA_ATTACH_CHILDREN))
-      {
-        my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
-        error= TRUE;
-        /* Remove table from open_tables. */
-        *prv_p= next;
-        if (next)
-          prv_p= &next->next;
-        /* Stack table on error list. */
-        table->next= *err_tables_p;
-        *err_tables_p= table;
-        continue;
-      }
+    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->children_attached= TRUE;
-        DBUG_PRINT("myrg", ("attached parent: '%s'.'%s' %p",
-                            table->s->db.str,
-                            table->s->table_name.str, table));
-      }
+        table_list->next_global->prev_global= table_list->prev_global;
     }
-    prv_p= &table->next;
   }
-  DBUG_RETURN(error);
 }
 
 
 /**
-    Reopen all tables with closed data files.
-
-    @param thd         Thread context
-    @param get_locks   Should we get locks after reopening tables ?
-
-    @note Since this function can't properly handle prelocking and
-          create placeholders it should be used in very special
-          situations like FLUSH TABLES or ALTER TABLE. In general
-          case one should just repeat open_tables()/lock_tables()
-          combination when one needs tables to be reopened (for
-          example see open_and_lock_tables()).
-
-    @note One should have lock on LOCK_open when calling this.
+  Reopen the tables locked with LOCK TABLES and temporarily closed
+  by a DDL statement or FLUSH TABLES.
 
-    @return FALSE in case of success, TRUE - otherwise.
+  @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 reopen_tables(THD *thd, bool get_locks)
+bool
+Locked_tables_list::reopen_tables(THD *thd)
 {
-  TABLE *table,*next,**prev;
-  TABLE **tables,**tables_ptr;			// For locks
-  TABLE *err_tables= NULL, *err_tab_tmp;
-  bool error=0, not_used;
-  bool merge_table_found= FALSE;
-
-  DBUG_ENTER("reopen_tables");
-
-  if (!thd->open_tables)
-    DBUG_RETURN(0);
-
-  safe_mutex_assert_owner(&LOCK_open);
-  if (get_locks)
-  {
-    /*
-      The ptr is checked later
-      Do not handle locks of MERGE children.
-    */
-    uint opens=0;
-    for (table= thd->open_tables; table ; table=table->next)
-      if (!table->parent)
-        opens++;
-    DBUG_PRINT("tcache", ("open tables to lock: %u", opens));
-    tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens);
-  }
-  else
-    tables= &thd->open_tables;
-  tables_ptr =tables;
+  enum enum_open_table_action ot_action_unused;
+  bool lt_refresh_unused;
 
-  prev= &thd->open_tables;
-  for (table=thd->open_tables; table ; table=next)
-  {
-    uint db_stat=table->db_stat;
-    next=table->next;
-    DBUG_PRINT("tcache", ("open table: '%s'.'%s' %p  "
-                          "parent: %p  db_stat: %u",
-                          table->s->db.str, table->s->table_name.str,
-                          table, table->parent, db_stat));
-    if (table->child_l && !db_stat)
-      merge_table_found= TRUE;
-    if (!tables || (!db_stat && reopen_table(table)))
-    {
-      my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias);
-      /*
-        If we could not allocate 'tables', we may close open tables
-        here. If a MERGE table is affected, detach the children first.
-        It is not necessary to clear the child or parent table reference
-        of this table because the TABLE is freed. But we need to clear
-        the child or parent references of the other belonging tables so
-        that they cannot be moved into the unused_tables chain with
-        these pointers set.
-      */
-      if (table->child_l || table->parent)
-        detach_merge_children(table, TRUE);
-      free_cache_entry(table);
-      error=1;
-    }
-    else
-    {
-      DBUG_PRINT("tcache", ("opened. need lock: %d",
-                            get_locks && !db_stat && !table->parent));
-      *prev= table;
-      prev= &table->next;
-      /* Do not handle locks of MERGE children. */
-      if (get_locks && !db_stat && !table->parent)
-      {
-	*tables_ptr++= table;			// need new lock on this
-        /*
-          We rely on having exclusive metadata lock on the table to be
-          able safely re-acquire table locks on it.
-        */
-        DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0,
-                                                table->s->db.str,
-                                                table->s->table_name.str));
-      }
-    }
-  }
-  *prev=0;
-  /*
-    When all tables are open again, we can re-attach MERGE children to
-    their parents. All TABLE objects are still present.
-  */
-  DBUG_PRINT("tcache", ("re-attaching MERGE tables: %d", merge_table_found));
-  if (!error && merge_table_found && reattach_merge(thd,
&err_tables))
-  {
-    while (err_tables)
-    {
-      err_tab_tmp= err_tables->next;
-      free_cache_entry(err_tables);
-      err_tables= err_tab_tmp;
-    }
-  }
-  DBUG_PRINT("tcache", ("open tables to lock: %u",
-                        (uint) (tables_ptr - tables)));
-  if (tables != tables_ptr)			// Should we get back old locks
+  for (TABLE_LIST *table_list= m_locked_tables;
+       table_list; table_list= table_list->next_global)
   {
     MYSQL_LOCK *lock;
-    const uint flags= MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK |
-                      MYSQL_LOCK_IGNORE_FLUSH;
-    /*
-      Since we have exclusive metadata locks on tables which we
-      are reopening we should always get these locks (We won't
-      wait on table level locks so can't get aborted and we ignore
-      other threads that set THD::some_tables_deleted by using
-      MYSQL_LOCK_IGNORE_FLUSH flag).
-    */
-    thd->some_tables_deleted=0;
-    if ((lock= mysql_lock_tables(thd, tables, (uint) (tables_ptr - tables),
-                                 flags, &not_used)))
+
+    if (table_list->table)                      /* The table was not closed */
+      continue;
+
+#if 0
+    /* Links into thd->open_tables upon success */
+    table_list->table= open_table(thd, table_list, thd->mem_root,
+                                  &ot_action_unused, MYSQL_OPEN_REOPEN);
+#endif
+    my_error(ER_OUT_OF_RESOURCES, MYF(0));
+    if (! table_list->table)
     {
-      thd->lock= mysql_lock_merge(thd->lock, lock);
+      unlink_all_closed_tables();
+      return TRUE;
     }
-    else
+    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)
     {
       /*
-        This case should only happen if there is a bug in the reopen logic.
-        Need to issue error message to have a reply for the application.
-        Not exactly what happened though, but close enough.
+        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));
-      error=1;
+      return TRUE;
     }
+    thd->lock= lock;
   }
-  if (get_locks && tables)
-  {
-    my_afree((uchar*) tables);
-  }
-  broadcast_refresh();
-  DBUG_RETURN(error);
-}
-
-
-/**
-   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));
+  return FALSE;
 }
 
 
@@ -3726,138 +3319,6 @@ err:
 
 
 /**
-   Load table definition from file and open table while holding exclusive
-   meta-data lock on it.
-
-   @param thd               Thread handle
-   @param entry             Memory for TABLE object to be created
-   @param table_list        TABLE_LIST with db, table_name & belong_to_view
-   @param alias             Alias name
-   @param cache_key         Key for table definition cache
-   @param cache_key_length  Length of cache_key
-
-   @note This auxiliary function is mostly inteded for re-opening table
-         in situations when we hold exclusive meta-data lock. It is not
-         intended for normal case in which we have only shared meta-data
-         lock on the table to be open.
-
-   @note Extra argument for open is taken from thd->open_options.
-
-   @note One must have a lock on LOCK_open as well as exclusive meta-data
-         lock on the table when calling this function.
-
-   @return FALSE in case of success, TRUE otherwise.
-*/
-
-static bool reopen_table_entry(THD *thd, TABLE *entry, TABLE_LIST *table_list,
-                               const char *alias, char *cache_key,
-                               uint cache_key_length)
-{
-  int error;
-  TABLE_SHARE *share;
-  uint discover_retry_count= 0;
-  DBUG_ENTER("reopen_table_entry");
-
-  safe_mutex_assert_owner(&LOCK_open);
-  DBUG_ASSERT(mdl_is_exclusive_lock_owner(&thd->mdl_context, 0,
-                                          table_list->db,
-                                          table_list->table_name));
-
-retry:
-  if (!(share= get_table_share_with_create(thd, table_list, cache_key,
-                                           cache_key_length, 
-                                           OPEN_VIEW |
-                                           table_list->i_s_requested_object,
-                                           &error)))
-    DBUG_RETURN(1);
-
-  if (share->is_view)
-  {
-    /*
-      This table is a view. Validate its metadata version: in particular,
-      that it was a view when the statement was prepared.
-    */
-    if (check_and_update_table_version(thd, table_list, share))
-      goto err;
-    if (table_list->i_s_requested_object &  OPEN_TABLE_ONLY)
-      goto err;
-    /* Attempt to reopen view will bring havoc to upper layers anyway. */
-    release_table_share(share);
-    my_error(ER_WRONG_OBJECT, MYF(0), share->db.str, share->table_name.str,
-             "BASE TABLE");
-    DBUG_RETURN(1);
-  }
-
-  if (table_list->i_s_requested_object &  OPEN_VIEW_ONLY)
-    goto err;
-
-  while ((error= open_table_from_share(thd, share, alias,
-                                       (uint) (HA_OPEN_KEYFILE |
-                                               HA_OPEN_RNDFILE |
-                                               HA_GET_INDEX |
-                                               HA_TRY_READ_ONLY),
-                                       (READ_KEYINFO | COMPUTE_TYPES |
-                                        EXTRA_RECORD),
-                                       thd->open_options, entry, OTM_OPEN)))
-  {
-    if (error == 7)                             // Table def changed
-    {
-      share->version= 0;                        // Mark share as old
-      if (discover_retry_count++)               // Retry once
-        goto err;
-
-      /*
-        Since we have exclusive metadata lock on the table here the only
-        practical case when share->ref_count != 1 is when we have several
-        instances of the table opened by this thread (i.e we are under LOCK
-        TABLES).
-      */
-      if (share->ref_count != 1)
-        goto err;
-
-      release_table_share(share);
-
-      if (ha_create_table_from_engine(thd, table_list->db,
-                                      table_list->table_name))
-        goto err;
-
-      mysql_reset_errors(thd, 1);         // Clear warnings
-      thd->clear_error();                 // Clear error message
-
-      goto retry;
-    }
-    if (!entry->s || !entry->s->crashed)
-      goto err;
-
-    entry->s->version= 0;
-
-    /* TODO: We don't need to release share here. */
-    release_table_share(share);
-    pthread_mutex_unlock(&LOCK_open);
-    error= (int)auto_repair_table(thd, table_list);
-    pthread_mutex_lock(&LOCK_open);
-
-    if (error)
-      goto err;
-
-    goto retry;
-  }
-
-  if (open_table_entry_fini(thd, share, entry))
-  {
-    closefrm(entry, 0);
-    goto err;
-  }
-
-  DBUG_RETURN(0);
-
-err:
-  release_table_share(share);
-  DBUG_RETURN(1);
-}
-
-
-/**
    Finalize the process of TABLE creation by loading table triggers
    and taking action if a HEAP table content was emptied implicitly.
 */
@@ -8559,21 +8020,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 18:49:21 +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 18:49:21 +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 18:49:21 +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 18:49:21 +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 18:49:21 +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 18:49:21 +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 18:49:21 +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)
@@ -3930,7 +3931,10 @@ static int prepare_for_repair(THD *thd, 
   const char **ext;
   MY_STAT stat_info;
   MDL_LOCK_DATA *mdl_lock_data;
+  enum enum_open_table_action ot_action_unused;
   DBUG_ENTER("prepare_for_repair");
+  uint reopen_for_repair_flags= (MYSQL_LOCK_IGNORE_FLUSH |
+                                 MYSQL_OPEN_HAS_MDL_LOCK);
 
   if (!(check_opt->sql_flags & TT_USEFRM))
     DBUG_RETURN(0);
@@ -4028,8 +4032,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, FALSE);
     table_list->table= 0;
   }
   /*
@@ -4057,21 +4062,24 @@ static int prepare_for_repair(THD *thd, 
     goto end;
   }
 
+  if (thd->locked_tables_list.reopen_tables(thd))
+    goto end;
+
   /*
     Now we should be able to open the partially repaired table
     to finish the repair in the handler later on.
   */
-  pthread_mutex_lock(&LOCK_open);
-  if (reopen_name_locked_table(thd, table_list))
+  if (! (table_list->table= open_table(thd, table_list, thd->mem_root,
+                                       &ot_action_unused,
+                                       reopen_for_repair_flags)))
   {
-    pthread_mutex_unlock(&LOCK_open);
     error= send_check_errmsg(thd, table_list, "repair",
                              "Failed to open partially repaired table");
     goto end;
   }
-  pthread_mutex_unlock(&LOCK_open);
 
 end:
+  thd->locked_tables_list.unlink_all_closed_tables();
   if (table == &tmp_table)
   {
     pthread_mutex_lock(&LOCK_open);
@@ -4767,6 +4775,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 +4883,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 +4902,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 +5679,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");
-  if (online)
+  DBUG_ENTER("mysql_fast_or_online_alter_table");
+
+  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 +5770,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 +6574,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 +6810,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 +6962,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 +6983,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 +7004,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 +7057,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 +7118,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 18:49:21 +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 18:49:21 +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