List:Commits« Previous MessageNext Message »
From:Ingo Struewing Date:October 15 2007 4:02pm
Subject:bk commit into 5.1 tree (istruewing:1.2576) BUG#26379
View as plain text  
Below is the list of changes that have just been committed into a local
5.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-10-15 16:02:51+02:00, istruewing@stripped +28 -0
  Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
              corrupts a MERGE table
  Bug 26867 - LOCK TABLES + REPAIR + merge table result in
              memory/cpu hogging
  Bug 26377 - Deadlock with MERGE and FLUSH TABLE
  Bug 25038 - Waiting TRUNCATE
  Bug 25700 - merge base tables get corrupted by
              optimize/analyze/repair table
  Bug 30275 - Merge tables: flush tables or unlock tables
              causes server to crash
  Bug 19627 - temporary merge table locking
  Bug 27660 - Falcon: merge table possible
  Bug 30273 - merge tables: Can't lock file (errno: 155)
  
  Not to be pushed. For intermediate review only.
  This is an implementation of a new MERGE table open approach.
  
  Still to be done:
  - Work on review comments regarding the tests.
  - Test if tampering with a child from another thread can require
    to detach (and re-attach later in reopen_tables()) the children in
    close_handle_and_leave_table_as_lock().
  - Test if another thread can break into CREATE...MERGE...SELECT
    as I need to unlock the children in create_table_from_items().
  - Test ALTER TABLE (fast + copy) within LOCK TABLES to see if I
    need to re-attach the children after reopen_table().
  - Decide what value ::reset() should return if children are
    detached.
  
  General patch description:
  
  When opening a MERGE table in open_tables() we do now add the
  child tables to the list of tables to be opened by open_tables()
  (the "query_list"). The children are not opened in the handler at
  this stage.
  
  After opening the parent, open_tables() opens each child from the
  now extended query_list. When the last child is opened, we remove
  the children from the query_list again and attach the children to
  the parent. This behaves similar to the old open. However it does
  not open the MyISAM tables directly, but grabs them from the already
  open children.
  
  When closing a MERGE table in close_thread_table() we detach the
  children only. Closing of the children is done implicitly because
  they are in thd->open_tables.
  
  For more detail see the comment at the top of ha_myisammrg.cc.
  
  Changed from open_ltable() to open_and_lock_tables() in all places
  that can be relevant for MERGE tables. The latter can handle tables
  added to the list on the fly. When open_ltable() was used in a loop
  over a list of tables, the list must be temporarily terminated
  after every table for open_and_lock_tables().
  table_list->required_type is set to FRMTYPE_TABLE to avoid open of
  special tables. Handling of derived tables is suppressed.
  These details are handled by the new function
  open_n_lock_single_table(), which has nearly the same signature as
  open_ltable() and can replace it in most cases.
  
  In reopen_tables() some of the tables open by a thread can be
  closed and reopened. When a MERGE child is affected, the parent
  must be closed and reopened too. Closing of the parent is forced
  before the first child is closed. Reopen happens in the order of
  thd->open_tables. MERGE parents do not attach their children
  automatically at open. This is done after all tables are reopened.
  So all children are open when attaching them.
  
  Special lock handling like mysql_lock_abort() or mysql_lock_remove()
  needs to be suppressed for MERGE children or forwarded to the parent.
  This depends on the situation. In loops over all open tables one
  suppresses child lock handling. When a single table is touched,
  forwarding is done.
  
  This patch changes the behavior of temporary MERGE tables.
  Temporary MERGE must have temporary children.
  The old behavior was wrong. A temporary table is not locked. Hence
  even non-temporary children were not locked. See
  Bug 19627 - temporary merge table locking.

  include/my_base.h@stripped, 2007-10-15 16:02:48+02:00, istruewing@stripped +7 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added HA_EXTRA_ATTACH_CHILDREN and HA_EXTRA_DETACH_CHILDREN.

  include/myisammrg.h@stripped, 2007-10-15 16:02:48+02:00, istruewing@stripped +9 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added element 'children_attached' to MYRG_INFO.
    Added declarations for myrg_parent_open(),
    myrg_attach_children() and myrg_detach_children()
    for the new MERGE table open approach.

  mysql-test/extra/binlog_tests/blackhole.test@stripped, 2007-10-15 16:02:49+02:00,
istruewing@stripped +9 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Preliminarily added new error message with a comment.

  mysql-test/r/merge.result@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +336
-16
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added test results.

  mysql-test/r/myisam.result@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +0
-18
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Moved test result for bug 8306 from here to merge.result.

  mysql-test/suite/binlog/r/binlog_stm_blackhole.result@stripped, 2007-10-15 16:02:49+02:00,
istruewing@stripped +1 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Fixed test result.

  mysql-test/t/delayed.test@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Exchanged error number by symbolic code.

  mysql-test/t/merge.test@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +387 -3
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Fixed test for new temporary MERGE table behavior.
    Exchanged error numbers by symbolic codes.
    Added tests. Included are tests for bugs
    8306 (moved from myisam.test), 26379, 19627, 25038, 25700, 26377,
    26867, 27660, 30275, and 30273.

  mysql-test/t/myisam.test@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +0 -26
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Moved test for bug 8306 from here to merge.test.

  sql/ha_ndbcluster_binlog.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +1
-1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added 'thd' argument to init_tmp_table_share().

  sql/handler.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +6 -5
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added 'thd' argument to init_tmp_table_share().

  sql/mysql_priv.h@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +19 -3
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added declaration for fix_merge_after_open().
    Renamed open_and_lock_tables() to open_and_lock_tables_derived()
    with additional parameter 'derived'.
    Added inline functions simple_open_n_lock_tables() and
    open_and_lock_tables(), which call open_and_lock_tables_derived()
    and add the argument for 'derived'.
    Added new function open_n_lock_single_table(), which can be used
    as an replacement for open_ltable() in most situations. Internally
    it calls simple_open_n_lock_tables() so hat it is appropriate for
    MERGE tables.
    Added 'thd' argument to init_tmp_table_share().

  sql/slave.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +2 -2
    ug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Changed from open_ltable() to open_n_lock_single_table().

  sql/sql_base.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +582 -74
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    
    Defined new functions add_merge_table_list(),
    attach_merge_children(), detach_merge_children(), and
    fix_merge_after_open() for the new MERGE table open approach.
    
    Added calls of the new functions to
    close_handle_and_leave_table_as_lock(), close_thread_tables(),
    close_thread_table(), reopen_table(), drop_locked_tables(),
    and open_tables() respectively.
    
    Prevented special lock handling of merge children (like
    mysql_lock_remove, mysql_lock_merge or mysql_lock_abort)
    at many places. Some of these calls are forwarded to the
    parent table instead.
    
    Added MERGE children to the list of unusable tables in open_table().
    Added MERGE table handling to reopen_table().
    Added code for re-attaching children in reopen_tables().
    Added code for removing the children list from the statement list
    to prepare for a repetition in open_tables().
    Added new function open_n_lock_single_table(), which can be used
    as an replacement for open_ltable() in most situations. Internally
    it calls simple_open_n_lock_tables() so hat it is appropriate for
    MERGE tables.
    Disabled use of open_ltable() for MERGE tables.
    Removed function simple_open_n_lock_tables(). It is now inline
    declared in mysql_priv.h.
    Renamed open_and_lock_tables() to open_and_lock_tables_derived()
    with additional parameter 'derived'. open_and_lock_tables() is now
    inline declared in mysql_priv.h.
    Added a check for end-of-list in two loops in lock_tables().
    Added 'thd' argument to init_tmp_table_share().

  sql/sql_insert.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +50 -2
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Changed from open_ltable() to open_n_lock_single_table() in
    handle_delayed_insert().
    Added 'thd' argument to init_tmp_table_share().
    Added MERGE handling to create_table_from_items().
    Asserted that the branch for non-temporary MERGE table in
    create_table_from_items() (CREATE...SELECT) cannot be
    used under LOCK TABLES.
    Added fix_merge_after_open() to the branch for temporary MERGE
    tables in create_table_from_items().

  sql/sql_parse.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +12 -13
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Changed from open_ltable() to open_n_lock_single_table() in
    mysql_table_dump().
    Removed condition for calling close_thread_tables() from
    dispatch_command() (in COM_QUERY after every mysql_parse() and
    after the big switch).
    Set lock_type to TL_WRITE in check_merge_table_access().

  sql/sql_partition.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Fixed comment typo.

  sql/sql_select.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +1 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added 'thd' argument to init_tmp_table_share().

  sql/sql_table.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +24 -2
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Changed from open_ltable() to open_n_lock_single_table() in
    mysql_alter_table() and mysql_checksum_table().
    Initialized table_list->table in mysql_recreate_table().

  sql/table.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +8 -2
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added 'thd' argument to init_tmp_table_share().
    Setting table_map_id from query_id in init_tmp_table_share().

  sql/table.h@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +11 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added elements for MERGE tables to TABLE and TABLE_LIST.

  storage/myisam/ha_myisam.cc@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped
+27 -0
    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.
    Added an unrelated comment to the function comment of table2myisam().

  storage/myisam/ha_myisam.h@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped +4
-0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Declared new function myisam_engine_handle() as friend of
    class ha_myisam.

  storage/myisammrg/ha_myisammrg.cc@stripped, 2007-10-15 16:02:49+02:00,
istruewing@stripped +555 -90
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added callback functions to support parent open and children attach
    of MERGE tables.
    Changed ha_myisammrg::open() to initialize storage engine structures
    and create a list of child tables only. Child tables are not opened.
    Added ha_myisammrg::attach_children(), which does now the main part
    of MERGE open.
    Added ha_myisammrg::detach_children().
    Added calls to ::attach_children() and ::detach_children() to
    ::extra() on HA_EXTRA_ATTACH_CHILDREN and HA_EXTRA_DETACH_CHILDREN
    respectively.
    Added a check for matching TEMPORARY type for children against
    parent.
    Added support for thd->open_options to attach_children().
    Changed child path name generation for temporary tables so that
    it does nothing special for temporary tables.

  storage/myisammrg/ha_myisammrg.h@stripped, 2007-10-15 16:02:49+02:00,
istruewing@stripped +8 -1
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added elements to class ha_myisammrg to support the new
    open approach.
    Changed empty destructor definition to a declaration.
    Implemented in ha_myisammrg.cc.
    Added declaration for methods attach_children() and
    detach_children().
    Added definition for method table_ptr() for use with
    callback functions.

  storage/myisammrg/myrg_close.c@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped
+27 -3
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Added a check to avoid closing of MyISAM tables when the
    child tables are not attached.
    Added freeing of rec_per_key_part when the child tables
    are not attached.

  storage/myisammrg/myrg_extra.c@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped
+4 -0
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    Some ::extra() functions and ::reset() can be called when
    children are detached.

  storage/myisammrg/myrg_open.c@stripped, 2007-10-15 16:02:49+02:00, istruewing@stripped
+308 -7
    Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE
                corrupts a MERGE table
    
    Kept old myrg_open() for MERGE use independent from MySQL.
    Removed an always true condition in myrg_open().
    Set children_attached for independent MERGE use in myrg_open().
    
    Added myrg_parent_open(), myrg_attach_children, and
    myrg_detach_children() for the new MERGE table open approach.

diff -Nrup a/include/my_base.h b/include/my_base.h
--- a/include/my_base.h	2007-07-25 00:58:05 +02:00
+++ b/include/my_base.h	2007-10-15 16:02:48 +02:00
@@ -187,7 +187,13 @@ enum ha_extra_function {
     Inform handler that an "INSERT...ON DUPLICATE KEY UPDATE" will be
     executed. This condition is unset by HA_EXTRA_NO_IGNORE_DUP_KEY.
   */
-  HA_EXTRA_INSERT_WITH_UPDATE
+  HA_EXTRA_INSERT_WITH_UPDATE,
+  /*
+    Orders MERGE handler to attach or detach its child tables. Used at
+    begin and end of a statement.
+  */
+  HA_EXTRA_ATTACH_CHILDREN,
+  HA_EXTRA_DETACH_CHILDREN
 };
 
 	/* The following is parameter to ha_panic() */
diff -Nrup a/include/myisammrg.h b/include/myisammrg.h
--- a/include/myisammrg.h	2007-05-10 11:59:24 +02:00
+++ b/include/myisammrg.h	2007-10-15 16:02:48 +02:00
@@ -69,6 +69,8 @@ typedef struct st_myrg_info
   uint	 merge_insert_method;
   uint	 tables,options,reclength,keys;
   my_bool cache_in_use;
+  /* If MERGE children attached to parent. See top comment in ha_myisammrg.cc */
+  my_bool children_attached;
   LIST	 open_list;
   QUEUE  by_key;
   ulong *rec_per_key_part;			/* for sql optimizing */
@@ -80,6 +82,13 @@ typedef struct st_myrg_info
 extern int myrg_close(MYRG_INFO *file);
 extern int myrg_delete(MYRG_INFO *file,const uchar *buff);
 extern MYRG_INFO *myrg_open(const char *name,int mode,int wait_if_locked);
+extern MYRG_INFO *myrg_parent_open(const char *parent_name,
+                                   int (*callback)(void*, const char*),
+                                   void *callback_param);
+extern int myrg_attach_children(MYRG_INFO *m_info, int handle_locking,
+                                MI_INFO *(*callback)(void*),
+                                void *callback_param);
+extern int myrg_detach_children(MYRG_INFO *m_info);
 extern int myrg_panic(enum ha_panic_function function);
 extern int myrg_rfirst(MYRG_INFO *file,uchar *buf,int inx);
 extern int myrg_rlast(MYRG_INFO *file,uchar *buf,int inx);
diff -Nrup a/mysql-test/extra/binlog_tests/blackhole.test
b/mysql-test/extra/binlog_tests/blackhole.test
--- a/mysql-test/extra/binlog_tests/blackhole.test	2007-06-15 18:35:09 +02:00
+++ b/mysql-test/extra/binlog_tests/blackhole.test	2007-10-15 16:02:49 +02:00
@@ -134,6 +134,15 @@ drop table t1,t2,t3;
 #             table
 #
 CREATE TABLE t1(a INT) ENGINE=BLACKHOLE;
+# NOTE: After exchanging open_ltable() by open_and_lock_tables() in
+# handle_delayed_insert() to fix problems with MERGE tables (Bug#26379),
+# problems with INSERT DELAYED and BLACKHOLE popped up. open_ltable()
+# does not check if the binlogging capabilities of the statement and the
+# table match. So the below used to succeed. But since INSERT DELAYED
+# switches to row-based logging in mixed-mode and BLACKHOLE cannot do
+# row-based logging, it could not really work. Until this problem is
+# correctly fixed, we have that error here.
+--error ER_BINLOG_LOGGING_IMPOSSIBLE
 INSERT DELAYED INTO t1 VALUES(1);
 DROP TABLE t1;
 
diff -Nrup a/mysql-test/r/merge.result b/mysql-test/r/merge.result
--- a/mysql-test/r/merge.result	2007-10-09 14:18:07 +02:00
+++ b/mysql-test/r/merge.result	2007-10-15 16:02:49 +02:00
@@ -584,9 +584,7 @@ insert into t1 values (1);
 insert into t2 values (2);
 create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
 select * from t3;
-a
-1
-2
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
 create temporary table t4 (a int not null);
 create temporary table t5 (a int not null);
 insert into t4 values (1);
@@ -597,6 +595,39 @@ a
 1
 2
 drop table t6, t3, t1, t2, t4, t5;
+create temporary table t1 (a int not null);
+create temporary table t2 (a int not null);
+insert into t1 values (1);
+insert into t2 values (2);
+create table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+select * from t3;
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+drop table t3, t2, t1;
+create table t1 (a int not null);
+create temporary table t2 (a int not null);
+insert into t1 values (1);
+insert into t2 values (2);
+create table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+select * from t3;
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+drop table t3;
+create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+select * from t3;
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+drop table t3, t2, t1;
+CREATE TEMPORARY TABLE t1 (c1 INT NOT NULL);
+CREATE TEMPORARY TABLE t2 (c1 INT NOT NULL);
+CREATE TABLE t3 (c1 INT NOT NULL);
+INSERT INTO t3 VALUES (1), (2);
+LOCK TABLES t3 READ;
+CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL) ENGINE=MERGE UNION=(t1,t2)
+INSERT_METHOD=LAST SELECT * FROM t3;
+SELECT * FROM t4;
+c1
+1
+2
+UNLOCK TABLES;
+DROP TABLE t4, t3, t2, t1;
 CREATE TABLE t1 (
 fileset_id tinyint(3) unsigned NOT NULL default '0',
 file_code varchar(32) NOT NULL default '',
@@ -621,7 +652,7 @@ id	select_type	table	type	possible_keys	
 EXPLAIN SELECT * FROM t2 WHERE fileset_id = 2
 AND file_code BETWEEN '0000000115' AND '0000000120' LIMIT 1;
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	SIMPLE	t2	range	PRIMARY,files	PRIMARY	35	NULL	5	Using where
+1	SIMPLE	t2	ref	PRIMARY,files	files	1	const	9	Using where
 EXPLAIN SELECT * FROM t1 WHERE fileset_id = 2
 AND file_code BETWEEN '0000000115' AND '0000000120' LIMIT 1;
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
@@ -708,7 +739,7 @@ a	b	c
 1	1	0
 show index from t3;
 Table	Non_unique	Key_name	Seq_in_index	Column_name	Collation	Cardinality	Sub_part	Packed	Null	Index_type	Comment
-t3	1	a	1	a	A	NULL	NULL	NULL	YES	BTREE	
+t3	1	a	1	a	A	0	NULL	NULL	YES	BTREE	
 t3	1	a	2	b	A	NULL	NULL	NULL	YES	BTREE	
 t3	1	a	3	c	A	NULL	NULL	NULL	YES	BTREE	
 drop table t1, t2, t3;
@@ -784,7 +815,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);
@@ -849,21 +880,18 @@ drop table t2;
 drop table t1;
 CREATE TABLE tm1(a INT) ENGINE=MERGE UNION=(t1, t2);
 SELECT * FROM tm1;
-ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+ERROR 42S02: Table 'test.t1' doesn't exist
 CHECK TABLE tm1;
 Table	Op	Msg_type	Msg_text
-test.tm1	check	Error	Table 'test.t1' is differently defined or of non-MyISAM type or
doesn't exist
-test.tm1	check	Error	Table 'test.t2' is differently defined or of non-MyISAM type or
doesn't exist
-test.tm1	check	Error	Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
-test.tm1	check	error	Corrupt
+test.tm1	check	Error	Table 'test.t1' doesn't exist
+test.tm1	check	status	OK
 CREATE TABLE t1(a INT);
 SELECT * FROM tm1;
-ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+ERROR 42S02: Table 'test.t2' doesn't exist
 CHECK TABLE tm1;
 Table	Op	Msg_type	Msg_text
-test.tm1	check	Error	Table 'test.t2' is differently defined or of non-MyISAM type or
doesn't exist
-test.tm1	check	Error	Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
-test.tm1	check	error	Corrupt
+test.tm1	check	Error	Table 'test.t2' doesn't exist
+test.tm1	check	status	OK
 CREATE TABLE t2(a BLOB);
 SELECT * FROM tm1;
 ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
@@ -871,7 +899,7 @@ CHECK TABLE tm1;
 Table	Op	Msg_type	Msg_text
 test.tm1	check	Error	Table 'test.t2' is differently defined or of non-MyISAM type or
doesn't exist
 test.tm1	check	Error	Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
-test.tm1	check	error	Corrupt
+test.tm1	check	status	OK
 ALTER TABLE t2 MODIFY a INT;
 SELECT * FROM tm1;
 a
@@ -885,3 +913,295 @@ CREATE TABLE IF NOT EXISTS t1 SELECT * F
 ERROR HY000: You can't specify target table 't1' for update in FROM clause
 DROP TABLE t1, t2;
 End of 5.0 tests
+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;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+REPAIR TABLE t1;
+INSERT INTO t2 VALUES (1);
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+DROP TABLE t1, t2;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+LOCK TABLE t1 WRITE;
+INSERT INTO t2 VALUES (1);
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+LOCK TABLE t1 WRITE;
+INSERT INTO t1 VALUES (1);
+FLUSH TABLES;
+FLUSH TABLES;
+SELECT * FROM t1;
+c1
+UNLOCK TABLES;
+DROP TABLE t1;
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT);
+INSERT INTO t3 VALUES (1), (2);
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+INSERT_METHOD=LAST SELECT * FROM t3;
+SHOW CREATE TABLE t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `c1` int(11) DEFAULT NULL
+) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=LAST UNION=(`t1`,`t2`)
+SELECT * FROM t4;
+c1
+1
+2
+DROP TABLE t4;
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+INSERT_METHOD=FIRST SELECT * FROM t3;
+SHOW CREATE TABLE t4;
+Table	Create Table
+t4	CREATE TABLE `t4` (
+  `c1` int(11) DEFAULT NULL
+) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 INSERT_METHOD=FIRST UNION=(`t1`,`t2`)
+SELECT * FROM t4;
+c1
+1
+2
+1
+2
+DROP TABLE t4;
+LOCK TABLES t1 WRITE, t2 WRITE, t3 READ;
+CREATE TABLE t4 (c1 INT);
+DROP TABLE t4;
+CREATE TABLE t4 (c1 INT) SELECT * FROM t3;
+ERROR HY000: Table 't4' was not locked with LOCK TABLES
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+INSERT_METHOD=LAST SELECT * FROM t3;
+ERROR HY000: Table 't4' was not locked with LOCK TABLES
+UNLOCK TABLES;
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2);
+LOCK TABLES t4 READ;
+SELECT * from t1, t4;
+ERROR HY000: Table 't1' was not locked with LOCK TABLES
+DROP TABLE t4;
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t3 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1, t2);
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+SELECT * FROM t3;
+c1
+1
+2
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+c1
+2
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (id INTEGER, grp TINYINT, id_rev INTEGER);
+SET @rnd_max= 2147483647;
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+SET @rnd= RAND();
+SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+SET @id_rev= @rnd_max - @id;
+SET @grp= CAST(127.0 * @rnd AS UNSIGNED);
+INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev);
+set @@read_buffer_size=2*1024*1024;
+CREATE TABLE t2 SELECT * FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+INSERT INTO t2 (id, grp, id_rev) SELECT id, grp, id_rev FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+INSERT INTO t2 (id, grp, id_rev) SELECT id, grp, id_rev FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+CREATE TABLE t3 (id INTEGER, grp TINYINT, id_rev INTEGER)
+ENGINE= MRG_MYISAM UNION= (t1, t2);
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+130
+SELECT COUNT(*) FROM t2;
+COUNT(*)
+80
+SELECT COUNT(*) FROM t3;
+COUNT(*)
+210
+SELECT COUNT(DISTINCT a1.id) FROM t3 AS a1, t3 AS a2
+WHERE a1.id = a2.id GROUP BY a2.grp;
+TRUNCATE TABLE t1;
+SELECT COUNT(*) FROM t1;
+COUNT(*)
+0
+SELECT COUNT(*) FROM t2;
+COUNT(*)
+80
+SELECT COUNT(*) FROM t3;
+COUNT(*)
+80
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+INSERT INTO t2 VALUES (1);
+SELECT * FROM t2;
+c1
+1
+LOCK TABLES t2 WRITE, t1 WRITE;
+FLUSH TABLES;
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+CHECK TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	check	status	OK
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+UNLOCK TABLES;
+CHECK TABLE t1 EXTENDED;
+Table	Op	Msg_type	Msg_text
+test.t1	check	status	OK
+LOCK TABLES t2 WRITE, t1 WRITE;
+SELECT * FROM t2;
+c1
+1
+LOCK TABLES t2 WRITE, t1 WRITE;
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+CHECK TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	check	status	OK
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+UNLOCK TABLES;
+CHECK TABLE t1 EXTENDED;
+Table	Op	Msg_type	Msg_text
+test.t1	check	status	OK
+DROP TABLE t1, t2;
+CREATE TABLE t1 ( a INT ) ENGINE=MyISAM;
+CREATE TABLE m1 ( a INT ) ENGINE=MRG_MYISAM UNION=(t1);
+LOCK TABLES t1 WRITE, m1 WRITE;
+FLUSH TABLE t1;
+UNLOCK TABLES;
+DROP TABLE m1, t1;
+CREATE TABLE t1 ( a INT ) ENGINE=MyISAM;
+CREATE TABLE m1 ( a INT ) ENGINE=MRG_MYISAM UNION=(t1);
+LOCK TABLES m1 WRITE, t1 WRITE;
+FLUSH TABLE t1;
+UNLOCK TABLES;
+DROP TABLE m1, t1;
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+LOCK TABLE t1 WRITE;
+INSERT INTO t2 VALUES (1);
+REPAIR TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	repair	status	OK
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+CREATE TABLE t1 (c1 INT, c2 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT, c2 INT) ENGINE= MyISAM;
+CREATE TABLE t3 (c1 INT, c2 INT) ENGINE= MRG_MYISAM UNION(t1, t2);
+INSERT INTO t1 VALUES (1, 1);
+INSERT INTO t2 VALUES (2, 2);
+SELECT * FROM t3;
+c1	c2
+1	1
+2	2
+ALTER TABLE t1 ENGINE= MEMORY;
+INSERT INTO t1 VALUES (0, 0);
+SELECT * FROM t3;
+ERROR HY000: Unable to open underlying table which is differently defined or of
non-MyISAM type or doesn't exist
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (c1 INT, KEY(c1));
+CREATE TABLE t2 (c1 INT, KEY(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+INSERT_METHOD=FIRST;
+LOCK TABLE t1 WRITE, t2 WRITE;
+FLUSH TABLES t2, t1;
+OPTIMIZE TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	optimize	status	Table is already up to date
+FLUSH TABLES t1;
+UNLOCK TABLES;
+FLUSH TABLES;
+INSERT INTO t1 VALUES (1);
+LOCK TABLE t1 WRITE, t2 WRITE;
+FLUSH TABLES t2, t1;
+OPTIMIZE TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	optimize	status	OK
+FLUSH TABLES t1;
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+CREATE TABLE t1 (ID INT) ENGINE=MYISAM;
+CREATE TABLE m1 (ID INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=FIRST;
+INSERT INTO t1 VALUES ();
+INSERT INTO m1 VALUES ();
+LOCK TABLE t1 WRITE, m1 WRITE;
+FLUSH TABLES m1, t1;
+OPTIMIZE TABLE t1;
+Table	Op	Msg_type	Msg_text
+test.t1	optimize	status	OK
+FLUSH TABLES m1, t1;
+UNLOCK TABLES;
+DROP TABLE t1, m1;
diff -Nrup a/mysql-test/r/myisam.result b/mysql-test/r/myisam.result
--- a/mysql-test/r/myisam.result	2007-05-29 14:57:33 +02:00
+++ b/mysql-test/r/myisam.result	2007-10-15 16:02:49 +02:00
@@ -606,24 +606,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');
diff -Nrup a/mysql-test/suite/binlog/r/binlog_stm_blackhole.result
b/mysql-test/suite/binlog/r/binlog_stm_blackhole.result
--- a/mysql-test/suite/binlog/r/binlog_stm_blackhole.result	2007-06-27 14:27:27 +02:00
+++ b/mysql-test/suite/binlog/r/binlog_stm_blackhole.result	2007-10-15 16:02:49 +02:00
@@ -124,6 +124,7 @@ master-bin.000001	#	Query	#	#	use `test`
 drop table t1,t2,t3;
 CREATE TABLE t1(a INT) ENGINE=BLACKHOLE;
 INSERT DELAYED INTO t1 VALUES(1);
+ERROR HY000: Binary logging not possible. Message: Row-based format required for this
statement, but not allowed by this combination of engines
 DROP TABLE t1;
 CREATE TABLE t1(a INT, b INT) ENGINE=BLACKHOLE;
 DELETE FROM t1 WHERE a=10;
diff -Nrup a/mysql-test/t/delayed.test b/mysql-test/t/delayed.test
--- a/mysql-test/t/delayed.test	2007-06-06 19:48:06 +02:00
+++ b/mysql-test/t/delayed.test	2007-10-15 16:02:49 +02:00
@@ -248,7 +248,7 @@ DROP TABLE t1;
 #
 CREATE TABLE t1(c1 INT) ENGINE=MyISAM;
 CREATE TABLE t2(c1 INT) ENGINE=MERGE UNION=(t1);
---error 1031
+--error ER_ILLEGAL_HA
 INSERT DELAYED INTO t2 VALUES(1);
 DROP TABLE t1, t2;
 
diff -Nrup a/mysql-test/t/merge.test b/mysql-test/t/merge.test
--- a/mysql-test/t/merge.test	2007-10-09 14:18:07 +02:00
+++ b/mysql-test/t/merge.test	2007-10-15 16:02:49 +02:00
@@ -221,6 +221,7 @@ create table t2 (a int not null);
 insert into t1 values (1);
 insert into t2 values (2);
 create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+--error ER_WRONG_MRG_TABLE
 select * from t3;
 create temporary table t4 (a int not null);
 create temporary table t5 (a int not null);
@@ -229,6 +230,43 @@ insert into t5 values (2);
 create temporary table t6 (a int not null) ENGINE=MERGE UNION=(t4,t5);
 select * from t6;
 drop table t6, t3, t1, t2, t4, t5;
+#
+# Bug#19627 - temporary merge table locking
+# MERGE table and its children must match in temporary type.
+# Forbid temporary merge on non-temporary children: shown above.
+# Forbid non-temporary merge on temporary children:
+create temporary table t1 (a int not null);
+create temporary table t2 (a int not null);
+insert into t1 values (1);
+insert into t2 values (2);
+create table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+--error ER_WRONG_MRG_TABLE
+select * from t3;
+drop table t3, t2, t1;
+# Forbid children mismatch in temporary:
+create table t1 (a int not null);
+create temporary table t2 (a int not null);
+insert into t1 values (1);
+insert into t2 values (2);
+create table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+--error ER_WRONG_MRG_TABLE
+select * from t3;
+drop table t3;
+create temporary table t3 (a int not null) ENGINE=MERGE UNION=(t1,t2);
+--error ER_WRONG_MRG_TABLE
+select * from t3;
+drop table t3, t2, t1;
+# Show CREATE...SELECT under LOCK TABLES:
+CREATE TEMPORARY TABLE t1 (c1 INT NOT NULL);
+CREATE TEMPORARY TABLE t2 (c1 INT NOT NULL);
+CREATE TABLE t3 (c1 INT NOT NULL);
+INSERT INTO t3 VALUES (1), (2);
+LOCK TABLES t3 READ;
+CREATE TEMPORARY TABLE t4 (c1 INT NOT NULL) ENGINE=MERGE UNION=(t1,t2)
+  INSERT_METHOD=LAST SELECT * FROM t3;
+SELECT * FROM t4;
+UNLOCK TABLES;
+DROP TABLE t4, t3, t2, t1;
 
 #
 # testing merge::records_in_range and optimizer
@@ -403,7 +441,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 ER_NO_SUCH_TABLE
 SELECT * FROM t2;
 DROP TABLE t2;
 
@@ -495,11 +533,11 @@ drop table t1;
 #             CREATE TABLE fails
 #
 CREATE TABLE tm1(a INT) ENGINE=MERGE UNION=(t1, t2);
---error 1168
+--error ER_NO_SUCH_TABLE
 SELECT * FROM tm1;
 CHECK TABLE tm1;
 CREATE TABLE t1(a INT);
---error 1168
+--error ER_NO_SUCH_TABLE
 SELECT * FROM tm1;
 CHECK TABLE tm1;
 CREATE TABLE t2(a BLOB);
@@ -526,3 +564,349 @@ CREATE TABLE IF NOT EXISTS t1 SELECT * F
 DROP TABLE t1, t2;
 
 --echo End of 5.0 tests
+
+#
+# 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.
+# 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;
+
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Preparation
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+connection default;
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Problem #1
+# A thread trying to lock a MERGE table performed busy waiting while
+# REPAIR TABLE or a similar table administration task was ongoing on one or
+# more of its MyISAM tables.
+# To allow for observability it was necessary to enter a multi-second sleep
+# in mysql_admin_table() after remove_table_from_cache(), which comes after
+# mysql_abort_lock(). The sleep faked a long running operation. One could
+# watch a high CPU load during the sleep time.
+# The problem was that mysql_abort_lock() upgrades the write lock to
+# TL_WRITE_ONLY. This lock type persisted until the final unlock at the end
+# of the administration task. The effect of TL_WRITE_ONLY is to reject any
+# attempt to lock the table. The trying thread must close the table and wait
+# until it is no longer used. Unfortunately there is no way to detect that
+# one of the MyISAM tables of a MERGE table is in use. When trying to lock
+# the MERGE table, all MyISAM tables are locked. If one fails on
+# TL_WRITE_ONLY, all locks are aborted and wait_for_tables() is entered.
+# But this doesn't see the MERGE table as used, so it seems appropriate to
+# retry a lock...
+#
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+send REPAIR TABLE t1;
+    connection con1;
+    sleep 1; # let repair run into its sleep
+    INSERT INTO t2 VALUES (1);
+connection default;
+reap;
+DROP TABLE t1, t2;
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Problem #2
+# A thread trying to lock a MERGE table performed 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 did
+# UNLOCK TABLES.
+# The difference against problem #1 is that the busy waiting took place
+# *after* the administration task. It was terminated by UNLOCK TABLES only.
+#
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+LOCK TABLE t1 WRITE;
+    connection con1;
+    send INSERT INTO t2 VALUES (1);
+connection default;
+sleep 1; # Let INSERT go into thr_multi_lock().
+REPAIR TABLE t1;
+sleep 2; # con1 performs busy waiting during this sleep.
+UNLOCK TABLES;
+    connection con1;
+    reap;
+connection default;
+DROP TABLE t1, t2;
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Problem #3
+# Two FLUSH TABLES within a LOCK TABLES segment could invalidate the lock.
+# This did *not* require a MERGE table.
+# To increase reproducibility it was necessary to enter a sleep of 2 seconds
+# at the end of wait_for_tables() after unlock of LOCK_open. In 5.0 and 5.1
+# the sleep must be inserted in open_and_lock_tables() after open_tables()
+# instead. wait_for_tables() is not used in this case.
+# The problem was that FLUSH TABLES releases LOCK_open while having unlocked
+# and closed all tables. When this happened while a thread was in the loop in
+# mysql_lock_tables() right after wait_for_tables() and before retrying to
+# lock, the thread got the lock.  (Translate to similar code places in 5.0
+# and 5.1). And it did not notice that the table needed a refresh. So it
+# executed its statement on the table.
+# The first FLUSH TABLES kicked the INSERT out of thr_multi_lock() and let
+# it wait in wait_for_tables(). (open_table() in 5.0 and 5.1). The second
+# FLUSH TABLES must happen while the INSERT was on its way from
+# wait_for_tables() to the next call of thr_multi_lock(). This needed to be
+# supported by a sleep to make it repeatable.
+#
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+LOCK TABLE t1 WRITE;
+    connection con1;
+    send INSERT INTO t1 VALUES (1);
+connection default;
+sleep 1; # Let INSERT go into thr_multi_lock().
+FLUSH TABLES;
+sleep 1; # Let INSERT go through wait_for_tables() where it sleeps.
+FLUSH TABLES;
+# This should give no result. But it will with sleep(2) at the right place.
+SELECT * FROM t1;
+UNLOCK TABLES;
+    connection con1;
+    reap;
+connection default;
+DROP TABLE t1;
+#
+# Bug#26379 - Combination of FLUSH TABLE and REPAIR TABLE corrupts a MERGE table
+# Cleanup
+disconnect con1;
+disconnect con2;
+#
+# Extra test for Bug#26379.
+# Test usage of reopen_name_locked_table() in CREATE ... SELECT
+#
+CREATE TABLE t1 (c1 INT);
+CREATE TABLE t2 (c1 INT);
+CREATE TABLE t3 (c1 INT);
+INSERT INTO t3 VALUES (1), (2);
+# Show that LAST table is opened.
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+  INSERT_METHOD=LAST SELECT * FROM t3;
+SHOW CREATE TABLE t4;
+SELECT * FROM t4;
+DROP TABLE t4;
+# Show that FIRST table is opened.
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+  INSERT_METHOD=FIRST SELECT * FROM t3;
+SHOW CREATE TABLE t4;
+SELECT * FROM t4;
+DROP TABLE t4;
+LOCK TABLES t1 WRITE, t2 WRITE, t3 READ;
+CREATE TABLE t4 (c1 INT);
+DROP TABLE t4;
+--error ER_TABLE_NOT_LOCKED
+CREATE TABLE t4 (c1 INT) SELECT * FROM t3;
+--error ER_TABLE_NOT_LOCKED
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2)
+  INSERT_METHOD=LAST SELECT * FROM t3;
+UNLOCK TABLES;
+CREATE TABLE t4 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1,t2);
+LOCK TABLES t4 READ;
+--error ER_TABLE_NOT_LOCKED
+SELECT * from t1, t4;
+# Show that dropping a lcked MERGE table works.
+DROP TABLE t4;
+UNLOCK TABLES;
+DROP TABLE t1, t2, t3;
+
+#
+# Bug#25038 - Waiting TRUNCATE
+#
+# Show that truncate of child table after use of parent table works.
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM; 
+CREATE TABLE t2 (c1 INT) ENGINE= MyISAM; 
+CREATE TABLE t3 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1, t2); 
+INSERT INTO t1 VALUES (1);
+INSERT INTO t2 VALUES (2);
+SELECT * FROM t3;
+TRUNCATE TABLE t1;
+SELECT * FROM t3;
+DROP TABLE t1, t2, t3;
+#
+# Show that truncate of child table waits while parent table is used.
+# (test partly borrowed from count_distinct3.)
+CREATE TABLE t1 (id INTEGER, grp TINYINT, id_rev INTEGER);
+SET @rnd_max= 2147483647;
+let $1 = 10;
+while ($1)
+{
+  SET @rnd= RAND();
+  SET @id = CAST(@rnd * @rnd_max AS UNSIGNED);
+  SET @id_rev= @rnd_max - @id;
+  SET @grp= CAST(127.0 * @rnd AS UNSIGNED); 
+  INSERT INTO t1 (id, grp, id_rev) VALUES (@id, @grp, @id_rev); 
+  dec $1;
+}
+set @@read_buffer_size=2*1024*1024;
+CREATE TABLE t2 SELECT * FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+INSERT INTO t2 (id, grp, id_rev) SELECT id, grp, id_rev FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+INSERT INTO t2 (id, grp, id_rev) SELECT id, grp, id_rev FROM t1;
+INSERT INTO t1 (id, grp, id_rev) SELECT id, grp, id_rev FROM t2;
+CREATE TABLE t3 (id INTEGER, grp TINYINT, id_rev INTEGER)
+  ENGINE= MRG_MYISAM UNION= (t1, t2);
+SELECT COUNT(*) FROM t1;
+SELECT COUNT(*) FROM t2;
+SELECT COUNT(*) FROM t3;
+connect (con1,localhost,root,,);
+    # As t3 contains random numbers, results are different from test to test. 
+    # That's okay, because we test only that select doesn't yield an
+    # error. Note, that --disable_result_log doesn't suppress error output.
+    --disable_result_log
+    send SELECT COUNT(DISTINCT a1.id) FROM t3 AS a1, t3 AS a2
+      WHERE a1.id = a2.id GROUP BY a2.grp;
+connection default;
+sleep 1;
+TRUNCATE TABLE t1;
+    connection con1;
+    reap;
+    --enable_result_log
+    disconnect con1;
+connection default;
+SELECT COUNT(*) FROM t1;
+SELECT COUNT(*) FROM t2;
+SELECT COUNT(*) FROM t3;
+DROP TABLE t1, t2, t3;
+
+#
+# Bug#25700 - merge base tables get corrupted by optimize/analyze/repair table
+#
+# Using FLUSH TABLES before REPAIR.
+CREATE TABLE t1 (c1 INT) ENGINE=MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=LAST;
+INSERT INTO t2 VALUES (1);
+SELECT * FROM t2;
+LOCK TABLES t2 WRITE, t1 WRITE;
+FLUSH TABLES;
+REPAIR TABLE t1;
+CHECK TABLE t1;
+REPAIR TABLE t1;
+UNLOCK TABLES;
+CHECK TABLE t1 EXTENDED;
+#
+# Not using FLUSH TABLES before REPAIR.
+LOCK TABLES t2 WRITE, t1 WRITE;
+SELECT * FROM t2;
+LOCK TABLES t2 WRITE, t1 WRITE;
+REPAIR TABLE t1;
+CHECK TABLE t1;
+REPAIR TABLE t1;
+UNLOCK TABLES;
+CHECK TABLE t1 EXTENDED;
+DROP TABLE t1, t2;
+
+#
+# Bug#26377 - Deadlock with MERGE and FLUSH TABLE
+#
+CREATE TABLE t1 ( a INT ) ENGINE=MyISAM;
+CREATE TABLE m1 ( a INT ) ENGINE=MRG_MYISAM UNION=(t1);
+# Lock t1 first. This did always work.
+LOCK TABLES t1 WRITE, m1 WRITE;
+FLUSH TABLE t1;
+UNLOCK TABLES;
+DROP TABLE m1, t1;
+#
+CREATE TABLE t1 ( a INT ) ENGINE=MyISAM;
+CREATE TABLE m1 ( a INT ) ENGINE=MRG_MYISAM UNION=(t1);
+# Lock m1 first. This did deadlock.
+LOCK TABLES m1 WRITE, t1 WRITE;
+FLUSH TABLE t1;
+UNLOCK TABLES;
+DROP TABLE m1, t1;
+
+#
+# Bug#26867 - LOCK TABLES + REPAIR + merge table result in memory/cpu hogging
+#
+CREATE TABLE t1 (c1 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT) ENGINE= MRG_MYISAM UNION= (t1) INSERT_METHOD= LAST;
+LOCK TABLE t1 WRITE;
+    connect (con1,localhost,root,,);
+    send INSERT INTO t2 VALUES (1);
+connection default;
+sleep 1; # Let INSERT go into thr_multi_lock().
+REPAIR TABLE t1;
+sleep 2; # con1 performs busy waiting during this sleep.
+UNLOCK TABLES;
+    connection con1;
+    reap;
+    disconnect con1;
+connection default;
+DROP TABLE t1, t2;
+
+#
+# Bug#27660 - Falcon: merge table possible
+#
+# Normal MyISAM MERGE operation.
+CREATE TABLE t1 (c1 INT, c2 INT) ENGINE= MyISAM;
+CREATE TABLE t2 (c1 INT, c2 INT) ENGINE= MyISAM;
+CREATE TABLE t3 (c1 INT, c2 INT) ENGINE= MRG_MYISAM UNION(t1, t2);
+INSERT INTO t1 VALUES (1, 1);
+INSERT INTO t2 VALUES (2, 2);
+SELECT * FROM t3;
+# Try an unsupported engine.
+ALTER TABLE t1 ENGINE= MEMORY;
+INSERT INTO t1 VALUES (0, 0);
+# Before fixing, this succeeded, but (0, 0) was missing.
+--error 1168
+SELECT * FROM t3;
+DROP TABLE t1, t2, t3;
+
+#
+# Bug#30275 - Merge tables: flush tables or unlock tables causes server to crash
+#
+CREATE TABLE t1 (c1 INT, KEY(c1));
+CREATE TABLE t2 (c1 INT, KEY(c1)) ENGINE=MRG_MYISAM UNION=(t1)
+  INSERT_METHOD=FIRST;
+LOCK TABLE t1 WRITE, t2 WRITE;
+FLUSH TABLES t2, t1;
+OPTIMIZE TABLE t1;
+FLUSH TABLES t1;
+UNLOCK TABLES;
+#
+FLUSH TABLES;
+INSERT INTO t1 VALUES (1);
+LOCK TABLE t1 WRITE, t2 WRITE;
+FLUSH TABLES t2, t1;
+OPTIMIZE TABLE t1;
+FLUSH TABLES t1;
+UNLOCK TABLES;
+DROP TABLE t1, t2;
+
+#
+# Test derived from test program for
+# Bug#30273 - merge tables: Can't lock file (errno: 155)
+#
+CREATE TABLE t1 (ID INT) ENGINE=MYISAM;
+CREATE TABLE m1 (ID INT) ENGINE=MRG_MYISAM UNION=(t1) INSERT_METHOD=FIRST;
+INSERT INTO t1 VALUES ();
+INSERT INTO m1 VALUES ();
+LOCK TABLE t1 WRITE, m1 WRITE;
+FLUSH TABLES m1, t1;
+OPTIMIZE TABLE t1;
+FLUSH TABLES m1, t1;
+UNLOCK TABLES;
+DROP TABLE t1, m1;
+
diff -Nrup a/mysql-test/t/myisam.test b/mysql-test/t/myisam.test
--- a/mysql-test/t/myisam.test	2007-08-29 14:46:27 +02:00
+++ b/mysql-test/t/myisam.test	2007-10-15 16:02:49 +02:00
@@ -576,32 +576,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 '',
diff -Nrup a/sql/ha_ndbcluster_binlog.cc b/sql/ha_ndbcluster_binlog.cc
--- a/sql/ha_ndbcluster_binlog.cc	2007-09-07 11:15:04 +02:00
+++ b/sql/ha_ndbcluster_binlog.cc	2007-10-15 16:02:49 +02:00
@@ -321,7 +321,7 @@ ndbcluster_binlog_open_table(THD *thd, N
   DBUG_ENTER("ndbcluster_binlog_open_table");
   
   safe_mutex_assert_owner(&LOCK_open);
-  init_tmp_table_share(table_share, share->db, 0, share->table_name, 
+  init_tmp_table_share(thd, table_share, share->db, 0, share->table_name, 
                        share->key);
   if ((error= open_table_def(thd, table_share, 0)))
   {
diff -Nrup a/sql/handler.cc b/sql/handler.cc
--- a/sql/handler.cc	2007-08-31 08:57:57 +02:00
+++ b/sql/handler.cc	2007-10-15 16:02:49 +02:00
@@ -2582,7 +2582,7 @@ int ha_create_table(THD *thd, const char
   TABLE_SHARE share;
   DBUG_ENTER("ha_create_table");
   
-  init_tmp_table_share(&share, db, 0, table_name, path);
+  init_tmp_table_share(thd, &share, db, 0, table_name, path);
   if (open_table_def(thd, &share, 0) ||
       open_table_from_share(thd, &share, "", 0, (uint) READ_ALL, 0, &table,
                             TRUE))
@@ -2649,7 +2649,7 @@ int ha_create_table_from_engine(THD* thd
   if (error)
     DBUG_RETURN(2);
 
-  init_tmp_table_share(&share, db, 0, name, path);
+  init_tmp_table_share(thd, &share, db, 0, name, path);
   if (open_table_def(thd, &share, 0))
   {
     DBUG_RETURN(3);
@@ -3655,11 +3655,12 @@ int handler::ha_reset()
 int handler::ha_write_row(uchar *buf)
 {
   int error;
+  DBUG_ENTER("handler::ha_write_row");
   if (unlikely(error= write_row(buf)))
-    return error;
+    DBUG_RETURN(error);
   if (unlikely(error= binlog_log_row<Write_rows_log_event>(table, 0, buf)))
-    return error;
-  return 0;
+    DBUG_RETURN(error);
+  DBUG_RETURN(0);
 }
 
 
diff -Nrup a/sql/mysql_priv.h b/sql/mysql_priv.h
--- a/sql/mysql_priv.h	2007-08-31 20:13:22 +02:00
+++ b/sql/mysql_priv.h	2007-10-15 16:02:49 +02:00
@@ -1142,6 +1142,8 @@ TABLE *table_cache_insert_placeholder(TH
 bool lock_table_name_if_not_cached(THD *thd, const char *db,
                                    const char *table_name, TABLE **table);
 TABLE *find_locked_table(THD *thd, const char *db,const char *table_name);
+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,bool in_refresh);
 void close_data_files_and_morph_locks(THD *thd, const char *db,
@@ -1392,8 +1394,21 @@ int init_ftfuncs(THD *thd, SELECT_LEX* s
 void wait_for_condition(THD *thd, pthread_mutex_t *mutex,
                         pthread_cond_t *cond);
 int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags);
-int simple_open_n_lock_tables(THD *thd,TABLE_LIST *tables);
-bool open_and_lock_tables(THD *thd,TABLE_LIST *tables);
+/* open_and_lock_tables with optional derived handling */
+bool open_and_lock_tables_derived(THD *thd, TABLE_LIST *tables, bool derived);
+/* simple open_and_lock_tables without derived handling */
+inline bool simple_open_n_lock_tables(THD *thd, TABLE_LIST *tables)
+{
+  return open_and_lock_tables_derived(thd, tables, FALSE);
+}
+/* open_and_lock_tables with derived handling */
+inline bool open_and_lock_tables(THD *thd, TABLE_LIST *tables)
+{
+  return open_and_lock_tables_derived(thd, tables, TRUE);
+}
+/* simple open_and_lock_tables without derived handling for single table */
+TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l,
+                                thr_lock_type lock_type);
 bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags);
 int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, bool *need_reopen);
 int decide_logging_format(THD *thd, TABLE_LIST *tables);
@@ -1969,7 +1984,8 @@ int format_number(uint inputflag,uint ma
 /* table.cc */
 TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
                                uint key_length);
-void init_tmp_table_share(TABLE_SHARE *share, const char *key, uint key_length,
+void init_tmp_table_share(THD *thd, TABLE_SHARE *share, const char *key,
+                          uint key_length,
                           const char *table_name, const char *path);
 void free_table_share(TABLE_SHARE *share);
 int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags);
diff -Nrup a/sql/slave.cc b/sql/slave.cc
--- a/sql/slave.cc	2007-08-29 23:28:34 +02:00
+++ b/sql/slave.cc	2007-10-15 16:02:49 +02:00
@@ -1013,8 +1013,8 @@ static int create_table_from_dump(THD* t
     goto err;                   // mysql_parse took care of the error send
 
   thd->proc_info = "Opening master dump table";
-  tables.lock_type = TL_WRITE;
-  if (!open_ltable(thd, &tables, TL_WRITE, 0))
+  tables.table= NULL; /* was set by mysql_rm_table(). */
+  if (!open_n_lock_single_table(thd, &tables, TL_WRITE))
   {
     sql_print_error("create_table_from_dump: could not open created table");
     goto err;
diff -Nrup a/sql/sql_base.cc b/sql/sql_base.cc
--- a/sql/sql_base.cc	2007-09-14 19:29:17 +02:00
+++ b/sql/sql_base.cc	2007-10-15 16:02:49 +02:00
@@ -671,6 +671,64 @@ TABLE_SHARE *get_cached_table_share(cons
 }  
 
 
+/**
+  @brief Detach MERGE children from the parent.
+
+  @note When closing thread tables at end of statement, both parent and
+    children are in thd->open_tables and will be closed. In most cases
+    the children will be closed before the parent. They are opened after
+    the parent and thus stacked into thd->open_tables before it.
+
+    Hence it is vital that detach does not touch the children in any
+    way.
+
+    All references to the children should be removed.
+
+  @param[in]    table           the TABLE object of the parent
+  @param[in]    clear_refs      if to clear TABLE references
+                                this must be true when called from
+                                close_thread_tables() to enable fresh
+                                open in open_tables()
+                                it must be false when called in preparation
+                                for reopen_tables()
+*/
+
+static void detach_merge_children(TABLE *table, bool clear_refs)
+{
+  TABLE_LIST *child_l;
+  DBUG_ENTER("detach_merge_children");
+  DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+                      table->s->table_name.str, (long) table));
+  DBUG_PRINT("myrg", ("MERGE parent table, detach children"));
+
+  /*
+    In a open_tables() loop it can happen that not all tables have their
+    children attached yet.
+  */
+  if (table->children_attached)
+  {
+    VOID(table->file->extra(HA_EXTRA_DETACH_CHILDREN));
+    table->children_attached= FALSE;
+  }
+
+  if (clear_refs)
+  {
+    /* Clear TABLE references to force new assignment at next open.*/
+    for (child_l= table->child_l; ; child_l= child_l->next_global)
+    {
+      /* Do not DBUG_ASSERT(child_l->table); open_tables might be incomplete. */
+      if (child_l->table)
+        child_l->table->parent= NULL;
+      child_l->table= NULL;
+      /* Break when this was the last child. */
+      if (&child_l->next_global == table->child_last_l)
+        break;
+    }
+  }
+  DBUG_VOID_RETURN;
+}
+
+
 /*
   Close file handle, but leave the table in the table cache
 
@@ -711,6 +769,12 @@ void close_handle_and_leave_table_as_loc
     share->tmp_table= INTERNAL_TMP_TABLE;       // for intern_close_table()
   }
 
+  /*
+    When closing a MERGE parent table, detach its children first.
+    Do not clear child table references to allow for reopen.
+  */
+  if (table->child_l)
+    detach_merge_children(table, FALSE);
   table->file->close();
   table->db_stat= 0;                            // Mark file closed
   release_table_share(table->s, RELEASE_NORMAL);
@@ -1116,11 +1180,21 @@ static void mark_used_tables_as_free_for
 
 void close_thread_tables(THD *thd, bool lock_in_use, bool skip_derived)
 {
+  TABLE *table;
   bool found_old_table;
   prelocked_mode_type prelocked_mode= thd->prelocked_mode;
   DBUG_ENTER("close_thread_tables");
 
   /*
+    Detach MERGE children to allow new attach at next open.
+  */
+  for (table= thd->temporary_tables; table; table= table->next)
+  {
+    if (table->child_l)
+      detach_merge_children(table, TRUE);
+  }
+
+  /*
     We are assuming here that thd->derived_tables contains ONLY derived
     tables for this substatement. i.e. instead of approach which uses
     query_id matching for determining which of the derived tables belong
@@ -1133,7 +1207,7 @@ void close_thread_tables(THD *thd, bool 
   */
   if (thd->derived_tables && !skip_derived)
   {
-    TABLE *table, *next;
+    TABLE *next;
     /*
       Close all derived tables generated in queries like
       SELECT * FROM (SELECT * FROM t1)
@@ -1262,8 +1336,17 @@ bool close_thread_table(THD *thd, TABLE 
   DBUG_ENTER("close_thread_table");
   DBUG_ASSERT(table->key_read == 0);
   DBUG_ASSERT(!table->file || table->file->inited == handler::NONE);
+  DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+                        table->s->table_name.str, (long) table));
 
   *table_ptr=table->next;
+  /*
+    When closing a MERGE parent table, detach its children first.
+    Clear child table references to force new assignment at next open.
+  */
+  if (table->child_l)
+    detach_merge_children(table, TRUE);
+
   if (table->needs_reopen_or_name_lock() ||
       thd->version != refresh_version || !table->db_stat)
   {
@@ -1696,16 +1779,19 @@ TABLE *find_temporary_table(THD *thd, TA
 bool close_temporary_table(THD *thd, TABLE_LIST *table_list)
 {
   TABLE *table;
+  DBUG_ENTER("close_temporary_table");
+  DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
+                          table_list->db, table_list->table_name));
 
   if (!(table= find_temporary_table(thd, table_list)))
-    return 1;
+    DBUG_RETURN(1);
   /*
     If LOCK TABLES list is not empty and contains this table,
     unlock the table and remove the table from this list.
   */
   mysql_lock_remove(thd, thd->locked_tables, table, FALSE);
   close_temporary_table(thd, table, 1, 1);
-  return 0;
+  DBUG_RETURN(0);
 }
 
 /*
@@ -1715,6 +1801,11 @@ bool close_temporary_table(THD *thd, TAB
 void close_temporary_table(THD *thd, TABLE *table,
                            bool free_share, bool delete_table)
 {
+  DBUG_ENTER("close_temporary_table");
+  DBUG_PRINT("tmptable", ("closing table: '%s'.'%s' 0x%lx  alias: '%s'",
+                          table->s->db.str, table->s->table_name.str,
+                          (long) table, table->alias));
+
   if (table->prev)
   {
     table->prev->next= table->next;
@@ -1741,6 +1832,7 @@ void close_temporary_table(THD *thd, TAB
     slave_open_temp_tables--;
   }
   close_temporary(table, free_share, delete_table);
+  DBUG_VOID_RETURN;
 }
 
 
@@ -1756,6 +1848,8 @@ void close_temporary(TABLE *table, bool 
 {
   handlerton *table_type= table->s->db_type();
   DBUG_ENTER("close_temporary");
+  DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
+                          table->s->db.str, table->s->table_name.str));
 
   free_io_cache(table);
   closefrm(table, 0);
@@ -1849,7 +1943,8 @@ void unlink_open_table(THD *thd, TABLE *
     if (list->s->table_cache_key.length == key_length &&
 	!memcmp(list->s->table_cache_key.str, key, key_length))
     {
-      if (unlock && thd->locked_tables)
+      /* Do not handle locks of MERGE children. */
+      if (unlock && thd->locked_tables && !list->parent)
         mysql_lock_remove(thd, thd->locked_tables, list, TRUE);
       VOID(hash_delete(&open_cache,(uchar*) list)); // Close table
     }
@@ -2341,9 +2436,14 @@ TABLE *open_table(THD *thd, TABLE_LIST *
                    table->s->table_name.str);
           DBUG_RETURN(0);
         }
+        /*
+          When looking for a usable TABLE, ignore MERGE children, as they
+          belong to their parent and cannot be used explicitly.
+        */
         if (!my_strcasecmp(system_charset_info, table->alias, alias) &&
             table->query_id != thd->query_id && /* skip tables already used
*/
-            !(thd->prelocked_mode && table->query_id))
+            !(thd->prelocked_mode && table->query_id) &&
+            !table->parent)
         {
           int distance= ((int) table->reginfo.lock_type -
                          (int) table_list->lock_type);
@@ -2486,6 +2586,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *
        table= (TABLE*) hash_next(&open_cache, (uchar*) key, key_length,
                                  &state))
   {
+    DBUG_PRINT("tcache", ("in_use table: '%s'.'%s' 0x%lx", table->s->db.str,
+                          table->s->table_name.str, (long) table));
     /*
       Here we flush tables marked for flush.
       Normally, table->s->version contains the value of
@@ -2570,6 +2672,8 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   }
   if (table)
   {
+    DBUG_PRINT("tcache", ("unused table: '%s'.'%s' 0x%lx", table->s->db.str,
+                          table->s->table_name.str, (long) table));
     /* Unlink the table from "unused_tables" list. */
     if (table == unused_tables)
     {						// First unused
@@ -2585,6 +2689,7 @@ TABLE *open_table(THD *thd, TABLE_LIST *
   {
     /* Insert a new TABLE instance into the open cache */
     int error;
+    DBUG_PRINT("tcache", ("opening new table"));
     /* Free cache if too big */
     while (open_cache.records > table_cache_size && unused_tables)
       VOID(hash_delete(&open_cache,(uchar*) unused_tables)); /* purecov: tested */
@@ -2651,7 +2756,9 @@ TABLE *open_table(THD *thd, TABLE_LIST *
       VOID(pthread_mutex_unlock(&LOCK_open));
       DBUG_RETURN(0); // VIEW
     }
-    DBUG_PRINT("info", ("inserting table 0x%lx into the cache", (long) table));
+    DBUG_PRINT("info", ("inserting table '%s'.'%s' 0x%lx into the cache",
+                        table->s->db.str, table->s->table_name.str,
+                        (long) table));
     VOID(my_hash_insert(&open_cache,(uchar*) table));
   }
 
@@ -2716,6 +2823,101 @@ TABLE *find_locked_table(THD *thd, const
 }
 
 
+/**
+  @brief Fix MERGE children after open.
+
+  @param[in]    old_child_list  first list member from original table
+  @param[in]    old_last        pointer to &next_global of last list member
+  @param[in]    new_child_list  first list member from freshly opened table
+  @param[in]    new_last        pointer to &next_global of last list member
+
+  @return       mismatch
+    @retval     FALSE           OK, no mismatch
+    @retval     TRUE            Error, lists mismatch
+
+  @detail Main action is to copy TABLE reference for each member of
+    original child list to new child list. After a fresh open these
+    references are NULL. Assign the old children to the new table.
+    Some of them might also be reopened or will be reopened soon.
+
+    Other action is to verify that the table definition with respect to
+    the UNION list did not change.
+*/
+
+bool fix_merge_after_open(TABLE_LIST *old_child_list, TABLE_LIST **old_last,
+                          TABLE_LIST *new_child_list, TABLE_LIST **new_last)
+{
+  bool mismatch= FALSE;
+  DBUG_ENTER("fix_merge_after_open");
+  DBUG_PRINT("myrg", ("old last addr: 0x%lx  new last addr: 0x%lx",
+                      (long) old_last, (long) new_last));
+
+  for (;;)
+  {
+    DBUG_PRINT("myrg", ("old list item: 0x%lx  new list item: 0x%lx",
+                        (long) old_child_list, (long) new_child_list));
+    /* Break if one of the list is at its end. */
+    if (!old_child_list || !new_child_list)
+      break;
+    /* Old table has references to child TABLEs. */
+    DBUG_ASSERT(old_child_list->table);
+    /* New table does not yet have references to child TABLEs. */
+    DBUG_ASSERT(!new_child_list->table);
+    DBUG_PRINT("myrg", ("old table: '%s'.'%s'  new table: '%s'.'%s'",
+                        old_child_list->db, old_child_list->table_name,
+                        new_child_list->db, new_child_list->table_name));
+    /* Child db.table names must match. */
+    if (strcmp(old_child_list->table_name, new_child_list->table_name) ||
+        strcmp(old_child_list->db,         new_child_list->db))
+    {
+      mismatch= TRUE;
+      break;
+    }
+    /*
+      Copy TABLE reference. Child TABLE objects are still in place
+      though not necessarily open yet.
+    */
+    DBUG_PRINT("myrg", ("old table ref: 0x%lx  replaces new table ref: 0x%lx",
+                        (long) old_child_list->table,
+                        (long) new_child_list->table));
+    new_child_list->table= old_child_list->table;
+    /*
+      Break if one of the lists is at its last child. When the child
+      list is part of the statement list, the list may continue after
+      the last child. The test for NULL is insufficient.
+    */
+    DBUG_PRINT("myrg", ("old next addr: 0x%lx  new next addr: 0x%lx",
+                        (long) &old_child_list->next_global,
+                        (long) &new_child_list->next_global));
+    if ((old_last && (&old_child_list->next_global == old_last)) ||
+        (new_last && (&new_child_list->next_global == new_last)))
+      break;
+    /* Step both lists. */
+    old_child_list= old_child_list->next_global;
+    new_child_list= new_child_list->next_global;
+  }
+  DBUG_PRINT("myrg", ("end of list, mismatch: %d", mismatch));
+  if (!mismatch &&
+      (/* old list at end */
+       (!old_child_list || (!old_last && !old_child_list->next_global) ||
+        (old_last && (&old_child_list->next_global == old_last)))
&&
+       /* and new list not at end */
+       (new_child_list && new_child_list->next_global &&
+        (!new_last || (&new_child_list->next_global != new_last)))) ||
+      (/* new list at end */
+       (!new_child_list || (!new_last && !new_child_list->next_global) ||
+        (new_last && (&new_child_list->next_global == new_last)))
&&
+       /* and old list not at end */
+       (old_child_list && old_child_list->next_global &&
+        (!old_last || (&old_child_list->next_global != old_last)))))
+    mismatch= TRUE;
+  if (mismatch)
+    my_error(ER_TABLE_DEF_CHANGED, MYF(0));
+
+  DBUG_RETURN(mismatch);
+}
+
+
 /*
   Reopen an table because the definition has changed.
 
@@ -2741,9 +2943,12 @@ bool reopen_table(TABLE *table)
   TABLE_LIST table_list;
   THD *thd= table->in_use;
   DBUG_ENTER("reopen_table");
+  DBUG_PRINT("tcache", ("table: '%s'.'%s' 0x%lx", table->s->db.str,
+                        table->s->table_name.str, (long) table));
 
   DBUG_ASSERT(table->s->ref_count == 0);
   DBUG_ASSERT(!table->sort.io_cache);
+  DBUG_ASSERT(!table->children_attached);
 
 #ifdef EXTRA_DEBUG
   if (table->db_stat)
@@ -2784,6 +2989,17 @@ bool reopen_table(TABLE *table)
   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;
+  }
+
   delete table->triggers;
   if (table->file)
     VOID(closefrm(table, 1));		// close file, free everything
@@ -2805,6 +3021,11 @@ bool reopen_table(TABLE *table)
   }
   if (table->triggers)
     table->triggers->set_table(table);
+  /*
+    Do not attach MERGE children here. The children might be reopened
+    after the parent. Do it after reopening all tables that require
+    reopen. See for example reopen_tables().
+  */
 
   broadcast_refresh();
   error=0;
@@ -2856,7 +3077,8 @@ void close_data_files_and_morph_locks(TH
     if (!strcmp(table->s->table_name.str, table_name) &&
 	!strcmp(table->s->db.str, db))
     {
-      if (thd->locked_tables)
+      /* Do not handle locks of MERGE children. */
+      if (thd->locked_tables && !table->parent)
         mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
       table->open_placeholder= 1;
       close_handle_and_leave_table_as_lock(table);
@@ -2891,6 +3113,7 @@ bool reopen_tables(THD *thd,bool get_loc
   TABLE *table,*next,**prev;
   TABLE **tables,**tables_ptr;			// For locks
   bool error=0, not_used;
+  bool merge_table_found= FALSE;
   DBUG_ENTER("reopen_tables");
 
   if (!thd->open_tables)
@@ -2899,10 +3122,15 @@ bool reopen_tables(THD *thd,bool get_loc
   safe_mutex_assert_owner(&LOCK_open);
   if (get_locks)
   {
-    /* The ptr is checked later */
+    /*
+      The ptr is checked later
+      Do not handle locks of MERGE children.
+    */
     uint opens=0;
     for (table= thd->open_tables; table ; table=table->next)
-      opens++;
+      if (!table->parent)
+        opens++;
+    DBUG_PRINT("tcache", ("open tables to lock: %u", opens));
     tables= (TABLE**) my_alloca(sizeof(TABLE*)*opens);
   }
   else
@@ -2914,6 +3142,12 @@ bool reopen_tables(THD *thd,bool get_loc
   {
     uint db_stat=table->db_stat;
     next=table->next;
+    DBUG_PRINT("tcache", ("open table: '%s'.'%s' 0x%lx  "
+                          "parent: 0x%lx  db_stat: %u",
+                          table->s->db.str, table->s->table_name.str,
+                          (long) table, (long) 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);
@@ -2922,9 +3156,12 @@ bool reopen_tables(THD *thd,bool get_loc
     }
     else
     {
+      DBUG_PRINT("tcache", ("opened. need lock: %d",
+                            get_locks && !db_stat && !table->parent));
       *prev= table;
       prev= &table->next;
-      if (get_locks && !db_stat)
+      /* Do not handle locks of MERGE children. */
+      if (get_locks && !db_stat && !table->parent)
 	*tables_ptr++= table;			// need new lock on this
       if (in_refresh)
       {
@@ -2933,6 +3170,36 @@ bool reopen_tables(THD *thd,bool get_loc
       }
     }
   }
+  /*
+    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)
+  {
+    for (table= thd->open_tables; table; table= next)
+    {
+      next= table->next;
+      DBUG_PRINT("tcache", ("check table: '%s'.'%s' 0x%lx  next: 0x%lx",
+                            table->s->db.str, table->s->table_name.str,
+                            (long) table, (long) 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);
+          VOID(hash_delete(&open_cache,(uchar*) table));
+          error=1;
+        }
+        else
+          table->children_attached= TRUE;
+      }
+    }
+  }
+  DBUG_PRINT("tcache", ("open tables to lock: %u",
+                        (uint) (tables_ptr - tables)));
   if (tables != tables_ptr)			// Should we get back old locks
   {
     MYSQL_LOCK *lock;
@@ -2978,6 +3245,11 @@ void close_old_data_files(THD *thd, TABL
 
   for (; table ; table=table->next)
   {
+    DBUG_PRINT("tcache", ("checking table: '%s'.'%s' 0x%lx",
+                          table->s->db.str, table->s->table_name.str,
+                          (long) table));
+    DBUG_PRINT("tcache", ("needs refresh: %d  is open: %u",
+                          table->needs_reopen_or_name_lock(), table->db_stat));
     /*
       Reopen marked for flush.
     */
@@ -2989,13 +3261,22 @@ void close_old_data_files(THD *thd, TABL
 	if (morph_locks)
 	{
           /*
-            Wake up threads waiting for table-level lock on this table
-            so they won't sneak in when we will temporarily remove our
-            lock on it. This will also give them a chance to close their
-            instances of this table.
+            Forward lock handling to MERGE parent. But unlock parent
+            once only.
           */
-          mysql_lock_abort(thd, table, TRUE);
-          mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
+          TABLE *ulcktbl= table->parent ? table->parent : table;
+          if (ulcktbl->lock_count)
+          {
+            /*
+              Wake up threads waiting for table-level lock on this table
+              so they won't sneak in when we will temporarily remove our
+              lock on it. This will also give them a chance to close their
+              instances of this table.
+            */
+            mysql_lock_abort(thd, ulcktbl, TRUE);
+            mysql_lock_remove(thd, thd->locked_tables, ulcktbl, TRUE);
+            ulcktbl->lock_count= 0;
+          }
           /*
             We want to protect the table from concurrent DDL operations
             (like RENAME TABLE) until we will re-open and re-lock it.
@@ -3140,7 +3421,9 @@ TABLE *drop_locked_tables(THD *thd,const
     if (!strcmp(table->s->table_name.str, table_name) &&
 	!strcmp(table->s->db.str, db))
     {
-      mysql_lock_remove(thd, thd->locked_tables, table, TRUE);
+      /* If MERGE child, forward lock handling to parent. */
+      mysql_lock_remove(thd, thd->locked_tables,
+                        table->parent ? table->parent : table, TRUE);
       if (!found)
       {
         found= table;
@@ -3148,6 +3431,12 @@ TABLE *drop_locked_tables(THD *thd,const
         if (table->db_stat)
         {
           table->db_stat= 0;
+          /*
+            When closing a MERGE parent table, detach its children first.
+            Clear child table references in case this object is opened again.
+          */
+          if (table->child_l)
+            detach_merge_children(table, TRUE);
           table->file->close();
         }
       }
@@ -3189,7 +3478,8 @@ void abort_locked_tables(THD *thd,const 
     if (!strcmp(table->s->table_name.str, table_name) &&
 	!strcmp(table->s->db.str, db))
     {
-      mysql_lock_abort(thd,table, TRUE);
+      /* If MERGE child, forward lock handling to parent. */
+      mysql_lock_abort(thd, table->parent ? table->parent : table, TRUE);
       break;
     }
   }
@@ -3220,7 +3510,8 @@ void abort_locked_tables(THD *thd,const 
     share->table_map_id is given a value that with a high certainty is
     not used by any other table (the only case where a table id can be
     reused is on wrap-around, which means more than 4 billion table
-    shares open at the same time).
+    share opens have been executed while one table was open all the
+    time).
 
     share->table_map_id is not ~0UL.
  */
@@ -3459,6 +3750,129 @@ err:
 }
 
 
+/**
+  @brief Add list of MERGE children to a TABLE_LIST list.
+
+  @detail When a MERGE parent table has just been opened, insert the
+    TABLE_LIST chain from the MERGE handle into the table list used for
+    opening tables for this statement. This lets the children be opened
+    too.
+
+  @param[in]    tlist           the TABLE_LIST object just opened
+
+  @return status
+    @retval     0               OK
+    @retval     != 0            Error
+*/
+
+static int add_merge_table_list(TABLE_LIST *tlist)
+{
+  TABLE       *parent= tlist->table;
+  TABLE_LIST  *child_l;
+  DBUG_ENTER("add_merge_table_list");
+  DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str,
+                      parent->s->table_name.str, (long) parent));
+
+  /* Must not call this with attached children. */
+  DBUG_ASSERT(!parent->children_attached);
+  /* Must not call this with children list in place. */
+  DBUG_ASSERT(tlist->next_global != parent->child_l);
+
+  /* Fix children.*/
+  for (child_l= parent->child_l; ; child_l= child_l->next_global)
+  {
+    /*
+      Note: child_l->table may still be set if this parent was taken
+      from the unused_tables chain. Ignore this fact here. The
+      reference will be replaced by the handler in
+      ::extra(HA_EXTRA_ATTACH_CHILDREN).
+    */
+
+    /* Set lock type. */
+    child_l->lock_type= tlist->lock_type;
+
+    /* Set parent reference. */
+    child_l->parent_l= tlist;
+
+    /* Break when this was the last child. */
+    if (&child_l->next_global == parent->child_last_l)
+      break;
+  }
+
+  /* Insert children into the table list. */
+  *parent->child_last_l= tlist->next_global;
+  tlist->next_global= parent->child_l;
+
+  /*
+    Do not fix the prev_global pointers. We will remove the
+    chain soon anyway.
+  */
+
+  DBUG_RETURN(0);
+}
+
+
+/**
+  @brief Attach MERGE children to the parent.
+
+  @detail When the last MERGE child has just been opened, let the
+    handler attach the MyISAM tables to the MERGE table. Remove MERGE
+    TABLE_LIST chain from the statement list so that it cannot be
+    changed or freed.
+
+  @param[in]    tlist           the TABLE_LIST object just opened
+
+  @return status
+    @retval     0               OK
+    @retval     != 0            Error
+*/
+
+static int attach_merge_children(TABLE_LIST *tlist)
+{
+  TABLE *parent= tlist->parent_l->table;
+  int error;
+  DBUG_ENTER("attach_merge_children");
+  DBUG_PRINT("myrg", ("table: '%s'.'%s' 0x%lx", parent->s->db.str,
+                      parent->s->table_name.str, (long) parent));
+
+  /* Must not call this with attached children. */
+  DBUG_ASSERT(!parent->children_attached);
+  /* Must call this with children list in place. */
+  DBUG_ASSERT(tlist->parent_l->next_global == parent->child_l);
+
+  /* Attach MyISAM tables to MERGE table. */
+  error= parent->file->extra(HA_EXTRA_ATTACH_CHILDREN);
+
+  /*
+    Remove children from the table list. Even in case of an error.
+    This should prevent tampering with them.
+  */
+  tlist->parent_l->next_global= *parent->child_last_l;
+
+  /*
+    Do not fix the last childs next_global pointer. It is needed for
+    stepping to the next table in the enclosing loop in open_tables().
+    Do not fix prev_global pointers. We did not set them.
+  */
+
+  if (error)
+  {
+    DBUG_PRINT("error", ("attaching MERGE children failed: %d", my_errno));
+    parent->file->print_error(error, MYF(0));
+    DBUG_RETURN(1);
+  }
+
+  parent->children_attached= TRUE;
+
+  /*
+    Note that we have the cildren in the thd->open_tables list at this
+    point.
+  */
+
+  DBUG_RETURN(0);
+}
+
+
 /*
   Open all tables in list
 
@@ -3549,6 +3963,9 @@ int open_tables(THD *thd, TABLE_LIST **s
   */
   for (tables= *start; tables ;tables= tables->next_global)
   {
+    DBUG_PRINT("tcache", ("opening table: '%s'.'%s'  item: 0x%lx",
+                          tables->db, tables->table_name, (long) tables));
+
     safe_to_ignore_table= FALSE;
 
     /*
@@ -3600,6 +4017,10 @@ int open_tables(THD *thd, TABLE_LIST **s
       else
         tables->table= open_table(thd, tables, &new_frm_mem, &refresh, flags);
     }
+    else
+      DBUG_PRINT("tcache", ("referenced table: '%s'.'%s' 0x%lx",
+                            tables->db, tables->table_name,
+                            (long) tables->table));
 
     if (!tables->table)
     {
@@ -3631,6 +4052,19 @@ int open_tables(THD *thd, TABLE_LIST **s
 	goto process_view_routines;
       }
 
+      /*
+        If in a MERGE table open, we need to remove the children list
+        from statement table list before restarting. Otherwise the list
+        will be inserted another time.
+      */
+      if (tables->parent_l)
+      {
+        TABLE_LIST *parent_l= tables->parent_l;
+        /* The parent table should be correctly open at this point. */
+        DBUG_ASSERT(parent_l->table);
+        parent_l->next_global= *parent_l->table->child_last_l;
+      }
+
       if (refresh)				// Refresh in progress
       {
         /*
@@ -3699,6 +4133,29 @@ int open_tables(THD *thd, TABLE_LIST **s
         thd->update_lock_default : tables->lock_type;
     tables->table->grant= tables->grant;
 
+    /* Attach MERGE children if not locked already. */
+    DBUG_PRINT("tcache", ("is parent: %d  is child: %d",
+                          test(tables->table->child_l),
+                          test(tables->parent_l)));
+    DBUG_PRINT("tcache", ("in lock tables: %d  in prelock mode: %d",
+                          test(thd->locked_tables), test(thd->prelocked_mode)));
+    if (((!thd->locked_tables && !thd->prelocked_mode) ||
+         tables->table->s->tmp_table) &&
+        ((tables->table->child_l &&
+          add_merge_table_list(tables)) ||
+         (tables->parent_l &&
+          (&tables->next_global == tables->parent_l->table->child_last_l)
&&
+          attach_merge_children(tables))))
+    {
+      /*
+        Some functions determine success as (tables->table != NULL).
+        tables->table is in thd->open_tables. It won't go lost.
+      */
+      tables->table= NULL;
+      result= -1;
+      goto err;
+    }
+
 process_view_routines:
     /*
       Again we may need cache all routines used by this view and add
@@ -3770,6 +4227,54 @@ static bool check_lock_and_start_stmt(TH
 }
 
 
+/**
+  @brief Open and lock one table
+
+  @param[in]    thd             thread handle
+  @param[in]    table_l         table to open is first table in this list
+  @param[in]    lock_type       lock to use for table
+
+  @return       table
+    @retval     != NULL         OK, opened table returned
+    @retval     NULL            Error
+
+  @note
+    If ok, the following are also set:
+      table_list->lock_type 	lock_type
+      table_list->table		table
+
+  @note
+    If table_l is a list, not a single table, the list is temporarily
+    broken.
+*/
+
+TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l,
+                                thr_lock_type lock_type)
+{
+  TABLE_LIST *save_next_global;
+  DBUG_ENTER("open_n_lock_single_table");
+
+  /* Remember old 'next' pointer. */
+  save_next_global= table_l->next_global;
+  /* Break list. */
+  table_l->next_global= NULL;
+
+  /* Set requested lock type. */
+  table_l->lock_type= lock_type;
+  /* Allow to open real tables only. */
+  table_l->required_type= FRMTYPE_TABLE;
+
+  /* Open the table. */
+  if (simple_open_n_lock_tables(thd, table_l))
+    table_l->table= NULL; /* Just to be sure. */
+
+  /* Restore list. */