List:Commits« Previous MessageNext Message »
From:ingo Date:April 28 2007 2:31pm
Subject:bk commit into 4.1 tree (istruewing:1.2630) BUG#26379
View as plain text  
Below is the list of changes that have just been committed into a local
4.1 repository of istruewing. When istruewing does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet@stripped, 2007-04-28 14:31:55+02:00, istruewing@stripped +22 -0
  Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
              a MERGE table
  
  Not to be pushed. This is kind of a technology preview.
  It is not yet complete. Though it does not seem to make
  anything really worse, it turned out that at least another
  step is required: When closing and opening the MERGE child
  tables, it must be taken into account if a real open/close
  or a reopen/close_old_data_files is going on. In the latter
  case the child tables must not be deleted, but kept for
  reopen. In reopen they must be picked up again.
  
  This bug report revealed three problems:
  
  1. A thread trying to lock a MERGE table performs busy waiting while
   REPAIR TABLE or a similar table administration task is ongoing on
   one or more of its MyISAM tables.
  
  2. A thread trying to lock a MERGE table performs busy waiting until all
   threads that did REPAIR TABLE or similar table administration tasks
   on one or more of its MyISAM tables in LOCK TABLES segments do UNLOCK
   TABLES. The difference against problem #1 is that the busy waiting
   takes place *after* the administration task. It is terminated by
   UNLOCK TABLES only.
  
  3. Two FLUSH TABLES within a LOCK TABLES segment can invalidate the
   lock. This does *not* require a MERGE table. The first FLUSH TABLES
   can be replaced by any statement that requires other threads to
   reopen the table. In 5.0 and 5.1 a single FLUSH TABLES can provoke
   the problem.
  
  Problem #1 and #2 are fixed by opening the MERGE child tables
  through the table cache. So they are visible to other threads
  and even for certain statements in the same thread. The child
  tables are chained to the parent table, but do not appear in
  thd->open_tables nor thd->locked_tables. This does also fix
  the bugs 26377, 26867, and 8306 at least. I reverted the
  preliminary fix for 8306.
  
  Problem #3 is fixed by setting THD::some_tables_deleted for all
  threads with open tables before releasing LOCK_open when waiting
  for all threads to close their tables.
  
  No test case. The misbehaviour cannot be repeated reliably by a
  deterministic test without sleep()-injection in the code.
  
  Please see the bug report for more information and tests for
  manual testing.
  

  include/myisammrg.h@stripped, 2007-04-28 14:31:50+02:00, istruewing@stripped +6 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added callback elements to MYRG_INFO.
    Added callback arguments to myrg_open().

  include/raid.h@stripped, 2007-04-28 14:31:50+02:00, istruewing@stripped +2 -2
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Fixed compiler warnings.

  myisam/mi_create.c@stripped, 2007-04-28 14:31:50+02:00, istruewing@stripped +0 -15
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Reverted fix for bug 8306. It is implicitly and better fixed with
    this patch.

  myisam/mi_open.c@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Reverted fix for bug 8306. It is implicitly and better fixed with
    this patch.

  myisam/myisamdef.h@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +0 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Reverted fix for bug 8306. It is implicitly and better fixed with
    this patch.

  myisammrg/myrg_close.c@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +16 -3
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Using callback for closing child tables if set. Otherwise close
    MyISAM tables directly.

  myisammrg/myrg_open.c@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +76 -16
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Using callback for opening child tables if set. Otherwise open MyISAM
    tables directly.
    Store close_callback and callback_param in MYRG_INFO for use with
    myrg_close().
    Fixed error handling for failed allocation.
    Fixed indentation.

  mysql-test/r/merge.result@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +18
-1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Fixed results.
    Moved test result for bug 8306 from myisam.result to here.

  mysql-test/r/myisam.result@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +0
-18
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Reverted fix for bug 8306. It is implicitly and better fixed with
    this patch. Moved test result from here to merge.result.

  mysql-test/t/merge.test@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +29 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Fixed error number.
    Moved test for bug 8306 from myisam.test to here.

  mysql-test/t/myisam.test@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +0 -26
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Reverted fix for bug 8306. It is implicitly and better fixed with
    this patch. Moved test from here to merge.test.

  mysys/thr_lock.c@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +35 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added DBUG calls.

  sql/ha_myisam.cc@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +21 -2
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added new function myisam_engine_handle() to extract a MI_INFO
    pointer from a handler object.

  sql/ha_myisam.h@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +1 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Declared new function myisam_engine_handle() as friend of
    ha_myisam class.

  sql/ha_myisammrg.cc@stripped, 2007-04-28 14:31:51+02:00, istruewing@stripped +177 -11
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added callback functions for open and close.
    Allowed lock_count() and store_lock() to work on a closed table.
    Fixed/added DBUG calls.

  sql/ha_myisammrg.h@stripped, 2007-04-28 14:31:52+02:00, istruewing@stripped +9 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added struct st_myrg_callback_param to use with callback functions.
    Added table_ptr() method to use with callback functions.

  sql/lock.cc@stripped, 2007-04-28 14:31:52+02:00, istruewing@stripped +30 -2
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added DBUG calls.

  sql/mysql_priv.h@stripped, 2007-04-28 14:31:52+02:00, istruewing@stripped +6 -3
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added 'is_child' parameter to open_table() for MERGE table childs.
    Added 'is_temporary' parameter to open_temporary_table() to tell
    the difference between a real temporary table and intermediate
    tables, which are used during ALTER TABLE.
    Added declaration for myisam_engine_handle() to use with MERGE
    childs.

  sql/sql_base.cc@stripped, 2007-04-28 14:31:52+02:00, istruewing@stripped +343 -87
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added awareness of MERGE parent or child tables to several functions.
    Added new parameter 'is_child' to open_tables(). If true don't
    lock/unlock LOCK_open as it is done for the whole MERGE table (when
    handling the parent). When picking the table from unused_tables, set
    in_use for children too. Do not link children in open_tables chain.
    When reopening a table, fix the mrg_parent links of all children.
    Extended table_in_use() to check parent and children too.
    Added parameter 'is_temporary' to open_temporary_table(). MERGE
    tables need to know if we open "real" temporary tables as the path
    names of their temporary children cannot be used to produce names
    for open_table(). Luckily there is no need for locking of temporary
    tables. So it is fine for temporary MERGE tables to open their
    children directly.
    When aborting locks, abort them for the parent table, if exists.

  sql/sql_delete.cc@stripped, 2007-04-28 14:31:52+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added argument to new 'is_temporary' parameter of
    open_temporary_table().

  sql/sql_table.cc@stripped, 2007-04-28 14:31:52+02:00, istruewing@stripped +13 -7
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added argument to new 'is_temporary' parameter of
    open_temporary_table().
    Added argument to new 'is_child' parameter of open_table().
    Moved locking of LOCK_open before intern_close_table() in
    mysql_alter_table(). If old table was MERGE, we may need to close
    child tables. This needs LOCK_open.

  sql/table.h@stripped, 2007-04-28 14:31:52+02:00, istruewing@stripped +2 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts
                a MERGE table
    Added mrg_parent and mrg_child to TABLE for use with MERGE tables.

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	istruewing
# Host:	chilla.local
# Root:	/home/mydev/mysql-4.1-bug26379

--- 1.15/include/myisammrg.h	2007-04-28 14:31:59 +02:00
+++ 1.16/include/myisammrg.h	2007-04-28 14:31:59 +02:00
@@ -73,6 +73,8 @@ typedef struct st_myrg_info
   LIST	 open_list;
   QUEUE  by_key;
   ulong *rec_per_key_part;			/* for sql optimizing */
+  void  *callback_param;        /* open/close callback parameter */
+  void  (*close_callback)();    /* Set if open_callback has been used. */
 } MYRG_INFO;
 
 
@@ -80,7 +82,10 @@ typedef struct st_myrg_info
 
 extern int myrg_close(MYRG_INFO *file);
 extern int myrg_delete(MYRG_INFO *file,const byte *buff);
-extern MYRG_INFO *myrg_open(const char *name,int mode,int wait_if_locked);
+extern MYRG_INFO *myrg_open(const char *name,int mode,int wait_if_locked,
+                            MI_INFO *(*open_callback)(void*, char*),
+                            void (*close_callback)(void*),
+                            void *callback_param);
 extern int myrg_panic(enum ha_panic_function function);
 extern int myrg_rfirst(MYRG_INFO *file,byte *buf,int inx);
 extern int myrg_rlast(MYRG_INFO *file,byte *buf,int inx);

--- 1.19/include/raid.h	2007-04-28 14:31:59 +02:00
+++ 1.20/include/raid.h	2007-04-28 14:31:59 +02:00
@@ -141,7 +141,7 @@ class RaidFd {
     inline void Calculate()
     {
       DBUG_ENTER("RaidFd::_Calculate");
-      DBUG_PRINT("info",("_position: %lu _raid_chunksize: %d, _size: %lu",
+      DBUG_PRINT("info",("_position: %lu  _raid_chunksize: %lu  _size: %lu",
 			 (ulong) _position, _raid_chunksize, (ulong) _size));
 
       _total_block = (ulong) (_position / _raid_chunksize);
@@ -149,7 +149,7 @@ class RaidFd {
       _remaining_bytes = (uint) (_raid_chunksize -
 				 (_position - _total_block * _raid_chunksize));
       DBUG_PRINT("info",
-		 ("_total_block: %d  this_block: %d  _remaining_bytes:%d",
+		 ("_total_block: %lu  this_block: %u  _remaining_bytes: %u",
 		  _total_block, _this_block, _remaining_bytes));
       DBUG_VOID_RETURN;
     }

--- 1.51/myisam/mi_create.c	2007-04-28 14:31:59 +02:00
+++ 1.52/myisam/mi_create.c	2007-04-28 14:31:59 +02:00
@@ -564,21 +564,6 @@ int mi_create(const char *name,uint keys
     create_flag=MY_DELETE_OLD;
   }
 
-  /*
-    If a MRG_MyISAM table is in use, the mapped MyISAM tables are open,
-    but no entry is made in the table cache for them.
-    A TRUNCATE command checks for the table in the cache only and could
-    be fooled to believe, the table is not open.
-    Pull the emergency brake in this situation. (Bug #8306)
-  */
-  if (test_if_reopen(filename))
-  {
-    my_printf_error(0, "MyISAM table '%s' is in use "
-                    "(most likely by a MERGE table). Try FLUSH TABLES.",
-                    MYF(0), name + dirname_length(name));
-    goto err;
-  }
-
   if ((file= my_create_with_symlink(linkname_ptr, filename, 0, create_mode,
 				    MYF(MY_WME | create_flag))) < 0)
     goto err;

--- 1.90/myisam/mi_open.c	2007-04-28 14:31:59 +02:00
+++ 1.91/myisam/mi_open.c	2007-04-28 14:31:59 +02:00
@@ -50,7 +50,7 @@ if (pos > end_pos)             \
 ** In MySQL the server will handle version issues.
 ******************************************************************************/
 
-MI_INFO *test_if_reopen(char *filename)
+static MI_INFO *test_if_reopen(char *filename)
 {
   LIST *pos;
 

--- 1.82/myisam/myisamdef.h	2007-04-28 14:31:59 +02:00
+++ 1.83/myisam/myisamdef.h	2007-04-28 14:31:59 +02:00
@@ -728,7 +728,6 @@ void mi_copy_status(void* to,void *from)
 my_bool mi_check_status(void* param);
 void mi_disable_non_unique_index(MI_INFO *info, ha_rows rows);
 
-extern MI_INFO *test_if_reopen(char *filename);
 my_bool check_table_is_closed(const char *name, const char *where);
 int mi_open_datafile(MI_INFO *info, MYISAM_SHARE *share, File file_to_dup);
 int mi_open_keyfile(MYISAM_SHARE *share);

--- 1.6/myisammrg/myrg_close.c	2007-04-28 14:31:59 +02:00
+++ 1.7/myisammrg/myrg_close.c	2007-04-28 14:31:59 +02:00
@@ -23,10 +23,23 @@ int myrg_close(MYRG_INFO *info)
   int error=0,new_error;
   MYRG_TABLE *file;
   DBUG_ENTER("myrg_close");
+  DBUG_PRINT("info", ("myrg_info: 0x%lx  close_cb: 0x%lx  cb_param: 0x%lx",
+                      (long) info, (long) info->close_callback,
+                      (long) info->callback_param));
 
-  for (file=info->open_tables ; file != info->end_table ; file++)
-    if ((new_error=mi_close(file->table)))
-      error=new_error;
+  /*
+    If there was a non-NULL close_callback argument given with the
+    corresponding myrg_open(), use this function for close instead
+    of closing the MyISAM tables directly.
+  */
+  if (info->close_callback)
+    (*info->close_callback)(info->callback_param);
+  else
+  {
+    for (file=info->open_tables ; file != info->end_table ; file++)
+      if ((new_error=mi_close(file->table)))
+        error=new_error;
+  }
   delete_queue(&info->by_key);
   pthread_mutex_lock(&THR_LOCK_open);
   myrg_open_list=list_delete(myrg_open_list,&info->open_list);

--- 1.32/myisammrg/myrg_open.c	2007-04-28 14:31:59 +02:00
+++ 1.33/myisammrg/myrg_open.c	2007-04-28 14:31:59 +02:00
@@ -23,14 +23,31 @@
 #include "mrg_static.c"
 #endif
 
-/*
-	open a MyISAM MERGE table
-	if handle_locking is 0 then exit with error if some table is locked
-	if handle_locking is 1 then wait if table is locked
-*/
+/**
+  @brief Open a MyISAM MERGE table
+
+  @detail If open_callback and close_callback functions are given,
+  they are used for opening and closing the child tables.
+  open_callback() is called for each child table. It opens one table.
+  close_callback() is called once for all child tables. It closes all
+  tables.
+
+  @param[in]      name            db/table_name
+  @param[in]      mode            O_RDONLY or O_RDWR
+  @param[in]      handle_locking  0: error if some table is locked
+                                  1: wait if table is locked
+  @param[in]      open_callback   function to use for opening a child table
+  @param[in]      close_callback  function to use for closing all child tables
+  @param[in]      callback_param  data pointer to give to the callbacks
 
+  @return       pointer to opened MERGE table structure
+    @retval     NULL on error
+*/
 
-MYRG_INFO *myrg_open(const char *name, int mode, int handle_locking)
+MYRG_INFO *myrg_open(const char *name, int mode, int handle_locking,
+                     MI_INFO *(*open_callback)(void*, char*),
+                     void (*close_callback)(void*),
+                     void *callback_param)
 {
   int save_errno,errpos=0;
   uint files= 0, i, dir_length, length, key_parts, min_keys= 0;
@@ -42,18 +59,23 @@ MYRG_INFO *myrg_open(const char *name, i
   MI_INFO *isam=0;
   uint found_merge_insert_method= 0;
   DBUG_ENTER("myrg_open");
+  DBUG_PRINT("enter", ("name: '%s'  mode: %d  flags: %d",
+                       name, mode, handle_locking));
+  DBUG_PRINT("enter", ("open_cb: 0x%lx  close_cb: 0x%lx  cb_param: 0x%lx",
+                       (long) open_callback, (long) close_callback,
+                       (long) callback_param));
 
   LINT_INIT(key_parts);
 
   bzero((char*) &file,sizeof(file));
   if ((fd=my_open(fn_format(name_buff,name,"",MYRG_NAME_EXT,4),
 		  O_RDONLY | O_SHARE,MYF(0))) < 0)
-     goto err;
-   errpos=1;
-   if (init_io_cache(&file, fd, 4*IO_SIZE, READ_CACHE, 0, 0,
+    goto err;
+  errpos=1;
+  if (init_io_cache(&file, fd, 4*IO_SIZE, READ_CACHE, 0, 0,
 		    MYF(MY_WME | MY_NABP)))
-     goto err;
-   errpos=2;
+    goto err;
+  errpos=2;
   dir_length=dirname_part(name_buff,name);
   while ((length=my_b_gets(&file,buff,FN_REFLEN-1)))
   {
@@ -88,10 +110,27 @@ MYRG_INFO *myrg_open(const char *name, i
     }
     else
       fn_format(buff, buff, "", "", 0);
-    if (!(isam=mi_open(buff,mode,(handle_locking?HA_OPEN_WAIT_IF_LOCKED:0))))
+    DBUG_PRINT("info", ("child: '%s'", buff));
+    /*
+      If a non-NULL open_callback argument is present, use it for
+      opening the MyISAM table instead of opening it directly.
+    */
+    if (open_callback)
     {
-      my_errno= HA_ERR_WRONG_MRG_TABLE_DEF;
-      goto err;
+      if (!(isam= (*open_callback)(callback_param, buff)))
+      {
+        DBUG_PRINT("myrg", ("open_callback failed, myerrno: %d", my_errno));
+        goto err;
+      }
+    }
+    else
+    {
+      if (!(isam= mi_open(buff, mode, (handle_locking ?
+                                       HA_OPEN_WAIT_IF_LOCKED : 0))))
+      {
+        my_errno= HA_ERR_WRONG_MRG_TABLE_DEF;
+        goto err;
+      }
     }
     if (!m_info)                                /* First file */
     {
@@ -100,7 +139,18 @@ MYRG_INFO *myrg_open(const char *name, i
                                            files*sizeof(MYRG_TABLE) +
                                            key_parts*sizeof(long),
                                            MYF(MY_WME|MY_ZEROFILL))))
+      {
+        /*
+          Close first file. errpos is still == 2.
+          If a non-NULL close_callback argument is present, use it
+          instead of closing the MyISAM table directly.
+        */
+        if (close_callback)
+          (*close_callback)(callback_param);
+        else
+          mi_close(isam);
         goto err;
+      }
       if (files)
       {
         m_info->open_tables=(MYRG_TABLE *) (m_info+1);
@@ -109,6 +159,9 @@ MYRG_INFO *myrg_open(const char *name, i
         files= 0;
       }
       m_info->reclength=isam->s->base.reclength;
+      /* Store callback information for use with myrg_close(). */
+      m_info->close_callback= close_callback;
+      m_info->callback_param= callback_param;
       min_keys= isam->s->base.keys;
       errpos=3;
     }
@@ -163,8 +216,15 @@ err:
   save_errno=my_errno;
   switch (errpos) {
   case 3:
-    while (files)
-      mi_close(m_info->open_tables[--files].table);
+    /*
+      If a non-NULL close_callback argument is present, use it
+      instead of closing the MyISAM tables directly.
+    */
+    if (close_callback)
+      (*close_callback)(callback_param);
+    else
+      while (files)
+        mi_close(m_info->open_tables[--files].table);
     my_free((char*) m_info,MYF(0));
     /* Fall through */
   case 2:

--- 1.42/mysys/thr_lock.c	2007-04-28 14:31:59 +02:00
+++ 1.43/mysys/thr_lock.c	2007-04-28 14:31:59 +02:00
@@ -137,6 +137,9 @@ static int check_lock(struct st_lock_lis
 	fprintf(stderr,
 		"Warning: prev link %d didn't point at previous lock at %s: %s\n",
 		count, lock_type, where);
+	DBUG_PRINT("warning",(
+		"Warning: prev link %d didn't point at previous lock at %s: %s\n",
+		count, lock_type, where));
 	return 1;
       }
       if (same_thread && ! pthread_equal(data->thread,first_thread) &&
@@ -145,6 +148,9 @@ static int check_lock(struct st_lock_lis
 	fprintf(stderr,
 		"Warning: Found locks from different threads in %s: %s\n",
 		lock_type,where);
+	DBUG_PRINT("warning",(
+		"Warning: Found locks from different threads in %s: %s\n",
+		lock_type,where));
 	return 1;
       }
       if (no_cond && data->cond)
@@ -152,6 +158,9 @@ static int check_lock(struct st_lock_lis
 	fprintf(stderr,
 		"Warning: Found active lock with not reset cond %s: %s\n",
 		lock_type,where);
+	DBUG_PRINT("warning",(
+		"Warning: Found active lock with not reset cond %s: %s\n",
+		lock_type,where));
 	return 1;
       }
       prev= &data->next;
@@ -160,6 +169,8 @@ static int check_lock(struct st_lock_lis
     {
       fprintf(stderr,"Warning: found too many locks at %s: %s\n",
 	      lock_type,where);
+      DBUG_PRINT("warning",("Warning: found too many locks at %s: %s\n",
+	      lock_type,where));
       return 1;
     }
   }
@@ -167,6 +178,8 @@ static int check_lock(struct st_lock_lis
   {
     fprintf(stderr,"Warning: last didn't point at last lock at %s: %s\n",
 	    lock_type, where);
+    DBUG_PRINT("warning",("Warning: last didn't point at last lock at %s: %s\n",
+	    lock_type, where));
     return 1;
   }
   return 0;
@@ -201,6 +214,8 @@ static void check_locks(THR_LOCK *lock, 
 	found_errors++;
 	fprintf(stderr,
 		"Warning at '%s': Locks read_no_write_count was %u when it should have been %u\n",
where, lock->read_no_write_count,count);
+	DBUG_PRINT("warning",(
+		"Warning at '%s': Locks read_no_write_count was %u when it should have been %u\n",
where, lock->read_no_write_count,count));
       }      
 
       if (!lock->write.data)
@@ -212,6 +227,9 @@ static void check_locks(THR_LOCK *lock, 
 	  fprintf(stderr,
 		  "Warning at '%s': No locks in use but locks are in wait queue\n",
 		  where);
+	  DBUG_PRINT("warning",(
+		  "Warning at '%s': No locks in use but locks are in wait queue\n",
+		  where));
 	}
 	if (!lock->write_wait.data)
 	{
@@ -221,6 +239,9 @@ static void check_locks(THR_LOCK *lock, 
 	    fprintf(stderr,
 		    "Warning at '%s': No write locks and waiting read locks\n",
 		    where);
+	    DBUG_PRINT("warning",(
+		    "Warning at '%s': No write locks and waiting read locks\n",
+		    where));
 	  }
 	}
 	else
@@ -236,6 +257,8 @@ static void check_locks(THR_LOCK *lock, 
 	    found_errors++;
 	    fprintf(stderr,
 		    "Warning at '%s': Write lock %d waiting while no exclusive read
locks\n",where,(int) lock->write_wait.data->type);
+	    DBUG_PRINT("warning",(
+		    "Warning at '%s': Write lock %d waiting while no exclusive read
locks\n",where,(int) lock->write_wait.data->type));
 	  }
 	}	      
       }
@@ -251,6 +274,9 @@ static void check_locks(THR_LOCK *lock, 
 	    fprintf(stderr,
 		    "Warning at '%s': Found WRITE_ALLOW_WRITE lock waiting for WRITE_ALLOW_WRITE
lock\n",
 		    where);
+	    DBUG_PRINT("warning",(
+		    "Warning at '%s': Found WRITE_ALLOW_WRITE lock waiting for WRITE_ALLOW_WRITE
lock\n",
+		    where));
 	  }
 	}
 	if (lock->read.data)
@@ -283,6 +309,11 @@ static void check_locks(THR_LOCK *lock, 
 		    where,
 		    (int) lock->read_wait.data->type,
 		    (int) lock->write.data->type);
+	    DBUG_PRINT("warning",(
+		    "Warning at '%s': Found read lock of type %d waiting for write lock of type %d\n",
+		    where,
+		    (int) lock->read_wait.data->type,
+		    (int) lock->write.data->type));
 	  }
 	}
       }
@@ -371,6 +402,7 @@ static my_bool wait_for_lock(struct st_l
   pthread_cond_t *cond=get_cond();
   struct st_my_thread_var *thread_var=my_thread_var;
   int result;
+  DBUG_ENTER("wait_for_lock");
 
   if (!in_wait_list)
   {
@@ -390,6 +422,8 @@ static my_bool wait_for_lock(struct st_l
     if (data->cond != cond)
       break;
   }
+  DBUG_PRINT("lock", ("aborted: %d  changed cond: %d",
+                      thread_var->abort, (data->cond != cond)));
 
   if (data->cond || data->type == TL_UNLOCK)
   {
@@ -419,7 +453,7 @@ static my_bool wait_for_lock(struct st_l
   thread_var->current_mutex= 0;
   thread_var->current_cond=  0;
   pthread_mutex_unlock(&thread_var->mutex);
-  return result;
+  DBUG_RETURN(result);
 }
 
 

--- 1.167/sql/ha_myisam.cc	2007-04-28 14:31:59 +02:00
+++ 1.168/sql/ha_myisam.cc	2007-04-28 14:31:59 +02:00
@@ -572,9 +572,11 @@ int ha_myisam::open(const char *name, in
 
 int ha_myisam::close(void)
 {
-  MI_INFO *tmp=file;
+  MI_INFO *myisam= file;
+  DBUG_ENTER("ha_myisam::close");
+
   file=0;
-  return mi_close(tmp);
+  DBUG_RETURN(mi_close(myisam));
 }
 
 int ha_myisam::write_row(byte * buf)
@@ -1819,3 +1821,20 @@ uint ha_myisam::checksum() const
   return (uint)file->s->state.checksum;
 }
 
+
+/**
+  @brief Extract the MyISAM table structure pointer from a handler object.
+
+  @param[in]      handler_handle        pointer to handler object
+
+  @return       MyISAM table structure pointer
+    @retval     NULL if handler has not a MyISAM table open
+*/
+
+MI_INFO *myisam_engine_handle(handler *handler_handle)
+{
+  DBUG_ENTER("myisam_engine_handle");
+  if (strcmp(handler_handle->table_type(), "MyISAM"))
+    DBUG_RETURN(NULL);
+  DBUG_RETURN(((ha_myisam*) handler_handle)->file);
+}

--- 1.65/sql/ha_myisam.h	2007-04-28 14:31:59 +02:00
+++ 1.66/sql/ha_myisam.h	2007-04-28 14:31:59 +02:00
@@ -133,4 +133,5 @@ class ha_myisam: public handler
   int dump(THD* thd, int fd);
   int net_read_dump(NET* net);
 #endif
+  friend MI_INFO *myisam_engine_handle(handler *handler_handle);
 };

--- 1.65/sql/ha_myisammrg.cc	2007-04-28 14:31:59 +02:00
+++ 1.66/sql/ha_myisammrg.cc	2007-04-28 14:31:59 +02:00
@@ -54,6 +54,134 @@ const char *ha_myisammrg::index_type(uin
 }
 
 
+/**
+  @brief Callback function for opening a MERGE child table.
+
+  @detail When using this function instead of opening the MyISAM table
+  directly, the MyISAM table is opened through the table cache. In a
+  couple of situations the MERGE child table can be found through the
+  table cache as in use. This solves a couple of MERGE problems.
+
+  This is called for each child table. It opens one table.
+
+  @param[in]      callback_param        data pointer as given to myrg_open()
+  @param[in]      filename              file name of MyISAM table
+                                        without extension.
+
+  @return       pointer to opened MyISAM table structure
+    @retval     NULL on error
+*/
+
+MI_INFO *myisammrg_open_callback(void *callback_param, char *filename)
+{
+  struct st_myrg_callback_param *open_param;
+  TABLE   *table;
+  TABLE   *parent;
+  MI_INFO *myisam;
+  char    *table_name;
+  char    *db;
+  uint    dirlen;
+  char    dir_path[FN_REFLEN];
+  bool    not_used;
+  DBUG_ENTER("myisammrg_open_callback");
+
+  /* Extract child table name and database name from filename. */
+  dirlen= dirname_length(filename);
+  if (dirlen >= FN_REFLEN)
+  {
+    my_errno= ENAMETOOLONG;
+    DBUG_RETURN(NULL);
+  }
+  table_name= filename + dirlen;
+  dirlen--; /* Strip trailing '/'. */
+  memcpy(dir_path, filename, dirlen);
+  dir_path[dirlen]= '\0';
+  db= base_name(dir_path);
+  DBUG_PRINT("file", ("open db: '%s'  table_name: '%s'", db, table_name));
+
+  /* Extract the MERGE table (parenT9 pointer from callback_param. */
+  open_param= (struct st_myrg_callback_param*) callback_param;
+  parent= open_param->myisammrg->table_ptr();
+
+  /* Open child table through table cache. */
+  table= open_table(open_param->thd, db, table_name, table_name, &not_used, 1);
+  if (!table)
+  {
+    DBUG_PRINT("file", ("open table '%s'.'%s' failed", db, table_name));
+    my_errno= HA_ERR_LOCK_DEADLOCK;
+    DBUG_RETURN(NULL);
+  }
+  DBUG_PRINT("file", ("opened table: '%s' 0x%lx",
+                      table->table_name, (long) table));
+
+  /* Extract the MyISAM table structure pointer from the handler object. */
+  if (!(myisam= myisam_engine_handle(table->file)))
+  {
+    DBUG_PRINT("file", ("no MyISAM handle for table: '%s' 0x%lx",
+                        table->table_name, (long) table));
+    table->version= 0; /* Force deletion from cache. */
+    VOID(close_thread_table(open_param->thd, &table));
+    my_errno= HA_ERR_WRONG_MRG_TABLE_DEF;
+  }
+  else
+  {
+    DBUG_ASSERT(!table->mrg_parent && !table->mrg_child);
+    /* Link parent table to child. */
+    table->mrg_parent= parent;
+    /* Link child table into child chain of parent. */
+    table->mrg_child= parent->mrg_child;
+    parent->mrg_child= table;
+  }
+  DBUG_PRINT("file", ("MyISAM handle: 0x%lx  my_errno: %d",
+                      (long) myisam, my_errno));
+  DBUG_RETURN(myisam);
+}
+
+
+/**
+  @brief Callback function for closing a MERGE child table.
+
+  @detail If the MERGE child tables were opened through
+  myisammrg_open_callback(), they must be closed through
+  myisammrg_close_callback(). Both function pointers as well as the data
+  pointer 'callback_param' are given to myrg_open().
+
+  This is called once only and closes all child tables.
+
+  @param[in]      callback_param        data pointer as given to myrg_open()
+*/
+
+void myisammrg_close_callback(void *callback_param)
+{
+  struct st_myrg_callback_param *open_param;
+  TABLE *child;
+  TABLE **list_head_p;
+  DBUG_ENTER("myisammrg_close_callback");
+
+  open_param= (struct st_myrg_callback_param*) callback_param;
+
+  /* Close all tables from the child chain. */
+  list_head_p= &open_param->myisammrg->table_ptr()->mrg_child;
+  while ((child= *list_head_p))
+  {
+    /*
+      Step to next child before closing the current one. We must not
+      access the table object after close any more.
+    */
+    *list_head_p= child->mrg_child;
+    /* Detach child from parent. */
+    child->mrg_parent= NULL;
+    child->mrg_child= NULL;
+    /* Force deletion from cache. */
+    child->version= 0;
+    /* Close child through table cache. */
+    VOID(close_thread_table(open_param->thd, &child));
+  }
+
+  DBUG_VOID_RETURN;
+};
+
+
 int ha_myisammrg::open(const char *name, int mode, uint test_if_locked)
 {
   MI_KEYDEF *keyinfo;
@@ -63,15 +191,30 @@ int ha_myisammrg::open(const char *name,
   uint keys= table->keys;
   int error;
   char name_buff[FN_REFLEN];
-
-  DBUG_PRINT("info", ("ha_myisammrg::open"));
-  if (!(file=myrg_open(fn_format(name_buff,name,"","",2 | 4), mode,
-		       test_if_locked)))
+  DBUG_ENTER("ha_myisammrg::open");
+  DBUG_PRINT("enter", ("name: '%s'  mode: %d  test_if_locked: %u",
+                       name, mode, test_if_locked));
+
+  /* callback_param is part of the ha_myisammrg object. */
+  callback_param.thd= current_thd;
+  callback_param.myisammrg= this;
+  /*
+    Temporary tables are not opened through the table cache.
+    They are local to the thread only. So there is no need to let the
+    table cache know about them. Supply callback information only to
+    non-temporary tables.
+  */
+  if (!(file= (test_if_locked & HA_OPEN_TMP_TABLE) ?
+        myrg_open(fn_format(name_buff,name,"","",2 | 4), mode, test_if_locked,
+                  NULL, NULL, NULL) :
+        myrg_open(fn_format(name_buff,name,"","",2 | 4), mode, test_if_locked,
+                  myisammrg_open_callback, myisammrg_close_callback,
+                  &callback_param)))
   {
-    DBUG_PRINT("info", ("ha_myisammrg::open exit %d", my_errno));
-    return (my_errno ? my_errno : -1);
+    DBUG_PRINT("exit", ("my_errno %d", my_errno));
+    DBUG_RETURN(my_errno ? my_errno : -1);
   }
-  DBUG_PRINT("info", ("ha_myisammrg::open myrg_extrafunc..."))
+  DBUG_PRINT("info", ("calling myrg_extrafunc..."))
   myrg_extrafunc(file, query_cache_invalidate_by_MyISAM_filename_ref);
   if (!(test_if_locked == HA_OPEN_WAIT_IF_LOCKED ||
 	test_if_locked == HA_OPEN_ABORT_IF_LOCKED))
@@ -116,16 +259,28 @@ int ha_myisammrg::open(const char *name,
     goto err;
   }
 #endif
-  return (0);
+  DBUG_RETURN(0);
 err:
   myrg_close(file);
   file=0;
-  return (my_errno= error);
+  DBUG_RETURN(my_errno= error);
 }
 
 int ha_myisammrg::close(void)
 {
-  return myrg_close(file);
+  int error;
+  DBUG_ENTER("ha_myisammrg::close");
+
+  DBUG_ASSERT(callback_param.myisammrg == this);
+  error= myrg_close(file);
+  /*
+    It is possible that the table is closed, but ha_myisammrg kept.
+    Avoid any attempt to access the closed engine table.
+    See lock_count() and store_locks().
+  */
+  file= NULL;
+  DBUG_ASSERT(!table->mrg_child);
+  DBUG_RETURN(error);
 }
 
 int ha_myisammrg::write_row(byte * buf)
@@ -335,7 +490,11 @@ int ha_myisammrg::external_lock(THD *thd
 
 uint ha_myisammrg::lock_count(void) const
 {
-  return file->tables;
+  /*
+    It is possible that the table is closed, but ha_myisammrg kept.
+    See close().
+  */
+  return file ? file->tables : 0;
 }
 
 
@@ -344,6 +503,13 @@ THR_LOCK_DATA **ha_myisammrg::store_lock
 					 enum thr_lock_type lock_type)
 {
   MYRG_TABLE *open_table;
+
+  /*
+    It is possible that the table is closed, but ha_myisammrg kept.
+    See close().
+  */
+  if (!file)
+    return to;
 
   for (open_table=file->open_tables ;
        open_table != file->end_table ;

--- 1.40/sql/ha_myisammrg.h	2007-04-28 14:31:59 +02:00
+++ 1.41/sql/ha_myisammrg.h	2007-04-28 14:31:59 +02:00
@@ -19,6 +19,13 @@
 #pragma interface			/* gcc class implementation */
 #endif
 
+/* Data to be passed to open_callback() and close_callback(). */
+struct st_myrg_callback_param
+{
+  THD                   *thd;
+  class ha_myisammrg    *myisammrg;
+};
+
 /* class for the the myisam merge handler */
 
 #include <myisammrg.h>
@@ -26,6 +33,7 @@
 class ha_myisammrg: public handler
 {
   MYRG_INFO *file;
+  struct st_myrg_callback_param callback_param; /* For open/close callbacks */
 
  public:
   ha_myisammrg(TABLE *table): handler(table), file(0) {}
@@ -82,4 +90,5 @@ class ha_myisammrg: public handler
   void update_create_info(HA_CREATE_INFO *create_info);
   void append_create_info(String *packet);
   MYRG_INFO *myrg_info() { return file; }
+  TABLE *table_ptr() { return table; }
 };

--- 1.68/sql/lock.cc	2007-04-28 14:31:59 +02:00
+++ 1.69/sql/lock.cc	2007-04-28 14:31:59 +02:00
@@ -111,6 +111,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, 
     if (! (sql_lock= get_lock_data(thd, tables, count, GET_LOCK_STORE_LOCKS,
                                    &write_lock_used)))
       break;
+    DBUG_PRINT("lock", ("lock sql_lock: 0x%lx  count: %u",
+                        (long) sql_lock, sql_lock->lock_count));
 
     if (global_read_lock && write_lock_used &&
         ! (flags & MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK))
@@ -173,6 +175,8 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, 
     thd->proc_info=0;
 
     /* some table was altered or deleted. reopen tables marked deleted */
+    DBUG_PRINT("lock", ("unlock sql_lock: 0x%lx  count: %u",
+                        (long) sql_lock, sql_lock->lock_count));
     mysql_unlock_tables(thd,sql_lock);
     thd->locked=0;
 retry:
@@ -234,6 +238,8 @@ static int lock_external(THD *thd, TABLE
 void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock)
 {
   DBUG_ENTER("mysql_unlock_tables");
+  DBUG_PRINT("lock", ("unlock sql_lock: 0x%lx  count: %u",
+                      (long) sql_lock, sql_lock->lock_count));
   if (sql_lock->lock_count)
     thr_multi_unlock(sql_lock->locks,sql_lock->lock_count);
   if (sql_lock->table_count)
@@ -251,9 +257,12 @@ void mysql_unlock_some_tables(THD *thd, 
 {
   MYSQL_LOCK *sql_lock;
   TABLE *write_lock_used;
+  DBUG_ENTER("mysql_unlock_some_tables");
+
   if ((sql_lock= get_lock_data(thd, table, count, GET_LOCK_UNLOCK,
                                &write_lock_used)))
     mysql_unlock_tables(thd, sql_lock);
+  DBUG_VOID_RETURN;
 }
 
 
@@ -321,6 +330,7 @@ void mysql_unlock_read_tables(THD *thd, 
 
 void mysql_lock_remove(THD *thd, MYSQL_LOCK *locked,TABLE *table)
 {
+  DBUG_ENTER("mysql_lock_remove");
   mysql_unlock_some_tables(thd, &table,1);
   if (locked)
   {
@@ -375,6 +385,7 @@ void mysql_lock_remove(THD *thd, MYSQL_L
       }
     }
   }
+  DBUG_VOID_RETURN;
 }
 
 /* abort all other threads waiting to get lock in table */
@@ -383,6 +394,8 @@ void mysql_lock_abort(THD *thd, TABLE *t
 {
   MYSQL_LOCK *locked;
   TABLE *write_lock_used;
+  DBUG_ENTER("mysql_lock_abort");
+
   if ((locked= get_lock_data(thd, &table, 1, GET_LOCK_UNLOCK,
                              &write_lock_used)))
   {
@@ -390,6 +403,7 @@ void mysql_lock_abort(THD *thd, TABLE *t
       thr_abort_locks(locked->locks[i]->lock);
     my_free((gptr) locked,MYF(0));
   }
+  DBUG_VOID_RETURN;
 }
 
 
@@ -667,7 +681,12 @@ static MYSQL_LOCK *get_lock_data(THD *th
     *to++= table;
     if (locks)
       for ( ; org_locks != locks ; org_locks++)
+      {
 	(*org_locks)->debug_print_param= (void *) table;
+        DBUG_PRINT("lock", ("table: 0x%lx '%s'  lock_count: %u  data: 0x%lx",
+                            (long) table, table->table_name,
+                            table->lock_count, (long) *org_locks));
+      }
   }
   DBUG_RETURN(sql_lock);
 }
@@ -830,6 +849,8 @@ int lock_table_name(THD *thd, TABLE_LIST
     my_free((gptr) table,MYF(0));
     DBUG_RETURN(-1);
   }
+  DBUG_PRINT("exit", ("locked by name table: '%s' 0x%lx",
+                      table->table_name, (long) table));
   
   if (remove_table_from_cache(thd, db, table_list->real_name, RTFC_NO_FLAG))
   {
@@ -851,12 +872,19 @@ void unlock_table_name(THD *thd, TABLE_L
 
 static bool locked_named_table(THD *thd, TABLE_LIST *table_list)
 {
+  DBUG_ENTER("locked_named_table");
   for (; table_list ; table_list=table_list->next)
   {
+    DBUG_PRINT("lock", ("check locked_named_table: '%s'",
+                        table_list->real_name));
     if (table_list->table && table_is_used(table_list->table,0))
-      return 1;
+    {
+      DBUG_PRINT("lock", ("yes"));
+      DBUG_RETURN(1);
+    }
   }
-  return 0;					// All tables are locked
+  DBUG_PRINT("lock", ("no locked_named_table"));
+  DBUG_RETURN(0);					// All tables are locked
 }
 
 

--- 1.388/sql/mysql_priv.h	2007-04-28 14:31:59 +02:00
+++ 1.389/sql/mysql_priv.h	2007-04-28 14:31:59 +02:00
@@ -621,8 +621,8 @@ int mysql_delete(THD *thd, TABLE_LIST *t
                  ha_rows rows, ulong options);
 int mysql_truncate(THD *thd, TABLE_LIST *table_list, bool dont_send_ok);
 TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type update);
-TABLE *open_table(THD *thd,const char *db,const char *table,const char *alias,
-		  bool *refresh);
+TABLE *open_table(THD *thd, const char *db, const char *table,
+                  const char *alias, bool *refresh, bool is_child);
 TABLE *reopen_name_locked_table(THD* thd, TABLE_LIST* table);
 TABLE *find_locked_table(THD *thd, const char *db,const char *table_name);
 bool reopen_table(TABLE *table,bool locked=0);
@@ -773,7 +773,8 @@ int open_normal_and_derived_tables(THD *
 void relink_tables_for_derived(THD *thd);
 int lock_tables(THD *thd, TABLE_LIST *tables, uint counter);
 TABLE *open_temporary_table(THD *thd, const char *path, const char *db,
-			    const char *table_name, bool link_in_list);
+			    const char *table_name, bool link_in_list,
+                            bool is_temporary);
 bool rm_temporary_table(enum db_type base, char *path);
 void free_io_cache(TABLE *entry);
 void intern_close_table(TABLE *entry);
@@ -1331,3 +1332,5 @@ bool check_stack_overrun(THD *thd,char *
 inline void kill_delayed_threads(void) {}
 #define check_stack_overrun(A, B) 0
 #endif
+
+MI_INFO *myisam_engine_handle(handler *handler_handle);

--- 1.276/sql/sql_base.cc	2007-04-28 14:31:59 +02:00
+++ 1.277/sql/sql_base.cc	2007-04-28 14:31:59 +02:00
@@ -87,10 +87,12 @@ static void check_unused(void)
       DBUG_PRINT("error",("Unused_links aren't connected")); /* purecov: inspected */
     }
   }
+  /* Count open tables in the table cache. */
   for (idx=0 ; idx < open_cache.records ; idx++)
   {
     TABLE *entry=(TABLE*) hash_element(&open_cache,idx);
-    if (!entry->in_use)
+    /* Skip unused tables and MERGE child tables. */
+    if (!entry->in_use && !entry->mrg_parent)
       count--;
   }
   if (count != 0)
@@ -162,7 +164,8 @@ OPEN_TABLE_LIST *list_open_tables(THD *t
       if (!strcmp(table->table,entry->real_name) &&
 	  !strcmp(table->db,entry->table_cache_key))
       {
-	if (entry->in_use)
+        /* Treat MERGE child tables as used. */
+        if (entry->in_use || entry->mrg_parent)
 	  table->in_use++;
 	if (entry->locked_by_name)
 	  table->locked++;
@@ -181,7 +184,8 @@ OPEN_TABLE_LIST *list_open_tables(THD *t
 	   strmov(((*start_list)->db= (char*) ((*start_list)+1)),
 		  entry->table_cache_key)+1,
 	   entry->real_name);
-    (*start_list)->in_use= entry->in_use ? 1 : 0;
+    /* Treat MERGE child tables as used. */
+    (*start_list)->in_use= (entry->in_use || entry->mrg_parent) ? 1 : 0;
     (*start_list)->locked= entry->locked_by_name ? 1 : 0;
     start_list= &(*start_list)->next;
     *start_list=0;
@@ -197,9 +201,13 @@ OPEN_TABLE_LIST *list_open_tables(THD *t
 
 void intern_close_table(TABLE *table)
 {						// Free all structures
+  DBUG_ENTER("intern_close_table");
+  DBUG_PRINT("table", ("table: '%s' 0x%lx", table->table_name, (long) table));
+
   free_io_cache(table);
   if (table->file)
     VOID(closefrm(table));			// close file
+  DBUG_VOID_RETURN;
 }
 
 /*
@@ -216,10 +224,11 @@ void intern_close_table(TABLE *table)
 static void free_cache_entry(TABLE *table)
 {
   DBUG_ENTER("free_cache_entry");
+  DBUG_PRINT("table", ("table: '%s' 0x%lx", table->table_name, (long) table));
   safe_mutex_assert_owner(&LOCK_open);
 
   intern_close_table(table);
-  if (!table->in_use)
+  if (!table->in_use && table->next)    /* MERGE childs are in no chain. */
   {
     table->next->prev=table->prev;		/* remove from used chain */
     table->prev->next=table->next;
@@ -276,6 +285,42 @@ bool close_cached_tables(THD *thd, bool 
 #endif
     }
     refresh_version++;				// Force close of open tables
+    DBUG_PRINT("table", ("incremented global refresh_version to: %lu",
+                         refresh_version));
+    if (if_wait_for_refresh)
+    {
+      /*
+        Other threads could wait in a loop in mysql_lock_table() trying
+        to lock one or more of our tables.
+
+        If they wait for the locks in thr_multi_lock(), their lock
+        request is aborted. If they wait for the table to become unused,
+        they see table->locked_by_flush=1. But if they passed the loop
+        in wait_for_tables(), released LOCK_open, we (the FLUSH TABLES
+        thread) are scheduled at this moment and enter
+        close_cached_tables(), they could awake while we sleep below,
+        waiting for others threads to close their open tables. If this
+        happens, the other threads would find the tables unlocked. They
+        would get the locks, one after the other, and could do their
+        destructive work. This is an issue if we have LOCK TABLES in
+        effect.
+
+        The fix for this problem is to set some_tables_deleted for all
+        threads with open tables. These threads can still get their locks,
+        but will immediately release them again after checking this
+        variable. They will then reenter wait_for_tables(). There they
+        will wait until we update all tables version below.
+
+        Setting some_tables_deleted is done by remove_table_from_cache()
+        in the other branch.
+      */
+      for (uint idx=0 ; idx < open_cache.records ; idx++)
+      {
+        TABLE *table=(TABLE*) hash_element(&open_cache,idx);
+        if (table->in_use)
+          table->in_use->some_tables_deleted= 1;
+      }
+    }
   }
   else
   {
@@ -400,11 +445,12 @@ void close_thread_tables(THD *thd, bool 
     VOID(pthread_mutex_lock(&LOCK_open));
   safe_mutex_assert_owner(&LOCK_open);
 
-  DBUG_PRINT("info", ("thd->open_tables=%p", thd->open_tables));
-
- found_old_table= 0;
+  found_old_table= 0;
   while (thd->open_tables)
+  {
+    DBUG_PRINT("info", ("close open_tables: 0x%lx", (long) thd->open_tables));
     found_old_table|=close_thread_table(thd, &thd->open_tables);
+  }
   thd->some_tables_deleted=0;
 
   /* Free tables to hold down open files */
@@ -426,10 +472,12 @@ void close_thread_tables(THD *thd, bool 
 
 bool close_thread_table(THD *thd, TABLE **table_ptr)
 {
-  DBUG_ENTER("close_thread_table");
-
   bool found_old_table= 0;
   TABLE *table= *table_ptr;
+  DBUG_ENTER("close_thread_table");
+  DBUG_PRINT("info", ("closing table: '%s' 0x%lx  version: %lu",
+                      table->table_name, (long) table, table->version));
+
   DBUG_ASSERT(table->key_read == 0);
 
   *table_ptr=table->next;
@@ -438,6 +486,7 @@ bool close_thread_table(THD *thd, TABLE 
   {
     VOID(hash_delete(&open_cache,(byte*) table));
     found_old_table=1;
+    DBUG_PRINT("info", ("deleted from cache"));
   }
   else
   {
@@ -452,15 +501,40 @@ bool close_thread_table(THD *thd, TABLE 
       table->file->reset();
     }
     table->in_use=0;
-    if (unused_tables)
+    /*
+      Do not add MERGE child tables to unused tables. Unused tables may
+      be deleted at any time. As long as a MERGE parent table is not
+      deleted, it will need its children when it is reopened. Since we
+      make the children kind of "floating" tables, we need to delete
+      them when the parent table is deleted.
+    */
+    if (table->mrg_parent)
     {
-      table->next=unused_tables;		/* Link in last */
-      table->prev=unused_tables->prev;
-      unused_tables->prev=table;
-      table->prev->next=table;
+      DBUG_PRINT("info", ("set floating merge child"));
+      table->next= table->prev= table;
     }
     else
-      unused_tables=table->next=table->prev=table;
+    {
+      DBUG_PRINT("info", ("set unused"));
+      if (unused_tables)
+      {
+        table->next=unused_tables;		/* Link in last */
+        table->prev=unused_tables->prev;
+        unused_tables->prev=table;
+        table->prev->next=table;
+      }
+      else
+        unused_tables=table->next=table->prev=table;
+    }
+    /* If the table has children, clear their in_use too.*/
+    for (TABLE *child= table->mrg_child; child; child= child->mrg_child)
+    {
+      DBUG_PRINT("info", ("set floating merge child: '%s' 0x%lx",
+                          child->table_name, (long) child));
+      DBUG_ASSERT(child->in_use == thd);
+      DBUG_ASSERT(child->mrg_parent == table);
+      child->in_use= NULL;
+    }
   }
   DBUG_RETURN(found_old_table);
 }
@@ -758,6 +832,8 @@ TABLE *unlink_open_table(THD *thd, TABLE
   char key[MAX_DBKEY_LENGTH];
   uint key_length=find->key_length;
   TABLE *start=list,**prev,*next;
+  DBUG_ENTER("unlink_open_table");
+
   prev= &start;
   memcpy(key,find->table_cache_key,key_length);
   for (; list ; list=next)
@@ -779,7 +855,7 @@ TABLE *unlink_open_table(THD *thd, TABLE
   *prev=0;
   // Notify any 'refresh' threads
   pthread_cond_broadcast(&COND_refresh);
-  return start;
+  DBUG_RETURN(start);
 }
 
 
@@ -790,6 +866,7 @@ TABLE *unlink_open_table(THD *thd, TABLE
 
 void wait_for_refresh(THD *thd)
 {
+  DBUG_ENTER("wait_for_refresh");
   safe_mutex_assert_owner(&LOCK_open);
 
   /* Wait until the current table is up to date */
@@ -799,14 +876,18 @@ void wait_for_refresh(THD *thd)
   proc_info=thd->proc_info;
   thd->proc_info="Waiting for table";
   if (!thd->killed)
+  {
+    DBUG_PRINT("pthreads", ("waiting for refresh"));
     (void) pthread_cond_wait(&COND_refresh,&LOCK_open);
-
+    DBUG_PRINT("pthreads", ("awoke from refresh"));
+  }
   pthread_mutex_unlock(&LOCK_open);	// Must be unlocked first
   pthread_mutex_lock(&thd->mysys_var->mutex);
   thd->mysys_var->current_mutex= 0;
   thd->mysys_var->current_cond= 0;
   thd->proc_info= proc_info;
   pthread_mutex_unlock(&thd->mysys_var->mutex);
+  DBUG_VOID_RETURN;
 }
 
 
@@ -843,6 +924,8 @@ TABLE *reopen_name_locked_table(THD* thd
   pthread_mutex_unlock(&LOCK_open);
   table->next = thd->open_tables;
   thd->open_tables = table;
+  DBUG_PRINT("table", ("put into open_tables: '%s' 0x%lx",
+                      table->table_name, (long) table));
   table->tablenr=thd->current_tablenr++;
   table->used_fields=0;
   table->const_table=0;
@@ -854,24 +937,37 @@ TABLE *reopen_name_locked_table(THD* thd
 }
 
 
-/******************************************************************************
-** open a table
-** Uses a cache of open tables to find a table not in use.
-** If refresh is a NULL pointer, then the is no version number checking and
-** the table is not put in the thread-open-list
-** If the return value is NULL and refresh is set then one must close
-** all tables and retry the open
-******************************************************************************/
+/**
+  @brief open a table
+
+  @detail Uses a cache of open tables to find a table not in use. If
+  refresh is a NULL pointer, then there is no version number checking
+  and the table is not put in the thread-open-list.
+
+  If the return value is NULL and refresh is set then one must close
+  all tables and retry the open.
+
+  @param[in]      thd           thread handle
+  @param[in]      db            database name
+  @param[in]      table_name    table name
+  @param[in]      alias         table alias
+  @param[in,out]  refresh       pointer to refresh flag, may be NULL
+  @param[in]      is_child      if opening a MERGE child table
+                                if TRUE, LOCK_open must be locked before
 
+  @return       pointer to the opened TABLE object
+    @retval     NULL on error
+*/
 
-TABLE *open_table(THD *thd,const char *db,const char *table_name,
-		  const char *alias,bool *refresh)
+TABLE *open_table(THD *thd, const char *db, const char *table_name,
+                  const char *alias, bool *refresh, bool is_child)
 {
   reg1	TABLE *table;
   char	key[MAX_DBKEY_LENGTH];
   uint	key_length;
   HASH_SEARCH_STATE state;
   DBUG_ENTER("open_table");
+  DBUG_PRINT("enter", ("db: '%s'  table_name: '%s'", db, table_name));
 
   /* find a unused table in the open table cache */
   if (refresh)
@@ -882,45 +978,52 @@ TABLE *open_table(THD *thd,const char *d
   int4store(key + key_length, thd->server_id);
   int4store(key + key_length + 4, thd->variables.pseudo_thread_id);
 
-  for (table=thd->temporary_tables; table ; table=table->next)
+  if (!is_child)
   {
-    if (table->key_length == key_length + TMP_TABLE_KEY_EXTRA &&
-	!memcmp(table->table_cache_key, key,
-                key_length + TMP_TABLE_KEY_EXTRA))
-    {
-      if (table->query_id == thd->query_id)
-      {
-	my_printf_error(ER_CANT_REOPEN_TABLE,
-			ER(ER_CANT_REOPEN_TABLE),MYF(0),table->table_name);
-	DBUG_RETURN(0);
-      }
-      table->query_id=thd->query_id;
-      table->clear_query_id=1;
-      thd->tmp_table_used= 1;
-      DBUG_PRINT("info",("Using temporary table"));
-      goto reset;
+    for (table=thd->temporary_tables; table ; table=table->next)
+    {
+      if (table->key_length == key_length + TMP_TABLE_KEY_EXTRA &&
+          !memcmp(table->table_cache_key, key,
+                  key_length + TMP_TABLE_KEY_EXTRA))
+      {
+        if (table->query_id == thd->query_id)
+        {
+          my_printf_error(ER_CANT_REOPEN_TABLE,
+                          ER(ER_CANT_REOPEN_TABLE),MYF(0),table->table_name);
+          DBUG_RETURN(0);
+        }
+        table->query_id=thd->query_id;
+        table->clear_query_id=1;
+        thd->tmp_table_used= 1;
+        DBUG_PRINT("info", ("Using temporary table: 0x%lx", (long) table));
+        goto reset;
+      }
     }
-  }
 
-  if (thd->locked_tables)
-  {						// Using table locks
-    for (table=thd->open_tables; table ; table=table->next)
-    {
-      if (table->key_length == key_length &&
-	  !memcmp(table->table_cache_key,key,key_length) &&
-	  !my_strcasecmp(system_charset_info, table->table_name, alias) &&
-	  table->query_id != thd->query_id)
+    if (thd->locked_tables)
+    {						// Using table locks
+      for (table=thd->open_tables; table ; table=table->next)
       {
-	table->query_id=thd->query_id;
-        DBUG_PRINT("info",("Using locked table"));
-	goto reset;
+        if (table->key_length == key_length &&
+            !memcmp(table->table_cache_key,key,key_length) &&
+            !my_strcasecmp(system_charset_info, table->table_name, alias) &&
+            table->query_id != thd->query_id)
+        {
+          table->query_id=thd->query_id;
+          DBUG_PRINT("info", ("Using locked table: 0x%lx", (long) table));
+          goto reset;
+        }
       }
+      my_printf_error(ER_TABLE_NOT_LOCKED,ER(ER_TABLE_NOT_LOCKED),MYF(0),alias);
+      DBUG_RETURN(0);
     }
-    my_printf_error(ER_TABLE_NOT_LOCKED,ER(ER_TABLE_NOT_LOCKED),MYF(0),alias);
-    DBUG_RETURN(0);
-  }
 
-  VOID(pthread_mutex_lock(&LOCK_open));
+    VOID(pthread_mutex_lock(&LOCK_open));
+  }
+  /*
+    A MERGE child of an intermediate table (e.g. ALTER TABLE) is opened
+    without LOCK_open. So don't safe_mutex_assert_owner(&LOCK_open).
+  */
 
   if (!thd->open_tables)
     thd->version=refresh_version;
@@ -928,21 +1031,32 @@ TABLE *open_table(THD *thd,const char *d
   {
     /* Someone did a refresh while thread was opening tables */
     *refresh=1;
-    VOID(pthread_mutex_unlock(&LOCK_open));
+    if (!is_child)
+      VOID(pthread_mutex_unlock(&LOCK_open));
+    DBUG_PRINT("info", ("Thread needs refresh"));
     DBUG_RETURN(0);
   }
 
   /* close handler tables which are marked for flush */
   mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE, TRUE);
 
+  /*
+    Find an unused table. But do not use a MERGE child.
+    While stepping through the cache, refresh "old" tables. But stop this
+    when an unused table is found.
+  */
   for (table= (TABLE*) hash_first(&open_cache, (byte*) key, key_length,
                                   &state);
-       table && table->in_use ;
+       table && (table->in_use || table->mrg_parent);
        table= (TABLE*) hash_next(&open_cache, (byte*) key, key_length,
                                  &state))
   {
-    if (table->version != refresh_version)
+    /* Do not refresh MERGE children. They are refreshed with their parent. */
+    if ((table->version != refresh_version) && !table->mrg_parent)
     {
+      DBUG_PRINT("info", ("Version mismatch, table: 0x%lx  version: %lu  "
+                          "refresh_version: %lu", (long) table,
+                          table->version, refresh_version));
       if (! refresh)
       {
         /* Ignore flush for now, but force close after usage. */
@@ -956,9 +1070,13 @@ TABLE *open_table(THD *thd,const char *d
       */
       close_old_data_files(thd,thd->open_tables,0,0);
       if (table->in_use != thd)
+      {
 	wait_for_refresh(thd);
-      else
-	VOID(pthread_mutex_unlock(&LOCK_open));
+        if (is_child)
+          VOID(pthread_mutex_lock(&LOCK_open));
+      }
+      else if (!is_child)
+        VOID(pthread_mutex_unlock(&LOCK_open));
       if (refresh)
 	*refresh=1;
       DBUG_RETURN(0);
@@ -975,6 +1093,7 @@ TABLE *open_table(THD *thd,const char *d
     table->prev->next=table->next;		/* Remove from unused list */
     table->next->prev=table->prev;
 
+    DBUG_PRINT("info", ("Unused table: 0x%lx", (long) table));
   }
   else
   {
@@ -985,33 +1104,58 @@ TABLE *open_table(THD *thd,const char *d
     /* make a new table */
     if (!(table=(TABLE*) my_malloc(sizeof(*table),MYF(MY_WME))))
     {
-      VOID(pthread_mutex_unlock(&LOCK_open));
+      if (!is_child)
+        VOID(pthread_mutex_unlock(&LOCK_open));
       DBUG_RETURN(NULL);
     }
+    /* Initialize MERGE table related pointers. */
+    table->mrg_parent= NULL;
+    table->mrg_child= NULL;
     if (open_unireg_entry(thd, table,db,table_name,alias) ||
 	!(table->table_cache_key=memdup_root(&table->mem_root,(char*) key,
 					     key_length)))
     {
       table->next=table->prev=table;
       free_cache_entry(table);
-      VOID(pthread_mutex_unlock(&LOCK_open));
+      if (!is_child)
+        VOID(pthread_mutex_unlock(&LOCK_open));
       DBUG_RETURN(NULL);
     }
     table->key_length=key_length;
     table->version=refresh_version;
     table->flush_version=flush_version;
-    DBUG_PRINT("info", ("inserting table %p into the cache", table));
+    DBUG_PRINT("info", ("New table 0x%lx in cache", (long) table));
     VOID(my_hash_insert(&open_cache,(byte*) table));
   }
 
   table->in_use=thd;
+
+  /* If the table has children, set their in_use too. */
+  for (TABLE *child= table->mrg_child; child; child= child->mrg_child)
+  {
+    /*
+      In case of new opened MERGE table, children are already set up
+      through open_unireg_entry().
+    */
+    DBUG_ASSERT(!child->in_use || (child->in_use == thd));
+    DBUG_ASSERT(child->mrg_parent == table);
+    child->in_use= thd;
+  }
   check_unused();				// Debugging call
-       
-  VOID(pthread_mutex_unlock(&LOCK_open));
-  if (refresh)
+
+  /* Do not add MERGE children to open_tables. */
+  if (is_child)
+    table->next= NULL;
+  else
   {
-    table->next=thd->open_tables;		/* Link into simple list */
-    thd->open_tables=table;
+    VOID(pthread_mutex_unlock(&LOCK_open));
+    if (refresh)
+    {
+      table->next=thd->open_tables;		/* Link into simple list */
+      thd->open_tables=table;
+      DBUG_PRINT("table", ("put into open_tables: '%s' 0x%lx",
+                           table->table_name, (long) table));
+    }
   }
   table->reginfo.lock_type=TL_READ;		/* Assume read */
 
@@ -1065,6 +1209,8 @@ TABLE *open_table(THD *thd,const char *d
     table->timestamp_field_type= table->timestamp_field->get_auto_set_type();
   DBUG_ASSERT(table->key_read == 0);
   DBUG_ASSERT(table->insert_values == 0);
+  DBUG_PRINT("exit", ("Opened table: '%s' 0x%lx  version: %lu",
+                      table->table_name, (long) table, table->version));
   DBUG_RETURN(table);
 }
 
@@ -1120,6 +1266,9 @@ bool reopen_table(TABLE *table,bool lock
     VOID(pthread_mutex_lock(&LOCK_open));
   safe_mutex_assert_owner(&LOCK_open);
 
+  /* Initialize MERGE table related pointers. */
+  tmp.mrg_parent= NULL;
+  tmp.mrg_child= NULL;
   if (open_unireg_entry(current_thd,&tmp,db,table_name,table->table_name))
     goto end;
   free_io_cache(table);
@@ -1158,8 +1307,13 @@ bool reopen_table(TABLE *table,bool lock
   if (table->file)
     VOID(closefrm(table));		// close file, free everything
 
+  /* Copy new TABLE structure to old TABLE structure. */
   *table=tmp;
+  /* Fix table pointer in handler object. */
   table->file->change_table_ptr(table);
+  /* Fix parent pointers in child tables. */
+  for (TABLE *child= table->mrg_child; child; child= child->mrg_child)
+    child->mrg_parent= table;
 
   DBUG_ASSERT(table->table_name);
   for (field=table->field ; *field ; field++)
@@ -1172,6 +1326,8 @@ bool reopen_table(TABLE *table,bool lock
     for (part=0 ; part < table->key_info[key].usable_key_parts ; part++)
       table->key_info[key].key_part[part].field->table= table;
   }
+  DBUG_PRINT("exit", ("Reopened table: '%s' 0x%lx  version: %lu",
+                      table->table_name, (long) table, table->version));
   VOID(pthread_cond_broadcast(&COND_refresh));
   error=0;
 
@@ -1251,7 +1407,20 @@ bool reopen_tables(THD *thd,bool get_loc
 	*tables_ptr++= table;			// need new lock on this
       if (in_refresh)
       {
-	table->version=0;
+	/*
+          Formerly we set table->version=0; here. I do not know what the
+          sense was to clear the freshly assigned version number of a
+          freshly reopened table. I just can say that it made a crash
+          when reopening the same table twice in a thread. For example
+          as a MyISAM table and as a MERGE child table. The table that
+          was reopend as the second one closed the previously reopend
+          table because of its zero version. table->file became NULL
+          again. But there is no means to reopen an already reopened
+          table again. The code after reopen_tables() assumed that all
+          tables are open and happily called table->file->lock_count()...
+          Removing table->version=0; fixed the problem and does not seem
+          to have a negative impact.
+        */
 	table->locked_by_flush=0;
       }
     }
@@ -1314,6 +1483,11 @@ void close_old_data_files(THD *thd, TABL
 }
 
 
+#define TABLE_IN_USE(_t_) ((_t_)->locked_by_flush || \
+                           (_t_)->locked_by_name && wait_for_name_lock || \
+                           (_t_)->db_stat && (_t_)->version <
refresh_version)
+
+
 /*
   Wait until all threads has closed the tables in the list
   We have also to wait if there is thread that has a lock on this table even
@@ -1322,6 +1496,9 @@ void close_old_data_files(THD *thd, TABL
 
 bool table_is_used(TABLE *table, bool wait_for_name_lock)
 {
+  DBUG_ENTER("table_is_used");
+  DBUG_PRINT("table", ("table: '%s' 0x%lx  wait_for_name_lock: %d",
+                       table->table_name, (long) table, wait_for_name_lock));
   do
   {
     HASH_SEARCH_STATE state;
@@ -1333,13 +1510,24 @@ bool table_is_used(TABLE *table, bool wa
          search= (TABLE*) hash_next(&open_cache, (byte*) key,
                                     key_length, &state))
     {
-      if (search->locked_by_flush ||
-	  search->locked_by_name && wait_for_name_lock ||
-	  search->db_stat && search->version < refresh_version)
-	return 1;				// Table is used
+      if (TABLE_IN_USE(search))
+	DBUG_RETURN(1);
+      /* For a MERGE table check its children too. */
+      for (TABLE *child= search->mrg_child; child; child= child->mrg_child)
+      {
+        if (TABLE_IN_USE(child))
+          DBUG_RETURN(1);
+      }
+      /* For a MERGE child, check its parent too. */
+      if (search->mrg_parent)
+      {
+        if (TABLE_IN_USE(search->mrg_parent))
+          DBUG_RETURN(1);
+      }
     }
   } while ((table=table->next));
-  return 0;
+  DBUG_PRINT("table", ("no table is used"));
+  DBUG_RETURN(0);
 }
 
 
@@ -1359,7 +1547,9 @@ bool wait_for_tables(THD *thd)
     mysql_ha_flush(thd, (TABLE_LIST*) NULL, MYSQL_HA_REOPEN_ON_USAGE, TRUE);
     if (!table_is_used(thd->open_tables,1))
       break;
+    DBUG_PRINT("pthreads", ("waiting for refresh"));
     (void) pthread_cond_wait(&COND_refresh,&LOCK_open);
+    DBUG_PRINT("pthreads", ("awoke from refresh"));
   }
   if (thd->killed)
     result= 1;					// aborted
@@ -1618,7 +1808,7 @@ int open_tables(THD *thd, TABLE_LIST *st
 	!(tables->table= open_table(thd,
 				    tables->db,
 				    tables->real_name,
-				    tables->alias, &refresh)))
+				    tables->alias, &refresh, 0)))
     {
       if (refresh)				// Refresh in progress
       {
@@ -1731,7 +1921,7 @@ TABLE *open_ltable(THD *thd, TABLE_LIST 
   thd->current_tablenr= 0;
   while (!(table=open_table(thd,table_list->db,
 			    table_list->real_name,table_list->alias,
-			    &refresh)) && refresh) ;
+			    &refresh, 0)) && refresh) ;
 
   if (table)
   {
@@ -1924,16 +2114,46 @@ int lock_tables(THD *thd, TABLE_LIST *ta
 
 
 /*
-  Open a single table without table caching and don't set it in open_list
-  Used by alter_table to open a temporary table and when creating
+  @brief Open a single table without table caching and don't set it in open_list
+
+  @detail Used by alter_table to open a intermediate table and when creating
   a temporary table with CREATE TEMPORARY ...
+
+  Temporary table can be created anywhere in the file system. The path
+  name is unrelated to the internally used db+table_name. For example
+  CREATE TEMPORARY TABLE db1.tt1 ... will create table files like
+  TMPDIR/#sql-1234-56.* but internally the table has the name 'tt1' and
+  belongs to the database 'db1'.
+
+  Intermediate tables as used by ALTER TABLE have also a "temporary" name
+  like #sql-1234-56, but are located in the same directory as the related
+  permanent table. They are normal tables in every aspect but the name.
+
+  For a MERGE table it is important to know if the new table is a real
+  temporary table. The path names of their temporary children cannot be
+  used to produce names for open_table(). In that case the child tables
+  are not opened through the table cache.
+
+  @param[in]      thd           thread handle
+  @param[in]      path          path name for table files (external use)
+  @param[in]      db            database for internal use
+  @param[in]      table_name    table name for internal use
+  @param[in]      link_in_list  if to link in thd->temporary_tables
+  @param[in]      is_temporary  if it is a temporary table, not an
+                                intermediate table as used by ALTER TABLE.
+
+  @return       pointer to opened TABLE object
+    @retval     NULL on error
 */
 
 TABLE *open_temporary_table(THD *thd, const char *path, const char *db,
-			    const char *table_name, bool link_in_list)
+                            const char *table_name, bool link_in_list,
+                            bool is_temporary)
 {
   TABLE *tmp_table;
   DBUG_ENTER("open_temporary_table");
+  DBUG_PRINT("enter", ("path: '%s'  db: '%s'  name: '%s'  link: %d  temp: %d",
+                       path, db, table_name, link_in_list, is_temporary));
 
   /*
     The extra size in my_malloc() is for table_cache_key
@@ -1948,7 +2168,8 @@ TABLE *open_temporary_table(THD *thd, co
     DBUG_RETURN(0);				/* purecov: inspected */
 
   if (openfrm(path, table_name,
-	      (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | HA_GET_INDEX),
+	      (uint) (HA_OPEN_KEYFILE | HA_OPEN_RNDFILE | HA_GET_INDEX |
+                      (is_temporary ? HA_OPEN_TEMPORARY : 0)),
 	      READ_KEYINFO | COMPUTE_TYPES | EXTRA_RECORD,
 	      ha_open_options,
 	      tmp_table))
@@ -1980,6 +2201,8 @@ TABLE *open_temporary_table(THD *thd, co
     if (thd->slave_thread)
       slave_open_temp_tables++;
   }
+  DBUG_PRINT("info", ("opened temporary table: '%s' 0x%lx",
+                      tmp_table->table_name, (long) tmp_table));
   DBUG_RETURN(tmp_table);
 }
 
@@ -3064,7 +3287,12 @@ void remove_db_from_cache(const char *db
     if (!strcmp(table->table_cache_key,db))
     {
       table->version=0L;			/* Free when thread is ready */
-      if (!table->in_use)
+      /*
+        If table is unused, move it first in unused link.
+        Do not do that for MERGE children. They are not in the unused
+        chain. They will be deleted with their parent.
+      */
+      if (!table->in_use && !table->mrg_parent)
 	relink_unused(table);
     }
   }
@@ -3109,7 +3337,8 @@ bool remove_table_from_cache(THD *thd, c
   TABLE *table;
   bool result=0, signalled= 0;
   DBUG_ENTER("remove_table_from_cache");
-
+  DBUG_PRINT("enter", ("db: '%s'  table_name: '%s'  flags: %u",
+                       db, table_name, flags));
 
   key_length=(uint) (strmov(strmov(key,db)+1,table_name)-key)+1;
   for (;;)
@@ -3117,6 +3346,7 @@ bool remove_table_from_cache(THD *thd, c
     HASH_SEARCH_STATE state;
     result= signalled= 0;
 
+    /* Remove all TABLE objects for this table from cache. */
     for (table= (TABLE*) hash_first(&open_cache, (byte*) key, key_length,
                                     &state);
          table;
@@ -3124,6 +3354,14 @@ bool remove_table_from_cache(THD *thd, c
                                    &state))
     {
       THD *in_use;
+
+      /*
+        If a MERGE child is found, switch to the parent. This removes
+        the parent and all of its children, including the found one.
+      */
+      if (table->mrg_parent)
+        table= table->mrg_parent;
+
       table->version=0L;		/* Free when thread is ready */
       if (!(in_use=table->in_use))
       {
@@ -3132,6 +3370,8 @@ bool remove_table_from_cache(THD *thd, c
       }
       else if (in_use != thd)
       {
+        DBUG_PRINT("info", ("Table in use: 0x%lx  by thread: %ld",
+                            (long) table, in_use->dbug_thread_id));
         in_use->some_tables_deleted=1;
         if (table->db_stat)
   	  result=1;
@@ -3158,12 +3398,22 @@ bool remove_table_from_cache(THD *thd, c
 	     thd_table ;
 	     thd_table= thd_table->next)
         {
+          /*
+            For a MERGE child abort the locks of the parent and thus
+            implicitly all children, including this one.
+          */
 	  if (thd_table->db_stat)		// If table is open
-	    signalled|= mysql_lock_abort_for_thread(thd, thd_table);
+	    signalled|= mysql_lock_abort_for_thread(thd,
+                                                    thd_table->mrg_parent ?
+                                                    thd_table->mrg_parent :
+                                                    thd_table);
         }
       }
       else
+      {
         result= result || (flags & RTFC_OWNED_BY_THD_FLAG);
+        DBUG_PRINT("info", ("Table in use: 0x%lx  by self", (long) table));
+      }
     }
     while (unused_tables && !unused_tables->version)
       VOID(hash_delete(&open_cache,(byte*) unused_tables));
@@ -3173,7 +3423,11 @@ bool remove_table_from_cache(THD *thd, c
       {
         dropping_tables++;
         if (likely(signalled))
+        {
+          DBUG_PRINT("pthreads", ("waiting for refresh"));
           (void) pthread_cond_wait(&COND_refresh, &LOCK_open);
+          DBUG_PRINT("pthreads", ("awoke from refresh"));
+        }
         else
         {
           struct timespec abstime;
@@ -3188,7 +3442,9 @@ bool remove_table_from_cache(THD *thd, c
             remove_table_from_cache routine.
           */
           set_timespec(abstime, 10);
+          DBUG_PRINT("pthreads", ("waiting for refresh 10 msec"));
           pthread_cond_timedwait(&COND_refresh, &LOCK_open, &abstime);
+          DBUG_PRINT("pthreads", ("awoke from refresh"));
         }
         dropping_tables--;
         continue;

--- 1.143/sql/sql_delete.cc	2007-04-28 14:31:59 +02:00
+++ 1.144/sql/sql_delete.cc	2007-04-28 14:31:59 +02:00
@@ -661,7 +661,7 @@ int mysql_truncate(THD *thd, TABLE_LIST 
     ha_create_table(path, &create_info,1);
     // We don't need to call invalidate() because this table is not in cache
     if ((error= (int) !(open_temporary_table(thd, path, table_list->db,
-					     table_list->real_name, 1))))
+					     table_list->real_name, 1, 1))))
       (void) rm_temporary_table(table_type, path);
     /*
       If we return here we will not have logged the truncation to the bin log

--- 1.312/sql/sql_table.cc	2007-04-28 14:31:59 +02:00
+++ 1.313/sql/sql_table.cc	2007-04-28 14:31:59 +02:00
@@ -1514,7 +1514,7 @@ int mysql_create_table(THD *thd,const ch
   if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
   {
     /* Open table and put in temporary table list */
-    if (!(open_temporary_table(thd, path, db, table_name, 1)))
+    if (!(open_temporary_table(thd, path, db, table_name, 1, 1)))
     {
       (void) rm_temporary_table(create_info->db_type, path);
       goto end;
@@ -1648,7 +1648,7 @@ TABLE *create_table_from_items(THD *thd,
   if (!mysql_create_table(thd, db, name, create_info, alter_info,
                           0, select_field_count))
   {
-    if (!(table=open_table(thd,db,name,name,(bool*) 0)))
+    if (!(table= open_table(thd, db, name, name, (bool*) 0, 0)))
       quick_rm_table(create_info->db_type,db,table_case_name(create_info,name));
   }
   reenable_binlog(thd);
@@ -1780,6 +1780,7 @@ static void wait_while_table_is_used(THD
 static bool close_cached_table(THD *thd, TABLE *table)
 {
   DBUG_ENTER("close_cached_table");
+  DBUG_PRINT("enter", ("table: '%s'", table->table_name));
 
   wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_DELETE);
   /* Close lock if this is not got with LOCK TABLES */
@@ -2490,7 +2491,7 @@ int mysql_create_like_table(THD* thd, TA
 
   if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
   {
-    if (err || !open_temporary_table(thd, dst_path, db, table_name, 1))
+    if (err || !open_temporary_table(thd, dst_path, db, table_name, 1, 1))
     {
       (void) rm_temporary_table(create_info->db_type,
 				dst_path); /* purecov: inspected */
@@ -3223,12 +3224,12 @@ int mysql_alter_table(THD *thd,char *new
       DBUG_RETURN(error);
   }
   if (table->tmp_table)
-    new_table=open_table(thd,new_db,tmp_name,tmp_name,0);
+    new_table= open_table(thd, new_db, tmp_name, tmp_name, 0, 0);
   else
   {
     char path[FN_REFLEN];
     build_table_path(path, sizeof(path), new_db, tmp_name, "");
-    new_table=open_temporary_table(thd, path, new_db, tmp_name,0);
+    new_table=open_temporary_table(thd, path, new_db, tmp_name, 0, 0);
   }
   if (!new_table)
   {
@@ -3296,9 +3297,13 @@ int mysql_alter_table(THD *thd,char *new
     goto end_temporary;
   }
 
+  /*
+    If old table was MERGE, we may need to close child tables. This
+    needs LOCK_open.
+  */
+  VOID(pthread_mutex_lock(&LOCK_open));
   intern_close_table(new_table);		/* close temporary table */
   my_free((gptr) new_table,MYF(0));
-  VOID(pthread_mutex_lock(&LOCK_open));
   if (error)
   {
     VOID(quick_rm_table(new_db_type,new_db,tmp_name));
@@ -3442,7 +3447,8 @@ int mysql_alter_table(THD *thd,char *new
     */
     char path[FN_REFLEN];
     build_table_path(path, sizeof(path), new_db, table_name, "");
-    table=open_temporary_table(thd, path, new_db, tmp_name,0);
+    table=open_temporary_table(thd, path, new_db, tmp_name, 0,
+                               create_info->options & HA_LEX_CREATE_TMP_TABLE);
     if (table)
     {
       intern_close_table(table);

--- 1.76/sql/table.h	2007-04-28 14:31:59 +02:00
+++ 1.77/sql/table.h	2007-04-28 14:31:59 +02:00
@@ -208,6 +208,8 @@ struct st_table {
   uint          derived_select_number;
   THD		*in_use;		/* Which thread uses this */
   struct st_table *next,*prev;
+  struct st_table *mrg_parent;          /* Parent MERGE table. */
+  struct st_table *mrg_child;           /* List of MERGE child tables. */
 };
 
 

--- 1.46/mysql-test/r/merge.result	2007-04-28 14:31:59 +02:00
+++ 1.47/mysql-test/r/merge.result	2007-04-28 14:31:59 +02:00
@@ -779,7 +779,7 @@ ERROR HY000: Unable to open underlying t
 DROP TABLE t1, t2;
 CREATE TABLE t2(a INT) ENGINE=MERGE UNION=(t3);
 SELECT * FROM t2;
-ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+ERROR 42S02: Table 'test.t3' doesn't exist
 DROP TABLE t2;
 CREATE TABLE t1(a INT, b TEXT);
 CREATE TABLE tm1(a TEXT, b INT) ENGINE=MERGE UNION=(t1);
@@ -819,3 +819,20 @@ ALTER TABLE m1 ENGINE=MERGE UNION=(t1);
 SELECT * FROM m1;
 c1	c2	c3	c4	c5	c6	c7	c8	c9
 DROP TABLE t1, m1;
+create table t1 (c1 int, index(c1));
+create table t2 (c1 int, index(c1)) engine=merge union=(t1);
+insert into t1 values (1);
+flush tables;
+select * from t2;
+c1
+1
+flush tables;
+truncate table t1;
+insert into t1 values (1);
+flush tables;
+select * from t2;
+c1
+1
+truncate table t1;
+insert into t1 values (1);
+drop table t1,t2;

--- 1.43/mysql-test/t/merge.test	2007-04-28 14:31:59 +02:00
+++ 1.44/mysql-test/t/merge.test	2007-04-28 14:31:59 +02:00
@@ -397,7 +397,7 @@ CREATE TABLE t2(a INT) ENGINE=MERGE UNIO
 SELECT * FROM t2;
 DROP TABLE t1, t2;
 CREATE TABLE t2(a INT) ENGINE=MERGE UNION=(t3);
---error 1168
+--error 1146
 SELECT * FROM t2;
 DROP TABLE t2;
 
@@ -453,5 +453,33 @@ CREATE TABLE m1 LIKE t1;
 ALTER TABLE m1 ENGINE=MERGE UNION=(t1);
 SELECT * FROM m1;
 DROP TABLE t1, m1;
+
+#
+# Bug #8306: TRUNCATE leads to index corruption
+# Obsoleted by fix for Bug#26379 (Combination of FLUSH TABLE and
+# REPAIR TABLE corrupts a MERGE table).
+#
+create table t1 (c1 int, index(c1));
+create table t2 (c1 int, index(c1)) engine=merge union=(t1);
+insert into t1 values (1);
+# Close all tables.
+flush tables;
+# Open t2 and (implicitly) t1.
+select * from t2;
+# Truncate after flush works (unless another threads reopens t2 in between).
+flush tables;
+truncate table t1;
+insert into t1 values (1);
+# Close all tables.
+flush tables;
+# Open t2 and (implicitly) t1.
+select * from t2;
+# Truncate t1, wich was not recognized as open without the bugfix.
+# After fix for Bug#8306 and before fix for Bug#26379,
+# it should fail with a table-in-use error message, otherwise succeed.
+truncate table t1;
+# The insert used to fail on the crashed table.
+insert into t1 values (1);
+drop table t1,t2;
 
 # End of 4.1 tests

--- 1.73/mysql-test/r/myisam.result	2007-04-28 14:31:59 +02:00
+++ 1.74/mysql-test/r/myisam.result	2007-04-28 14:31:59 +02:00
@@ -593,24 +593,6 @@ select count(*) from t1 where a is null;
 count(*)
 2
 drop table t1;
-create table t1 (c1 int, index(c1));
-create table t2 (c1 int, index(c1)) engine=merge union=(t1);
-insert into t1 values (1);
-flush tables;
-select * from t2;
-c1
-1
-flush tables;
-truncate table t1;
-insert into t1 values (1);
-flush tables;
-select * from t2;
-c1
-1
-truncate table t1;
-ERROR HY000: MyISAM table 't1' is in use (most likely by a MERGE table). Try FLUSH
TABLES.
-insert into t1 values (1);
-drop table t1,t2;
 create table t1 (c1 int, c2 varchar(4) not null default '',
 key(c2(3))) default charset=utf8;
 insert into t1 values (1,'A'), (2, 'B'), (3, 'A');

--- 1.59/mysql-test/t/myisam.test	2007-04-28 14:31:59 +02:00
+++ 1.60/mysql-test/t/myisam.test	2007-04-28 14:31:59 +02:00
@@ -573,32 +573,6 @@ select count(*) from t1 where a is null;
 drop table t1;
 
 #
-# Bug #8306: TRUNCATE leads to index corruption 
-#
-create table t1 (c1 int, index(c1));
-create table t2 (c1 int, index(c1)) engine=merge union=(t1);
-insert into t1 values (1);
-# Close all tables.
-flush tables;
-# Open t2 and (implicitly) t1.
-select * from t2;
-# Truncate after flush works (unless another threads reopens t2 in between).
-flush tables;
-truncate table t1;
-insert into t1 values (1);
-# Close all tables.
-flush tables;
-# Open t2 and (implicitly) t1.
-select * from t2;
-# Truncate t1, wich was not recognized as open without the bugfix.
-# Now, it should fail with a table-in-use error message.
---error 1105
-truncate table t1;
-# The insert used to fail on the crashed table.
-insert into t1 values (1);
-drop table t1,t2;
-
-#
 # bug9188 - Corruption Can't open file: 'table.MYI' (errno: 145)
 #
 create table t1 (c1 int, c2 varchar(4) not null default '',
Thread
bk commit into 4.1 tree (istruewing:1.2630) BUG#26379ingo28 Apr